Retour


DEBUGGER (Partie 3)
MEMORY REVERSE ENGENEERING




Bon comme je vous l'avais dit, je suis tombé y a pas longtemps (aujourd'hui on est le S.16/09/2000) sur un programme qui après s'être fait tracer une première fois par le Loader de SoftIce, s'est comme immunisé contre lui. Maintenant plus aucun BP ne fonctionne sur ce programme. Avec W32Dasm j'avais retrouvé une ligne de code intéressante un saut qui mettait 'Registered to:' si à la ligne précedente 'xxxx cmp [bbbb], 0' était vrai. Ce qu'il me fallait c'est donc poser un 'BPMB bbbb w', mais voilà W32Dasm étant incapable de le faire et SoftIce étant hors jeu, il fallait trouver autre chose. Voici donc un prog en ASM qui recrée cette fonction.

Je vous ai mis ce petit debugger ainsi qu'une cible appelée Win2.exe dans le Zip.
La cible possède 4 instructions qui modifient le contenu de l'adresse 00403020. Vous trouverez des trucs dans le genre : :00401053 Mov [00403020], 0. Ce debugger inspectera donc ligne après ligne le contenu de 00403020 et vous verrez qu'il retrouvera entre autre l'adresse 00401053.
Sachez que ce simple debugger fonctionnera sur n'importe quel autre prog, il suffira d'adapter avant l'adresse que vous souhaiter espionner. Une dernière chose 'ligne par ligne' signifie qu'il fonctionne en 'Single-Step' ou 'Pas à Pas' donc c'est très très lent. Si SoftIce peut breaker en qques secondes, lui peut attendre pendant plus d'une dizaine de minute, cependant il y arrivera lentement mais sûrement.









Je ne vais pas tout décortiquer comme dans les deux précédents cas, je vais plutôt vous faire voir les étapes qui m'ont petit à petit amenées à cette solution.

L'ossature est le Tut30 d'ACZELION.
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "BPMB_EMULATION (Morgatte)",0 ;<-- Titre de mon unique fenêtre (une MessageBox) ofn OPENFILENAME <> ;<-- Structure pour créer la DialogBox qui sert à FilterString db "Executable Files",0,"*.exe",0 ;choisir un fichier (et son filtre de type de fichiers) db "All Files",0,"*.*",0,0 ;?????????? ;?????????? ;?????????? ;?????????? ;?????????? ;?????????? ;?????????? .data? buffer db 512 dup(?) startinfo STARTUPINFO <> ;<-- Structure pour la fonction GetStartUpInfo pi PROCESS_INFORMATION <> ;<-- Structure pour les fonctions Get&SetThreadContext DBEvent DEBUG_EVENT <> ;<-- Structure pour receuillir le Debugging événement (WaitForDebugEvent) context CONTEXT <> ;<-- Stucture pour recupérer le context (en particulier regEip) ;?????????? ;?????????? .code start: mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER \ or OFN_HIDEREADONLY ;Dans un premier temps on ouvre la DialogBox permettant invoke GetOpenFileName, ADDR ofn ;de sélectionner le programme cible qui sera debuggé .if eax==TRUE invoke GetStartupInfo,addr startinfo ;On récupère des infos sur lui (pour son demarrage) invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \ ;Crée le Process et le DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, \ ;lien primaire de la cible NULL, NULL, addr startinfo, addr pi ;puis la démarre. ;?????????? ;?????????? ;?????????? .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE ;Attend qu'un Debugging événement ne se ;produise ;--------------ICI-mettre-la-valeur-de-regEip-(Donc de la ligne suspecte)------- ;?????????? ;?????????? ;?????????? ;?????????? ;?????????? ;------------------------------------------------------------------------------- .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT ; si cet évén représente la fin .break ; de la cible alors on sort du debugger. .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT ; si c'est une exception et en particulier .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT ; un BP, mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context ; alors on récupère le context en particulier or context.regFlag,100h ;regEip et le Trap Bit qu'on met à 1 pour fonctionner en pas à pas. invoke SetThreadContext,pi.hThread, addr context ; on réinjecte ce context dans la cible. invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue ; on redemarre Toute cette partie n'est exécutée que la toute première fois pour initialiser ; le mode de fonctionnement en pas à pas. .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP ; Maintenant si on fonctionne en mode pas à pas, on entre dans cette branche. Cependant le fait d'avoir ;détecté ce mode l'annule maintenant car le Trap Bit est remis automatiquement à 0 pour arrêté le pas à ;pas. Il faudra donc de nouveau reprendre le mode pas à pas dans les lignes suivantes. ;-----------ICI-mettre-la-lecture-du-contenu-de-l'adresse--------------------- ;?????????? ;?????????? ;?????????? ;?????????? ;?????????? ;?????????? ;----------------------------------------------------------------------------- ;?????????? ;?????????? invoke GetThreadContext,pi.hThread,addr context ; on rappelle le context or context.regFlag,100h ; on remet le Trap Bit à 1 invoke SetThreadContext,pi.hThread, addr context ; Et on réinjecte le new context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, \ DBG_EXCEPTION_NOT_HANDLED .endw .endif invoke CloseHandle,pi.hProcess ;Quand on termine totalement le débugger on DOIT refermer le Process et le Lien invoke CloseHandle,pi.hThread ;primaire de la cible, sinon la fonction CreateProcess n'apprécie pas du tout, et là invoke ExitProcess, 0 ;votre ordi plante. end start

EXPLICATIONS:

Quand on lance un programme (CreateProcess ou DebugActiveProcess), avant même que sa première instruction ne soit exécutée, windows nous envoie le 'Debugging événement' : EXCEPTION_BREAKPOINT. La première routine .ElseIf .Endif sert donc à redemarrer car ce BP nous aurait stoppé là. Mais elle sert aussi et surtout à mettre pour la première fois le Trap Bit à 1 pour qu'on puisse tracer ligne par ligne (en pas à pas). Par la suite jamais plus nous n'entrerons dans cette branche car il n'y aura pas d'autres EXCEPTION_BREAKPOINT.

La seconde branche nous fait rentrer si on fonctionne en Pas à Pas (ou EXCEPTION_SINGLE_STEP), et maitenant c'est vrai. Dès que cet événement est détecté, le Trap Bit est purifié pour un traçage normal, mais ça ne nous arrange pas donc dans les lignes qui suivent, il faudra de nouveau rappeler le context pour récupérer le Trap Bit et le remettre à 1, puis réinjecter ce nouveau context, et enfin on redémarre.

Pour l'instant ce programme fonctionne. Il ne fait rien d'autre qu'exécuter la cible ligne par ligne, c'est tout. Puisqu'il s'arrête sur chaque instruction, le bon truc serait qu'il vérifie le contenu d'une adresse fixe pour voir si une de ces instructions modifie justement le contenu de l'adresse en question...









.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "BPMB_EMULATION (Morgatte)",0 ofn OPENFILENAME <> FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 ;?????????? ;?????????? ;?????????? ;?????????? change dd FALSE ;<------------ pas de changement du contenu à surveiller contenu_de_depart dd 00000065h ;<--- initialisation de la valeur du contenu (101 pas courant) adresse_a_lire dd 00403020h ;<--- adresse a surveiller dans Win2.exe .data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> context CONTEXT <> lire dd ? ; <--- buffer qui recevra le contenu de l'adresse instruction après instruction. ;?????????? .code start: mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER \ or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \ DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, \ NULL, NULL, addr startinfo, addr pi invoke ReadProcessMemory, pi.hProcess, adresse_a_lire, addr lire, 4, NULL mov eax, lire ;Le contenu de l'adresse à surveillée est placé dans LIRE. mov contenu_de_depart, eax ; ici on initialise Contenu_de_depart pour qu'il ; prenne la même valeur que lire. Comme ça CHANGE est Faux! .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE ;--------------ICI-mettre-la-valeur-de-regEip-(Donc de la ligne suspecte)------- ;?????????? ;?????????? ;?????????? ;?????????? ;?????????? ;------------------------------------------------------------------------------- .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT .break .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP ;-----------ICI-mettre-la-lecture-du-contenu-de-l'adresse--------------------- invoke ReadProcessMemory, pi.hProcess, adresse_a_lire, addr lire, 4, NULL mov eax, lire ; Le contenu de l'adresse à surveillée est .if contenu_de_depart!=eax; placé dans LIRE. Si LIRE et son ancienne mov contenu_de_depart, eax; valeur sont <> alors CHANGE est Vrai mov change, TRUE ; .endif ;----------------------------------------------------------------------------- ;?????????? ;?????????? invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, \ DBG_EXCEPTION_NOT_HANDLED .endw .endif invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread invoke ExitProcess, 0 end start

EXPLICATIONS:

Voilà maintenant, si la valeur qui est dans l'adresse à surveillée est modifiée alors CHANGE prend la valeur Vrai. Il ne nous reste plus qu'à créer une MessageBox qui ne s'ouvrira que lorsque CHANGE est Vrai, avec comme contenu:
- l'adresse à vérifier
- le contenu de cette adresse
- et l'adresse de la ligne qui vient de modifier son contenu.









.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib includelib \masm32\lib\user32.lib .data AppName db "BPMB_EMULATION (Morgatte)",0 ofn OPENFILENAME <> FilterString db "Executable Files",0,"*.exe",0 db "All Files",0,"*.*",0,0 resultat db "COMPTE RENDU :",0Dh,0Ah db "Surveillance du contenu de l'adresse : %lx"," (ou: Bpmb %lx w)",0dh,0ah db "Le contenu vient de prendre la valeur : %lx",0dh,0ah db "Adresse suspecte qui a modifiee le contenu : %lx",0 change dd FALSE contenu_de_depart dd 00000065h adresse_a_lire dd 00403020h .data? buffer db 512 dup(?) startinfo STARTUPINFO <> pi PROCESS_INFORMATION <> DBEvent DEBUG_EVENT <> context CONTEXT <> lire dd ? EipMoinsUn dd ? ; <--- EIP=adresse suivante de la ligne suspecte...donc EIP-1ligne=le suspect!!! .code start: mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER \ or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke GetStartupInfo,addr startinfo invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \ DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, \ NULL, NULL, addr startinfo, addr pi invoke ReadProcessMemory, pi.hProcess, adresse_a_lire, addr lire, 4, NULL mov eax, lire mov contenu_de_depart, eax .while TRUE invoke WaitForDebugEvent, addr DBEvent, INFINITE ;--------------ICI-mettre-la-valeur-de-regEip-(Donc de la ligne suspecte)------- .if change==TRUE mov change, FALSE invoke wsprintf, addr buffer, addr resultat, adresse_a_lire, adresse_a_lire, lire, EipMoinsUn invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION .endif ;------------------------------------------------------------------------------- .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT .break .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, pi.hThread, addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE .continue .elseif DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP ;-----------ICI-mettre-la-lecture-du-contenu-de-l'adresse--------------------- invoke ReadProcessMemory, pi.hProcess, adresse_a_lire, addr lire, 4, NULL mov eax, lire .if contenu_de_depart!=eax mov contenu_de_depart, eax mov change, TRUE .endif ;----------------------------------------------------------------------------- mov eax, context.regEip ;------------On garde en mémoire la valeur mov EipMoinsUn, eax ;------------du EIP précédant invoke GetThreadContext,pi.hThread,addr context or context.regFlag,100h invoke SetThreadContext,pi.hThread, addr context invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId,DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, \ DBG_EXCEPTION_NOT_HANDLED .endw .endif invoke CloseHandle,pi.hProcess invoke CloseHandle,pi.hThread invoke ExitProcess, 0 end start

EXPLICATIONS:

Maintenant qu'on sait lire et qu'on sait détecter si le contenu de l'adresse change, on crée une MessageBox si la condition est Vraie.
invoke wsprintf, addr buffer, addr resultat, adresse_a_lire, adresse_a_lire, lire, EipMoinsUn sont les 4 valeurs à placer avec le texte de la MessageBox (oui 4 car je me sert 2 fois de la valeur de l'adresse à surveiller).
Normalement l'adresse suspecte est l'adresse qui vient de modifier le contenu, c'est donc le EIP où nous sommes rendu dans la cible. Donc à la place de EipMoinsUn on devrait avoir context.regEip. Mais vous savez bien qu'une instruction modifie une valeur qu'une fois que cette instruction à étée exécutée, donc on se retrouve sur le EIP suivant. Il faut donc garder le EIP précedent en mémoire pour pouvoir se servir de lui et non pas le passer. c'est pour ça qu'un peu plus bas dans le programme EipMoinsUn garde en Mémoire le context.regEip.









Voilà tout est parfait maintenant... Tout, non! deux choses posent problème.

- 1) Si la valeur du contenu est X et que le programme cible récrit par dessus un X alors il ne sera pas détecté. Par exemple la valeur du contenu étant initialement à 0 et le programme place un 1 pour 'Registered' ou un 'xor eax,eax', 'mov [contenu], eax' peu entrainé la réécriture d'un 0 pour 'Unregistered' et là... Pas de détection.

- 2) L'adresse à surveiller est directement écrite dans le code source du programme en ASM. Si vous voulez réutiliser ce prog sur une autre cible, il faudra, modifier l'adresse, puis recompiler. c'est pas génial!
Le mieux aurait été de fabriquer une simple DialogBox avec une Boîte d'édition qui demande la valeur de l'adresse à vérifier puis stocker cette valeur comme c'est fait ici dans le Buffer nommé 'adresse_a_lire'. C'est sûrement pas très dur. Mais puisque je ne m'en suis servit qu'occasionnellement j'ai opté pour la simplicité.


  • Une dernière chose peut-être... Voyez la puissance de ces fonctions, ReadProcessMemory et WriteProcessMemory associées à GetThreadContext et SetThreadContext. Ne pourrions nous pas Reverser W32Dasm par exemple pour lui incorporer une nouvelle fonction BPMB ou bien créer un crack-me qui pour 0 serait 'Unregistered' et pour 1 serait 'Registered' mais qui s'auto-patcherait avec WriteProcessMemory pour remettre un 0 quoi qu'on écrive en Hard-Patching.

    Les projets ne manquent pas!


  • Retour
    (Par Morgatte)