Tracing your API call under window is one of the most needed task.
Under Linux this task is very well made with a proggie like strace.
But under windows this is not really easy.
about API tracing there is a lots of program who could do this task.
Free or not but i never been really happy with'em. and really why looking at something else
when you could make it in 30 seconds with a free and very solid debugger !
Here i call windbg !
So let's fire your favorite debugger (Word Windbg ;) )
Imagine you want to trace the call made to WinInet.dll
For this example we will use windows live mail. (wlmail.exe)
Load the executable
(172c.116c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0006fb08 edx=77859a94 esi=fffffffe edi=7785b6f8
eip=77847dfe esp=0006fb20 ebp=0006fb50 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
77847dfe cc int 3
0:000> lm
start end module name
00400000 00428000 wlmail (deferred)
70300000 703e2000 UXCore (deferred)
72c50000 72ceb000 MSVCR80 (deferred)
74fe0000 7517e000 COMCTL32 (deferred)
75360000 75365000 MSIMG32 (deferred)
76db0000 76dce000 IMM32 (deferred)
76dd0000 76e6d000 USER32 (deferred)
76eb0000 76f76000 ADVAPI32 (deferred)
76f80000 770c4000 ole32 (deferred)
771a0000 77268000 MSCTF (deferred)
77270000 77332000 RPCRT4 (deferred)
77420000 774ad000 OLEAUT32 (deferred)
77540000 7758b000 GDI32 (deferred)
77590000 7763a000 msvcrt (deferred)
77670000 776c8000 SHLWAPI (deferred)
77800000 77927000 ntdll (pdb symbols) C:\Program Files\Debugging Tools for Windows\sym\ntdll.pdb\B958B2F91A5A46B889DAFAB4D140CF252\ntdll.pdb
77980000 77a5b000 kernel32 (deferred)
lm list the current modules loaded as you see there as you see our Module is not yet loaded.
So yet we have nothing we could trace.
the easy way is to set a Breakpoint at LoadLibrary waiting for wininet.dll like this
we will not miss anycall to wininet.
If you want to trace kernel32 or something else you could trace from here.
(If you want to trace the dll at the loading time (init routine for example) or tracing thread/process Creation you will have to do it in a different way but that's a another article)
Add your Breakpoint
0:000> bp kernel32!LoadLibraryW
0:000> bp kernel32!LoadLibraryA <--- shouldnt happen windows mostly call *W function
0:000> g
Breakpoint 0 hit
eax=00b30000 ebx=00000000 ecx=0006f324 edx=77859a94 esi=7784298c edi=00000000
eip=779a361f esp=0006f300 ebp=0006f324 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!LoadLibraryW:
yet windbg didnt printed that's wininet is loaded so continue until it's loaded
...
0:000> g
ModLoad: 5e100000 5e373000 C:\Program Files\Windows Live\Mail\MSMAIL.DLL
ModLoad: 5a780000 5a7d8000 C:\Program Files\Windows Live\Mail\ContactsUX.dll
ModLoad: 59100000 5912c000 C:\Program Files\Windows Live\Mail\MSNCore.dll
ModLoad: 776d0000 777f9000 C:\Windows\system32\urlmon.dll
ModLoad: 77930000 77975000 C:\Windows\system32\iertutil.dll
ModLoad: 77340000 77410000 C:\Windows\system32\WININET.dll
HERE IS our dll !!!
Ok now it's time to trace !
If you want a simple trace you could use a command like "bm"
"bm" will add your breakpoint to the place who match your place
"bm" work like the command "x"
try to this command x wininet!*Internet*
0:000> x wininet!*Internet*
7734fee2 WININET!InternetIndicateStatus =
773bce38 WININET!INTERNET_CONNECT_HANDLE_OBJECT::ExpireDependents =
7734182f WININET!InternetDestroyThreadInfo =
773aa2be WININET!CFsm_InternetReadFile::CFsm_InternetReadFile =
773ab173 WININET!InternetShowSecurityInfoByURLW =
773ab959 WININET!INTERNET_FILE_HANDLE_OBJECT::INTERNET_FILE_HANDLE_OBJECT =
You could see all the symbols into this module.
so let's add our breakpoint for tracing !
0:000> bm wininet!*
No matching code symbols found, no breakpoints set.
If you are using public symbols, switch to full or export symbols.
??? hey what's wrong !
Ok windbg dont know how to put our breakpoint because per default it using the symbols file
for placing is breakpoint. but like wininet contains also "data" symbol and another kind of information that would be totally wrong to breakpoint (never try to breakpoint into a readonly memory map)
So for fixing this and use only the symbol's exported from the dll (PE export table)
we will have to do a little hack.
0:000> .sympath C:\somewhere_not_real
Symbol search path is: C:\somewhere_not_real
WARNING: Inaccessible path: 'C:\somewhere_not_real'
0:000> .reload -f wininet.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\WININET.dll -
dont mind of the ERROR this not an error :)
now check again with "x wininet!*" as you see there no more debugging symbol we are back to the straight "exported" symbol by the dll itself.
0:000> bm wininet!*
3: 77343f62 @!"WININET!InternetCrackUrlW"
4: 77346151 @!"WININET!InternetCanonicalizeUrlW"
5: 773481dd @!"WININET!GetUrlCacheEntryInfoExA"
6: 77348425 @!"WININET!GetUrlCacheEntryInfoA"
....
now try to run the code !
"g"
Ok now your code is breaking at each time you are calling a wininet functions!
But this is not really want you thinked when we talked about tracing ?
Exactly this is not very good...
So what's to do ?
bm and many of the command of windbg can run a command at each time they execute !
First we want to get ride of the "register" displayed at each time and the disassembly
.prompt_allow -reg -dis +ea
Try this !
bm wininet!* "ln . ; g"
at each time a breakpoint will be executed you will get the name of your location "ln ."
and continue the program !
here it's starting to get good to see exactly what is called !
But What if i want to get something more verbose ?
Like displaying the data Received or the URL opened or the data sent ? here is my collection
of breakpoint to add (dont mind i will explain the the diffcult one)
bp WININET!InternetConnectW ".echo InternetConnectW; du /c 64 poi(@esp+0x8) L100 ; g"
bp WININET!HttpOpenRequestW ".echo HttpOpenRequestW; du /c 64 poi(@esp+0x8); du /c 64 poi(@esp+0XC) L2000 ; g"
bp WININET!HttpSendRequestW ".echo HttpSendRequest->headerW; du /c 64 poi(@esp+0x8) L4000 ;.echo SendRequestA->Option; du /c 64 poi (@esp+0x10) L5500 ; g "
bp WININET!HttpAddRequestHeadersW ".echo HttpAddRequestHeadersW; du /c 64 poi(@esp+0x8) L4000; g"
bp WININET!GetUrlCacheEntryInfoW ".echo GetUrlCacheEntryInfoW; du /c 64 poi(@esp+0x8) L4000; g"
bp WININET!SetUrlCacheEntryInfoW ".echo SetUrlCacheEntryInfoW; du /c 64 poi (@esp+0x4) L4000; g"
bp WININET!RetrieveUrlCacheEntryStreamW ".echo RetrieveUrlCacheEntryStreamW; du /c 64 poi(@esp+0x4) L4000; g"
bp WININET!CreateUrlCacheEntryW ".echo CreateUrlCacheEntryW; du /c 64 poi (@esp+0x4) L4000; g"
bp WININET!InternetSetCookieW ".echo InternetSetCookieW ; .echo Cookiename; du /c 64 poi (@esp+0x8);.echo CookieValuee ; du /c 64 poi (@esp+0xC); g"
bp WININET!InternetConnectA ".echo InternetConnectA; da /c 64 poi(@esp+0x8) L100 ; g"
bp WININET!HttpOpenRequestA ".echo HttpOpenRequestA; da /c 64 poi(@esp+0x8); da poi(@esp+0XC) L2000 ; g"
bp WININET!HttpSendRequestA ".echo HttpSendRequest->headerA; da /c 64 poi(@esp+0x8) L4000 ; .echo SendRequestA->Option; da /c 64 poi (@esp+0x10) L5500 ; g "
bp WININET!HttpAddRequestHeadersA ".echo HttpAddRequestHeadersA; da /c 64 poi(@esp+0x8) L4000; g"
bp WININET!GetUrlCacheEntryInfoA ".echo GetUrlCacheEntryInfoA; da /c 64 poi(@esp+0x8) L4000; g"
bp WININET!SetUrlCacheEntryInfoA ".echo SetUrlCacheEntryInfoA; da /c 64 poi (@esp+0x4) L4000; g"
bp WININET!RetrieveUrlCacheEntryStreamA ".echo RetrieveUrlCacheEntryStreamA; da /c 64 poi (@esp+0x4) L4000; g"
bp WININET!CreateUrlCacheEntryA ".echo CreateUrlCacheEntryA; da /c 64 poi (@esp+0x4) L4000; g"
bp WININET!InternetWriteFile ".echo InternetWriteFile; da /c 64 poi(@esp+0x8) L4000 "
bp WININET!InternetReadFile ".echo InternetReadFile; r $t0 = poi(@esp+0x8); r $t1 = poi(@esp+0x10); g @$ra; da /c 64 @$t0 Lpoi(@$t1); g"
bp WININET!InternetReadFileExA ".echo InternetReadFileExA; r $t0 = poi(@esp+0x8);g @$ra; da /c 64 poi (@$t0+0x14) Lpoi(@$t0+0x18);.echo InternetReadFileEx_Header; da /c 64 poi(@$t0+0x8) Lpoi(@$t0+0xC); g"
bp WININET!ReadUrlCacheEntryStream ".echo ReadUrlCacheEntryStream; r $t0 = poi(@esp+0xC); r $t1 = poi(@esp+0x10); g @$ra; da /c 64 @$t0 Lpoi(@$t1); g"
bp WININET!InternetSetOptionA ".echo InternetSetOptionA; dw @esp+0x8; .echo stringifany; da /c 64 poi (@esp+0xC) L300; g"
Ok FIRST we used "bp" instead of "bm" simply because we want to have a special processing
for thous call only (other call to wininet could be simply "displayed" it will be ok for us )
Now let' take a look a this breakpoint
bp WININET!InternetReadFile ".echo InternetReadFile; r $t0 = poi(@esp+0x8); r $t1 = poi(@esp+0x10); g @$ra; da /c 64 @$t0 Lpoi(@$t1); g"
and is function prototype for reminder
BOOL InternetReadFile(
__in HINTERNET hFile,
__out LPVOID lpBuffer,
__in DWORD dwNumberOfBytesToRead,
__out LPDWORD lpdwNumberOfBytesRead
);
1).echo InternetReadFile
Here first i display the name of the breakpoint with .echo
2)r $t0 = poi (@esp+0x8)
here i assign to the pseudo register zero the value of the address where lpBuffer is pointing
(the Buffer that will contains the data after the execution of the function )
Why do i use @esp+0x8 and not @ebp+0x8
Because our breakpoint start at the very first instruction so the prolog of the function
has not been executed
you know push ebp ; push ebp, esp
So we gonna use the register @esp for referencing the argument of the function.
poi () mean " gimme the value at this address" (ie: ESP+0x8 ) or the C equivalent would be
*(esp+0x8)
3)r $t1 = poi(@esp+0x10) //lpdwNumberOfBytesRead
Here i assign to a pseudo register the address of the DWORD that will contains the actual numbers of data that has been 'read'
4) g @$ra;
Now we 'execute' the function UNTIL we reach the return address
The function would have filled our buffer and updated lpdwNumberOfBytesRead.
Now we are just before 'ret' so it's time to display the data
4) da /c 64 @$t0 Lpoi(@$t1)
"da" mean display ascii
/c 64 mean it will be displayed 64 by 64 characters
@$t0 it's the actual address of the buffer who contains the data
Lpoi(@$t1) L mean Length L100 would have display only 100 bytes
But i want to display the whole buffer remenber $t1 was pointing to the DWORD NumberOfBytesRead so Lpoi(@$t1) will get the value pointed by @$t1.
and we will display the right number of characters.
Here is the end of this first post !
Now you have enough knowledge for trace every API you want to :)
I hope you will be interested to look at windbg and discover all is magic power !
Next time i will talk about tracing with windbg !
Bye
-Antilove
No comments:
Post a Comment