Tutoriel réalisé par Faiseur
Niveau: [ Intermédiaire - Confirmé ]
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
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
Bonjour,
RépondreSupprimerExcellent 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