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 https://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:
Dynamically attaching a debugger
Is a process hijacking your machine?
Customize the VS debugger display of your data
MSJ Bugslayer column: https://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
Anonymous
July 27, 2007
When you start a program on your Windows XP computer, a process is created and several DLLs (DynamicAnonymous
July 27, 2007
When you start a program on your Windows XP computer, a process is created and several DLLs (DynamicAnonymous
August 08, 2007
You can use CreateToolhelp32Snapshot and its family of functions to enumerate the running processes onAnonymous
February 26, 2008
Often I want to write the SAME code that will display the name of the currently executing method or function.Anonymous
February 26, 2008
Often I want to write the SAME code that will display the name of the currently executing method or functionAnonymous
July 16, 2008
<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>Anonymous
January 16, 2009
The comment has been removedAnonymous
January 16, 2009
The comment has been removed