mercredi 26 mars 2008

Anti-Debugging Part I

Bonjour/Bonsoir,
j'ai décidé récemment de me pencher un peu sur les techniques d'anti-debugging. Je vais donc vous en présenter quelques unes dans cet article, et d'autres je l'espère dans un prochain.
Au programme :
-CheckRemoteDebuggerPresent() ainsi qu'une petit analyse de son fonctionnement que je trouve plutôt intéressant.
-SetUnhandledExceptionFilter() avec une petite introduction aux exceptions SEH.
-Vérification du bit BeingDebugged du PEB et IsDebuggerPresent().
-Un petit anti-Olly connu de tous, l'exploit OutputDebugString().
-Une petite connerie pour le fun que j'ai rencontré sur le crackme #5 de Kaine, l'utilisation de CreateFile() pour empêcher le reload du programme.

A noter dans le titre de cet article le "Part I" qui promet d'autres points en espérant qu'ils soit un peu plus pertinants que certains que je vais présenter ici.

I.Make your own debugger

Avant de commencer je vais juste vous montrer un petit peu à quoi ressemble un debugger. C'est tout simple :
1.On crée le process (CreateProcess()) avec le flag DEBUG_ONLY_THIS_PROCESS
2.Et on récupère les DebugEvents avec WaitForDebugEvent() dans la structure DEBUG_EVENT.

Notre debugger ressemble alors à ça :


#include <windows.h>

#define USAGE "debugger.exe <process path>"
int main(int args, char *argv[])
{
if(args < 2)
{
printf("Usage : %s\n\n",USAGE);
system("pause");
exit(0x0);
}

STARTUPINFO si = {0x0};
PROCESS_INFORMATION pi = {0x0};
DEBUG_EVENT DebugEvent;

printf("Starting Process...");

if(!CreateProcess(argv[1], NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi))
{
printf("FAILED : 0x%x\n\n", GetLastError());
system("pause");
exit(0x0);
}

printf("OK");

printf("\nDebugging Process (ID : 0x%x)...\n\n", pi.dwProcessId);

while(1)
{

WaitForDebugEvent(&DebugEvent, INFINITE);

if(DebugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
{
printf("\n\nProcess exited\n\n");
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
break;
}

ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_CONTINUE);
}

system("pause");
return 0x0;
}



Voilà vous pouvez maintenant faire des tests avec votre propre pseudo-debugger.


II.CheckRemoteDebuggerPresent()

Voyons un peu comment fonctionne cette fonction.
On commence par un petit disass :


7C859902 8BFF MOV EDI,EDI
...
7C859915 6A 00 PUSH 0
7C859917 6A 04 PUSH 4
7C859919 8D45 08 LEA EAX,DWORD PTR SS:[EBP+8]
7C85991C 50 PUSH EAX
7C85991D 6A 07 PUSH 7
7C85991F FF75 08 PUSH DWORD PTR SS:[EBP+8]
7C859922 FF15 AC10807C CALL DWORD PTR DS:[<&ntdll.NtQueryInformationProcess>]
...


Oh mais que voit-on ? un appel à la fonction NtQueryInformationProcess(). Tiens ça devient intéressant tout ça. En effet NtQueryInformationProcess() est une fonction du kernel land, elle est référencée dans la SSDT. On regarde de plus près (pour faire style de se servir de WinDbg :p):


kd> dd nt!KeServiceDescriptorTable
80552180 80501030 00000000 0000011c 805014a4
80552190 00000000 00000000 00000000 00000000
805521a0 00000000 00000000 00000000 00000000
805521b0 00000000 00000000 00000000 00000000
805521c0 00002710 bf80db87 00000000 00000000
805521d0 f94559e0 81692050 816720f0 806dff40
805521e0 00000000 00000000 00000000 00000000
805521f0 67b093c0 01c88fe4 00000000 00000000
kd> dps 80501030
80501030 8059847e nt!NtAcceptConnectPort
80501034 805e5664 nt!NtAccessCheck
80501038 805e8eaa nt!NtAccessCheckAndAuditAlarm
....
80501294 80599232 nt!NtQueryInformationPort
80501298 805c14bc nt!NtQueryInformationProcess <----- La voici :)
8050129c 805c0088 nt!NtQueryInformationThread
...


Au passage je met à dispo un dump de ma SSDT, si ça peut servir on sait jamais : DumpSSDT.txt

Plus sérieusement si on va chercher le prototype de la fonction dans la msdn on retrouve ceci :


NTSTATUS WINAPI NtQueryInformationProcess(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
);


On remarque alors la structure PROCESSINFOCLASS :


typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation, 0
ProcessQuotaLimits, 1
ProcessIoCounters, 2
ProcessVmCounters, 3
ProcessTimes, 4
ProcessBasePriority, 5
ProcessRaisePriority, 6
ProcessDebugPort, 7 <------ Ce qui nous interresse :)
ProcessExceptionPort 8
ProcessAccessToken, 9
ProcessLdtInformation, 10
ProcessLdtSize, 11
ProcessDefaultHardErrorMode, 12
ProcessIoPortHandlers, 13
ProcessPooledUsageAndLimits, 14
ProcessWorkingSetWatch, 15
ProcessUserModeIOPL, 16
ProcessEnableAlignmentFaultFixup, 17
ProcessPriorityClass, 18
ProcessWx86Information, 19
ProcessHandleCount, 20
ProcessAffinityMask, 21
ProcessPriorityBoost, 22
ProcessDeviceMap, 23
}


C'est le paramètre ProcessDebugPort qui va nous intéresser. En effet si le processus est débuggé, ce paramètre aura une valeur non-égale à zéro, super :). Seulement ceci ne marche que pour les debuggers ring3 :( :


ProcessDebugPort NtQueryInformationProcess fills in a DWORD with the port number of the
debugger for the process being queried. The ProcessInformationLength parameter to
NtQueryInformationProcess should be set to sizeof(DWORD). The debug port is a value that's
useles eans that the process
s to ring 3 code. However, you can infer that a nonzero debug port m
is bein run under the control of a ring 3 debugger such as the Visual C++ IDE or Turbo Debugger.


Bon on a a donc finis avec CheckRemoteDebuggerPresent(), et j'espère que vous avez maintenant compris comment elle fonctionne. Un petit code :


#define _WIN32_WINNT 0x0501

#include

int main()
{
BOOL dwIsDebugged;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &dwIsDebugged);
if(!dwIsDebugged)
MessageBox(NULL, "Okay :)", "Okay :)", MB_OK);
else
MessageBox(NULL, "I'm Debugged !!!", "I'm Debugged !!!", MB_OK);

return 0x0;
}


III.SetUnhandledExceptionFilter()

Voilà une fonction intéressante également. Cette fonction va se baser sur la gestion des exceptions. En effet vous devez tous avoir déjà eu à faire à une méchante msgBox qui vous dit que votre programme à planté pour telle ou telle raison comme le fameux "Memory access violation". Bref je vais pas m'attarder sur ce sujet, je m'y pencherais certainement dans un prochain article. En fait, SetUnhandledExceptionFilter() permet de poser un handler de haut niveau. C'est alors le système qui va, lorsqu'une exception est générée, vérifier dans une table si un handler a été définit par la fonction SetUnhandledExceptionFilter(). Si c'est le cas, la fonction UnhandledExceptionFilter() va, de la même façon que le fait CheckRemoteDebuggerPresent(), regarder si le process est débuggé ou pas. Si la fonction a détecté un debugger, alors elle n'execute pas le handler, sinon elle saute dessus. Du coup il nous est facile de connaitre la présence d'un debugger, si le handler est executé alors le process n'est pas débuggé, sinon il l'est.
Voici le code :


#include

DWORD NotDebugged()
{
MessageBox(NULL, "Okay :)", "Okay :)", MB_OK);
}
int main()
{
SetUnhandledExceptionFilter(&NotDebugged);
RaiseException(EXCEPTION_FLT_DIVIDE_BY_ZERO, 0, 0, NULL); //Génère l'exception
MessageBox(NULL, "I'm Debugged !!!", "I'm Debugged !!!", MB_OK);

return 0x0;
}


Le principe est simple, je filtre les exceptions avec SetUnhandledExceptionFilter() avec ma fonction NotDebugged() comme handler. Ensuite je génère l'exception avec RaiseException(). Si le programme est débuggé et que le debugger "ignore" l'exception on va arriver sur la MsgBox() "I'm Debugged" sinon la fonction NotDebugged() va être exécutée.
Voilà un petit apperçu de cette fonction.

IV.Process Evironment Block : BeingDebugged

Cette fois je vais m'intéresser à la structure PEB :

kd> dt nt!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
...


On remarque le champ BeingDebugged :). En fait lorsqu'un programme est debuggé, le système va positionner cet octet à 1. Il suffit donc de le vérifier pour s'appercevoir de la présence d'un debugger.
Voilà ce que ça donne en C :

#include

typedef struct _PEB
{
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
/*
...
*/
} PEB, *PPEB;

int main()
{
PVOID (*GetCurrentPEB)();
PPEB ProcessPEB;

GetCurrentPEB = (PVOID *)GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlGetCurrentPeb");
ProcessPEB = (PPEB)(*GetCurrentPEB)();

if(!ProcessPEB->BeingDebugged)
MessageBox(NULL, "Okay :)", "Okay :)", MB_OK);
else
MessageBox(NULL, "I'm Debugged !!!", "I'm Debugged !!!", MB_OK);

return 0x0;
}

C'est en fait ce que fait la fonction IsDebuggerPresent() que vous connaissez tous !
En effet si on désassemble cette fonction on trouve :


7C812E03 > 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
7C812E09 8B40 30 MOV EAX,DWORD PTR DS:[EAX+30] //Se Positionne sur le PEB
7C812E0C 0FB640 02 MOVZX EAX,BYTE PTR DS:[EAX+2] //Récupère le 3e octet
7C812E10 C3 RETN


Voilà, vous savez maintenant comment marche cette fonction.

V.Exploit OutputDebugString()

Voilà un petit trick anti-OllyDBG bien connu des reversers. Il s'agit en fait de passer une chaine mal-formaté à la fonction OutputDebugString() que Olly va mal gérer. Celui-ci va alors crasher. Je m'attarderais pas sur ça, un petit code :


#include

int main()
{
OutputDebugString("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%
s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%
s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s");
return 0x0;
}


VI.Anti reloading

Et pour finir une petite connerie :)
On va empêcher Olly de recharger le programme qu'il veut débugger. Pour celà on va utiliser CreateFilA(). Notre programme va ressembler à ceici :


#include

int main()
{
HANDLE hFile = CreateFileA("main.exe", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
MessageBox(NULL, "Reload me :p", "Reload me :p", MB_OK);
CloseHandle(hFile);
return 0x0;
}


Voilà juste pour emmerder notre amis reverser.

C'est finis pour cette petite intro aux anti-debugs, tout ça pour dire qu'il y a beaucoup de façons de détecter la présence d'un debugger mais elles resteront toujours bypassables. Je pense présenter d'autres techniques dans un prochain article et notamment parler un peu plus des SEHs.

Bon debugging ;)

Remerciements à Baboon pour les erreurs qu'il m'a fait remarquer.

Les codes d'anti-dbg dispo ici : Path

Le code du debugger : Source+binaire

Une excellente doc sur les anti-debugs : Windows anti-debug reference

1 commentaire:

Anonyme a dit…

Very interesting!