Zoom Icon

Tutorial 29 - Debug API 2 parte

From UIC

Tutorial 29: Win32 Debug API Part 2

Contents


Tutorial 29 - Debug API 2 parte
Author: Iczelion
Email: Translate: Antelox
Website: Mirror
Date: 03/05/2009 (dd/mm/yyyy)
Level: Luck and skills are required
Language: Italian Flag Italian.gif
Comments: Formattazione Wiki: Antelox



Introduzione

Continuiamo a proposito delle Win32 Debug API. In questo tutorial, impareremo come modificare il debuggee ( il debuggee indica il processo da debuggare NdT ).


Tools

Esempio


Essay

Nel precedente tutorial, abbiamo scoperto come caricare il processo da dubuggare ( debugge ;) NdT ) e gestire gli eventi che avvengono nel processo stesso. Per divenire utile, il nostro programma deve essere in grado di modificare il processo debuggato. Per tale scopo ci sono diverse API:

  • ReadProcessMemory Questa funzione ci permette di leggere la memoria del processo specificato. Il prototipo della funzione è il seguente:
ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

hProcess è l'handle del processo che si vuole leggere.

lpBaseAddress specifica l'indirizzo alla quale vuoi far iniziare la lettura nel processo. Per esempio, se io voglio iniziare a leggere 4 byte dal processo debuggato all'indirizzo 401000h, il valore di questo parametro deve essere 401000h.

lpBuffer è l'indirizzo del buffer che riceve i byte letti dal processo.

nSize è il numero di byte che vuoi leggere.

lpNumberOfBytesRead è l'indirizzo delle variabili di dimensione di una dword che riceve il numero di byte effettivamente letti. Se per voi questo parametro non è importante, potete utilizzare NULL.

  • WriteProcessMemory è la controparte di ReadProcessMemory. Vi permette di scrivere la memoria del processo. I parametri sono esattamente gli stessi di ReadProcessMemory.

Le prossime due api necessitano di un ulteriore background di conoscenze. Al di sotto di un OS multitasking come Windows, contemporaneamente possono esserci diversi programmi in esecuzione. Windows impartisce una certa quantità di tempo ad ogni thread ( processo NdT ). Quando questa quantità di tempo si esaurisce, Windows blocca il thread presente e passa al prossimo thread che ha priorità maggiore. Poco prima di passare ad un'altro thread, Windows memorizza i valori del thread presente in un registro in modo tale che quando arriva il momento di ripristinare il thread, Windows è in grado di ripristinare l'ultimo "ambiente" di quel thread. I valori salvati nei registri sono chiamati "contesti". Torniamo al nostro argomento. Quando si verifica un'evento di debug, Windows sospende il processo da debuggare, e il suo contesto viene salvato. Poichè esso è sospeso, possiamo essere certi che i valori nel contesto rimarranno invariati. Siamo in grado di ottenere i valori nel contesto con GetThreadContext e possiamo cambiarli con SetThreadContext. Queste due API sono molto potenti. Con loro, hai a portata di mano il potere VxD-like sul processo da debuggare: è possibile modificare i valori salvati nel registro poco prima che venga ripristinato il debuggee, i valori nel contesto saranno scritti all'interno dei registri. Ogni cambiamento, grazie al contesto si riflette sul debuggee. Pensate: è anche possibile modificare il valore del registro EIP e deviare il flusso di esecuzione in qualsiasi posto preferisci! Sempre se non sarà in grado di farlo in condizioni normali.

GetThreadContext proto hThread:DWORD, lpContext:DWORD

hThread è l'handle del thread di cui vuoi ottenere il contesto.

lpContext è l'indirizzo della struttura CONTEXT che sarà riempito quando la funzione tornerà con successo.

SetThreadContext ha esattamente gli stessi parametri. Guardiamo com'è una struttura CONTEXT:


CONTEXT STRUCT

ContextFlags dd ?

---------------------------------------------------------------------------------------------------------

;Questa sezione viene restituita se ContextFlags contiene il valore CONTEXT_DEBUG_REGISTERS

---------------------------------------------------------------------------------------------------------

iDr0 dd ?
iDr1 dd ?
iDr2 dd ?
iDr3 dd ?
iDr6 dd ?
iDr7 dd ?

---------------------------------------------------------------------------------------------------------

;Questa sezione viene restituita se ContextFlags contiene il valore CONTEXT_FLOATING_POINT

---------------------------------------------------------------------------------------------------------

FloatSave FLOATING_SAVE_AREA <>

---------------------------------------------------------------------------------------------------------

;Questa sezione viene restituita se ContextFlags contiene il valore CONTEXT_SEGMENTS

---------------------------------------------------------------------------------------------------------
regGs dd ?
regFs dd ?
regEs dd ?
regDs dd ?

---------------------------------------------------------------------------------------------------------

;Questa sezione viene restituita se ContextFlags contiene il valore CONTEXT_INTEGER

---------------------------------------------------------------------------------------------------------

regEdi dd ?
regEsi dd ?
regEbx dd ?
regEdx dd ?
regEcx dd ?
regEax dd ?

---------------------------------------------------------------------------------------------------------

;Questa sezione viene restituita se ContextFlags contiene il valore CONTEXT_CONTROL

---------------------------------------------------------------------------------------------------------

regEbp dd ?
regEip dd ?
regCs dd ?
regFlag dd ?
regEsp dd ?
regSs dd ?

---------------------------------------------------------------------------------------------------------

;Questa sezione viene restituita se ContextFlags contiene il valore CONTEXT_EXTENDED_REGISTERS

---------------------------------------------------------------------------------------------------------

ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS

Come puoi vedere, i membri di questa struttura sono la simulazioni dei registri reali del processore. Prima che tu possa usare questa struttura, hai bisogno di specificare che gruppo di registri vuoi leggere/scrivere nel membro ContextFlags. Per esempio, se tu vuoi leggere/scrivere tutti i registri, puoi specificare in ContextFlags CONTEXT_FULL. Se tu vuoi leggere/scrivere solo regEbp, regEip, regCs, regFlag, regEsp or regSs, devi specificare in ContextFlags CONTEXT_CONTROL.

Devi ricordare una cosa quando usi la struttura CONTEXT: quest'ultima deve essere allineata al termine della dword altrimenti si ottengono strani risultati sotto NT. Devi mettere "align dword" appena sopra la riga di dichiarazione, come questo:


align dword
MyContext CONTEXT <>


Esempio

Il primo esempio dimostra l'uso di DebugActiveProcess. Per primo, abbiamo bisogno di avviare il target chiamato win.exe che entra in un loop infinito appena prima che la finestra venga mostrata sullo schermo. Quando l'esempio è avviato, potrai "attaccarti" allo stesso win.exe e modificare il suo codice non appena esce dal loop infinito e mostra la propria finestra.

.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 "Win32 Debug Example no.2",0
ClassName db "SimpleWinClass",0
SearchFail db "Cannot find the target process",0
TargetPatched db "Target patched!",0
buffer dw 9090h

.data?
DBEvent DEBUG_EVENT <>
ProcessId dd ?
ThreadId dd ?
align dword
context CONTEXT <>

.code
start:
invoke FindWindow, addr ClassName, NULL
.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId
    .while TRUE
       invoke WaitForDebugEvent, addr DBEvent, INFINITE
       .break .if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context          
          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL
          invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION
       .elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
          .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
             invoke ContinueDebugEvent, DBEvent.dwProcessId,DBEvent.dwThreadId, DBG_CONTINUE
             .continue
          .endif
       .endif
       invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
   .endw
.else
    invoke MessageBox, 0, addr SearchFail, addr AppName,MB_OK+MB_ICONERROR .endif
invoke ExitProcess, 0
end start

;---------------------------------------------------------------------------------------------------------
; Parte del codice di win.asm, il nostro processo da debuggare. Si tratta
; infatti della finestra dell'esempio del tutorial 2 nella quale è inserito
; un loop infinito prima che entri nel loop del messaggio.
;---------------------------------------------------------------------------------------------------------

......
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\ CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,\ hInst,NULL
mov hwnd,eax
jmp $ <---- Qui il nostro loop infinito. Si compone di EB FE
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
   invoke GetMessage, ADDR msg,NULL,0,0
   .break .if (!eax)
   invoke TranslateMessage, ADDR msg
   invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp


Analisi

invoke FindWindow, addr ClassName, NULL

Il nostro programma deve attaccarsi al debuggee con DebugActiveProcesss che richiede appunto il suo ID. Possiamo ottenere l'ID del processo chiamando GetWindowsThreadProcessId che a sua volta come parametro ha bisogno dell'handle della finestra. Quindi per primo dobbiamo ottenere l'handle della finestra. Con FindWindow, possiamo specificare il nome della classe della finestra di cui abbiamo bisogno. Essa ritorna l'handle della finestra creata dalla classe della finestra stessa. Se essa ritorna NULL, non è presentenessuna finestra della classe.

.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId

Dopo che abbiamo ottenuto l'ID del processo, possiamo chiamare DebugActiveProcess. Poi entriamo nel loop di debug in attesa di eventi dello stesso.

.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context


Quando otteniamo CREATE_PROCESS_DEBUG_INFO, significa che il debuggee è sospeso, quindi pronto per poter effettuare un'intervento "chirurgico" sul processo. In questo esempio, sovrascriveremo l'istruzione del loop infinito del debuggee (0EBh 0FEh) con i NOP ( 90h 90h ). Prima abbiamo ottenuto l'indirizzo dell'istruzione. Poichè il debuggee è già nel loop, possiamo "attaccare" il nostro programma a lui, eip punterà già all'istruzione. Tutto quello che c'è da fare è ottenere il valore di EIP. Usiamo GetThreadContext per il raggiungimento di tale obiettivo. Abbiamo impostato il membro ContextFlags con CONTEXT_CONTROL in modo tale da dire a GetThreadContext che vogliamo riempire i membri "control" del registro della struttura CONTEXT.

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

Adesso che abbiamo ottenuto il valore di EIP, possiamo chiamare WriteProcessMemory per sovrascrivere l'istruzione " jmp $ " con i NOP, quindi effettivamente aiuta il debuggee ad uscire dal loop infinito. Dopo che è stato visualizzato il messaggio per l'utente, chiamiamo ContinueDebugEvent per ripristinare il debuggee. Poichè l'istruzione " jmp $ " è sovrascritta dai NOP, il debuggee sarà in grado di continuare con la visualizzazione della finestra e entrare nel loop del messaggio. La dimostrazione è che vedremo la finestra sullo schermo.

L'altro esempio usa un'approcio differente per interrompere il loop infinito del debuggee.

.......
.......
.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
   mov context.ContextFlags, CONTEXT_CONTROL
   invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   add context.regEip,2
   invoke SetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context
   invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION
.......
.......

Viene chiamato ancora GetThreadContext per ottenere il valore corrente dell'EIP ma invece di sovrascrivere l'istruzione " jmp $ "incrementiano il valore di regEip a 2 per poter "saltare" l'istruzione. Il risultato è che quando il debuggee riacquista il controllo, riprende l'esecuzione dall'istruzione successiva a quella relativa al " jmp $ ".

Adesso puoi vedere la potenza di Get/SetThreadContext. Puoi anche modificare i registri delle altre immagini come pure i loro valori e di coseguenza si rifletteranno al debuggee. Nel processo da debuggare è possibile inserire anche istruzioni int 3 per mettere breakpoint.


Disclaimer

I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.

Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.