How to log application API calls using import module addresses


Let’s log all the calls that Excel makes to open or create a file.


 


Start Visual Studio (any version), choose File->Open->Projects. In the dialog, change the “Files of Type” to “Executable Files (*.exe)”


Choose any application like Excel: C:\Program Files\Microsoft Office\OFFICE11\EXCEL.EXE


 


Hit Ctrl-B to bring up the Breakpoints dialog. Paste in 0x7C810760 (which is the address of CreateFileW on WinXP (below). On Vista, use 0x75F2866C)


Hit F5 to start Excel


 


The debugger will stop when Excel calls the API. Put this expression in the Watch Window:


(char *)(*(int *)((esp+4))),su


 


The ESP register is the stack pointer. At the breakpoint it points to the return address of the caller. The next stack entry (esp+4) is the first parameter to the function. (There are four 8 bit bytes per 32 bit word.) The watch expression dereferences (“*(int *)”)the value and casts it as a string (“char *”)so you can see the value. The “,su” means to format it as a Unicode string.  We know the first parameter is the file name from the CreateFileW documentation.


 


My debugger shows


 "C:\Program Files\Microsoft Office\OFFICE11\1033\xlintl32.dll"


 


Right click the bpt, choose BreakPoint->When Hit->Print a Message -> “CFileUnicode {(char *)(*(int *)((esp+4))),su}”


(The When Hit feature is VS 2005 only)


Now watch the output window as you run the target application.


I see these file names passed to the CreateFileW function as Excel runs:


 


CFileUnicode "C:\Program Files\Microsoft Office\OFFICE11\1033\xlintl32.dll"


CFileUnicode "C:\WINDOWS\WindowsShell.Manifest"


CFileUnicode "C:\WINDOWS\system32\msctfime.ime"


CFileUnicode "C:\WINDOWS\system32\msctfime.ime"


CFileUnicode "C:\WINDOWS\system32\OLEACCRC.DLL"


CFileUnicode "C:\WINDOWS\Registration\R000000000049.clb"


CFileUnicode "C:\Program Files\Common Files\Microsoft Shared\office11\1033\msointl.dll"


CFileUnicode "C:\Documents and Settings\Calvinh\Application Data\Microsoft\Office\Excel11.pip"


CFileUnicode "C:\WINDOWS\system32\tipres.dll"


CFileUnicode "\\.\PIPE\lsarpc"


CFileUnicode "\\.\PIPE\lsarpc"


CFileUnicode "C:\DOCUME~1\ALLUSE~1\APPLIC~1\MICROS~1\OFFICE\DATA\OPA11.BAK"


CFileUnicode "C:\WINDOWS\system32\oleacc.dll"


CFileUnicode "C:\DOCUME~1\ALLUSE~1\APPLIC~1\MICROS~1\OFFICE\DATA\opa11.dat"


CFileUnicode "C:\DOCUME~1\ALLUSE~1\APPLIC~1\MICROS~1\OFFICE\DATA\opa11.dat"


CFileUnicode "C:\Program Files\Microsoft Office\OFFICE11\1033\id_011.dpc"


 


Similarly, you can spy on a program’s calls to other API functions, such as registry operations, window handling, etc. (see also Spy on your programs).


 


Of course, you can use one of the utilities from www.sysinternals.com to monitor registry/file usage, but this is another tool in your arsenal.


 


For another example, suppose you use a program (perhaps Word or Excel) to create a directory. Does the program check for the existence of the dir and if it already exists, not even call the CreateDirectory API? Or does it call the API and test for failure? Put a breakpoint on the CreateDirectory Api and see.


 


Actually there are a few versions of the CreateDirectory APIs: CreateDirectoryA, CreateDirectoryW are the AnsiWide versions (see DECLARE DLL performance questions).


 


A while ago I posted Find all statically linked libraries required before your process can start (be sure to fix the code by adding “[i]”: see the comments) which showed how to list all the DLLs that an application needs before it gets loaded.


 


I’ve modified the program and added a feature to list the addresses of the imported functions. These addresses are useful when using a debugger as above. CreateDirectoryA has a relative address of 0x217AC. The absolute address is just the sum of the relative address and the module load address.


 


Run the sample code to get a table of exported functions and their addresses used by a particular application.


 


You can use GetModuleHandle to find the address of a module as loaded within the VFP process:


DECLARE integer GetModuleHandle IN WIN32API string


?TRANSFORM(GetModuleHandle("kernel32.dll"),"@0x")


 


This shows kernel32 loaded at 0x7c80000


 


You can also use the Modules Window of the VS Debugger to get the actual module load address. For essential modules, like kernel32.dll, the load address is the same for most processes. They get loaded first, and there are no preferred load address collisions yet. As more modules get loaded, one might collide with another and must be rebased to a different load address.


 


Attach the debugger: start Visual Studio (any version). Hit Ctrl-Alt-P (Debug->Attach to Process) and attach to the target process (VFP or anything else that will call CreateDirectory)


 


0x7c80000+ 217AC = 0x7c8217ac


Put a breakpoint at the calculated address: 0x7c356eac


 


Make a directory using the debuggee: Type this into the command window:


 


MD  ThisIsATestDir


 


As before, In the Watch window we an see the parameters of the call. Paste this in: (char *)(*(int *)((esp+4)))


 


+                      (char *)(*(int *)((esp+4))) 0x0013fb68 "ThisIsATestDir"      char *


 


 


 


Of course, another way to determine the function’s relative address is:


C:\Program Files\Microsoft Visual Studio 8\VC\bin>link /dump /exports c:\windows\system32\kernel32.dll | find /i "CreateDirectory"


 


         72   47 000217AC CreateDirectoryA


         73   48 0005B23B CreateDirectoryExA


         74   49 0005A5F2 CreateDirectoryExW


         75   4A 000323D2 CreateDirectoryW


 


But this way requires knowing what to search for and which DLL it resides in.


The sample program below creates table of all the imports used by all modules that are statically loaded so you can search them regardless of host module.


 


Yet another way to set a bpt is to use the VS syntax, such as {,,kernel32.dll}_LoadLibraryA@4 (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsdebug/html/vchowcontextoperator.asp)


 


However, this requires you to know the DLL name and the decorated symbol name (which can be different if you have symbols loaded or not).


 


To load symbols under the debugger when the process starts, use Tools->Options->Debugging->Symbols. Uncheck “Search the above locations only when the symbols are loaded manually”.


 


 


 


Related topics:


Very Advanced Debugging tips


Dynamically attaching a debugger


Is a process hijacking your machine?


Customize the VS debugger display of your data


MSJ Bugslayer column: http://www.microsoft.com/msj/0698/bugslayer0698.aspx


 


 


 


Sample Code:


 


SYS(3000,0)


CLEAR ALL


CLEAR


 


ox=CREATEOBJECT("CModules")


SELECT exports


INDEX on funcname TAG funcname


BROWSE LAST NOWAIT


 


DEFINE CLASS CModules as Custom


          nMaxDepth=20000    && recursion depth


          cVars=LOCFILE("c:\Program Files\Microsoft Visual Studio 8\Vc\bin\vcvars32.bat")      && VS2005


*        cVars=LOCFILE("c:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\vcvars32.bat") && VS2003


          PROCEDURE init(nMaxDepth as Integer)


                   IF VARTYPE(nMaxDepth)== "N"


                             this.nMaxDepth=nMaxDepth && keep this small if you want things close by


                   ENDIF


                   DECLARE integer GetModuleFileName IN WIN32API integer hModule, string @ lpfilename, integer nSize


                   DECLARE integer LoadLibrary IN WIN32API  string FileName


                   DECLARE integer FreeLibrary IN WIN32API integer hModule


                   DECLARE integer GetModuleHandle IN WIN32API string


 


                   CREATE CURSOR modules (module c(40),ModuleFullName c(200),Level i)


                   INDEX on UPPER(module) TAG module


                   CREATE CURSOR imports (Parent c(200), Module c(40),address c(10),FuncName c(40))


                   INDEX on FuncName+ module TAG FuncName


                   CREATE CURSOR Exports (Module c(40),address c(10),FuncName c(40),AbsAddress c(10))


 


                   *this.GetImports("c:\program files\internet explorer\iexplore.exe", 1)


*                  this.GetImports("d:\fox90\dvfp9.exe",1)


                   this.GetImports(_vfp.FullName,1)


                   ?"Now geting Exports"


                   SELECT distinct ModuleFullName FROM modules INTO CURSOR distmodules


                   SCAN  && for each distinct module, get the address of the imported function


                             ?ModuleFullName


                             this.GetExports(ALLTRIM(ModuleFullName ))


                   ENDSCAN


          PROCEDURE GetExports(cModulePath as String)


                   this.CallDumpBin("exports",cModulePath,1)


 


          PROCEDURE GetImports(Parent as String,nLevel as Integer)


                   LOCAL ihm,cStrnLen,Parent


                   hm = LoadLibrary(m.parent)          && load/unload so we can get the full path


                   IF hm=0


                             ?"Can't load "+m.parent


                   ELSE


                             cStr=SPACE(260)


                             nLen=GetModuleFileName(hm,@cStr,LEN(cStr))


                             m.Parent=LOWER(LEFT(cStr,nLen)) && now parent is full path


                             FreeLibrary(hm)


                             INSERT INTO modules VALUES (LOWER(JUSTFNAME(m.Parent)), m.Parent,nLevel)


                             this.CallDumpBin("imports",ALLTRIM(m.Parent),nLevel)


                   ENDIF


          PROCEDURE CallDumpBin(cMode as String, cModulePath as String, nLevel as Integer)


                   LOCAL i,nLines,cLine,aa[1],cCurModule


                   cCurModule=""


                    TEXT TO mybat TEXTMERGE NOSHOW


                             call "<<this.cVars>>"


                             dumpbin /<<cMode>>  "<<cModulePath>>" >  c:\t.txt


                   ENDTEXT


                   STRTOFILE(mybat,"t.bat")


                   !cmd /c t.bat


                   nLines=ALINES(aa,FILETOSTR("c:\t.txt"))


                   FOR i = 1 TO nLines


                             cLine=aa [i]


                             IF cLine="  Summary"


                                      EXIT


                             ENDIF


                             IF cLine = "DUMPBIN : fatal error " OR cLine="    Bound to"


                                      ?cLine


                                      EXIT


                             ENDIF


                             IF !EMPTY(cLine) AND LEFT(cLine,4)="    "


*                                     ?cLine


                                      cCurModule=this.Process&cMode.(cLine,cModulePath,cCurModule,nLevel)         && use macro substitution to call processor


                             ENDIF


                   ENDFOR


          PROCEDURE ProcessImports(cLine as String, cModulePath as String, cCurModule as String , nLevel as Integer)


                   LOCAL m.Parent,m.Module,m.FuncName,m.Address


                   IF SUBSTR(cLine,5)!=' '


                             cCurModule=LOWER(ALLTRIM(cline))                   && cCurModule has lifetime of several lines


                             ?cCurModule


                             IF !SEEK(UPPER(cCurModule),"modules") AND nLevel < this.nMaxDepth && if not processed yet


                                      this.GetImports(cCurModule,nLevel+1) &&Recur! m.Module doesn't have full path


                             ENDIF


                   ELSE


                             IF LEFT(cline,8) = SPACE(8)                                      


                                      m.Address = GETWORDNUM(cline,1)


                                      m.FuncName = GETWORDNUM(cline,2)


                                      m.Parent = cModulePath


                                      m.Module=cCurModule


                                      *filter out import address table, import name table


                                      IF m.FuncName != "Import" AND "0" != m.address AND "FFFFFFFF" != m.address


                                                INSERT INTO imports FROM MEMVAR


                                      ENDIF


                             ENDIF


                   ENDIF


                   RETURN cCurModule


          PROCEDURE ProcessExports(cLine as String,cModulePath as String, cCurModule as string,nLevel as Integer)


                   m.Ordinal=GETWORDNUM(cLine,1)


                   m.Hint = GETWORDNUM(cLine,2)


                   m.Address=GETWORDNUM(cLine,3)


                   m.FuncName = GETWORDNUM(cLine,4)


                   m.Module=JUSTFNAME(cModulePath)


*                  ?m.Hint,m.Address,m.FuncName


                   IF  ""!= m.FuncName


                             IF  SEEK(PADR(m.FuncName,FSIZE("funcname","imports"))+m.Module,"imports")      && if this func is imported


                                      m.AbsAddress=""


                                      nHandle=GetModuleHandle(m.Module)


                                      IF nHandle>0


                                                m.AbsAddress=TRANSFORM(nHandle+ VAL("0x"+m.Address) ,"@0x")             &&add Rel addr to base to get Abs Addr


                                      ENDIF


                                      INSERT INTO exports FROM memvar


                             ENDIF


                   ENDIF


         


ENDDEFINE


 

Comments (8)

  1. When you start a program on your Windows XP computer, a process is created and several DLLs (Dynamic

  2. When you start a program on your Windows XP computer, a process is created and several DLLs (Dynamic

  3. You can use CreateToolhelp32Snapshot and its family of functions to enumerate the running processes on

  4. Often I want to write the SAME code that will display the name of the currently executing method or function.

  5. Often I want to write the SAME code that will display the name of the currently executing method or function

  6. Leoni says:

    <a href= http://index1.yourifits.com >national cheer association</a> <a href= http://index5.yourifits.com >online adult sex games</a> <a href= http://index2.yourifits.com >great big tits</a> <a href= http://index3.yourifits.com >67classic chevy van</a> <a href= http://index4.yourifits.com >breast augmentation louisiana</a>

  7. jyner_jm says:

    <a href= http://wkvuqmc.angelfire.com >the best of milfs</a> <a href= http://kosaaea.angelfire.com >beverly kirk and wjla</a> <a href= http://ziinneo.angelfire.com >where do i get knives sharpened</a> <a href= http://ytewzyf.angelfire.com >orlando speedworld</a> <a href= http://etfrmou.angelfire.com >karen dorfman</a>

  8. jyner_gf says:

    <a href= http://daoisjo.angelfire.com >stephanie reitz and associated press</a> <a href= http://gyouoxe.angelfire.com >find trademaked names</a> <a href= http://keueean.angelfire.com >i reached back like a pimp and punk ass tripped</a> <a href= http://hppjgwz.angelfire.com >franks supply</a> <a href= http://vlejlpy.angelfire.com >cattleman hats for kids</a>

Skip to main content