Last week we introduced the new Appcall feature 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.
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.
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.
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:
In IDC, you can do something like:
auto s_in = "SomeEncryptedBuffer", s_out = strfill(SizeOfBuffer); decrypt_buffer(&s_in, &s_out, SizeOfBuffer);
Or in Python:
# 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.
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)
It is important to enable the APPCALL_DEBEV Appcall option in order to retrieve the last exception that occurred during the Appcall.
To inject libraries in the debuggee simply Appcall LoadLibrary():
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:
getlasterror = Appcall.proto("kernel32_GetLastError", "DWORD __stdcall GetLastError();") print "lasterror=", getlasterror()
Similarly we can do the same to set the last error code value:
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:
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:
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):
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()
And if you need to allocate a new memory page:
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:
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 script containing the prototypes of the API functions used in this blog entry.
Please send your suggestions/questions to support@hex-rays.com