* generic tracer 0.1, http://conus.info/gt This tool is an extremely simple Win32 tracer. * Main features: 1) Setting breakpoint at any function, monitoring its arguments and return value. 2) Monitoring global variables access. In a way, it is a kind "strace" utility: http://en.wikipedia.org/wiki/Strace Significant differences vs Strace are: 1) gt is Win32 only. 2) Breakpoints not just system calls, but any function. 3) Only 4 breakpoints, because of x86 architecture limitation. 4) Usage of Oracle .SYM files: ORACLE_HOME should be defined in environment. * Let's start. There're two major modes: process loading and process attaching. Process loading is used when process is yet to be loaded and run. Process attaching is used when the process is already running. To load process, invoke: gt.exe -l:filename.exe Optional "-c" define command line for loading process. gt.exe -l:bzip2.exe -c:--help Sometimes the command line contains spaces, if so, it need to be enclosed in double quotes to distinguish from other options: gt.exe -l:rar.exe "-c:a archive.rar *" Process attaching is simple too: gt.exe -a:filename.exe Process with this filename should be already loaded. If there're several processes with the same name, gt will offer to specify PID explicitly: gt.exe -a:10268 "--loading" option can be used for dumping all module filenames and base addresses while loading. "-q" option is just "be quitet", no output. "--allsymbols" option: dump all loading symbols. Regular expressions may be used for filter, for example "--allsymbols:somedll.dll!.*" can be used for dumping all symbols in some DLL. Another possible way to use feature: "--allsymbols:.*printf", gt will print something like this: New symbol. Module=[ntdll.dll], address=[0x77C004BC], name=[_snprintf] New symbol. Module=[ntdll.dll], address=[0x77B8E61F], name=[_snwprintf] [...skipped...] New symbol. Module=[msvcrt.dll], address=[0x75725F37], name=[vswprintf] New symbol. Module=[msvcrt.dll], address=[0x75726649], name=[vwprintf] New symbol. Module=[msvcrt.dll], address=[0x756C3D68], name=[wprintf] "--child" option allow to handle all child process. For example, you could run "gt.exe -l:cmd.exe", this will open cmd window and every process running inside command interpreter will be handled by gt. "@" option allows to store all options in a text file profile and use the latter: gt.exe @filename Each line in the profile represents an option. This can be handy for lengthy and/or often used options, like bytemasks (see below). Of course, "@" option can be used along with any other options: gt.exe -l:filename.exe @additional_options @even_more_options While loading or attaching, gt will inspect all modules: main executable and all DLL files loaded after. It will fetch all present symbols, incuding export entries of DLL files. It will also look for FileName.MAP file and try to parse information from it. MAP file has the same format as that produced by IDA disassembler . Gt will also look for FileName.SYM file and try to load symbols from it, treating those as Oracle RDBMS SYM file format: ORACLE_HOME environment value should be set for this. If DLL contain only exports by ordinals, e.g., without names (MFC DLLs, for example), the name of ordinal will be generated in compliance with "ordinal_" format, for example, "ordinal_12". All information dumped to stdout is also written to gt.log file. This file is erased at each start. * Breakpoints. ** Breakpoint on function execution. Often, we don't have time to load debugger, set breakpoint manually to some function just to see its arguments. gt.exe -l:bzip2.exe -c:--help bpf=kernel32.dll!WriteFile After execution we will see: 1188 (0) KERNEL32.dll!WriteFile () (called from 0x610AC912 (cygwin1.dll!sigemptyset+0x1022)) 1188 (0) KERNEL32.dll!WriteFile -> 1 Not so much information, however, we see who call WriteFile function and what it returns. Well, most likely, this function is used to write bzip2 help to stdout. Let's add "args" option: gt.exe -l:bzip2.exe -c:--help bpf=kernel32.dll!WriteFile,args:5 09D0 (0) KERNEL32.dll!WriteFile (0x0000001B, " If no file names are given, bzip2 compresses or decompresses", 0x0000003F, "?", 0) (called from 0x61031A3F (cygwin1.dll!cygwin_internal+0x8f6f)) 09D0 (0) KERNEL32.dll!WriteFile -> 1 09D0 (0) KERNEL32.dll!WriteFile (0x0000001B, " from standard input to standard output. You can combinesses", 0x0000003B, ";", 0) (called from 0x61031A3F (cygwin1.dll!cygwin_internal+0x8f6f)) 09D0 (0) KERNEL32.dll!WriteFile -> 1 09D0 (0) KERNEL32.dll!WriteFile (0x0000001B, " short flags, so `-v -4' means the same as -v4 or -4v, &c.ses", 0x0000003C, "<", 0) (called from 0x61031A3F (cygwin1.dll!cygwin_internal+0x8f6f)) 09D0 (0) KERNEL32.dll!WriteFile -> 1 What we see here is an attempt to read 5 arguments at each WriteFile function call. If some of these arguments are pointers to some area within process memory, and the data at the pointer can be interpreted as ASCII string, it will be printed instead. This is useful when intercepting string functions like strcmp(), strlen(), strtok(), atoi(), and so on. It is not a problem to make mistake on arguments number (except using "skip_stdcall" option, see below). If defined arguments number greater than real, captured local variables of caller function probably will be printed. Or any other useless junk. If defined arguments number is less than real, then only part of arguments will be visible. Now let's write a very simple "hello world" program and compile it in MS VC 2008 with /MD option - it will use MSVCR90.DLL library instead of standard C library static compiling. While debugging, turn on -s option, which will dump stack on each breakpoint. gt.exe -l:hello.exe -s bpf=kernel32.dll!WriteFile,args:5 We will see: 23B4 (0) KERNEL32.dll!WriteFile (7, "hello to gt!\r\n", 0x0000000E, 0x0017E3A4, 0) (called from 0x7317754E (MSVCR90.dll!_lseeki64+0x56b)) Call stack of thread 0x23B4 return address=731778D8 (MSVCR90.dll!_write+0x9f) return address=7313FB4A (MSVCR90.dll!_fdopen+0x1c0) return address=7313F70C (MSVCR90.dll!_flsbuf+0x6e1) return address=73141E50 (MSVCR90.dll!printf+0x84) return address=0040100E (hello.exe!BASE+0x100e) return address=0040116F (hello.exe!BASE+0x116f) return address=76FCE4A5 (KERNEL32.dll!BaseThreadInitThunk+0xe) return address=77C9CFED (ntdll.dll!RtlCreateUserProcess+0x8c) return address=77C9D1FF (ntdll.dll!RtlCreateProcessParameters+0x4e) 23B4 (0) KERNEL32.dll!WriteFile -> 1 Now we can see a path from hello.exe to printf() and then to KERNEL32.dll!WriteFile. Stack dump can be very handy, for example, we have a program showing Message Box once and by intercepting USER32.DLL!MessageBoxA call we can see a path to this call. We can also use regular expressions in BPF option. It is Perl syntax. http://www.boost.org/doc/libs/1_39_0/libs/regex/doc/html/boost_regex/syntax/perl_syntax.html For example: gt.exe -l:hello.exe -s bpf=.*!printf,args:1 Attention: it is not wildcard characters! gt will look for "printf" symbol in each loading module. If the same name occurs in different modules, gt will use only the first occurence. One more example: dump some Windows registry access functions: gt.exe -l:someprocess.exe bpf=advapi32.dll!RegOpenKeyExA,args:5 bpf=advapi32.dll!RegQueryValueExA,args:6 bpf=advapi32.dll!RegSetValueExA,args:6 gt can also use raw addresses instead of symbolic or regular expressions. Let's compile this piece of code: =============================================================================== void f (int i) { printf ("i=%d\n", i); }; void main() { for (int i=0; i<10; i++) f (i); }; =============================================================================== Let's use our favorite disassembler to determine f() function address. When I compile it using MS VC 2008, the address is 0x00401000: wow, it is just the beginning of .text executable section! Now let's run: 2290 (0) 0x00401000 (0) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (1) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (2) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (3) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (4) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (5) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (6) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (7) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (8) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 2290 (0) 0x00401000 (9) (called from 0x00401045 (cycle.exe!BASE+0x1045)) 2290 (0) 0x00401000 -> 4 Not bad. But why does this function return 4? Actually, gt doesn't know this function is void type, e.g., it doesn't return any value. So it just takes the value at EAX register. In our case, EAX value was inherited from printf() call. printf() usually returns number of symbols printed - that is 4 in our case. It is also possible to use bytemask instead of symbol name or address. Sometimes, we would like to degine a function inside of an executable by byte pattern, just like IDA signature. So, we can define the function from our last example as bytestring: gt -l:cycle.exe bpf=558BEC8B45085068........FF15........83C4085DC3,args:1 gt will look for this pattern in each executable section of each loading module and will print: =============================================================================== cycle.exe: searching for bytemask_0 in .text section... bytemask_0 is resolved to address 0x00401000 (cycle.exe) =============================================================================== Two dots instead of two hexadecimal digits mean *any*byte*. This can be useful for skipping FIXUPs, etc. In my example, I skip PUSH argument (which is the pointer "i=%d\n" string (the address of this string in the code can be veeeery unstable) and the address of printf function, which will be determined only after MSVCR90.DLL loading. It is also possible to mention "[skip:n]" to skip several bytes during bytemask search, for example: "bpf=55[skip:10]1100" - this is equivalent to "bpf=55....................1100" Of course, only the bytes from the beginning of function are enough. But the problem is that these bytes can appear more than once in one module. gt will warn us on, and use only the first occurence by default. This means that function byte pattern is to be constructed carefully. Another BPF option is "rt", it is used to replace the returning value of any function by something else, on fly. gt -l:filename.exe bpf=function,args:1,rt:0x12345678 Another useful BPF option is "skip", meaning, gt will bypass a function. This can be used with "rt" too. gt -l:filename.exe bpf=function,args:1,rt:0x12345678,skip This means that the function just gets bypassed and its return value is fixed at 0x12345678. Without "0x" prefix, this value would be interpreted as decimal number. The last BPF option is "skip_stdcall", it is the same as "skip" but rather used for stdcall functions. http://en.wikipedia.org/wiki/X86_calling_conventions The difference between cdecl and stdcall calling conventions is just that cdecl function doesn't align stack pointer at exit (caller should do this). stdcall function aligns stack pointer at exit. cdecl is the most used calling convention. However, stdcall is used in MS Windows. So, if you would like to skip a function in KERNEL32.DLL or USER32.DLL, you should use skip_stdcall. Consequently, in this case, gt must know the exact arguments number, without it the process may crash. If you'd like to suppress all WriteFile calls, do this: gt.exe -l:hello.exe bpf=kernel32.dll!WriteFile,args:5,skip_stdcall,rt:1 Wow, and don't forget to make it return 1, so the caller will not suspect anything! WriteFile arguments number is just 5. Change it to something different, and process crashes. You can also suppress noisy beeping: gt.exe -l:beeper.exe bpf=kernel32.dll!Beep,args:2,skip_stdcall,rt:1 You can also suppress Message Box by making it appear to a caller that the user presses OK every time (IDOK constant is 1): gt.exe -l:filename.exe bpf=user32.dll!MessageBoxA,args:4,skip_stdcall,rt:1 ... or CANCEL (IDCANCEL constant is 2): gt.exe -l:filename.exe bpf=user32.dll!MessageBoxA,args:4,skip_stdcall,rt:2 Real-world example. When you run Windows (XP SP3) FreeCell and press F2 (New game), you will get a message box "Do you want to resign this game?" We can suppress all that beeping and also make illusion to FreeCell user always press YES: gt.exe -l:c:\windows\system32\freecell.exe bpf=user32.dll!messagebeep,args:1,skip_stdcall bpf=user32.dll!messageboxw,args:4,skip_stdcall,rt:6 IDYES constant is 6. We use MessageBoxW - W mean unicode version of MessageBox. Another fun is intercepting rand() function in various games. For example, Windows Solitaire card game use it to generate random deal. We can fix rand() return at zero, and Solitaire will do the same deal each time, forever: gt.exe -l:c:\windows\system32\sol.exe bpf=.*!rand,rt:0 ** Breakpoints on memory value access. x86 architecture allows to set breakpoints on a memory value access. That is, if someone or something modifies some value, gt will be instantly notified. Options are: breakpoint on BYTE and DWORD; and breakpoint on value WRITE or both READ/WRITE. Because of some reason unknown to me, Intel offers only two opportunities. It is also should be noted that these breakpoints only practical for global variables, not local ones (stored in stack). So, let's illustrate this with on Oracle RDBMS 11.1.0.7.0. We can set breakpoint on DWORD granularity, for symbol _ktsmgd_, READ/WRITE. gt.exe -a:oracle.exe -s bpmd=oracle.exe!_ktsmgd_,rw We'll see who and when reads or writes this. Also, note -s option. It is useful here too. Run "ALTER SYSTEM SET "_disable_txn_alert" = 1;" command from SYS console and you'll see: Breakpoint oracle.exe!_ktsmgd_ is resolved to symbol ORACLE.EXE!_ktsmgd_ and address 0x05DBD0E4 0178 (0) 0x011EF660 / ORACLE.EXE!_ktsmgdcb+0x18: some code reading or writting ORACLE.EXE!_ktsmgd_ DWORD variable (which is currently 0x00000001) Call stack of thread 0x0178 return address=00470B96 (ORACLE.EXE!_kspptval+0x79a) return address=0046F535 (ORACLE.EXE!_kspset0+0x9e9) return address=046711F8 (ORACLE.EXE!__VInfreq__kkyasy+0xdb0) return address=0094F9D8 (ORACLE.EXE!_kksExecuteCommand+0x3ec) return address=01EF1F7E (ORACLE.EXE!_opiexe+0x4e2e) return address=01E2A545 (ORACLE.EXE!_kpoal8+0x8f9) return address=00A0AF5A (ORACLE.EXE!_opiodr+0x516) return address=610362E1 (oracommon11.dll!_ttcpip+0xae9) return address=00A0A12E (ORACLE.EXE!_opitsk+0x4fe) return address=01FAA6BC (ORACLE.EXE!_opiino+0x430) return address=00A0AF5A (ORACLE.EXE!_opiodr+0x516) return address=00457BDC (ORACLE.EXE!_opidrv+0x52c) return address=004581C6 (ORACLE.EXE!_sou2o+0x32) return address=00401193 (ORACLE.EXE!_opimai_real+0x87) return address=00401061 (ORACLE.EXE!_opimai+0x61) return address=00401CAD (ORACLE.EXE!_OracleThreadStart@4+0x31d) return address=77E64829 (KERNEL32.dll!GetModuleHandleA+0xdf) Now we can see not just where this value is modified but also calling stack to this condition. Visit http://blogs.conus.info/node/3 for more information about this stuff. We can set breakpoint for this symbol waiting only for WRITE access mode: gt.exe -a:oracle.exe -s bpmd=oracle.exe!_ktsmgd_,w And of course, we can limit ourselves just to BYTE value rather than DWORD. And of course, we can use just address here: gt.exe -a:filename.exe -s bpmb=0x00401000,w ** 4 breakpoints. x86 architecture allow to use up to 4 breakpoints simultaneously. So, these 3 features can be combined in any order 4 times. ** Console input. a) Press ESC or Ctrl-C to detach from the running process. b) Press SPACE to see current call stacks for each thread. For example: attach to some running application with opened Message Box, press SPACE and see what caused it. ** Detaching. gt use DebugActiveProcessStop() function to detach from the running process. It is present in all modern NT-based operation systems, probably, except Windows NT and Windows 2000. So all gt can do is just to kill the running process - sorry! ** Some other technical notes. Stack dumping feature consider stack frames "divided" with EBP base pointer: http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames This means that any function which doesnt use this scheme will be excluded from stack dump - unintentionally. ** Conclusion. This release is not tested properly yet. So please be prepared for any possible crash. I strongly advice to do all experimentation in virtual machine. If you find an annoying bug, please drop me a line: dennis@conus.info Please attach gt.log file and screenshot of the last gt output. I'll also be thankful for any comments and suggestions related to gt tool. -- Dennis Yurichev