Auteur

Auteur: Faiseur

dimanche 25 juillet 2010

API HOOK EN ASSEMBLEUR - PARTIE 2






Tutoriel réalisé par Faiseur

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

0 commentaires:

Enregistrer un commentaire