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