HookFunc v1.00 FINAL
Voilà, le code est enfin parfait :). J'ai écrit ce programme dans un seul but : comprendre. C'était un peu comme un défi. Je voudrais maintenant vous expliquer un peu sont fonctionnement.
- LE LOADER -
Voici les grandes étapes de son exécution :
- Recherche des informations et
allocation de la mémoire pour les y inscrire
- Exécution du programme
concerné et attente qu'il soit à l'EntryPoint
- Modification du code du
programme et sauveguarde du Context
- Attente de la fin de l'exécution
du code injecté
- Restauration du code du
programme ainsi que du context
- Attente de la fin du programme
pour terminer
I. Recherche des informations et allocation de la mémoire pour les y inscrire
Pour démarrer, je récupère l'Handle de mon programme
invoke GetModuleHandle,0
mov hInstance, eax
Je récupère ensuite l'addresse du label DataPageAdd qui se trouve dans l'InjectedCode et qui contient l'adresse de la structure DATA_PAGE qui contiendra les information concernant le Hook. Une fois l'adresse récupérer, je modifie la caracteristique de la mémoire pour autoriser l'acces en ecriture afin de ne pas avoir de page fault.
lea ebx, offset DataPageAdd
invoke VirtualProtect,ebx,
4, PAGE_EXECUTE_READWRITE, offset OldProt
J'alloue ensuite de l'espace mémoire en une
zone mémoire qui peut être utilisée par tous les process. Ceci est possible en utilisant le
paramètre non documenté 08000000h. Cette page mémoire sera utilisée pour écrire
les donnée nécessaire au
Hook et ne comportera qu'une structure DATA_PAGE.
Je sauvegarde l'adresse de la mémoire allouée dans pDataPage.
invoke VirtualAlloc, NULL, SizeOf
DATA_PAGE, MEM_COMMIT OR 08000000h, PAGE_EXECUTE_READWRITE
mov pDataPage, eax
Maintenant que j'ai l'adresse qui contiendra la structure
DATA_PAGE en mémoire, je l'inscrit dans l'InjectedCode.
mov dword ptr[ebx], eax
J'écrit récupère juste après
les adresses de LoadLibraryA et GetProcAddress et je les inscrit dans la structure DataPage
Rem : Les Adresses sont les même pour tous les process car ces 2 Fcts sont dans
Kernel32.dll qui se trouve toujours dans le même espace mémoire!
invoke LoadLibraryA,offset Kernel32_Text
;Récupère l'handle de KERNEL32
push eax
invoke GetProcAddress,eax,offset
GetProcAddress_Text ;Récupère l'adresse de GetProcAddress
mov ebx, pDataPage
assume ebx:PTR DATA_PAGE
;EBX est un pointeur vers une structure DATA_PAGE
mov [ebx].pGetProcAddress,eax
;inscrit l'adresse de GetProcAddresse dans la struct.
pop eax
invoke GetProcAddress,eax,offset
LoadLibrary_Text ;fait la même chose avec loadLibraryA
mov [ebx].pLoadLibraryA,eax
Je transfère ensuite le Nom de la DLL de Hook que nous voulons injecter dans l'espace d'addresse du programme concerné ainsi que de la HookFunc dans la structure DATA_PAGE et on y inscrit aussi l'adresse du pointeur.
mov ecx, SizeOf HookDLL
mov esi, offset HookDLL
lea edi, [ebx].Lib_Name
rep movsb
;Transfère ECX bytes de DS:ESI vers DS:EDI
mov ecx, SizeOf HookFunc
mov esi, offset HookFunc
lea edi, [ebx].Fct_Name
rep movsb
mov eax, pFunc
mov [ebx].pFct, eax
Là, je récupère l'adresse
de l'EntryPoint. Je vous conseille de reguarder le fichier PE.TXT fournit avec ce programme pour bien comprendre
le Format PE.
invoke CreateFileA,ADDR ProgramName,GENERIC_READ+GENERIC_WRITE,\
;Onvre le fichier en Read/Write
FILE_SHARE_WRITE,0,OPEN_EXISTING,\
FILE_ATTRIBUTE_NORMAL,0
mov hFile, eax
;Sauve l'Handle
invoke SetFilePointer, hFile,
03Ch, NULL, FILE_BEGIN
;Récupère l'adresse du Coff Header
invoke ReadFile, hFile, ADDR
Buffer, 4, ADDR NumberOfBytesRead, NULL
mov eax, Buffer
add eax, 028h
invoke SetFilePointer, hFile,
eax, NULL, FILE_BEGIN
invoke ReadFile, hFile, ADDR
pEntryPoint, 4, ADDR NumberOfBytesRead, NULL ;Récupère l'EntryPoint
mov eax, Buffer
add eax, 034h
invoke SetFilePointer, hFile,
eax, NULL, FILE_BEGIN
invoke ReadFile, hFile, ADDR
Buffer, 4, ADDR NumberOfBytesRead, NULL ;Récupère l'ImageBase
mov eax,Buffer
add pEntryPoint,eax
;Rajoute l'ImageBase à l'EntryPoint
invoke CloseHandle, hFile
;Ferme le fichier
II. Exécution du programme concerné et attente qu'il soit à l'EntryPoint
Maintenant que nous avons mis toutes les informations dans la structure, je crée le process en mode Suspendu. ( Cela veux dire que le programme est mappé dans son espace mémoire, mais que les DLL importée ne le sont pas encore et que l'IAT n'est donc toujours pas rectifiée. ) J'active le mode DEBUG_PROCESS aussi afin d'utiliser les DEBUG_API gentillement fournit par Microsoft pour prendre le contrôle d'un programme.
invoke GetStartupInfo,ADDR startInfo
invoke CreateProcess,ADDR
ProgramName,NULL,NULL,NULL,FALSE,\
NORMAL_PRIORITY_CLASS OR DEBUG_PROCESS,\
NULL,NULL,ADDR startInfo,ADDR processInfo
Ensuite, le programme rentre dans une boucle WaitForDebugEvent/ContinueDebugEvent qui loop jusqu'à ce qu'a ce qu'il y aie une Exception car une Exception est produite lorsque l'EIP du programme est l'EntryPoint.
@Loop1:
invoke
WaitForDebugEvent, ADDR lpDebugEvent, 0FFFFFFFFh
mov
eax, lpDebugEvent.dwDebugEventCode
cmp
eax, EXCEPTION_DEBUG_EVENT
;On reguarde si il y a eu une Exception ?
jz
@EndLoop1
;Si oui, on est à l'EntryPoint
@ContinueLoop1:
invoke
ContinueDebugEvent , processInfo.dwProcessId, processInfo.dwThreadId, DBG_CONTINUE
jmp
@Loop1
@EndLoop1:
III. Modification du code du programme et sauveguarde du Context
J'injecte à présent le code dans le process sans oublier de sauveguarder le code original. pour cela, je vais utiliser ReadProcessMemory et WriteProcessMemory qui permet de lire la mémoire d'un autre Process.
invoke OpenProcess, PROCESS_VM_OPERATION,
TRUE, processInfo.dwProcessId ;avant d'utilise Read/WriteProcessMemory
mov hProcess,eax
;Il vous faut ouvrir le process en mode
mov eax, offset EndInjectedCode
;PROCESS_VM_OPERATION
sub eax, offset InjectedCode
;On met la taille de l'InjectedCode dans EAX
push eax
invoke VirtualAlloc, NULL,
eax, MEM_COMMIT, PAGE_EXECUTE_READWRITE ;On alloue de la mémoire pour
sauver le code
mov pBufferPage, eax
;original
pop eax
push eax
invoke ReadProcessMemory,
hProcess, pEntryPoint, pBufferPage, eax, NULL ;On sauve le code original dans le buffer
pop eax
push eax
invoke VirtualProtectEx, hProcess,
pEntryPoint, eax, PAGE_EXECUTE_READWRITE, offset OldProt
lea eax,pFunc
;On autorise l'acces en écriture
mov eax,dword ptr [eax]
invoke VirtualProtectEx, hProcess,
eax, 4, PAGE_EXECUTE_READWRITE, NULL
pop eax
mov ebx, offset InjectedCode
invoke WriteProcessMemory,
hProcess, pEntryPoint, ebx, eax, NULL ;Et on injecte notre Code
On sauveguarde le context ( Etat des registres, etc..
) pour le rétablir après exécution !
Rem : Ceci n'aurait pas été nécessaire si nous aurions mis un PUSHA
POPA dans notre InjectedCode.. m'enfin ! :)
mov ThreadContext.ContextFlags,
CONTEXT_FULL
invoke GetThreadContext, processInfo.hThread,
ADDR ThreadContext
IV. Attente de la fin de l'exécution du code injecté
Comme tout à l'heure, on rentre dans une boucle Wait/Continue et on attend qu'une Exception soit produite car l'InjectedCode termine par xor eax,eax / mov [eax],eax qui provoque une Exception Page Fault.
@ContinueLoop2:
invoke ContinueDebugEvent
, processInfo.dwProcessId, processInfo.dwThreadId, DBG_CONTINUE
@Loop2:
invoke
WaitForDebugEvent, ADDR lpDebugEvent, 0FFFFFFFFh
mov
eax, lpDebugEvent.dwDebugEventCode
cmp
eax, EXCEPTION_DEBUG_EVENT
jnz
@ContinueLoop2
Rem : On doit d'abord faire le ContinueDebugEvent car c'est un WaitForDebugEvent qui a terminer le loop précédent !
V. Restauration du code du programme ainsi que du context
Ce qu'on doit maintenant faire, c'est remettre le code original et rectifier l'EIP.
mov eax, offset EndInjectedCode
sub eax, offset InjectedCode
push eax
;On met la taille de l'InjectedCode dans EAX
invoke WriteProcessMemory,
hProcess, pEntryPoint, pBufferPage, eax, NULL ;injecte le code original dans le process
pop eax
invoke VirtualProtectEx, hProcess,
pEntryPoint, eax, OldProt, offset OldProt ;Restaure la protection mémoire
invoke VirtualFree, pBufferPage,
NULL, MEM_RELEASE
;Libere le buffer et la DataPage
invoke VirtualFree, pDataPage,
NULL, MEM_RELEASE
mov ebx, pEntryPoint
mov ThreadContext.regEip,
ebx
;Remet l'adresse de l'EP dans EIP
invoke SetThreadContext, processInfo.hThread,
ADDR ThreadContext ;Et restaure le Context
VI. Attente de la fin du programme pour terminer
On rentre à nouveau dans une dernière
boucle de Debug qui attend que le programme soit terminé pour quitter.
@Loop3:
invoke ContinueDebugEvent
, processInfo.dwProcessId, processInfo.dwThreadId, DBG_CONTINUE
invoke WaitForDebugEvent,
ADDR lpDebugEvent, 0FFFFFFFFh
mov eax, lpDebugEvent.dwDebugEventCode
cmp eax, EXIT_PROCESS_DEBUG_EVENT
jnz @Loop3
invoke CloseHandle, hProcess
invoke ExitProcess,0
- L'InjectedCode -
Je ne crois pas avoir besoin de faire plus de commentaire.
InjectedCode:
jmp StartIC
DataPageAdd:
dd ?
StartIC:
call @@1
;On récupère l'EIP
@@1:
pop eax
sub eax, 9
;EAX pointe sur DataPageAdd ( modifié par le Loader ! )
mov eax,[eax]
assume eax:PTR DATA_PAGE
;EAX est un pointeur vers une structure DATA_PAGE
mov ebx, [eax].pGetProcAddress
;On récupère les infos de la structure DATA_PAGE
lea ecx, [eax].Lib_Name
lea edi, [eax].Fct_Name
mov esi, [eax].pFct
mov eax, [eax].pLoadLibraryA
push ECX
call eax
;invoke LoadLibraryA, 'Hook.dll'
push EDI
push eax
call ebx
;invoke GetProcAddress, HookHANDLE, 'NewMessageBoxA'
mov dword ptr[esi],eax ;modifie le pointeur vers la fonction à hooker.
xor eax, eax
mov dword ptr[eax],eax
;Crée une Exception type PageFault.
EndInjectedCode:
- UN EXEMPLE D'UTILISATION : Hook de MessageBoxA dans NotePad.EXE -
Toute la conception du programme à été faite avec cet unique but : hooker la fonction API MessageBoxA dans NotePad.EXE. Donc, pour faire cela, c'est maintenant très simple, il suffit de modifier les variable suivantes :
ProgramName
db 'Notepad.EXE',0
HookDLL
db 'Hook.dll',0
HookFunc
db 'NewMessageBoxA',0
pFunc
dd 004064A8h
pFunc est ici l'adresse du pointer vers MessageBoxA dans l'IAT. Pour le récupérer, avec WDasm vous trouvez :
* Reference To: USER32.MessageBoxA, Ord:01ACh
|
:00401ECB FF15A8644000
Call dword ptr [004064A8]
Pour hooker MessageBoxA, Il suffit donc de modifier l'adresse se trouvant en 004064A8 qui est la valeur de pFunc. !
Rem : HookFunc peut s'utiliser dans des situation plus générale. Par exemple, Christal propose aujourd'hui (28/04/2000) comme Defi le programme CutClean qui a été écrit en Delphi ( reconnaissable par la présence de TForm1 dans le ressources ). En utilisant DeDe disponible sur Protools , on peut récupèrer les adresses des différentes fonctions exécutée lors d'évènement précis. Par exemple, si nous voulons hooker la fonction exécutée lors de l'appuis sur le bouton Cut, il suffit de récupérer le DFM avec DeDe et vous recherchez après 'Cut' et vous trouvez :
object Button1: TButton
Left
= 192
Top
= 0
Width
= 73
Height
= 33
Caption
= 'Cut'
Enabled
= False
TabOrder
= 3
OnClick
= Button1Click ;Fonction Button1Click exécutée lors du Click sur le bouton Cut !
end
On va ensuite dans DCU et on aperçois dans la colonne à coté de Button1Click le RVA de cette fonction : 43BFD8h. On ouvre ensuite CutClean.EXE avec HView et on recherche D8 BF 43 00 en hexa et on se retrouve ici :
_ CUTCLEAN.EXE FR
PE.0043B4CA -------- 356864 ¦ Hiew 6.10 (c)SEN
_.0043B410: 34 B6 43 00-0C 43 6F 6D-70
75 74 65-50 61 63 6B 4ÂC ComputePack
_.0043B420: 73 0F 00 80-B8 43 00 08-4E
61 6D 65-46 69 6C 65 s¤ Ç©C NameFile
_.0043B430: 1B 00 90 BE-43 00 14 44-72
69 76 65-43 6F 6D 62 É¥C ¶DriveComb
_.0043B440: 6F 42 6F 78-31 43 68 61-6E
67 65 1E-00 B0 BE 43 oBox1Change _¥C
_.0043B450: 00 17 44 69-72 65 63 74-6F
72 79 4C-69 73 74 42 DirectoryListB
_.0043B460: 6F 78 31 43-68 61 6E 67-65
13 00 E4-BE 43 00 0C ox1Change õ¥C
_.0043B470: 42 75 74 74-6F 6E 32 43-6C
69 63 6B-16 00 F4 BE Button2Click ¶¥
_.0043B480: 43 00 0F 43-6F 6D 62 6F-42
6F 78 31-43 68 61 6E C ¤ComboBox1Chan
_.0043B490: 67 65 18 00-6C BF 43 00-11
52 61 64-69 6F 42 75 ge l+C RadioBu
_.0043B4A0: 74 74 6F 6E-37 43 6C 69-63
6B 1E 00-D4 BF 43 00 tton7Click È+C
_.0043B4B0: 17 44 69 72-65 63 74 6F-72
79 4C 69-73 74 42 6F DirectoryListBo
_.0043B4C0: 78 32 43 68-61 6E 67 65-13
00 D8 BF-43 00 0C 42 x2Change Ï+C B
Maintenant, si vous voulez hooker cette fonction, il suffit d'utiliser HookFunc et inscrire dans les variable
pFunc dd 0043B4BAh
-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-
-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-FIN-
Amicalement,
TeeJi