Date de publication : vendredi 30 juin 2006
Auteur : BeatriX
Je vais faire un listing complet des tricks anti-debugs utilisés dans ce binaire en proposant une source masm32 pour chacun d'eux.
Sans aucun doute, c'est le plus connu de tous les tricks. La fonction kernel32.IsDebuggerPresent renvoie 0 dans eax si le programme n'est pas débuggué. Voici ce que fait réellement cette fonction :
mov eax, dword ptr fs:[18h] mov eax, dword ptr ds:[eax+30h] movzx eax, byte ptr ds:[eax+2] retn
Cette fonction accède au PEB (Process Environment Block). Voici le début de cette structure complexe :
Process Environment Block +000 byte InheritedAddressSpace +001 byte ReadImageFileExecOptions +002 byte BeingDebugged
Pour masquer le debuggueur, il suffit donc au démarrage de placer la valeur 00 sur le 3ème byte du PEB. Sous OllyDebugger, dans la fenêtre de dump, il suffit de taper CTRL+G et de saisir fs:[30h] puis de remplacer le 3ème octet comme ceci :

Le programme réalise un snapshot des processus et récupère les noms des modules de chacun de ces processus. Il compare alors ces noms à des noms de modules relatifs aux outils IDA - OllyDbg - WinDbg - SoftIce.
Pour OllyDbg, il recherche les modules suivants : ollydebugger.exe - ollydump.dll - Cmdbar.dll - hideDebugger.dll - IsDebug.dll et loader32.exe.
Pour IDA, il recherche les modules suivants : ida.wll - idag64.exe et ida64.wll.
Pour WinDbg, il recherche les modules suivants : windbg.exe et dbgeng.dll.
Pour SoftIce, il recherche icedat.dll.
Si l'un des modules précédents est repéré, le binaire termine le processus brutalement !
Voici un exemple pour détecter OllyDebugger et le fermer violemment :
.386 .Model Flat ,StdCall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib .Data lpNumberOfBytesWritten DWORD 0 Module_IsDebugger BYTE "isdebug.dll",0 hProcessSnap DWORD 0 hModuleSnap DWORD 0 pProcessEntry PROCESSENTRY32 <> pModuleEntry MODULEENTRY32 <> hProcess DWORD 0 .Code Main: push 0 push TH32CS_SNAPPROCESS call CreateToolhelp32Snapshot ; Snapshot of processes mov hProcessSnap, eax mov pProcessEntry.dwSize, SIZEOF PROCESSENTRY32 push offset pProcessEntry push hProcessSnap call Process32First ; Get first process Test_Process: cmp eax, 0 jnz Scan_Process jmp sortie Scan_Process: push pProcessEntry.th32ProcessID push TH32CS_SNAPMODULE call CreateToolhelp32Snapshot ; Snapshot of modules in the current process mov hModuleSnap, eax mov pModuleEntry.dwSize, SIZEOF MODULEENTRY32 push offset pModuleEntry push hModuleSnap call Module32First ; Get first Module Test_Module: cmp eax, 1 jnz Next_Process push offset pModuleEntry.szModule push offset Module_IsDebugger call lstrcmpiA ; compare with "isdebug.dll" test eax, eax jz Olly_Detected NextModule: push offset pModuleEntry push hModuleSnap call Module32Next jmp Test_Module Next_Process: push hModuleSnap call CloseHandle push offset pProcessEntry push hProcessSnap call Process32Next jmp Test_Process sortie: push 0 call ExitProcess ; ************************************ OllyDbg detected ! Olly_Detected: push pProcessEntry.th32ProcessID push 0 push PROCESS_TERMINATE call OpenProcess mov hProcess, eax push hProcess call TerminateProcess push 0 call ExitProcess End Main
Voici une technique vieille comme le monde qui permet de détecter deux services actifs si SoftIce est lancé : ntice.sys et sice.sys.
.386
.Model Flat ,StdCall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.Data
service BYTE "\\.\NTICE",0
titre BYTE "gloops",0
texte BYTE "Soft Ice détecté !",0
.Code
Main:
push 0
push FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push 0
push FILE_SHARE_READ + FILE_SHARE_WRITE
push GENERIC_READ + GENERIC_WRITE
push offset service
Call CreateFileA
cmp eax, -1
jz @F
push MB_OK + MB_ICONHAND + MB_APPLMODAL ; Soft Ice est détecté
push offset titre
push offset texte
push 0
call MessageBoxA
@@:
push 0
call ExitProcess
End Main
Le programme utilise également un système très classique qui consiste à évaluer le temps écoulé entre deux relevés. S'il dépasse une certaine valeur seuil, on suppose que le programme est débuggué et on agit en conséquence. Les fonctions utilisées sont GetTickCount, msvcrt.time et GetSystemTime.
Voici un exemple utilisant GetTickCount :
.386
.Model Flat ,StdCall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.Data
titre BYTE "ok",0
texte BYTE "no debugger detected !",0
.Code
Main:
Call GetTickCount
mov ebx, eax
Call GetTickCount
sub ebx,eax
cmp ebx, 1388h
jl @F
push 0
call ExitProcess
@@:
push MB_OK + MB_ICONHAND + MB_APPLMODAL
push offset titre
push offset texte
push 0
call MessageBoxA
push 0
call ExitProcess
End Main
La technique la plus surprenante au premier abord est sans aucun doute l'usage de la fonction WriteProcessmemory pour patcher les routines critiques. Le programme restaure certaines portions de son propre code de façon régulière. Ceci a pour effet de supprimer les BreakPoints posés et je reconnais que ça surprend de prime abord ! L'effet est immédiat : OllyDbg ne breake plus et le programme s'exécute tranquillement. Comme la technique est utilisées conjointement à l'usage de threads, on peut rapidement perdre pied si on ne se méfie pas. Je ne vais pas proposer un code pour cette technique mais voici les étapes :
Comme je l'ai dit précédemment, si la fonction WriteProcessMemory n'est utilisée que pour la restauration du code, il est très facile de mettre cette technique en échec en plaçant un retn 14 au début de la fonction.
Copyright (C)- FRET (2006)