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:
It can be called by simply typing the following in the IDC CLI (press "." to jump to the CLI):
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:
Or simply directly as:
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:
The Appcall IDC function requires you to pass a function address, function type information (various forms are accepted) and the parameters (if any):
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:
Before calling this function with dbg_appcall
we have two options:
Pass the prototype as a string
Or, parse the prototype separately and pass the returned type info object.
This is how we can do it using the first option:
As for the second option, we can use parse_decl()
first, then proceed as usual:
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()
:
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:
We can use this code from IDC:
To call a C function that takes a string buffer and modifies it:
We need to create a buffer and pass it by reference to the function:
__usercall calling convention
It is possible to Appcall functions with non standard calling conventions, such as routines written in assembler that expect parameters in various registers and so on. One way is to use the __usercall calling convention.
Consider this function:
And from IDC:
Variadic functions
In C:
And in IDC:
Calling functions that might cause exceptions
Exceptions may occur during an Appcall. To capture them, use the try/catch mechanism:
The exception object "e" will be populated with the following fields:
description: description text generated by the debugger module while it was executing the Appcall
file: The name of the file where the exception happened.
func: The IDC function name where the exception happened.
line: The line number in the script
qerrno: The internal code of last error occurred
For example, one could get something like this:
In some cases, the exception object will contain more information.
Functions that accept or return structures
Appcall mechanism also works with functions that accept or return structure types. Consider this C code:
We can create a couple of records and link them up together:
Because we issued an Appcall, when listRecords
is called, we expect to see the following output in the console:
We can then access the fields naturally (even the linked objects). We can verify that if we just dump the first record through the IDC CLI (or just by calling IDC's print
function):
Notice how when rec1
is dumped, its next
field is automatically followed and properly displayed. The same happens for rec2
and rec3
.
We can also directly access the fields of the structure from IDC and have those changes reflected in the debugee's memory:
Notable observations:
Objects are always passed by reference (no need to use the &)
Objects are created on the stack
Objects are untyped
Missing object fields are automatically created by IDA and filled with zero
Calling an API that receives a structure and its size
Let us take another example where we call the GetVersionExA API function:
This API requires one of its input fields to be initialized to the size of the structure. Therefore, we need to initialize the structure correctly before passing it to the API to be further populated therein:
Now if we dump the ver object contents we observe something like this:
Working with opaque types
Opaque types like FILE
, HWND
, HANDLE
, HINSTANCE
, HKEY
, etc. are not meant to be used as structures by themselves but like pointers.
Let us take for example the FILE
structure that is used with fopen()
; its underlying structure looks like this (implementation details might change):
And the fopen()
function prototype is:
Let us see how we can get a "FILE *"" and use it as an opaque type and issue an fclose()
call properly:
Nothing special about the fopen/fclose Appcalls except that we see the __at__ attribute showing up although it does not belong to the FILE structure definition. This is a special attribute that IDA inserts into all objects, and it contains the memory address from which IDA retrieved the object attribute values. We can use the __at__ to retrieve the C pointer of a given IDC object.
Previously, we omitted the __at__ field from displaying when we dumped objects output, but in reality this is what one expects to see as part of the objects attributes used in Appcalls. Let's create a user record again:
..and observe the output:
Please note that it is possible to pass as integer (which is a pointer) to a function that expects a pointer to a structure.
FindFirst/FindNext APIs example
In this example, we call the APIs directly without permanently setting their prototype first.
Using LoadLibrary/GetProcAddress
In this example, we are going to initialize the APIs by setting up their prototypes correctly so we can use them later conveniently.
Retrieving application's command line
Specifying Appcall options
Appcall can be configured with set_appcall_options()
and passing one or more options:
APPCALL_MANUAL: Only set up the appcall, do not run it (you should call
cleanup_appcall()
when finished). Please Refer to the "Manual Appcall" section for more information.APPCALL_DEBEV: If this bit is set, exceptions during appcall will generate IDC exceptions with full information about the exception. Please refer to the "Capturing exception debug events" section for more information.
It is possible to retrieve the Appcall options, change them and then restore them back. To retrieve the options use the get_appcall_options()
.
Please note that the Appcall options are saved in the database so if you set it once it will retain its value as you save and load the database.
Manual Appcall
So far, we've seen how to issue an Appcall and capture the result from the script, but what if we only want to setup the environment and manually step through a function?
This can be achieved with manual Appcall. The manual Appcall mechanism can be used to save the current execution context, execute another function in another context and then pop back the previous context and continue debugging from that point.
Let us directly illustrate manual Appcall with a real life scenario:
You are debugging your application
You discover a buggy function (foo()) that misbehaves when called with certain arguments: foo(0xdeadbeef)
Instead of waiting until the application calls foo() with the desired arguments that can cause foo() to misbehave, you can manually call foo() with the desired arguments and then trace the function from its beginning.
Finally, one calls
cleanup_appcall()
to restore the execution context
To illustrate, let us take the ref1
function (from the previous example above) and call it with an invalid pointer:
Set manual Appcall mode:
Call the function with an invalid pointer:
Directly after doing that, IDA will switch to the function and from that point on we can debug:
Now you are ready to single step that function with all its arguments properly set up for you. When you are done, you can return to the previous context by calling cleanup_appcall()
.
Initiating multiple manual Appcalls
It is possible to initiate multiple manual Appcalls. If manual Appcall is enabled, then issuing an Appcall from an another Appcall will push the current context and switch to the new Appcall context. cleanup_appcall()
will pop the contexts one by one (LIFO style).
Such technique is useful if you happen to be tracing a function then you want to debug another function and then resume back from where you were!
Manual Appcalls are not designed to be called from a script (because they don't finish), nonetheless if you use them from a script:
We observe the following:
First Appcall will be initiated
The script will loop and display the values of i in IDA's output window
Another Appcall will be initiated
The script finishes. None of the two Appcalls actually took place
The execution context will be setup for tracing the last issued Appcall
After this Appcall is finished, we observe "Loop finished"
We issue
cleanup_appcall
and notice that the execution context is back to printf but this time it will print "Loop started"Finally when we call again
cleanup_appcall
we resume our initial execution context
Capturing exception debug events
We previously illustrated that we can capture exceptions that occur during an Appcall, but that is not enough if we want to learn more about the nature of the exception from the operating system point of view.
It would be better if we could somehow get the last debug_event_t that occured inside the debugger module. This is possible if we use the APPCALL_DEBEV option. Let us repeat the previous example but with the APPCALL_DEBEV option enabled:
And in this case, if we dump the exception object's contents, we get these attributes:
Appcall related functions
There are some functions that can be used while working with Appcalls.
parse_decl/get_tinfo/sizeof
The get_tinfo()
function is used to retrieve the typeinfo string associated with a given address.
The parse_decl()
function is used to construct a typeinfo string from a type string. We already used it to construct a typeinfo string and passed it to dbg_appcall()
.
And finally, given a typeinfo string, one can use the sizeof()
function to calculate the size of a type:
Accessing enum members as constants
In IDC, it is possible to access all the defined enumerations as if they were IDC constants:
Then one can type:
This syntax makes it even more convenient to use enumerations when calling APIs via Appcall.
Storing/Retrieving typed elements
It is possible to store/retrieve (aka serialize/deserialize) objects to/from the database (or the debugee's memory). To illustrate, let us consider the following memory contents:
And we know that this maps to a given type:
To retrieve (deserialize) the memory contents into a nice IDC object, we can use the object.retrieve()
function:
Here is an example:
And now if we dump the contents of o:
and again we notice the __at__ which holds the address of the retrieved object.
To store (serialize) the object back into memory, we can use the object.store()
function:
Here's an example continuing from the previous one:
And finally to verify, we go to the memory address:
Using Appcall with IDAPython
The Appcall concept remains the same between IDC and Python, nonetheless Appcall/Python has a different syntax (using references, unicode strings, etc.)
The Appcall mechanism is provided by ida_idd
module (also via idaapi
) through the Appcall variable. To issue an Appcall using Python:
One can take a reference to an Appcall:
In case you have a function with a mangled name or with characters that cannot be used as an identifier name in the Python language, then use the following syntax:
In case you want to redefine the prototype of a given function, then use the
Appcall.proto(func_name or func_ea, prototype_string)
syntax as such:
To pass unicode strings you need to use the Appcall.unicode() function:
To pass int64 values to a function you need to use the
Appcall.int64()
function:
Python Appcall code:
If the returned value is also an int64, then you can use the int64.value
to unwrap and retrieve the value.
To define a prototype and then later assign an address so you can issue an Appcall:
Things to note:
We used the Appcall.Consts syntax to access enumerations (similar to what we did in IDC)
If you replicate this specific example, a new memory page will be allocated. You need to refresh the debugger memory layout (with
idaapi.refresh_debugger_memory()
) to access it
Passing arguments by reference
To pass function arguments by reference, one has to use the
Appcall.byref()
:
To call a C function that takes a string buffer and modifies it, we need to use the
Appcall.buffer(initial_value, [size])
function to create a buffer:
Another real life example is when we want to call the GetCurrentDirectory() API:
To pass int64 values by reference:
We use the following Python code:
To call a C function that takes an array of integers or an array of a given type:
First we need to use the Appcall.array()
function to create an array type, then we use the array_object.pack()
function to encode the Python values into a buffer: