Hex Blog
January 18th, 2010, 07:17
Last week we introduced the new Appcall feature ("http://hexblog.com/2010/01/introducing_the_appcall_featur_1.html") in IDA Pro 5.6. Today we will talk a little about how it's implemented and describe some of the uses of Appcall in various scenarios.</p>How Appcall works
Given a function with a correct prototype, the Appcall mechanism works like this:
When you encounter the control breakpoint:
you can issue the CleanupAppcall() IDC command to restore the previously saved thread context and resume your debugging session. Using the debuggee functions
Sometimes it is useful to call certain functions from inside your debuggee's context:
In IDC, you can do something like:Or in Python:Function level fuzzing
Instead of generating input strings and passing them to the application as command line arguments, input files, etc...it is also possible to test the application on a function level using Appcall.
It is sufficient to find the functions we want to test, give them appropriate prototypes and Appcall each one of these functions with the desired set of (malformed) input.
Given a function with a correct prototype, the Appcall mechanism works like this:
In the case of a manual Appcall, the debugger module will do all but the last two steps, thus giving you a chance to debug interactively the function in question.
Save the current thread context
Serialize the parameters (we do not allocate memory for the parameters, we use the debuggee's stack)
Modify the input registers in question
Set the instruction pointer to the beginning of the function to be called
Adjust the return address so it points to a special area where we have a breakpoint (we refer to it as control breakpoint)
Resume the program and wait until we get an exception or the control breakpoint (inserted in the previous step)Deserialize back the input (only for parameters passed by reference) and save the return value
When you encounter the control breakpoint:
http://hexblog.com/ida_pro/pix/appcall_manual_control.gif
you can issue the CleanupAppcall() IDC command to restore the previously saved thread context and resume your debugging session. Using the debuggee functions
Sometimes it is useful to call certain functions from inside your debuggee's context:
Let's take a program that contains a decryption routine that we want to use:
Functions that you identified as cryptographic functions: encrypt/decrypt/hashing functions
Explicitly call not-so-popular functions: instead of waiting the program to call a certain function, simply call it directly
Change the program logic: by calling certain debuggee functions it is possible to change the logic and the internal state of the program
Extend your program: since Appcall can be used inside the condition expression of a conditional breakpoint, it is possible to extend applications that way
Fuzzing applications: easily fuzz your program on a function level...
http://hexblog.com/ida_pro/pix/appcall_xdecrypt.gif
In IDC, you can do something like:
Code:
auto s_in = "SomeEncryptedBuffer", s_out = strfill(SizeOfBuffer);
decrypt_buffer(&s_in, &s_out, SizeOfBuffer);
Code:
# Explicitly create the buffer as a byref object
s_in = Appcall.byref("SomeEncryptedBuffer"
# Buffers are always returned byref
s_out = Appcall.buffer(" ", SizeOfBuffer)
# Call the debuggee
Appcall.decrypt_buffer(s_in, s_out, SizeOfBuffer)
# Print the result
print "decrypted=", s_out.value
Instead of generating input strings and passing them to the application as command line arguments, input files, etc...it is also possible to test the application on a function level using Appcall.
It is sufficient to find the functions we want to test, give them appropriate prototypes and Appcall each one of these functions with the desired set of (malformed) input.
It is important to enable the APPCALL_DEBEV Appcall option in order to retrieve the last exception that occurred during the Appcall.Injecting Libraries in the DebuggeeCode:def fuzz_func1():
"""
Finds functions with one parameter that take a string buffer and tries to see if one
of these functions will crash if a malformed input was passed
"""
# prepare functions search criteria
tps = ['LPCWSTR', 'LPCSTR', 'char *', 'const char *', 'wchar_t *']
tpsf = [1 , 0 , 0 , 0 , 1]
pat = r'\((%s)\s*\w*\)' % "|".join(tps).replace('*', r'\*')
# set Appcall options
old_opt = Appcall.set_appcall_options(Appcall.APPCALL_DEBEV)
# Enumerate all functions
for x in Functions():
# Get the type string
t = GetType(x)
if not t:
continue
# Try to parse its declaration
t = re.search(pat, t)
if not t:
continue
# Check if the parameter is a unicode string or not
is_unicode = tpsf[tps.index(t.group(1))]
# Form the input string: here we can generate mutated input
# and keep on looping until our input pool for this function is exhausted.
# For demonstration purposes only one string is passed to the Appcalled functions
s = "A" * 1000
# Do the Appcall but protect it with try/catch to receive the exceptions
try:
# Create the buffer appropriately
if is_unicode:
buf = Appcall.unicode(s)
else:
buf = Appcall.buffer(s)
print "%x: calling. unicode=%d" % (x, is_unicode)
# Call the function in question
r = Appcall[x](buf)
except OSError, e:
exc_code = idaapi.as_uint32(e.args[0].code)
print "%X: Exception %X occurred @ %X. Info: <%s>\n" % (x,
exc_code, e.args[0].ea, e.args[0].info)
# stop the test
break
except Exception, e:
print "%x: Appcall failed!" % x
break
# Restore Appcall options
Appcall.set_appcall_options(old_opt)
To inject libraries in the debuggee simply Appcall LoadLibrary():Set/Get the last errorCode:loadlib = Appcall.proto("kernel32_LoadLibraryA", "int __stdcall loadlib(const char *fn);"
hmod = loadlib("dll_to_inject.dll"
To retrieve the last error value we can either parse it manually from the TIB or Appcall the GetLastError() API:Similarly we can do the same to set the last error code value:Code:getlasterror = Appcall.proto("kernel32_GetLastError", "DWORD __stdcall GetLastError();"
print "lasterror=", getlasterror()
Retrieving the command line valueCode:setlasterror = Appcall.proto("kernel32_SetLastError", "void __stdcall SetLastError(int dwErrCode);"
setlasterror(5)
To retrieve the command line of your program we can either parse it from the PEB or Appcall the GetCommandLineA() API:Setting/Resetting eventsCode:getcmdline = Appcall.proto("kernel32_GetCommandLineA", "const char *__stdcall getcmdline();"
print "command line:", getcmdline()
Sometimes the debugged program may deadlock while waiting on a semaphore or an event. You can manually release the semaphore or signal the event.Killing a thread is possible too:Change the debuggee's virtual memory configurationCode:releasesem = Appcall.proto("kernel32_ReleaseSemaphore",
"BOOL __stdcall ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);"
resetevent = Appcall.proto("kernel32_SetEvent",
"BOOL __stdcall SetEvent(HANDLE hEvent);"
termthread = Appcall.proto("kernel32_TerminateThread",
"BOOL __stdcall TerminateThread(HANDLE hThread, DWORD dwExitCode);"
It is possible to change a memory page's protection. In the following example we will change the PE header page protection to execute/read/write (normally it is read-only):And if you need to allocate a new memory page:Code:virtprot = Appcall.proto("kernel32_VirtualProtect",
"BOOL __stdcall VirtualProtect(LPVOID addr, DWORD sz, DWORD newprot, PDWORD oldprot);"
r = virtprot(0x400000, 0x1000, Appcall.Consts.PAGE_EXECUTE_READWRITE, Appcall.byref(0));
print "VirtualProtect returned:", r
RefreshDebuggerMemory()
Load a library and call an exported functionCode:virtalloc = Appcall.proto("kernel32_VirtualAlloc",
"int __stdcall VirtualAlloc(int addr, SIZE_T sz, DWORD alloctype, DWORD protect);"
m = virtualalloc(0, Appcall.Consts.MEM_COMMIT, 0x1000, Appcall.Consts.PAGE_EXECUTE_READWRITE)
RefreshDebuggerMemory()
With Appcall it is also possible to load a library, resolve a function address and call it. Let us illustrate with an example:Closing wordsCode:def get_appdata():
hshell32 = loadlib("shell32.dll"
if hshell32 == 0:
print "failed to load shell32.dll"
return False
print "%x: shell32 loaded" % hshell32
# make sure to refresh the debugger memory after loading a new library
RefreshDebuggerMemory()
# resolve the function address
p = getprocaddr(hshell32, "SHGetSpecialFolderPathA"
if p == 0:
print "shell32.SHGetSpecialFolderPathA() not found!"
return False
# create a prototype
shgetspecialfolder = Appcall.proto(p,
"BOOL SHGetSpecialFolderPath(HWND hwndOwner, LPSTR lpszPath, int nFolder, BOOL fCreate);"
print "%x: SHGetSpecialFolderPath() resolved..."
# create a buffer
buf = Appcall.buffer("\x00" * 260)
# CSIDL_APPDATA = 0x1A
if not shgetspecialfolder(0, buf, 0x1A, 0):
print "SHGetSpecialFolderPath() failed!"
else:
print "AppData Path: >%s<" % Appcall.cstr(buf.value)
return True
Appcall has a variety of applications, hopefully it will be handy while solving your day to day reversing problems.For your convenience, please download this ("http://hexblog.com/ida_pro/files/appcall_prototypes.py") script containing the prototypes of the API functions used in this blog entry.
Please send your suggestions/questions to support@hex-rays.com
http://hexblog.com/2010/01/practical_appcall_examples_1.html