Iczelion - 29 - Win32 Debug API Teil 2

Tutorial 29: Win32 Debug API Teil 2


Wir fahren mit dem Thema Win32 Debug API fort. In diesem Tutorial lernen wir, wie man den debuggenden Prozess modifiziert.
Laden Sie das Beispiel hier herunter.

Theorie:

Im letzten Tutorial haben wir gelernt, wie man das zu debuggende Programm lädt und Debug-Ereignisse handlet, die während des Prozess auftreten. Um nützlich zu sein, muss unser Programm in der Lage sein, den zu debuggenden Prozess zu modifizieren. Es gibt verschiedene APIs nur für dieses Vorhaben.

  • ReadProcessMemory Diese Funktion erlaubt Ihnen Speicher in dem spezifizierten Prozess zu lesen. Der Funktions-Prototyp ist wie folgt:
    ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD


    hProcess ist das Handle des Prozesses den Sie lesen wollen.
    lpBaseAddress ist die Adresse im Ziel-Prozess, den Sie lesen wollen. Wenn Sie zum Beispiel 4 Bytes aus dem zu debuggenden Programm ab 401000h lesen wollen, muss der Wert dieses Parameters 401000h sein.
    lpBuffer ist die Adresses des Buffers, der die erhaltenen Bytes aus dem Prozess aufnimmt.
    nSize ist die Anzahl der Bytes die Sie lesen wollen.
    lpNumberOfBytesRead ist die Adresse der DWord-Variable, die die Anzahl der tatsächlich gelesenen Bytes enthält. Wenn Sie das nicht interessiert, können Sie NULL angeben.

  • WriteProcessMemory ist der Pendant zu ReadProcessMemory. Es ermöglicht Ihnen in den Speicher des Ziel-Prozesses zu schreiben. Die Parameter sind genau die selben wie von ReadProcessMemory Die nächsten beiden API-Funktionen benötigen ein wenig Hintergrundwissen. Unter einem Multitasking Betriebssystem wie Windows, können mehrere Programme gleichzeitig laufen. Windows gibt jedem Thread eine Zeitscheibe. Wenn die Zeitscheibe abgelaufen ist, friert Windows den aktuellen Thread ein und wächselt zum nächsten Thread, der die höchste Priorität hat. Kurz bevor zum anderen Thread gewechselt wird, sichert Windows die Register des aktuellen Threads, so dass er zu gegebenen Zeit fortgesetzt werden kann, Windows kann somit die letzte *Umgebung* des Threads wiederherstellen. Die gesicherten Werten der Register werden kollektiv Kontext genannt.
    Zurück zu unserem Thema. Wenn ein Debug-Ereignis auftritt, hält Windows das zu debuggende Programm an. Dessen Kontext wird gespeichert. Da das Programm unterbrochen ist, können wir davon ausgehen, dass die Werte in dem Kontext unverändert bleiben. Wir können die Werte aus dem Kontext mit GetThreadContext erhalten und wir können sie mit SetThreadContext ändern.
    Diese beiden APIs sind sehr mächtig. Mit ihnen haben Sie die VxD-ähnliche Macht über das zu debuggende Programm: Sie können die gesicherten Werte verändern und genau bevor das debuggende Programm fortgesetzt wird, werden die Werte aus dem Kontext zurück in die Register geschrieben. Jede Änderung die Sie im Kontext gemacht haben, wird reflektiert an das debuggende Programm. Denken Sie mal darüber nach: Sie können sogar den Wert des EIP-Registers ändern und den Ablauf irgendwo fortsetzen wo Sie wollen! Unter normalen Umständen könnten Sie das nicht.

    GetThreadContext proto hThread:DWORD, lpContext:DWORD

    hThread ist das Handle des Threads, von dem Sie den Kontext erhalten wollen.
    lpContext ist die Adresse der CONTEXT Struktur die gefüllt wird, wenn die Funktion erfolgreich war.

    SetThreadContext hat exakt die selben Parameter. Schauen wir uns an, wie die CONTEXT Struktur aussieht:

  • CONTEXT STRUCT
  • ContextFlags dd ?
    ;----------------------------------------------------------------------------------------------------------
    ; Diese Sektion wird zurückgeliefert, wenn ContextFlags den Wert CONTEXT_DEBUG_REGISTERS enthält
  • ;-----------------------------------------------------------------------------------------------------------
    iDr0 dd ?
    iDr1 dd ?
    iDr2 dd ?
    iDr3 dd ?
    iDr6 dd ?
    iDr7 dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Diese Sektion wird zurückgeliefert, wenn ContextFlags den Wert CONTEXT_FLOATING_POINT enthält.
  • ;-----------------------------------------------------------------------------------------------------------
  • FloatSave FLOATING_SAVE_AREA
  • ;----------------------------------------------------------------------------------------------------------
    ; Diese Sektion wird zurückgeliefert, wenn ContextFlags den Wert CONTEXT_SEGMENTS enthält.
  • ;-----------------------------------------------------------------------------------------------------------
  • regGs dd ?
    regFs dd ?
    regEs dd ?
    regDs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Diese Sektion wird zurückgeliefert, wenn ContextFlags den Wert CONTEXT_INTEGER enthält.
  • ;-----------------------------------------------------------------------------------------------------------
  • regEdi dd ?
    regEsi dd ?
    regEbx dd ?
    regEdx dd ?
    regEcx dd ?
    regEax dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Diese Sektion wird zurückgeliefert, wenn ContextFlags den Wert CONTEXT_CONTROL enthält.
  • ;-----------------------------------------------------------------------------------------------------------
  • regEbp dd ?
    regEip dd ?
    regCs dd ?
    regFlag dd ?
    regEsp dd ?
    regSs dd ?
  • ;----------------------------------------------------------------------------------------------------------
    ; Diese Sektion wird zurückgeliefert, wenn ContextFlags den Wert CONTEXT_EXTENDED_REGISTERS enthält.
  • ;-----------------------------------------------------------------------------------------------------------
  • ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS Wie Sie beobachten können, die Elemente dieser Struktur sind Abbilder der realen Prozessor Register. Bevor Sie diese Struktur benutzen können, müssen Sie spezifizieren, welche Gruppe von Registern Sie lesen/schreiben wollen und zwar im ContextFlags Element. Wenn Sie zum Beispiel alle Register lesen/schreiben wollen, müssen Sie CONTEXT_FULL in ContextFlags angeben. Wenn Sie nur regEbp, regEip, regCs, regFlag, regEsp oder regSs lesen/schreiben wollen, müssen Sie CONTEXT_CONTROL in ContextFlags angeben.

    Etwas was Sie beachten müssen, wenn Sie die CONTEXT Struktur benutzen: sie muss an einer DWord Grenze ausgerichtet sein, ansonsten erhalten Sie eigenartige Ergebnisse unter NT. Sie müssen nur die Zeile "align dword" direkte über den Zeilen, wo Sie sie deklarieren, einfügen, ungefähr so:

    align dword MyContext CONTEXT


Beispiel:

Das erste Beispiel demonstriert die Benutzung von DebugActiveProcess. Als erstes müssen Sie ein Ziel-Programm starten, namens win.exe, welches in eine Endlosschleife eintritt, genau vor dem Anzeigen des Fensters. Dann starten Sie das Beispiel, welches sich selbst an win.exe anfügt und den Code von win.exe so modifiziert, dass win.exe die Endlosschleife verlässt und sein eigenes Fenster zeigt.

.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 ;-------------------------------------------------------------------- ; Der Code-Auszug unseres zu debuggenden Programms, win.asm. Tatsächlich ist es ; das einfache Fenster-Beispiel aus Tutorial 2, mit einer eingefügten Endlosschleife ; bevor die Message Loop betreten wird. ;---------------------------------------------------------------------- ...... 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 $ 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


Analyse:

invoke FindWindow, addr ClassName, NULL


Unser Programm muss sich selbst an das zu debuggende Programm anhängen und zwar mit der DebugActiveProcess Funktion welche die Prozess-ID des zu debuggenden Programms benötigt. Wir können die ID erhalten, indem wir GetWindowThreadProcessId aufrufen, welche wiederum das Fenster-Handle als Parameter benötigt. Demnach müssen wir erst das Fenster-Handle ermitteln.
Mit FindWindow können wir den Namen der Fenster-Klasse, die wir benötigen, spezifizieren. Sie gibt das Handle des Fensters zurück, welches durch die Fenster-Klasse erzeugt wurde. Wenn sie NULL zurückliefertm ist kein Fenster dieser Fensterklasse verfügbar.

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


Nachdem wir die Prozess-ID ermittelt haben, können wir DebugActiveProcess aufrufen. Dann betreten wir die Debug-Schleife und warten auf die Debug-Ereignisse.

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


Wenn wir CREATE_PROCESS_DEBUG_INFO erhalten, bedeutet das, dass das zu debuggende Programm unterbrochen ist, bereit um den Prozess zu behandeln. In diesem BEsipeil werden wir die Opcodes der Endlosschleife (0EBh 0FEh) in dem Programm mit NOPs (90h 90h) überschreiben.
Als erstes benötigen wir die Adresse der Befehle. Da das zu debuggende Programm bereits in der Endlosschleife ist, wenn unser Programm angefügt wird, zeigt EIP immer auf diese Befehle. Alles was wir machen müssen, ist den Wert aus EIP auslesen. Wir benutzen GetThreadContext um das zu erreichen. Wir setzen das ContextFlags Element auf CONTEXT_CONTROL um GetThreadContext mitzuteilen, dass wir die "Kontroll" Register Elemente der CONTEXT Struktur füllen wollen.

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


Nun, da wir den Wert von EIP haben, können wir WriteProcessMemory aufrufen, um den "jmp $" Befehl mit NOPs zu überschreiben, was effektiv hilft, das zu debuggende Programm aus der Endlosschleife zu holen. Danach zeigen wir eine Nachricht dem Benutzer an und rufen ContinueDebugEvent auf, um das zu debuggende Programm fortzusetzen. Da der "jmp $" Befehl mit NOPs überschrieben ist, ist das zu debuggende Programm in der Lage fortzufahren, das Fenster anzuzeigen und die Message Loop zu betreten. Der Beweis dafür ist, dass wir das Fenster sehen werden.

Das andere Beispiel benutzt eine etwas andere Vorgehensweise um das zu debuggende Programm in der Endlosschleife zu unterbrechen.

....... ....... .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 ....... .......


Es ruft immer noch GetThreadContext auf, um den aktuellen Wert von EIP herauszufinden, aber statt "jmp $" zu überschreiben, wird der Wert von regEip um 2 inkrementiert. Das Ergebnis ist, dass das Programm nach "jmp $" fortgesetzt wird.

Nun können Sie sehen, wir mächtig Get/SetThreadContext ist. Sie können auch andere Register-Abbilder modifizieren und so an das zu debuggende Programm weitergeben. Sie können sogar einen int 3h Befehl einfügen, um Breakpoints zu setzen.


Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage