Debugger működése Megszakítás és nyomkövetés. Izsó Tamás 2013. október 31. Izsó Tamás Debugger működése/ 1
Section 1 Debugger Izsó Tamás Debugger működése/ 2
Debugger felépítése Proccess indítás nyomkövetéssel. Nyomkövető ciklus létrehozása. STARTUPINFO s i ; PROCESS_INFORMATION p i ; ZeroMemory ( &si, sizeof ( s i ) ) ; s i. cb = sizeof ( s i ) ; ZeroMemory ( &pi, sizeof ( p i ) ) ; CreateProcess ( ProcessNameToDebug, NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, & p i ) ; Izsó Tamás Debugger működése/ 3
Definíciók és követelmények 1 Debugger az a processz amely egy másik processzt monitoroz. 2 Debuggolt program a nyomkövetett program. 3 Debuggolt program csak egy debugger-hez lehet hozzárendelve. 4 Egy debugger több processzt is nyomkövethet. 5 A processz indítása és a processz debuggolására alkalmas ciklus ugyanabban a processzben kell hogy legyen. Izsó Tamás Debugger működése/ 4
Debug ciklus DEBUG_EVENT debug_event = { 0 } ; for ( ; ; ) { i f (! WaitForDebugEvent (& debug_event, INFINITE ) ) return ; / / User-defined function, not API ProcessDebugEvent (& debug_event ) ; } ContinueDebugEvent ( debug_event. dwprocessid, debug_event. dwthreadid, DBG_CONTINUE ) ; DBG_CONTINUE folytatjuk a nyomkövetést. DBG_EXCEPTION_NOT_HANDLED valami hiba volt és feladjuk. Izsó Tamás Debugger működése/ 5
Nyomkövetési esemény rekordja struct DEBUG_EVENT { DWORD dwdebugeventcode ; DWORD dwprocessid ; DWORD dwthreadid ; union { EXCEPTION_DEBUG_INFO Exception ; CREATE_THREAD_DEBUG_INFO CreateThread ; CREATE_PROCESS_DEBUG_INFO CreateProcessInfo ; EXIT_THREAD_DEBUG_INFO ExitThread ; EXIT_PROCESS_DEBUG_INFO ExitProcess ; LOAD_DLL_DEBUG_INFO Loa ddll ; UNLOAD_DLL_DEBUG_INFO UnloadDll ; OUTPUT_DEBUG_STRING_INFO DebugString ; RIP_INFO R i p I n f o ; } u ; } ; Izsó Tamás Debugger működése/ 6
Debug ciklus kicsit részletesebben bool bcontinuedebugging = t r u e ; while ( bcontinuedebugging ) { i f (! WaitForDebugEvent (& debug_event, INFINITE ) ) return ; switch ( debug_event. dwdebugeventcode ) { case CREATE_PROCESS_DEBUG_EVENT:... break ; case CREATE_THREAD_DEBUG_EVENT:... break ; case EXIT_THREAD_DEBUG_EVENT:... break ; case EXIT_PROCESS_DEBUG_EVENT:... break ; case LOAD_DLL_DEBUG_EVENT:... break ; case UNLOAD_DLL_DEBUG_EVENT:... break ; case OUTPUT_DEBUG_STRING_EVENT:... break ; case EXCEPTION_DEBUG_EVENT: }... } EXCEPTION_DEBUG_INFO& exception = debug_event. u. Exception ;... break ; Izsó Tamás Debugger működése/ 7
CREATE_PROCESS_DEBUG_EVENT esemény Ez az esemény a nyomkövetett program betöltése után a futás előtt következik be. Itt lehet megadni a breakpoint-ot. s t r u c t CREATE_PROCESS_DEBUG_INFO { HANDLE h F i l e ; / / (.EXE) file handle HANDLE hprocess ; / / process handle HANDLE hthread ; / / első thread handle LPVOID lpbaseofimage ; 1 DWORD dwdebuginfofileoffset ; DWORD ndebuginfosize ; LPVOID lpthreadlocalbase ; LPTHREAD_START_ROUTINE lpstartaddress ; 2 LPVOID lpimagename ; 3 WORD funicode ; / / fájlnév kódolása } ; 1 A memóriába betöltött exe fájl kezdőcíme. 2 Első végrehajtandó utasítás címe. 3 Program nevének a címe a debuggolt program címterében. Izsó Tamás Debugger működése/ 8
CREATE_PROCESS_DEBUG_EVENT esemény használata Ez az esemény a nyomkövetett program betöltése után a futás előtt következik be. Itt lehet megadni a breakpoint-ot. case CREATE_PROCESS_DEBUG_EVENT: { char filename = GetFileNameFromHandle ( debug_event. u. CreateProcessInfo. h F i l e ) ; } ; A betöltött végrehajtható fájl nevét nem a lpimagename alapján, hanem a hozzá tartozó fájl handler alapján a GetFileNameFromHandle függvény segítségével állítjuk elő. A függvény leírását az MSDN-en Obtaining a File Name From a File Handle címmel találhatjuk meg. Kb 10 rendszerhívást tartalmaz, ami számunkra most nem izgalmas. Izsó Tamás Debugger működése/ 9
OUTPUT_DEBUG_STRING_EVENT esemény Ez az esemény akkor következik be, ha a nyomkövetett programban diagnosztikai üzeneteket írunk ki a OutputDebugString rendszerhívás segítségével. Ezeket az üzeneteket a debugger kapja meg. struct OUTPUT_DEBUG_STRING_INFO { LPSTR lpdebugstringdata ; / / char* WORD funicode ; WORD ndebugstringlength ; } ; Izsó Tamás Debugger működése/ 10
OUTPUT_DEBUG_STRING_EVENT esemény case OUTPUT_DEBUG_STRING_EVENT: { char msg ; 1 OUTPUT_DEBUG_STRING_INFO & DebugString = debug_event. u. DebugString ; msg=new char [ DebugString. ndebugstringlength + 1 ] ; } ReadProcessMemory ( p i. hprocess, 2 DebugString. lpdebugstringdata, 3 msg, 4 DebugString. ndebugstringlength, NULL ) ; p r i n t f ( " Debug i n f o : %s \ n ", msg ) ; d e l e t e [ ] msg ; 1 Nem Unicode-ot használunk. 2 Debuggolt processz handle-t a processz indításánál kaptuk meg. 3 Üzenet címe a debuggolt program címterében. 4 Üzenet másolata a debugger címterében. Izsó Tamás Debugger működése/ 11
EXIT_PROCESS_DEBUG_INFO esemény Nyomkövetett program befejeződésénél jelentkező esemény. st ru ct EXIT_PROCESS_DEBUG_INFO { DWORD dwexitcode ; } ; Használata: bool bcontinuedebugging= t r u e ;... case EXIT_PROCESS_DEBUG_EVENT: { p r i n t f ( " Process e x i t e d with code : 0x%x ", debug_event. u. ExitProcess. dwexitcode ) ; bcontinuedebugging= f a l s e ; } break ; Izsó Tamás Debugger működése/ 12
(LOAD/ UNLOAD)_DLL_DEBUG_EVENT esemény A DLL betöltéséhez kapcsolódó információk azonosak a processz betöltésénél tanult információkkal. struct LOAD_DLL_DEBUG_INFO { HANDLE h F i l e ; / / (.DLL) file handle LPVOID lpbaseofdll ; 1 DWORD dwdebuginfofileoffset ; DWORD ndebuginfosize ; LPVOID lpimagename ; WORD funicode ; } ; 1 Dll betöltési címe a debuggolt processz szemszögéből. struct UNLOAD_DLL_DEBUG_INFO { LPVOID lpbaseofdll ; } ; Izsó Tamás Debugger működése/ 13
DLL-ek nyomkövetése Debugger std : : map<lpvoid, std : : s t r i n g > DllNameMap ; case LOAD_DLL_DEBUG_EVENT: { s t r i n g name = GetFileNameFromHandle ( debug_event. u. LoadDll. h F i l e ) ; DllNameMap [ debug_event. u. LoadDll. lpbaseofdll ] =name ; cout << " Load DLL " << name << " " << std : : hex << debug_event. u. LoadDll. lpbaseofdll << std : : endl ; } break ; case UNLOAD_DLL_DEBUG_EVENT: cout << " Unload DLL " << DllNameMap [ debug_event. u. UnloadDll. lpbaseofdll ] << std : : endl ; break ; Izsó Tamás Debugger működése/ 14
EXCEPTION_DEBUG_EVENT esemény Azokat az eseményeket kapjuk itt el, amit az előző fejezetben a struktúrált kivétel kezelésnél (SEH) tárgyaltunk. struct EXCEPTION_DEBUG_INFO { EXCEPTION_RECORD ExceptionRecord ; DWORD dwfirstchance ; } ; struct EXCEPTION_RECORD { DWORD ExceptionCode ; DWORD ExceptionFlags ; s t r u c t _EXCEPTION_RECORD ExceptionRecord ; PVOID ExceptionAddress ; DWORD NumberParameters ; ULONG_PTR ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS ] ; } ; Izsó Tamás Debugger működése/ 15
EXCEPTION_DEBUG_EVENT esemény case EXCEPTION_DEBUG_EVENT: { EXCEPTION_DEBUG_INFO& exception = debug_event. u. Exception ; switch ( exception. ExceptionRecord. ExceptionCode ) { case STATUS_BREAKPOINT: p r i n t f ( " Break p o i n t " ; ) dwcontinuestatus = DBG_CONTINUE; break ; } default : i f ( exception. dwfirstchance == 1) { p r i n t f ( " Addr : %x code : %d " exception. ExceptionRecord. ExceptionAddress, exception. ExceptionRecord. ExceptionCode ) ; } dwcontinuestatus = DBG_EXCEPTION_NOT_HANDLED; } break ; Izsó Tamás Debugger működése/ 16
Hogyan írjuk meg a debuggert A CREATE_PROCESS_DEBUG_EVENT esemény az a pont, ahol a debugger bele tud szólni a debuggolt program életébe 1 Határozzuk meg azt a memória címet, ahol meg akarjuk nézni a debuggolt processz állapotát. De hol van ez a pont? 2 Módosítsuk az adott helyen lévő utasítást, például tegyünk oda breakpoint-ot ( int 3 vagy 0xCC értékű gépi utasítás). 3 Futtassuk tovább a programot. 4 Kezeljuk le a breakpoint eseményt. 5 Állítsuk vissza az eredeti utasítást, amit a breakpoint-tal felülírtunk. 6 Írjuk ki a memória és regiszterek értékeit, vagy egyéb adatokat, amelyre a felhasználó kiváncsi. 7 Folytassuk a nyomkövetést. Izsó Tamás Debugger működése/ 17
Hogyan módosíthatjuk a debuggolt processzt 1 Mentsük el a felülírandó utasítás első byte-ját. 2 Írjuk felül a breakpoint 0xCC utasítással. 3 Frissítsük az utasítás cache-t (gyorsítótárat). DWORD startaddress = GetStartAddress ( p i. hprocess, p i. hthread ) ; BYTE i n s t r u c t i o n ; DWORD nbyte ; / / Debuggolt processz utasításkódjának olvasása / / Utasítás első byte-jának olvasása ReadProcessMemory ( p i. hprocess, ( void ) startaddress,& i n s t r u c t i o n,1,& nbyte ) ; / / Eredeti érték elmentése o r i g i n a l I n s t r u c t i o n = i n s t r u c t i o n ; / / Utasítás felülírása i n s t r u c t i o n = 0xCC ; WriteProcessMemory ( p i. hprocess, ( void ) startaddress, &i n s t r u c t i o n,1,& nbyte ) ; FlushInstructionCache ( pi, ( void ) startaddress, 1 ) ; Izsó Tamás Debugger működése/ 18
Hogyan folytassuk a programot 1 Állítsuk vissza az eredeti utasítást. 2 Frissítsük az utasítás cache-t (gyorsítótárat). 3 Adjuk rá a visszaállított utasításra a vezérlést. DWORD nbyte ; WriteProcessMemory ( p i. hprocess, startaddress,& o r i g i n a l I n s t r u c t i o n,1,& nbyte ) ; FlushInstructionCache ( p i. hprocess, startaddress, 1 ) ; / / Debuggolt processz állapotának (regisztereinek) olvasása CONTEXT context ; context. ContextFlags = CONTEXT_ALL; GetThreadContext ( p i. hthread, &context ) ; / / Eredeti érték elmentése context. Eip ; / / Visszalépés eggyel SetThreadContext ( p i. hthread, &context ) ; Gondoljuk át mi van, ha a program újból ráfut a startaddress-en lévő utasításra. Észreveszi ezt a debugger? Izsó Tamás Debugger működése/ 19
Lépésenkénti futtatás Debugger Az EXCEPTION_SINGLE_STEP kivétel elkapásával megoldható a probléma..... CONTEXT context ; context. ContextFlags = CONTEXT_ALL; GetThreadContext ( p i. hthread, &context ) ; / / Eredeti érték elmentése context. Eip ; / / Visszalépés eggyel context. EFlags = 0x100 ; / / Trap flag SetThreadContext ( p i. hthread, &context ) ; Az EXCEPTION_SINGLE_STEP bekövetkezése után visszaírhatjuk a breakpoint-ot Izsó Tamás Debugger működése/ 20
Nyomkövetési szolgáltatások biztosítása Hogyan valósítható meg a lépésenkénti nyomkövetés, hívott függvénybe történő visszalépés, adott sorig (kurzorig) tört futtatás, feltételes breakpoint? Izsó Tamás Debugger működése/ 21
Nyomkövetési szolgáltatások biztosítása Hogyan valósítható meg a lépésenkénti nyomkövetés, hívott függvénybe történő visszalépés, adott sorig (kurzorig) tört futtatás, feltételes breakpoint? Segítség: EXCEPTION_SINGLE_STEP felhasználásával Láthatatlan breakpoint-ok (debugger a felhasználó tudta nélkül definiálja) létesítésével és felszedésével. Izsó Tamás Debugger működése/ 21
Demo Debugger cd E:\Reverse.Uj\Debug\example1\Debug\ example1.exe debugger.exe breakpoint.exe Mit tapasztalunk, ha a breakpoint programot debugger alatt futtatjuk? Hol használható fel ez a tapasztalat? Izsó Tamás Debugger működése/ 22
Mi az amit még nem tudunk? 1 Stackframe követését. Mivel a stackframe szervezést később vesszük, ezt itt nem tárgyaljuk. 2 Szimbolikus információk és forrássorok megjelenítését. 3 Disassemblert készíteni. (Nem kell, forrásban elérhető pár megoldás. Pl. PIN-ben). Minden fordító saját formában létrehoz nyomkövetéshez szolgáló információkat. Mik ezek? Szimbolikus változó és függvényneveket, forrásfájlok neve, keresztreferenciát annak eldöntésére, hogy melyik gépi utasítás melyik sor alapján keletkezett, stb. A Visual Studio ezeket az információkat a.pdb fájlban tárolja. Ennek a fájlnak a kezelését szolgáló függvények a DbgHelp.DLL-ben vannak megvalósítva. Izsó Tamás Debugger működése/ 23
Milyen területeket érintett az előadás Számítógép Architektúrák (cache, virtuális memória, interrupt kezelés, gépi utasítások, regiszterek) Operációs rendszerek (Windows rendszerhívások) Fordítók kódgenerálása (kivételkezelés megvalósítása, debugger információk) Izsó Tamás Debugger működése/ 24
Irodalom Debugger Matt Pietrek, A Crash Course on the Depths of Win32 Structured Exception Handling http://www.microsoft. com/msj/0197/exception/exception.aspx Ajay Vijayvargiya, Writing a basic Windows debugger http://www.codeproject.com/articles/43682/ Writing-a-basic-Windows-debugger Vishal Kochhar, How a C++ compiler implements exception handling, http://www.codeproject.com/articles/2126/ How-a-C-compiler-implements-exception-handling Eli Bendersky, How debuggers work, http://eli.thegreenplace.net/2011/01/23/ how-debuggers-work-part-1// Izsó Tamás Debugger működése/ 25