Appcall

IDA Pro - Appcall user guide

Copyright 2023 Hex-Rays SA

Introduction

Appcall is a mechanism to call functions under a debugger session in the context of the debugged program using IDA's CLI (Command Line Interpreter) or from a script. Such a mechanism can be used for seamless blackbox function calling and use, fuzzing, process instrumentation, DLL injection, testing or extending applications.

Appcall mechanism highly depends on the type information of the called function. For that reason, it is necessary to have a correct function prototype before doing an Appcall, otherwise different or incorrect results may be returned.

In a nutshell, Appcall works by first hijacking the current thread's stack (please do switch threads explicitly if you want to Appcall in a different context), then pushing the arguments, and then temporarily adjusting the instruction pointer to the beginning of the called function. After the function returns (or an exception occurs), the original stack, instruction pointer, and other registers are restored, and the result is returned to the caller.

Please note that while most of the examples in this document are illustrated using a Windows user mode application, Appcall is not limited to Windows and can be used with any platform supported by IDA debuggers.

Quick start

Let's start explaining the basic concepts of Appcall using the IDC CLI. Let's imagine we have the following printf() in the disassembly somewhere:

.text:00000001400015C0 ; __int64 printf(const char *, ...)
.text:00000001400015C0 _printf         proc near               
.text:00000001400015C0                                         
.text:00000001400015C0
.text:00000001400015C0 arg_0           = qword ptr  8
.text:00000001400015C0 arg_8           = qword ptr  10h
.text:00000001400015C0 arg_10          = qword ptr  18h
.text:00000001400015C0 arg_18          = qword ptr  20h
.text:00000001400015C0
.text:00000001400015C0                 mov     [rsp+arg_0], rcx
.text:00000001400015C5                 mov     [rsp+arg_8], rdx
.text:00000001400015CA                 mov     [rsp+arg_10], r8
.text:00000001400015CF                 mov     [rsp+arg_18], r9
...

It can be called by simply typing the following in the IDC CLI (press "." to jump to the CLI):

_printf("hello world\n");

As you noticed, we invoked an Appcall by simply treating _printf as if it was a built-in IDC function. If the application had a console window, then you should see the message printed in it.

If you have a function with a mangled name or with characters that cannot be used as an identifier name in the IDC language, such as "_my_func@8", then you can use the LocByName function to get its address given its name, then using the address variable (which is callable) we issue the Appcall:

auto myfunc = LocByName("_my_func@8");
myfunc("hello", "world");

Or simply directly as:

LocByName("_my_func@8")("hello", "world");

Using AppCall with IDC

Apart from calling Appcall naturally as shown in the previous section, it is possible to call it explicitly using the dbg_appcall function:

// Call application function
//      ea - address to call
//      type - type of the function to call. can be specified as:
//              - declaration string. example: "int func(void);"
//              - typeinfo object. example: get_tinfo(ea)
//              - zero: the type will be retrieved from the idb
//      ... - arguments of the function to call
// Returns: the result of the function call
// If the call fails because of an access violation or other exception,
// a runtime error will be generated (it can be caught with try/catch)
// In fact there is rarely any need to call this function explicitly.
// IDC tries to resolve any unknown function name using the application labels
// and in the case of success, will call the function. For example:
//      _printf("hello\n")
// will call the application function _printf provided that there is
// no IDC function with the same name.

anyvalue dbg_appcall(ea, type, ...);    

The Appcall IDC function requires you to pass a function address, function type information (various forms are accepted) and the parameters (if any):

auto msgbox;
msgbox = LocByName("__imp_MessageBoxA");
// Pass "0" for the type to deduce it from the database
dbg_appcall(msgbox, 0, 0, "Hello world", "Info", 0);

We've seen so far how to call a function if it already has type information, now suppose we have a function that does not:

user32.dll:00007FFF3AD730F0 user32_FindWindowA proc near
user32.dll:00007FFF3AD730F0      mov     r9, rdx
user32.dll:00007FFF3AD730F3      mov     r8, rcx
user32.dll:00007FFF3AD730F6      xor     edx, edx
user32.dll:00007FFF3AD730F8      xor     ecx, ecx
user32.dll:00007FFF3AD730FA      jmp     sub_7FFF3ADC326C
user32.dll:00007FFF3AD730FA user32_FindWindowA endp  

Before calling this function with dbg_appcall we have two options:

  1. Pass the prototype as a string

  2. Or, parse the prototype separately and pass the returned type info object.

This is how we can do it using the first option:

auto window_handle;
window_handle = dbg_appcall(
    LocByName("user32_FindWindowA"),
    "long __stdcall FindWindow(const char *cls, const char *wndname)",
    0,
    "Calculator");
    
msg("handle=%d\n", window_handle);

As for the second option, we can use parse_decl() first, then proceed as usual:

auto window_handle, tif;

tif = parse_decl("long __stdcall FindWindow(const char *cls, const char *wndname)", 0);

window_handle = dbg_appcall(
    LocByName("user32_FindWindowA"),
    tif,
    0,
    "Calculator");
    
msg("handle=%d\n", window_handle);
  

Note that we used parse_decl() function to construct a typeinfo object that we can pass to dbg_appcall.

It is possible to permanently set the prototype of a function programmatically using apply_type():

auto tif;
tif = parse_decl("long __stdcall FindWindow(const char *cls, const char *wndname)", 0);
apply_type(
    LocByName("user32_FindWindowA"),
    tif);

In the following sections, we are going to cover different scenarios such as calling by reference, working with buffers and complex structures, etc.

Passing arguments by reference

To pass function arguments by reference, it suffices to use the & symbol as in the C language.

  • For example to call this function:

void ref1(int *a)
{
  if (a == NULL)
    return;
  int o = *a;
  int n = o + 1;
  *a = n;
  printf("called with %d and returning %d\n", o, n);
}

We can use this code from IDC:

auto a = 5;
msg("a=%d", a);
ref1(&a);
msg(", after the call=%d\n", a);
  • To call a C function that takes a string buffer and modifies it:

/* C code */
int ref2(char *buf)
{
  if (buf == NULL)
    return -1;

  printf("called with: %s\n", buf);
  char *p = buf + strlen(buf);
  *p++ = '.';
  *p = '\0';
  printf("returned with: %s\n", buf);