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.
How Appcall works
Given a function with a correct prototype, the Appcall mechanism works like this:
- 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
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.
Using the debuggee functions
Sometimes it is useful to call certain functions from inside your debuggee’s context:
- 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
- …
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
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.
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.
Injecting Libraries in the Debuggee
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")
Set/Get the last error
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)
Retrieving the command line value
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()
Setting/Resetting events
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);")
Change the debuggee’s virtual memory configuration
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()
Load a library and call an exported function
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
Closing words
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