
                               ...................
                         ...::: phearless zine #3 :::...                       

....................>---[ Debugging Programs On Win32 ]---<...................

............................>---[ by deroko ]---<.............................
                                                       deroko[at]gmail[dot]com    

1. Blah
2. Osnova
3. Muvanje CONTEXTa
4. Pisanje/Citanje memorije procesa
5. Tracovanje kroz single step
6. SEH i HideDebugger
7. Mogucnosti ovakvog tracovanja...
8. Reference

///////////////////////////////////////////////////////////////////////////
--==<[ 1. Blah
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

   Debugovanje na win32 moze da koristi ako zelite da pisete svoj unpacker ili 
de-protektor prilikom reversanja nekih meta. Normalno na raspolaganju vam stoji
gomila opcija. Takodje ovo mozete koristiti i kako biste pisali protektore tipa
Armadilo... Najvaznije je ovde da mozete lako naci neko mesto u kodu koje ce 
biti izvrseno i tu ubaciti viri... veoma zgodna stvar... Emulator ce morati da
emulira ceo kod ne bi li naso vas kod, a jos bolje ovde mozemo nauciti i neke
anti-emu trikove... Pa da pocnemo...

///////////////////////////////////////////////////////////////////////////
--==<[ 2. Osnova
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

   Posto radimo debugovanje programa, moramo pokrenuti proces. Naime debugovanje
se vrsi kroz seriju DEBUG_EVENT-ova koje program salje nasem debuggeru. O tome
ce da bude reci malo dole. No sad se moramo usredsrediti na CreateProcess API.

   CreateProcessA/W ima argument koji se zove -> dwCreationFlags i u ovom slucaju
tu moramo da predamo sledece flagove DEBUG_PROCESS i DEBUG_ONLY_THIS_PROCESS

DEBUG_PROCESS flag ce reci da je proces koji pravimo debugovan i da se svi EVENT
              salju nasem debugeru.
DEBUG_ONLY_THIS_PROCESS - sluzi da ne bi debugovali i procese koje pravi proces
                          koji debugujemo. Shodno tome usredsredjuemo se na nas
                          proces.
   
*Poziv funkciji izgleda ovako:

       push offset pinfo                         ;
       push offset sinfo                         ;
       push 0                                    ;
       push 0                                    ;
       push DEBUG_ONLY_THIS_PROCESS + DEBUG_PROCESS ;
       push 0                                    ;
       push 0                                    ;
       push 0                                    ;
       push 0                                    ;
       push offset fname                         ; 
       callW CreateProcessA                      ; create process for 
       test eax, eax                             ; debuging
       jz error                                  ;

Ok, nas proces je spreman za debugovanje sad moramo da procesiramo EVENTove koje
mi zelimo. Za tako nesto nam sluze 2 APIja:
       WaitForDebugEvent i ContinueDebugEvent...

WaitForDebugEvent bi trebalo da sledi odmah posle CreateProcess i ima sledecu
sintaksu:

BOOL WaitForDebugEvent(
    LPDEBUG_EVENT lpDebugEvent,    // address of structure for event information  
    DWORD dwMilliseconds           // number of milliseconds to wait for event 
   );
   
DEBUG_EVENT je struktura koja izgleda ovako:

DEBUG_EVENT STRUCT
       de_code    DWORD       ?
       de_ProcessId         DWORD       ?
       de_ThreadId          DWORD       ?
       de_u                 DEBUGSTRUCT <>
DEBUG_EVENT ENDS

de_code - govori nam koji je EVENT u pitanju i moze biti jedan od ovih kodova:
EXCEPTION_DEBUG_EVENT       - u programu se javio neki EXCEPTION	
CREATE_THREAD_DEBUG_EVENT	- startovan je novi thread u programu
CREATE_PROCESS_DEBUG_EVENT	- proces je startovan
EXIT_THREAD_DEBUG_EVENT	- neki thread je izasao
EXIT_PROCESS_DEBUG_EVENT	- ovaj EVENT se javlja kad program izadje
LOAD_DLL_DEBUG_EVENT	       - javlja se kad se ucitava DLL (prilikom startovanja)
                              ili poziva LoadLibrary()
UNLOAD_DLL_DEBUG_EVENT	- javlja se kad se oslobadjaju DLLovi, kraj programa ili
                              FreeLibrary()
OUTPUT_DEBUG_STRING_EVENT	- program je u sebi upotrebio OutputDebugStringA
RIP_EVENT	              - ????


de_ProcessId - je ID procesa
de_ThreadId  - je ID Threada u kom se odigrao event
de_u         - je UNIJA sledeceg oblika:

DEBUGSTRUCT UNION
       DU_Exception         EXCEPTION_DEBUG_INFO <>
       DU_CreateThread      CREATE_THREAD_DEBUG_INFO <>
       DU_CreateProcessInfo CREATE_PROCESS_DEBUG_INFO <>
       DU_ExitThread        EXIT_THREAD_DEBUG_INFO <>
       DU_ExitProcess       EXIT_PROCESS_DEBUG_INFO <>
       DU_LoadDll           LOAD_DLL_DEBUG_INFO <>
       DU_UnloadDll         UNLOAD_DLL_DEBUG_INFO <>
       DU_DebugString       OUTPUT_DEBUG_STRING_INFO <>
       DU_RipInfo           RIP_INFO <>
DEBUGSTRUCT ENDS

Naime u zavisnosti od Eventa koristicete neku od ovih struktura. Nama je za 
tracovanje najvazniji EXCEPTION_DEBUG_EVENT, CREATE_PROCESS_DEBUG_EVENT i
EXIT_PROCESS_DEBUG_EVENT jer ce nam oni sluziti za tracovanje i debugovanje
koda, shodno tome oni zasluzuju da se detaljnije objasne.

CREATE_PROCESS_DEBUG_INFO STRUCT
  CPID_hFile                 DWORD      ?
  CPID_hProcess              DWORD      ?
  CPID_hThread               DWORD      ?
  CPID_BaseOfImage           DWORD      ?
  CPID_dwDebugInfoFileOffset DWORD      ?
  CPID_nDebugInfoSize        DWORD      ?
  CPID_lpThreadLocalBase     DWORD      ?
  CPID_StartAddress          DWORD      ?
  CPDI_lpImageName           DWORD      ?
  CPDI_fUnicode              WORD      ?
CREATE_PROCESS_DEBUG_INFO ENDS

Ovde nam je znacajno sledece -> 
       BaseOfImage  - je bazna adresa procesa
       StartAddress - je ustvari adresa entrypointa

EXIT_PROCESS_DEBUG_EVENT:
       ovde samo treba znati da kad se odigra moramo da zavrsimo sa tracovanjem

EXCEPTION_DEBUG_EVENT:
       Uhhh ovaj je najvazniji za tracovanje i to iz vise razloga. Kad tracujemo
program mi to radimo preko single stepinga (TF) i putem int3h sto nije nista vise
do exception u kodu koji ce nas debuger da primi i da ih procesira svaki za sebe.

Struktura koja je ovde u pitanju je :
EXCEPTION_DEBUG_INFO STRUCT
  EDI_ExceptionRecord  EXCEPTION_RECORD <>
  EDI_FirstChance      dd      ?
EXCEPTION_DEBUG_INFO ENDS

EXCEPTION_RECORD                STRUC
        ER_ExceptionCode        DD      ?
        ER_ExceptionFlags       DD      ?
        ER_ExceptionRecord      DD      EXCEPTION_RECORD PTR ?
        ER_ExceptionAddress     DD      BYTE PTR ? ; CODE PTR
        ER_NumberParameters     DD      ?
        ER_ExceptionInformation DD      EXCEPTION_MAXIMUM_PARAMETERS DUP (?)
EXCEPTION_RECORD                ENDS

no da vidimo kako to radi -> poceli smo sa tracovanjem i dobili smo prvi event:
       cmp de.de_code, EXCEPTION_DEBUG_EVENT     ; process exceptions...
       jne __skip3                               ; no exception? check next posibility
       cmp de.de_u.ER_ExceptionCode, EXCEPTION_SINGLE_STEP; check if singlstep 
       je process_single_step                    ;
                                                 ;
       cmp de.de_u.ER_ExceptionCode, EXCEPTION_BREAKPOINT;
       jne other_exceptions                      ; nah, not int 3h...
       cmp first, 0                              ; 1st int3h?
       jne restore_and_set                       ;
       mov first, 1                              ;
       call HideDebugger                         ; hide debugger from process
       jmp continue_dbg                          ;

ok prvo proveravamo da li je u pitanju EXCEPTION_DEBUG_EVENT? jeste, sad je red
da proverimo koji je Exception u pitanju preko de.de_u.ER_ExceptionCode, ja pratim
samo EXCEPTION_BREAKPOINT (int 3h) i EXCEPTION_SINGLE_STEP (int1 iliti TF) i u
zavisnosti od njih ja procesiram te exceptione dok sve druge exceptione predajem
SEHu programa koje debugujem (setimo se SHIFT+F7/F8/F9 u Olly-ju)...

Ok kad procesiramo exceptione na ovaj nacin moramo da nastavimo sa izvrsavanjem
programa putem ContinueDebugEvent, ova funkcija je jako bitna i prima 3 parametra:

BOOL ContinueDebugEvent(
  DWORD dwProcessId,
  DWORD dwThreadId,
  DWORD dwContinueStatus
);

dwProcessId je ID procesa koji debugujemo
dwThreadID je ID threda koji debugujemo...
dwContinueStatus moze biti DBG_CONTINUE, DBG_EXCEPTION_NOT_HANDLED

DBG_CONTINUE - kaze nasem traceru da nastavi dalje sa tracovanjem
DBG_EXCEPTION_NOT_HANDLED - sluz da bi reko programu da mi nismo handlovali
                            ovaj exception i da program pozove svoj SEH...

       push DBG_EXCEPTION_NOT_HANDLED            ;
       push pinfo.pi_dwThreadId                  ;
       push pinfo.pi_dwProcessId                 ;
       callW ContinueDebugEvent                  ;
       jmp begin_dbg                             ;
       
       u slucaju da ovaj Exception ja ne obradjujem ili :

       push DBG_CONTINUE                         ; continue_dbg
       push pinfo.pi_dwThreadId                  ; Id of thread
       push pinfo.pi_dwProcessId                 ; Id of process
       callW ContinueDebugEvent                  ; and continue debug event
       jmp begin_dbg                             ; go to WaitForDebugEvent
       
  DBG_CONTINUE ide posto smo procesirali Exception, doduse ovo se koristi za svaki
drugi EVENT (LODA/UNLOAD_DLL, OUTPUTSTRING, itd...)

Teoretski prost tracer bi ovako izgledao:

       CreateProcess();
       bla:
           WaitForDebugEvent();
           
           <ovde ide procesiranje EVENTova>
           
           ContinueDebugEvent();
       jmp bla
       
///////////////////////////////////////////////////////////////////////////
--==<[ 3. Muvanje CONTEXTa
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Ovde nam trebaju 2 funckije GetThreadContext i SetThreadContext, sa ovim APIjima
mozemo uvek da dobijemo sadrzaj registra threada koji debugujemo, a takodje mozemo
da namestamo registre procesa.

Sintaksa je krajnje prosta oba uzimaju 2 argumenta:
       1. handle threada koji debugujemo
       2. pointer na CONTEXT strukturu (tj. struktura gde su sadrzani registri)

Prost primer upotrebe evo ga ovde:
;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
;get_ctx...
;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
get_ctx proc                                     ;
       mov ctx.CONTEXT_ContextFlags, CONTEXT_FULL;
       push offset ctx                           ;
       push pinfo.pi_hThread                     ;
       callW GetThreadContext                    ;
       ret                                       ;
get_ctx endp                                     ;
;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
;set_ctx                                  
;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
set_ctx proc                                     ;
       push offset ctx                           ;
       push pinfo.pi_hThread                     ;
       callW SetThreadContext                    ;
       ret                                       ;
set_ctx endp                                     ;

i onda se manipulise sa contextom threada ovako:
       call get_ctx                              ;
       mov ctx.CONTEXT_Eax, 0                    ;
       call set_ctx                              ;
       
U ovom slucaju eax ce imati 0 kad thread krene sa svojim izvrsavanjem

///////////////////////////////////////////////////////////////////////////
--==<[ 4. Pisanje/Citanje memorije procesa
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Za ovo Windows ima dve sjajne funkcije :
WriteProcessMemory i ReadProcessMemory... Njihova sintaksa je krajnje laka,
recimo da hocemo da citamo nesto iz procesa koji debugujemo:

get_buffer proc address:dword, nsize:dword       ;
       push 0                                    ; stavite 0
       push nsize                                ; koliko?
       push offset buffer                        ; gde?
       push address                              ; odakle?
       push pinfo.pi_hProcess                    ; handle procesa...
       callW ReadProcessMemory                   ;
       ret                                       ;
get_buffer endp                                  ;

address je adresa u okvirus procesa sa koje citamo, a nsize je kolicina podataka
koje zelimo da procitamo... posle ove funkcije program ce u buffer imati podatke
sa address i odgovarajucu kolicinu podataka... isto je i sa WriteProcessMemory,
isti argumenti i sve, samo sto pise na adresu.

///////////////////////////////////////////////////////////////////////////
--==<[ 5. Tracovanje kroz single step
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Ok tu su bile sve funkcije koje nam trebaju, sad ono sto nas zanima je tracovanje
instrukcija.

Ovde moramo da postavimo Trap Flag u CONTEXTu... to radimo prosto ovako:

set_tf proc                                      ;
       pushad                                    ;
       call get_ctx                              ;
                                                 ;
       or ctx.CONTEXT_EFlags, 100h               ;
                                                 ;
       call set_ctx                              ;
       popad                                     ;
       ret                                       ;
set_tf endp                                      ;

Sledeci EVENT koji ce se odigrati posle ContinueDebugEvent je :
EXCEPTION_DEBUG_EVENT sa exceptionCode - EXCEPTION_SINGLE_STEP, sad slobodno
mozete uraditi sta hocete, proveriti raspon EIP, procitati insturkciju, analizirati
registre posle svake instrukcije, to je vasa stvar, samo ne zaboravite da, pre nego
sto pozovete ContinueDebugEvent, postavite opet trap flag jer se on cisti kad god je
to moguce...

Sad bismo mogli uopsteno da napisemo nas mali tracer:

       CreateProcess()
       blah:
              WaitForDebugEvent();
              if (EXCEPTION_DEBUG_EVENT)
                     if (EXCEPTION_SINGLE_STEP)
                            set_tf()
              ContinueDebugEvent();
              jmp blah

Ovakva konstrukcija debugera ce uciniti da se nas kod ceo prati od pocetka, sto
ce reci EntryPointa DLLova, pa kroz pozive DLLovima itd... sto moze da traje
poprilicno. Da bismo sve to izbegli mozemo se posluziti trikom svakog normalnog
debugera a to je seldece:
       - ako detektujemo da je EIP izvan opsega naseg programa
         postavicemo INT 3h iza call koji nas je tamo uputio
       - ocisticemo trap_flag i cekamo da se generise EXCEPTION_BREAKPOINT
       - opet postavljamo TF i nastavljam dalje tracovanje...

Ok sacemo da analiziramo ove korake:
       - Ovo mozemo raditi na prost nacin analizom PE hedera i nalazenjem SizeOfImage
         i proveravanjem da li je EIP iznad ili ispod.
       - ako je iznad (znaci da smo uleteli u neki DLL), normalno ulaskom u dll
         koji se vrsi preko call na staku ostaje EIP od sledece instrukcije.
         Shodno tome moramo uraditi sledece:
         pozvati GetThreadContext i dobiti adresu ESP, onda cemo koristit 
         ReadProcessMemory da bismo sa te adrese uzeli 4 byte sto je nista drugo
         do sacuvan EIP, sa tako dobijene adrese cemo procitati 1 byte (velicina
         int 3h) i onda cemo na tu istu adresu staviti int 3h (0CCh opcode),
         sad samo treba da ocistimo TrapFlag i da nastavimo sa izvrsavanjem.
       - kad dobijemo EXCEPTION_DEBUG_EVENT i EXCEPTION_BREAKPOINT znamo da smo
         izasli iz dll-a i sve sto sad treba jeste da vratimo sacuvani opcode
         sa WriteProcessMemory i da postavimo trap_flag opet i da nastavimo sa
         tracovanjem.
         
Veoma je bitno da program prvo izbacuje DebugBreak koji ce generisati int 3h
exception, shodno tome moramo zaobici PRVI INT3h i samo nastaviti dalje sa
izvrsavanjem, ali logicno se namece pitanje kako da se zaustavimo na entrypointu
jer postavljanje TRAP_FLAG-a ovde ce izazvati tracovanje svih DLLova na pocetku.

Trik je ovde jako prost, kad dobijemo EVENT CREATE_PROCESS_DEBUG_EVENT, postavi-
cemo int 3h na EntryPoint... Posto znamo da kad popijemo ovaj EVENT da ce:
       de.de_u.CPID_StartAddress imati tu adresu, nista lakse, tu postavljamo
breakpoint i nastavljamo sa izvrsavanjem koda dok ne dobijemo BREAK_POINT, ali
zapamtite da prvi EXCEPTION_BREAKPOINT moramo ignorisati... teoretski to ovako
izgleda:

       CreateProcess()
       blah:
              WaitForDebugEvent();
              if (EXCEPTION_DEBUG_EVENT)
                     if (EXCEPTION_SINGLE_STEP)
                            set_tf()
                            jmp cont
                     else
                     if (EXCEPTION_BREAKPOINT)
                            if (first)
                               jmp cont
                            else
                                get_eip
                                restor_opcode()
                                set_tf()
       cont:                 
              ContinueDebugEvent();
              jmp blah      
       

To je cela mudrost tracovanje...

///////////////////////////////////////////////////////////////////////////
--==<[ 6. SEH i HideDebugger
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Ok, za debugovanje postoji jos jedna bitna funkcija a to je :

GetThreadSelectorEntry koja ima sledecu sintaksu:
BOOL GetThreadSelectorEntry(
  HANDLE hThread,
  DWORD dwSelector,
  LPLDT_ENTRY lpSelectorEntry
);

hThread je handle naseg threada
dwSelector je vrednost Segment registra koji trazimo u nasem slucaju FS
lpSelectoryEntry je pointer na ovo strukturu:

LDT_ENTRY STRUCT
         ldt_LimitLow       dw     ?
         ldt_BaseLow        dw     ?
         ldt_BaseMid        db     ?
         ldt_Flags1         db     ?
         ldt_Flags2         db     ?
         ldt_BaseHi         db     ?
LDT_ENTRY ENDS

pri cemu je - ldt_BaseLow donjih 16 bita (0-15)
            - ldt_BaseMid je (16-23)
            - ldt_BaseHi je (24-31)

Pomocu ove funkcije cemo dobiti adresu FS selektora i mozemo je analizirati
kako da bi pronasli BeingDebugged ili SEH-chain...

Primer upotrebe:
       callW get_ctx
       push offset ldt
       push ctx.CONTEXT_SegFs
       push pinfo.pi_hThread
       callW GetThreadSelectorEntry...
       
       sad moramo da konstruisemo adresu iz LDT strukture:
       
       mov al, ldt.ldt_BaseMid     ; u al srednjih 8 bita
       mov ah, ldt.ldt_BaseHi      ; u ah gornjih 8 bita
       shl eax, 16                 ; sad ih pomeramo na gornjih 16 bita
       mov ax, ldt.ldt_BaseLow     ; i stavljamo donjih 16 bita u ax
       
       Sad smo formirali celu adresu na koju pokazuje FS selektor...
       
E sad kako da sakrijemo od naseg procesa da je debugovan:
  BeingDebugged se nalazi u PEB + 2, standardna fora za proveru da li ste u 
debuggeru je :
       mov eax, FS:[30h]           ; PEB
       movzx eax, byte ptr[eax+2]  ; BeingDebugged

Sad sto moramo uciniti je:
      - na EAX dodati 30h i odatle procitati 4 byte kako bi dobili adresu PEB-a
      - procitamo 4 byte odatle i treci postavimo na 0
      - zapisemo tih 4 byte na tu adresu
      (normalno da na PEB+2) mozete odmah pisati 0, ali ja sam nesto probavao 
      pa je ovako ostalo u mom traceru/debugeru.

SEH je prica za sebe, on nam moze zadati problem, a to je da ukoliko dobijemo
Exception koji procesiramo preko DBG_EXCEPTION_NOT_HANDLED onda ce SEH ukloniti
trap_flag i mozemo izgubimo kontrolu nad programom. Ok, trik ovde je, prost,
preko gore pomenutog APIja nadjemo FS i analiziramo SEH chain, nadjemo gde je
lociran seh handle i na njega postavimo int 3h, posle toga ocistimo trap_flag
i cekamo da se popije nas Int 3h i posle nastavljamo normalno izvrsavanje kroz
trap_flag, bas kao i kad izlazimo iz APIja...

Pa to je to...

///////////////////////////////////////////////////////////////////////////
--==<[ 7. Mogucnosti ovakvog tracovanja...
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

Hmmm, mogucnosti i primene su velike, na ovaj nacin mozete napisati svoj unpacker.
Koji ces ocas posla i bez muke da otpakuje neki program.
Recimo : tacujemo instrukciju po instrukciju i dog god je EIP u poslednjem sectionu
mi radimo singlestep, kad EIP izadje iz poslednje sectiona, mozemo zaustaviti 
tracovanje i sa ReadProcessMemory i malo popravljanje PointerToRawData u section
headeru da dobijemo dumpovan fajl bez po muke, doduse i dalje ostaje problem sa
fiksiranjem Importa, ali to mozete i sa ImportRec da uradite na dumpovanom file.

Druga mogucnost je tracovanje procedura prilikom pravljenja OEPa, sto da ne,
tracujete program i nadjete gde da ubacite vas OEP. U planu mi je da ovu metodu
kombinujem sa dizzy enginom kako bi lako i efikasno naso valjane procedure, a 
mozda takodje da jednom i za vjek i vjekova sjebemo AV kompanije koje samo lazu
jadne musterije... ko ce ga znati, zivi bili pa videli...


8. Reference
-------------------------------------------------------------------------------
www.win32asm.cjb.net       --- (3 tutoriala)
Tracing under win32 z0mbie --- http://vx.netlux.org/lib/vzo28.html 
MSDN                       --- http://msdn.microsoft.com

9. Primer
-------------------------------------------------------------------------------
Primer je sadrzan uz tekst (debug.exe + src)

10. Greets
-------------------------------------------------------------------------------
#blackhat
hmmm, to je to...


-------------------------------------------------------------------------------
                                          deroko - www.blackhatz.net/~deroko/


      

   
