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)