Latest available version: IDA and decompilers v8.4.240320 see all releases
Hex-Rays logo State-of-the-art binary code analysis tools
email icon

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:

  1. Save the current thread context
  2. Serialize the parameters (we do not allocate memory for the parameters, we use the debuggee’s stack)
  3. Modify the input registers in question
  4. Set the instruction pointer to the beginning of the function to be called
  5. Adjust the return address so it points to a special area where we have a breakpoint (we refer to it as control breakpoint)
  6. Resume the program and wait until we get an exception or the control breakpoint (inserted in the previous step)
  7. 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