Auteur

Auteur: Faiseur

dimanche 25 juillet 2010

API HOOK EN ASSEMBLEUR - PARTIE 3





Tutoriel réalisé par Faiseur

Niveau: [ Intermédiaire - Confirmé ]




INJECTION


Où en sommes nous arrivés à la fin de la deuxième partie ? Nous avons mis en place un Hook respectueux de Windows et compatible avec les derniers OS (Windows 2000, XP, Vista, Seven). Pour aller plus loin il nous reste à l'installer sur les processus qui nous intéressent et également à pouvoir le désinstaller.

Nous allons nous servir de l'injection de code pour placer le Hook sur un processus de notre choix. Je ne détaillerai pas le principe de l'injection de code, qui a fait l'étude de nombreux articles sur internet. Ce procédé est très efficace et j'en propose une version en forme de "moteur d'injection". Il suffit d'envoyer le nom de l'exécutable au moteur et celui-ci s'occupe d'y injecter notre Hook.

Mon moteur d'injection de code est complètement autonome, intégré avec deux procédures dans ma librairie: 'ListProcess' et 'Injection'. En pratique seule la procédure 'Injection' nous concerne.

Code:

ListProcess PROC Cible:DWORD
   LOCAL ProcEntry:PROCESSENTRY32
   LOCAL szFilename[MAX_PATH]:BYTE 
   LOCAL hSnap:DWORD
 
   mov ProcEntry.dwSize, SIZEOF ProcEntry
   mov hSnap,rv(CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS, 0)
   mov edi, Cible 
   invoke Process32First, hSnap, ADDR ProcEntry
 
Next: invoke lstrcmpiA, edi, ADDR ProcEntry.szExeFile
   cmp eax,0
   jnz Cont
   invoke CloseHandle,hSnap
   mov eax, ProcEntry.th32ProcessID
   ret
Cont:  invoke Process32Next, hSnap, ADDR ProcEntry
   cmp eax,0
   jnz Next
   invoke CloseHandle,hSnap
   xor eax,eax
   ret
ListProcess ENDP 


Injection   PROC Nom:DWORD ; retourne 0 si erreur
   LOCAL hModule,hNewModule,hProcess,dwSize,dwPid,dwBytesWritten,dwTid:DWORD

   mov hModule,rv(GetModuleHandle, 0)
   mov edi, eax
   assume edi:ptr IMAGE_DOS_HEADER
   add edi, [edi].e_lfanew
   add edi, sizeof dword
   add edi, sizeof IMAGE_FILE_HEADER
   assume edi:ptr IMAGE_OPTIONAL_HEADER32
   mov eax, [edi].SizeOfImage
   mov dwSize, eax
   assume edi:NOTHING

   fn ListProcess,Nom
   test eax,eax
   je @F

   mov hProcess,rv(OpenProcess, PROCESS_ALL_ACCESS, FALSE, eax)
   invoke VirtualFreeEx, hProcess, hModule, 0, MEM_RELEASE
   mov hNewModule,rv(VirtualAllocEx, hProcess, hModule, dwSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE )
   invoke WriteProcessMemory, hProcess, hNewModule, hModule, dwSize, addr dwBytesWritten
   test eax,eax
   je @F
   invoke CreateRemoteThread, hProcess, 0, 0, addr InjectThread, hModule, 0, addr dwTid
   ret
 
 
   @@:
   xor eax,eax
   ret
Injection endp   


Syntaxe d'appel:

invoke Injection, [processus cible]



Ce nouveau développement de notre Hook correspond à l'exemple 4. Cet exemple va ajouter:

- Notre moteur d'injection pour injecter une routine d'interception dans un processus cible
- La routine d'interception un poil modifiée pour installer le Hook




EXEMPLE 4


Comme nous allons cibler un processus précis il nous faut être sûr qu'il existe. J'ai choisis la calculatrice Windows: 'calc.exe'. Notre exemple va préalablement l'exécuter puis injecter notre Hook dans son processus. Le Hook s'occupe cette fois de détourner l'API ExitProcess. Ainsi lorsque l'utilisateur termine la calculatrice, celle-ci affiche une MessageBox  :)


Voici le code au complet de l'exemple 4:

Code:

   comment *
   Exemple d'API Hook en user mode de type inline hook
   par Faiseur
   Compatible Windows 2000/XP/Vista/Seven :)
   *

   .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
      notification         db "Hooked",0    
      Stub          db 20 dup (90h) ; si le stub doit dépasser 5 bytes on a de la marge       
   .code

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 = adresse effective du saut 
   sub eax, 5 ; - 5 bytes

 
   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

   ; On place un saut à la procédure originale (targetProc) à la fin du stub
   mov esi, ebx 
   add esi, SizeStub ;on se positionne à la fin du stub

   sub edi, ebx
   sub edi, 5; SizeStub
 
   mov BYTE PTR [esi], 0E9h ; on place le saut relatif
   mov [esi +1], edi
 
   ret
 
InstallHook endp


ExHook PROC uses ebx edi esi ebp uExitCode:DWORD

   invoke MessageBox,0,addr notification,addr notification,0
   invoke pr1 PTR Stub,uExitCode 
   ret
ExHook endp
 
InjectThread PROC uses ebx
   LOCAL Buf[128]:BYTE
   LOCAL count:DWORD
    

   LoadProcAddress "kernel32.dll","ExitProcess"
   .if eax == 0
      ret
   .endif
   push eax
    
    
   mov ebx,eax
   mov count,0
   .WHILE count <= 4
      invoke c_Catchy,ebx
      add count,eax
      add ebx,eax
   .ENDW

   pop eax
   invoke InstallHook,addr ExHook,eax,addr Stub,count

   ret
InjectThread endp

ListProcess PROC Cible:DWORD
   LOCAL ProcEntry:PROCESSENTRY32
   LOCAL szFilename[MAX_PATH]:BYTE 
   LOCAL hSnap:DWORD
 
   mov ProcEntry.dwSize, SIZEOF ProcEntry
   mov hSnap,rv(CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS, 0)
   mov edi, Cible 
   invoke Process32First, hSnap, ADDR ProcEntry
 
Next: invoke lstrcmpiA, edi, ADDR ProcEntry.szExeFile
   cmp eax,0
   jnz Cont
   invoke CloseHandle,hSnap
   mov eax, ProcEntry.th32ProcessID
   ret
Cont:  invoke Process32Next, hSnap, ADDR ProcEntry
   cmp eax,0
   jnz Next
   invoke CloseHandle,hSnap
   xor eax,eax
   ret
ListProcess ENDP 


Injection   PROC Nom:DWORD ; retourne 0 si erreur
   LOCAL hModule,hNewModule,hProcess,dwSize,dwPid,dwBytesWritten,dwTid:DWORD

   mov hModule,rv(GetModuleHandle, 0)
   mov edi, eax
   assume edi:ptr IMAGE_DOS_HEADER
   add edi, [edi].e_lfanew
   add edi, sizeof dword
   add edi, sizeof IMAGE_FILE_HEADER
   assume edi:ptr IMAGE_OPTIONAL_HEADER32
   mov eax, [edi].SizeOfImage
   mov dwSize, eax
   assume edi:NOTHING

   fn ListProcess,Nom
   test eax,eax
   je @F

   mov hProcess,rv(OpenProcess, PROCESS_ALL_ACCESS, FALSE, eax)
   invoke VirtualFreeEx, hProcess, hModule, 0, MEM_RELEASE
   mov hNewModule,rv(VirtualAllocEx, hProcess, hModule, dwSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE )
   invoke WriteProcessMemory, hProcess, hNewModule, hModule, dwSize, addr dwBytesWritten
   test eax,eax
   je @F
   invoke CreateRemoteThread, hProcess, 0, 0, addr InjectThread, hModule, 0, addr dwTid
   ret
 
 
   @@:
   xor eax,eax
   ret
Injection endp   

start:
   fn WinExec,"calc.exe",SW_SHOW
   fn Injection,"calc.exe"
   exit
end start


Pour que l'injection fonctionne il est nécessaire de modifier certains paramètres lors de la compilation: accès en écriture, réunir les données .data dans la section .text. et modifier l'adresse de l'entrypoint pour éviter un conflit avec le processus cible.

Avec polink ces options additionnelles suffisent: /merge:.data=.text /base:0x13140000

Avec le linker de Masm (link.exe) il est nécessaire de préciser le mode "écriture" pour la section jointe: /base:0x13140000 /merge:.data=.text /section:.text,RWX




HOOK GLOBAL

Nous pourrions mettre en place un hook global. Cela peut se faire de différentes manières mais ce n'est pas l'object de ce tutoriel d'en proposer un.

On peut bien sûr injecter le même Hook dans tous les processus actifs avec notre moteur d'injection. Cela sera fait mais il faut penser ensuite à la création des nouveaux processus. Le Hook peut par exemple intercepter la création de nouveaux processus en contrôlant l'API 'CreateProcessW'.

D'autres voies sont possibles comme l'utilisation de 'SetWindowsHookEx' pour injecter une dll dans tous les processus graphiques ou encore avec la clef 'HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs'.




UNINSTALL


Il nous reste à mettre en place une fonction 'UninstallHook' pour prévenir tous les cas de figure. En effet, comment faire pour désinstaller le hook si nous n'avons plus besoin de surveillance ? C'est très simple à mettre en place du moment que, je sais je me répète, l'on a assimilé le principe. La procédure de désinstallation doit simplement:

1. Récupérer l'adresse de la fonction API à unhook
2. Remplacer le jmp par les 5 premiers bytes copiés dans le Stub.

Et c'est tout !


Voici ma fonction 'UninstallHook':

Code:

UninstallHook proc uses ebx edi dll,fonction,pStub:DWORD
   LOCAL lpflOldProtect:ULONG

   mov ebx, pStub
   invoke LoadLibrary,dll
   invoke GetProcAddress,eax,fonction
   .if eax == 0
      ret
   .endif
   mov edi,eax ; fonction cible
 
   invoke VirtualProtect, edi, 5, PAGE_EXECUTE_READWRITE, addr lpflOldProtect ; accès écriture
   invoke RtlMoveMemory,edi,ebx,5 ; on enlève le jump
   invoke VirtualProtect, edi, 5, lpflOldProtect, addr lpflOldProtect ; protection en écriture
   ret

UninstallHook endp


On récupère l'adresse de l'API avec 'LoadLibrary' et 'GetProcAddress'. Une fois cela fait on ouvre la mémoire aux 5 premiers bytes de l'API puis on copie les 5 premiers bytes du stub, qui vont ainsi remplacer les 5 premiers bytes du saut. Nous n'avons pas besoin de copier plus que les 5 premiers bytes. C'est certainement la fonction la plus simple de notre librairie de Hook. Elle sera donc ajoutée à notre exemple 5 (notez les 5 * 5, hé hé).




EXEMPLE 5


Dans cet exemple j'ai mis en place deux fonctions de ma librairie Hook, qui s'avère ainsi plus complète et facile d'appel: 'InstallHook' et 'UninstallHook'.


'InstallHook'

invoke InstallHook, [Dll], [Fonction], [fonction d'interception], [Stub]


Exemple:
Code:
invoke InstallHook, "kernel32.dll","ExitProcess",addr ExHook,addr Stub



'UninstallHook'

invoke UninstallHook, [Dll], [Fonction], [Stub]


Exemple:
Code:
invoke UninstallHook,"kernel32.dll","ExitProcess",addr Stub





EXEMPLE 5


Voici le code:

Code:

   comment *
   Exemple d'API Hook en user mode de type inline hook
   par Faiseur
   Compatible Windows 2000/XP/Vista/Seven :)
   *

   .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
      nouveau         db "Hooked",0    
      Stub          db 20 dup (90h)  ; si le stub doit dépasser 5 bytes on a de la marge
       
   .code

InstallHook proc uses ebx edi esi dll,fonction,hookProc,pStub:DWORD
   LOCAL lpflOldProtect:ULONG
   LOCAL sizeStub:DWORD ; taille du Stub
 
   invoke LoadLibrary,dll
   invoke GetProcAddress,eax,fonction
 
   .if eax == 0
      ret
   .endif
   push eax
    
   mov ebx,eax
   mov sizeStub,0
   .WHILE sizeStub <= 4
      invoke c_Catchy,ebx
      add sizeStub,eax
      add ebx,eax
   .ENDW 
 
   pop eax
   mov edi, eax ; fonction cible
   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 = adresse effective du saut 
   sub eax, 5 ; - 5 bytes
 
   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
 
   ; On place un saut à la procédure originale (targetProc) à 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

   mov eax,sizeStub
   ret
 
InstallHook endp

UninstallHook proc uses ebx edi dll,fonction,pStub:DWORD
   LOCAL lpflOldProtect:ULONG

   mov ebx, pStub
   invoke LoadLibrary,dll
   invoke GetProcAddress,eax,fonction
   .if eax == 0
      ret
   .endif
   mov edi,eax ; fonction cible
 
   invoke VirtualProtect, edi, 5, PAGE_EXECUTE_READWRITE, addr lpflOldProtect ; accès écriture
   invoke RtlMoveMemory,edi,ebx,5 ; on enlève le jump
   invoke VirtualProtect, edi, 5, lpflOldProtect, addr lpflOldProtect ; protection en écriture
   ret

UninstallHook endp


ExHook PROC uses ebx edi esi ebp handle:DWORD

   invoke MessageBox,0,addr nouveau,addr nouveau,0
   invoke pr1 PTR Stub,handle 
   ret
ExHook endp
 
InjectThread PROC

   fn InstallHook, "kernel32.dll","ExitProcess",addr ExHook,addr Stub
   fn UninstallHook,"kernel32.dll","ExitProcess",addr Stub
   ret
InjectThread endp

ListProcess PROC Cible:DWORD
  LOCAL ProcEntry:PROCESSENTRY32
  LOCAL szFilename[MAX_PATH]:BYTE 
  LOCAL hSnap:DWORD
 
  mov ProcEntry.dwSize, SIZEOF ProcEntry
  mov hSnap,rv(CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS, 0)
  mov edi, Cible 
  invoke Process32First, hSnap, ADDR ProcEntry
 
Next: invoke lstrcmpiA, edi, ADDR ProcEntry.szExeFile
  cmp eax,0
  jnz Cont
  invoke CloseHandle,hSnap
  mov eax, ProcEntry.th32ProcessID
  ret
Cont:  invoke Process32Next, hSnap, ADDR ProcEntry
  cmp eax,0
  jnz Next
  invoke CloseHandle,hSnap
  xor eax,eax
  ret
ListProcess ENDP 


Injection   PROC Nom:DWORD ; retourne 0 si erreur
   LOCAL hModule,hNewModule,hProcess,dwSize,dwPid,dwBytesWritten,dwTid:DWORD

   mov hModule,rv(GetModuleHandle, 0)
   mov edi, eax
   assume edi:ptr IMAGE_DOS_HEADER
   add edi, [edi].e_lfanew
   add edi, sizeof dword
   add edi, sizeof IMAGE_FILE_HEADER
   assume edi:ptr IMAGE_OPTIONAL_HEADER32
   mov eax, [edi].SizeOfImage
   mov dwSize, eax
   assume edi:NOTHING

   fn ListProcess,Nom
   test eax,eax
   je @F

   mov hProcess,rv(OpenProcess, PROCESS_ALL_ACCESS, FALSE, eax)
   invoke VirtualFreeEx, hProcess, hModule, 0, MEM_RELEASE
   mov hNewModule,rv(VirtualAllocEx, hProcess, hModule, dwSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE )
   invoke WriteProcessMemory, hProcess, hNewModule, hModule, dwSize, addr dwBytesWritten
   test eax,eax
   je @F
   invoke CreateRemoteThread, hProcess, 0, 0, addr InjectThread, hModule, 0, addr dwTid
   ret
 
   @@:
   xor eax,eax
   ret
Injection endp   

start:
   fn WinExec,"calc.exe",SW_SHOW
   fn Injection,"calc.exe"
   exit
end start


Résultat: la calculatrice n'affichera plus de MessageBox une fois terminée...puisque nous avons ajouté tout de suite après le Hook un UnHook à cet endroit:

Code:

InjectThread PROC

  fn InstallHook, "kernel32.dll","ExitProcess",addr ExHook,addr Stub
  fn UninstallHook,"kernel32.dll","ExitProcess",addr Stub
  ret
InjectThread endp



Et voilà. Si tout va bien j'ajouterai plus tard un appendice à ce tutoriel pour développer d'autres aspects que je n'ai fait que survoler: une méthode de hook global et une méthode pour unhook tout type de hook par patch d'API (c'est possible sous certaines conditions).


A bientôt,

Faiseur

1 commentaires:

  1. Bonjour,

    Excellent article, qui m'a permis de détourner
    afin de les améliorer, les librairies HotBasic.

    Ce langage supporte les fonctions exportée de
    librairies statiques.

    Pierrot

    pierrotstudio@yahoo.fr

    RépondreSupprimer