Tutoriel réalisé par Faiseur
Niveau: [ Intermédiaire - Confirmé ]
Niveau: [ Intermédiaire - Confirmé ]
STUB DE RETOUR
Soyons justes: l'exemple 1 ne permet pas de revenir sur l'API d'origine après son interception. En pratique les deux secondes de pauses que nous envoyons à l'API Sleep ne sont pas prises en compte.
Il s'agit donc d'un hook incomplet. Ce premier exemple aura servit à
démontrer que la base d'un Hook est très simple à coder du moment que
l'on sait quoi faire. Je vous invite à revenir sur l'exemple 1 à chaque fois que
quelque chose vous semblera peu clair dans la suite de mes explications.
Pour
continuer son développement il faut à présent mettre en place un stub
(stub = un code minimal) qui permettra, après avoir terminé notre
interception, de revenir sur l'API d'origine. Reprenons notre schéma:
Nous
avons suivis les étapes 1 à 3 mais pas l'étape 4 du retour à l'API. Au
lieu de cela nous avons terminé notre fonction d'interception comme
s'il s'agissait de la fin de l'appel de l'API: l'étape 7.
La
mise en place d'un stub pour continuer à l'étape 4 n'ajoutera que
quelques lignes de code mais, encore une fois, il faudra bien en
assimiler le principe. Pour revenir sur l'API d'origine nous allons:
1. Avant d'écraser les 5 premiers octets de l'API en sauvegarder les données dans un stub
2 Ajouter dans le stub, après les 5 premiers octets copiés, un saut qui redirige vers l'API d'origine
De
cette manière nous pourrons utiliser le stub après notre fonction
d'interception pour rendre la main à l'API Sleep. Ce qui nous donne le
schéma d'appel suivant:
[PROCESSUS] call ---> [API SLEEP] jmp ---> [FONCTION D'INTERCEPTION] call ---> [STUB] jmp ---> [API SLEEP]
EXEMPLE 2
Voici la nouvelle procédure de Hook de l'exemple 2:
- Code:
InstallHook proc uses ebx edi esi hookProc:DWORD, target:DWORD, pStub:DWORD
LOCAL lpflOldProtect:ULONG
mov edi, target
mov ebx, pStub
invoke VirtualProtect, edi,5, PAGE_EXECUTE_READWRITE, addr lpflOldProtect
invoke RtlMoveMemory, ebx, edi,5
mov eax, hookProc
sub eax, edi
sub eax, 5
mov BYTE PTR [edi], 0E9h
mov [edi + 1], eax
invoke VirtualProtect, edi,5, lpflOldProtect, ADDR lpflOldProtect
mov esi, ebx
add esi,5
sub edi, ebx
sub edi, 5
mov BYTE PTR [esi], 0E9h
mov [esi +1], edi
ret
InstallHook endp
Notez
que depuis cette version la procédure de Hook ferme l'accès en écriture
qui a été ouvert avec un second appel à 'VirtualProtect'. C'est plus
propre.
La procédure inclus une nouvelle variable: 'pStub'.
La syntaxe d'appel est donc:
invoke InstallHook, [hookProc], [target], [pStub]
Ce qui a été ajouté:
On sauvegarde, avant d'écrire le saut, les 5 premiers octets de l'API Sleep dans le stub (EBX pointe sur le stub) à cet endroit:
- Code:
invoke VirtualProtect, edi,5, PAGE_EXECUTE_READWRITE, addr lpflOldProtect
invoke RtlMoveMemory, ebx, edi,5
On utilise le registre ESI pour pointer à la fin des instructions copiées dans le stub:
- Code:
mov esi, ebx
add esi,5
Nous
allons placer un saut qui renvoie vers l'API d'origine APRES ses 5
premiers octets (le stub lit ses 5 premiers octets, il faut donc placer
le saut juste après). Le calcul du saut suit la même formule
qu'auparavant, cette fois avec d'autres variables:
[Adresse API] – [Stub] - 5
Dans
notre code le registre EDI pointe sur l'API d'origine et le registre
EBX sur le Stub, ce qui permet de faire le calcul de cette manière:
- Code:
sub edi, ebx
sub edi, 5
Les
explications sont un peu longues mais encore une fois c'est simple à
coder lorsque le principe est assimilé. Il ne nous reste plus qu'à
copier l'adresse du saut à la fin du stub:
- Code:
mov BYTE PTR [esi], 0E9h
mov [esi +1], edi
A
présent la fonction d'interception doit inclure l'appel au stub pour
retourner à l'API. Il faut également inclure les différents paramètres
d'appel à la fonction. Pour l'API Sleep il n'y a qu'un paramètre:
'dwMilliseconds'. En syntaxe Masm on peut l'écrire de cette manière qui
est très pratique:
invoke pr1 PTR Stub, dwMilliseconds
Je
n'ai pas trouvé de raccourci avec invoke et 'pr1' pour JWasm. Voici un
exemple de syntaxe compatible JWasm ou autre assembleur:
push dwMilliseconds
mov eax,offset Stub
call eax
La fonction d'interception qui ajoute le retour à l'API donne à présent ceci:
- Code:
ExecuteHook PROC dwMilliseconds:DWORD
fn MessageBox,0,chr$("Fonction API Sleep Hooked !"),"Message",0
invoke pr1 PTR Stub, dwMilliseconds
ret
ExecuteHook endp
Il ne reste qu'à définir le stub de cette manière:
- Code:
.data?
Stub db 10 dup (?)
La longueur du stub doit pouvoir contenir les 5 premiers octets de l'API suivis de 5 octets correspondant à la longueur de l'instruction du saut relatif.
Voici le code au complet:
HookExemple2.asm
- Code:
comment *
Exemple d'API Hook en user mode de type inline hook
par Faiseur
*
.386
.model FLAT,STDCALL
option casemap:none
include \masm32\include\Windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
include \masm32\macros\macros.asm
.data?
targetHook dd ?
Stub db 10 dup (?)
.code
ExecuteHook PROC dwMilliseconds:DWORD
fn MessageBox,0,chr$("Fonction API Sleep Hooked !"),"Message",0
invoke pr1 PTR Stub,dwMilliseconds
ret
ExecuteHook endp
InstallHook proc uses ebx edi esi hookProc:DWORD, target:DWORD, pStub:DWORD
LOCAL lpflOldProtect:ULONG
mov edi, target
mov ebx, pStub
invoke VirtualProtect, edi,5, PAGE_EXECUTE_READWRITE, addr lpflOldProtect
invoke RtlMoveMemory, ebx, edi,5
mov eax, hookProc
sub eax, edi
sub eax, 5
mov BYTE PTR [edi], 0E9h
mov [edi + 1], eax
invoke VirtualProtect, edi,5, lpflOldProtect, ADDR lpflOldProtect
mov esi, ebx
add esi,5
sub edi, ebx
sub edi, 5
mov BYTE PTR [esi], 0E9h
mov [esi +1], edi
ret
InstallHook endp
start:
LoadProcAddress "kernel32.dll","Sleep"
test eax,eax
je @F
invoke InstallHook, ExecuteHook,eax,addr Stub
invoke Sleep,2000
invoke Sleep,2000
invoke Sleep,2000
@@:
invoke ExitProcess, NULL
end start
LENGTH DISASSEMBLER ENGINE
Reste
encore un petit soucis: nous avons sauvegardé les 5 premiers octets de
l'API, mais si par exemple le prologue de l'API Sleep contenait une
première instruction de 3 octets et une suivante de 5 octets ? Lorsque
nous revenons avec le stub sur l'API, au 6ème octet, nous sommes en
plein milieu de la deuxième instruction...Il y a toutes les chances
pour que ça plante ! Hé oui, nous n'avons pas vérifié si les premières
instructions sont compatibles avec nos 5 premiers octets (le saut).
Ce
serait évidemment une erreur de considérer que le prologue de chaque
API utiliserait des instructions identiques et compatibles avec nos 5
octets. Heureusement c'est le cas des API courantes telles Sleep mais
il est toujours possible de trouver des exceptions. C'est pourquoi il
faut pouvoir calculer la taille des instructions. Des moteurs de ce
type existent et sont appelés en anglais "Length Disassembler Engine".
Nous allons nous servir d'un "Length Disassembler Engine" pour calculer
la taille des premières instructions de l'API jusqu’à obtenir un bloc
de taille supérieure ou égale à 5 octets. Par exemple, si le prologue
de l'API devait être un jeu d'instructions de 3 et 5, alors nous
copions les 8 premiers octets dans le stub.
Il existe plusieurs
moteurs pour calculer la taille des instructions. En C/C++ on se sert
très souvent du Length Disassembly Engine de Zombie. En assembleur
compatible Masm/JWasm il existe le "Catchy32 Length Disassembler
Engine", qui sera utilisé pour notre Hook.
EXEMPLE 3
La routine pour calculer la taille des premières instructions suivra un code de ce type:
- Code:
mov ebx,eax
xor edi,edi
.WHILE edi <= 4
invoke c_Catchy,ebx
add edi,eax
add ebx,eax
.ENDW
EBX pointe sur l'adresse de l'API
EDI pointe sur l'addition de la taille des instructions
On
incrémente le registre EBX de manière à calculer l'instruction suivante
à analyser dans le moteur jusqu'à rejoindre ou dépasser nos 5 premiers
bytes.
Le résultat sera envoyé à notre procédure de Hook qui inclus une nouvelle variable: la taille du stub à copier.
InstallHook, [hookProc], [target], [pStub], [SizeStub]
Enfin,
cette version ajoute également le choix "facultatif" de préserver les
registres lors de l'interception. Les registres préservés par une API
sont EBX, EDI et ESI. C'est pourquoi si votre fonction d'interception
devait les modifier il faut pouvoir les replacer à leur état d'origine.
On ne sait jamais si le processus appelant l'API devait se servir de
ces registres. Notre routine d'interception va préserver également le
registre EBP.
Voici l'exemple 3:
HookExemple3.asm
- Code:
comment *
Exemple d'API Hook en user mode de type inline hook
par Faiseur
*
.386
.model FLAT,STDCALL
option casemap:none
include \masm32\include\Windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
include \masm32\include\masm32.inc
includelib \masm32\lib\masm32.lib
include \masm32\macros\macros.asm
include Lib/catchy32.inc
.data?
targetHook dd ?
Stub db 20 dup (?)
.code
ExecuteHook PROC uses ebx edi esi ebp dwMilliseconds:DWORD
fn MessageBox,0,chr$("Fonction API Sleep Hooked !"),"Message",0
invoke pr1 PTR Stub,dwMilliseconds
ret
ExecuteHook endp
InstallHook proc uses ebx edi esi hookProc:DWORD, target:DWORD, pStub:DWORD,SizeStub:DWORD
LOCAL lpflOldProtect:ULONG
mov edi, target
mov ebx, pStub
invoke VirtualProtect, edi,SizeStub, PAGE_EXECUTE_READWRITE, addr lpflOldProtect ; on permet l'écriture dans la procédure cible
invoke RtlMoveMemory, ebx, edi,SizeStub ;on copie les bytes de la cible avant de la modifier (cela permettra d'appeler la fonction originale)
; on remplace les bytes par un jmp à hookProc
mov eax, hookProc
sub eax, edi ; adresse de la procédure Hook - adresse de la fonction originale - 5 bytes = adresse effective du saut
sub eax, 5 ;
mov BYTE PTR [edi], 0E9h ; on place le saut relatif
mov [edi + 1], eax
invoke VirtualProtect, edi,SizeStub, lpflOldProtect, ADDR lpflOldProtect ; reprotection de la mémoire
; Nous allons placer un saut à la procédure originale (target) à la fin du Stub
mov esi, ebx
add esi, SizeStub ;on se positionne à la fin du Stub
sub edi, ebx
sub edi, 5
mov BYTE PTR [esi], 0E9h ; on place le saut relatif
mov [esi +1], edi
ret
InstallHook endp
start:
LoadProcAddress "kernel32.dll","Sleep"
test eax,eax
je @F
mov targetHook,eax
mov ebx,eax
xor edi,edi
.WHILE edi <= 4
invoke c_Catchy,ebx
add edi,eax
add ebx,eax
.ENDW
invoke InstallHook, ExecuteHook,targetHook,addr Stub,edi
invoke Sleep,2000
invoke Sleep,2000
invoke Sleep,2000
@@:
invoke ExitProcess, NULL
end start
Le moteur de calcul d'instructions:
Catchy32.inc
- Code:
;==================================================================================================================================================
; ******** *** *********** ********* *** *** *** *** ******* *******
; *********** **** **** *********** *********** *** *** *** *** ***** ***** ***** *****
; *** *** *** *** *** *** *** *** *** *** *** ** *** ** ****
; *** *********** *** *** *********** ** ** **** ****
; *** *** *********** *** *** *** *********** *** ** *** ****
; *********** *** *** *** *********** *** *** *** ***** ***** ***********
; ********* *** *** *** ********* *** *** *** ******* ***********
;==================================================Catchy32 v1.6 - Length Disassembler Engine 32bit================================================
;SIZE=580 bytes
;Version:
;1.0-test version
;1.1-added: support prefix
;1.2-added: TableEXT
;1.3-added: support for 0F6h and 0F7h groups
;1.4-tables fixed
; -SIB byte handling fixed
;1.5-code fixed&optimized
; -processing 0F6h and 0F7h groups is corrected
; -processing 0A0h-0A3h groups is corrected
;1.6-code fixed
; -added: max lenght=15 bytes
;==================================================================================================================================================
;in: esi - pointer to opcode
;out: eax - opcode length or 0ffffffffh if error
;(c) sars [HI-TECH] 2003
;sars@ukrtop.com
;==================================================================================================================================================
.code
c_Table:
; 01 23 45 67 89 AB CD EF
db 011h,011h,028h,000h,011h,011h,028h,000h;0Fh
db 011h,011h,028h,000h,011h,011h,028h,000h;1Fh
db 011h,011h,028h,0F0h,011h,011h,028h,0F0h;2Fh
db 011h,011h,028h,0F0h,011h,011h,028h,0F0h;3Fh
db 000h,000h,000h,000h,000h,000h,000h,000h;4Fh
db 000h,000h,000h,000h,000h,000h,000h,000h;5Fh
db 000h,011h,0FFh,0FFh,089h,023h,000h,000h;6Fh
db 022h,022h,022h,022h,022h,022h,022h,022h;7Fh
db 039h,033h,011h,011h,011h,011h,011h,011h;8Fh
db 000h,000h,000h,000h,000h,0C0h,000h,000h;9Fh
db 088h,088h,000h,000h,028h,000h,000h,000h;AFh
db 022h,022h,022h,022h,088h,088h,088h,088h;BFh
db 033h,040h,011h,039h,060h,040h,002h,000h;CFh
db 011h,011h,022h,000h,011h,011h,011h,011h;DFh
db 022h,022h,022h,022h,088h,0C2h,000h,000h;EFh
db 0F0h,0FFh,000h,011h,000h,000h,000h,011h;FFh
;==============================================
Lentable equ $-c_Table
;===============EXTENDED OPCODES===============
c_TableEXT:
; 01 23 45 67 89 AB CD EF
db 011h,011h,0E0h,000h,000h,0EEh,0E1h,003h;0Fh
db 011h,011h,011h,011h,01Eh,0EEh,0EEh,0EEh;1Fh
db 011h,011h,01Eh,01Eh,011h,011h,011h,011h;2Fh
db 000h,000h,000h,0EEh,0EEh,0EEh,0EEh,0EEh;3Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;4Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;5Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;6Fh
db 033h,033h,011h,010h,011h,011h,011h,011h;7Fh
db 088h,088h,088h,088h,088h,088h,088h,088h;8Fh
db 011h,011h,011h,011h,011h,011h,011h,011h;9Fh
db 000h,001h,031h,011h,000h,001h,031h,011h;AFh
db 011h,011h,011h,011h,0EEh,031h,011h,011h;BFh
db 011h,031h,033h,031h,000h,000h,000h,000h;CFh
db 0E1h,011h,011h,011h,011h,011h,011h,011h;DFh
db 011h,011h,011h,011h,011h,011h,011h,011h;EFh
db 0E1h,011h,011h,011h,011h,011h,011h,01Eh;FFh
;==============================================
pref66h equ 1
pref67h equ 2
c_Catchy proc uses esi ebx dwInst
;----Flags extraction, checks for some opcodes----
mov esi, dwInst
xor ecx, ecx
c_ExtFlags:
xor eax, eax
xor ebx, ebx
cdq
lodsb ;al <- opcode
mov cl, al ;cl <- opcode
cmp al, 0fh ;Test on prefix 0Fh
je c_ExtdTable
cmp word ptr [esi-1], 20CDh ;Test on VXD call
jne c_NormTable
inc esi ;If VXD call (int 20h), then command length is 6 bytes
lodsd
jmp c_CalcLen
c_ExtdTable: ;Load flags from extended table
lodsb
inc ah ;EAX=al+100h (100h/2 - lenght first table)
c_NormTable: ;Load flags from normal table
shr eax, 1 ;Elements tables on 4 bits
mov al, byte ptr [c_Table+eax]
c_CheckC1:
jc c_IFC1
shr eax, 4 ;Get high 4-bits block if offset is odd, otherwise...
c_IFC1:
and eax, 0Fh ;...low
xchg eax, ebx ;EAX will be needed for other purposes
;--------------Opcode type checking---------------
c_CheckFlags:
cmp bl, 0Eh ;Test on ErrorFlag
je c_Error
cmp bl, 0Fh ;Test on PrefixFlag
je c_Prefix
or ebx, ebx ;One byte command
jz c_CalcLen
btr ebx, 0 ;Command with ModRM byte
jc c_ModRM
btr ebx, 1 ;Test on imm8,rel8 etc flag
jc c_incr1
btr ebx, 2 ;Test on ptr16 etc flag
jc c_incr2
;-----imm16/32,rel16/32, etc types processing-----
c_16_32:
and bl, 11110111b ;Reset 16/32 sign
cmp cl, 0A0h ;Processing group 0A0h-0A3h
jb c_Check66h
cmp cl, 0A3h
ja c_Check66h
test ch, pref67h
jnz c_incr2
jmp c_incr4
c_Check66h: ;Processing other groups
test ch, pref66h
jz c_incr4
jmp c_incr2
;---------------Prefixes processing---------------
c_Prefix:
cmp cl, 66h
je c_SetFlag66h
cmp cl, 67h
jne c_ExtFlags
c_SetFlag67h:
or ch, pref67h
jmp c_ExtFlags
c_SetFlag66h:
or ch, pref66h
jmp c_ExtFlags
;--------------ModR/M byte processing-------------
c_ModRM:
lodsb
c_Check_0F6h_0F7h: ;Check on 0F6h and 0F7h groups
cmp cl, 0F7h
je c_GroupF6F7
cmp cl, 0F6h
jne c_ModXX
c_GroupF6F7: ;Processing groups 0F6h and 0F7h
test al, 00111000b
jnz c_ModXX
test cl, 00000001b
jz c_incbt1
test ch, 1
jnz c_incbt2
inc esi
inc esi
c_incbt2: inc esi
c_incbt1: inc esi
c_ModXX: ;Processing MOD bits
mov edx, eax
and al, 00000111b ;al <- only R/M bits
test dl, 11000000b ;Check MOD bits
jz c_Mod00
jp c_CheckFlags ;Or c_Mod11
js c_Mod10
c_Mod01:
test ch, pref67h
jnz c_incr1 ;16-bit addressing
cmp al, 4 ;Check SIB
je c_incr2
jmp c_incr1
c_Mod00:
test ch, pref67h
jz c_Mod00_32 ;32-bit addressing
cmp al, 6
je c_incr2
jmp c_CheckFlags
c_Mod00_32:
cmp al, 4 ;Check SIB
jne c_disp32
c_SIB: ;Processing SIB byte
lodsb
and al, 00000111b
cmp al, 5
je c_incr4
jmp c_CheckFlags
c_disp32:
cmp al, 5
je c_incr4
jmp c_CheckFlags
c_Mod10:
test ch, pref67h
jnz c_incr2 ;16-bit addressing
cmp al, 4 ;Check SIB
je c_incr5
jmp c_incr4
c_incr5: inc esi
c_incr4: inc esi
inc esi
c_incr2: inc esi
c_incr1: inc esi
jmp c_CheckFlags
;-----------Command length calculation------------
c_CalcLen:
sub esi, dwInst
cmp esi, 15
ja c_Error
mov eax, esi
jmp c_Exit
;----------------Setting the error----------------
c_Error:
xor eax, eax
dec eax
;---------Restore the registers and exit----------
c_Exit:
ret
;-------------------------------------------------
c_Catchy endp
;==================================================================================================================================================
Cette
version inclus un Hook à présent complet. Lors du prochain tutoriel
nous allons mettre en place un Hook par injection de code et inclure
également le moyen de désinstaller ce Hook.
Faiseur
Faiseur

0 commentaires:
Enregistrer un commentaire