Debugger
Debugger muködése ˝ Megszakítás és nyomkövetés.
Izsó Tamás
2013. október 31.
Izsó Tamás
Debugger muködése/ ˝ 1
Debugger
Section 1 Debugger
Izsó Tamás
Debugger muködése/ ˝ 2
Debugger
Debugger felépítése
Proccess indítás nyomkövetéssel. Nyomköveto˝ ciklus létrehozása . STARTUPINFO s i ; PROCESS_INFORMATION p i ; ZeroMemory ( &s i , s i z e o f ( s i ) ) ; s i . cb = s i z e o f ( s i ) ; ZeroMemory ( &p i , s i z e o f ( p i ) ) ; CreateProcess ( ProcessNameToDebug , NULL , NULL , NULL , FALSE , DEBUG_ONLY_THIS_PROCESS, NULL , NULL , &s i , & p i ) ;
Izsó Tamás
Debugger muködése/ ˝ 3
Debugger
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 muködése/ ˝ 4
Debugger
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 muködése/ ˝ 5
Debugger
Nyomkövetési esemény rekordja s t r u c t DEBUG_EVENT { DWORD dwDebugEventCode ; DWORD dwProcessId ; DWORD dwThreadId ; union { EXCEPTION_DEBUG_INFO E x c ep t i o n ; CREATE_THREAD_DEBUG_INFO CreateThread ; CREATE_PROCESS_DEBUG_INFO C r e a t e P r o c e s s I n f o ; EXIT_THREAD_DEBUG_INFO E x i t T h r e a d ; EXIT_PROCESS_DEBUG_INFO E x i t P r o c e s s ; LOAD_DLL_DEBUG_INFO L o a d D l l ; UNLOAD_DLL_DEBUG_INFO U n l o a d D l l ; OUTPUT_DEBUG_STRING_INFO DebugString ; RIP_INFO R i p I n f o ; } u; }; Izsó Tamás
Debugger muködése/ ˝ 6
Debugger
Debug ciklus — kicsit részletesebben b o o l bContinueDebugging = t r u e ; while ( bContinueDebugging ) { i f ( ! WaitForDebugEvent (& debug_event , INFINITE ) ) r e t u r n ; 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& e x c e p t i o n = debug_event . u . E x c e p t i o n ; . . . break ; } } ...
Izsó Tamás
Debugger muködése/ ˝ 7
Debugger
CREATE_PROCESS_DEBUG_EVENT esemény Ez az esemény a nyomkövetett program betöltése után a futás ˝ következik be. Itt lehet megadni a breakpoint-ot. elott 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 ; / / elso˝ thread handle 1 LPVOID lpBaseOfImage ; DWORD d w D e b u g I n f o F i l e O f f s e t ; DWORD nDebugInfoSize ; LPVOID lpThreadLocalBase ; LPTHREAD_START_ROUTINE l p S t a r t A d d r e s s ; LPVOID lpImageName ; 3 WORD fUnicode ; / / fájlnév kódolása };
2
˝ 1 A memóriába betöltött exe fájl kezdocíme. 2 Elso˝ végrehajtandó utasítás címe. 3 Program nevének a címe a debuggolt program címterében. Izsó Tamás
Debugger muködése/ ˝ 8
Debugger
CREATE_PROCESS_DEBUG_EVENT esemény használata Ez az esemény a nyomkövetett program betöltése után a futás ˝ következik be. Itt lehet megadni a breakpoint-ot. elott case CREATE_PROCESS_DEBUG_EVENT: { char ∗ f i l e n a m e = GetFileNameFromHandle ( debug_event . u . C r e a t e P r o c e s s I n f o . 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 ˝ A GetFileNameFromHandle függvény segítségével állítjuk elo. 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 muködése/ ˝ 9
Debugger
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. s t r u c t OUTPUT_DEBUG_STRING_INFO { LPSTR lpDebugStringData ; / / char* WORD fUnicode ; WORD nDebugStringLength ; };
Izsó Tamás
Debugger muködése/ ˝ 10
Debugger
OUTPUT_DEBUG_STRING_EVENT esemény case OUTPUT_DEBUG_STRING_EVENT : { 1 char ∗ msg ; OUTPUT_DEBUG_STRING_INFO & DebugString = debug_event . u . DebugString ; msg=new char [ DebugString . nDebugStringLength + 1 ] ; ReadProcessMemory ( p i . hProcess , DebugString . lpDebugStringData , msg , DebugString . nDebugStringLength ,
2 3 4
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 muködése/ ˝ 11
Debugger
EXIT_PROCESS_DEBUG_INFO esemény ˝ Nyomkövetett program befejezodésénél jelentkezo˝ esemény. s t r u c t EXIT_PROCESS_DEBUG_INFO { DWORD dwExitCode ; };
Használata: b o o l bContinueDebugging= t r u e ; ... case EXIT_PROCESS_DEBUG_EVENT : { p r i n t f ( " Process e x i t e d w i t h code : 0x%x " , debug_event . u . E x i t P r o c e s s . dwExitCode ) ; bContinueDebugging= f a l s e ; } break ;
Izsó Tamás
Debugger muködése/ ˝ 12
Debugger
(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. s t r u c t LOAD_DLL_DEBUG_INFO { HANDLE h F i l e ; / / (.DLL) file handle 1 LPVOID l p B a s e O f D l l ; DWORD d w D e b u g I n f o F i l e O f f s e t ; DWORD nDebugInfoSize ; LPVOID lpImageName ; WORD fUnicode ; };
˝ 1 Dll betöltési címe a debuggolt processz szemszögébol. s t r u c t UNLOAD_DLL_DEBUG_INFO { LPVOID l p B a s e O f D l l ; }; Izsó Tamás
Debugger muködése/ ˝ 13
Debugger
DLL-ek nyomkövetése s t d : : map
DllNameMap ; case LOAD_DLL_DEBUG_EVENT : { s t r i n g name = GetFileNameFromHandle ( debug_event . u . L o a d D l l . h F i l e ) ; DllNameMap [ debug_event . u . L o a d D l l . l p B a s e O f D l l ] =name ; c o u t << " Load DLL " << name << " " << s t d : : hex << debug_event . u . L o a d D l l . l p B a s e O f D l l << s t d : : e n d l ; } break ; case UNLOAD_DLL_DEBUG_EVENT : c o u t << " Unload DLL " << DllNameMap [ debug_event . u . U n l o a d D l l . l p B a s e O f D l l ] << s t d : : e n d l ; break ;
Izsó Tamás
Debugger muködése/ ˝ 14
Debugger
EXCEPTION_DEBUG_EVENT esemény ˝ o˝ fejezetben a Azokat az eseményeket kapjuk itt el, amit az eloz struktúrált kivétel kezelésnél (SEH) tárgyaltunk. s t r u c t EXCEPTION_DEBUG_INFO { EXCEPTION_RECORD ExceptionRecord ; DWORD dwFirstChance ; };
s t r u c t EXCEPTION_RECORD { DWORD ExceptionCode ; DWORD ExceptionFlags ; s t r u c t _EXCEPTION_RECORD ∗ ExceptionRecord ; PVOID ExceptionAddress ; DWORD NumberParameters ; ULONG_PTR E x c e p t i o n I n f o r m a t i o n [EXCEPTION_MAXIMUM_PARAMETERS ] ; };
Izsó Tamás
Debugger muködése/ ˝ 15
Debugger
EXCEPTION_DEBUG_EVENT esemény case EXCEPTION_DEBUG_EVENT : { EXCEPTION_DEBUG_INFO& e x c e p t i o n = debug_event . u . E x c e p t i o n ; switch ( e x c e p t i o n . ExceptionRecord . ExceptionCode ) { case STATUS_BREAKPOINT : p r i n t f ( " Break p o i n t " ; ) dwContinueStatus = DBG_CONTINUE ; break ; default : i f ( e x c e p t i o n . dwFirstChance == 1 ) { p r i n t f ( " Addr : %x code : %d " e x c e p t i o n . ExceptionRecord . ExceptionAddress , e x c e p t i o n . ExceptionRecord . ExceptionCode ) ; } dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED ; } break ; }
Izsó Tamás
Debugger muködése/ ˝ 16
Debugger
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évo˝ utasítást , például tegyünk oda breakpoint-ot ( int 3 vagy 0xCC értéku˝ 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 muködése/ ˝ 17
Debugger
Hogyan módosíthatjuk a debuggolt processzt 1
Mentsük el a felülírandó utasítás elso˝ 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 s t a r t A d d r e s s = G e t S t a r t A d d r e s s ( 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 elso˝ byte-jának olvasása ReadProcessMemory ( p i . hProcess , ( void ∗ ) s t a r t A d d r e s s ,& i n s t r u c t i o n , 1 , & nByte ) ; / / Eredeti érték elmentése originalInstruction =instruction ;
/ / Utasítás felülírása i n s t r u c t i o n = 0xCC ; WriteProcessMemory ( p i . hProcess , ( void ∗ ) s t a r t A d d r e s s , & i n s t r u c t i o n , 1 , & nByte ) ; F l u s h I n s t r u c t i o n C a c h e ( p i , ( void ∗ ) s t a r t A d d r e s s , 1 ) ; Izsó Tamás
Debugger muködése/ ˝ 18
Debugger
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 , s t a r t A d d r e s s ,& o r i g i n a l I n s t r u c t i o n , 1 , & nByte ) ; F l u s h I n s t r u c t i o n C a c h e ( p i . hProcess , s t a r t A d d r e s s , 1 ) ; / / Debuggolt processz állapotának (regisztereinek) olvasása CONTEXT c o n t e x t ; c o n t e x t . Co n t ex t F l a g s = CONTEXT_ALL ; GetThreadContext ( p i . hThread , &c o n t e x t ) ; / / Eredeti érték elmentése c o n t e x t . Eip −−; / / Visszalépés eggyel SetThreadContext ( p i . hThread , &c o n t e x t ) ;
Gondoljuk át mi van, ha a program újból ráfut a startAddress-en lévo˝ utasításra. Észreveszi ezt a debugger? Izsó Tamás
Debugger muködése/ ˝ 19
Debugger
Lépésenkénti futtatás Az EXCEPTION_SINGLE_STEP kivétel elkapásával megoldható a probléma. . . . . CONTEXT c o n t e x t ; c o n t e x t . Co n t ex t Fl a g s = CONTEXT_ALL ; GetThreadContext ( p i . hThread , &c o n t e x t ) ; / / Eredeti érték elmentése c o n t e x t . Eip −−; / / Visszalépés eggyel c o n t e x t . EFlags | = 0x100 ; / / Trap flag SetThreadContext ( p i . hThread , &c o n t e x t ) ;
Az EXCEPTION_SINGLE_STEP bekövetkezése után visszaírhatjuk a breakpoint-ot
Izsó Tamás
Debugger muködése/ ˝ 20
Debugger
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éno˝ visszalépés, adott sorig (kurzorig) tört futtatás, feltételes breakpoint ?
Izsó Tamás
Debugger muködése/ ˝ 21
Debugger
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éno˝ 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 muködése/ ˝ 21
Debugger
Demo
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 muködése/ ˝ 22
Debugger
Mi az amit még nem tudunk? 1 2 3
Stackframe követését. Mivel a stackframe szervezést ˝ vesszük, ezt itt nem tárgyaljuk. késobb Szimbolikus információk és forrássorok megjelenítését. Disassemblert készíteni. (Nem kell, forrásban elérheto˝ 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 muködése/ ˝ 23
Debugger
˝ Milyen területeket érintett az eloadá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 muködése/ ˝ 24
Debugger
Irodalom 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 muködése/ ˝ 25