In this blog entry we are going to talk about the new Appcall feature that was introduced in IDA Pro 5.6.
Briefly, Appcall is a mechanism used to call functions inside the debugged program from the debugger or your script as if it were a built-in function. If you’ve used GDB (call command), VS (Immediate window), or Borland C++ Builder then you’re already familiar with such functionality.
(Screenshot showing how we called three functions (printf, MessageBoxA, GetDesktopWindow) using IDC syntax)
Before diving in, please keep in mind that this blog entry is a short version of the full Appcall reference found here.
Quick start
To start with, we explain the basic concepts of Appcall using the IDC command line:
It can be called by simply typing:
As you notice, we invoked an Appcall by simply treating _printf as if it were a built-in IDC function.
If you have a function with a mangled name or containing characters that cannot be used as an identifier name in the IDC language:
then issue the Appcall with this syntax:
We use the LocByName function to get the address of the function given its name, then using the address (which is callable) we issue the Appcall. In two steps this can be achieved with:
auto myfunc = LocByName("_my_func@8"); myfunc("hello", "world");
Please note that Appcalls take place in the context of the current thread. If you want to execute in a different thread then switch to the desired thread first.
Appcall and IDC
The Appcall mechanism can be used from IDC through the following 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: GetTinfo(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 Appcall(ea, type, ...);
The Appcall IDC function requires you to pass a function address, function type information and the parameters (if any):
auto p = LocByName("_printf"); auto ret = Appcall(p, GetTinfo(p), "Hello %s\n", "world");
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 Appcall() we need first to get the type information (stored in a typeinfo object) by calling ParseType() and then pass the function ea and type to Appcall():
auto p = ParseType("long __stdcall FindWindow(const char *cls, const char *wndname)", 0); Appcall(LocByName("user32_FindWindowA"), p, 0, "Untitled - Notepad");
Note that we used ParseType() function to construct a typeinfo object that we can pass to Appcall(), however it is possible to permanently set the prototype of a function, thus:
SetType(LocByName("user32_FindWindowA"), "long __stdcall FindWindow(const char *cls, const char *wndname)");
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; Message("a=%d", a); ref1(&a); Message(", 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); int n=0; for (;p!=buf;p--) n += *p; return n; }
We need to create a buffer and pass it, thus:
auto s = strfill('\x00', 20); // create a buffer of 20 characters s[0:5] = "hello"; // initialize the buffer ref2(&s); // call the function and pass the string by reference if (s[5] != ".") Message("not dot\n"); else Message("dot\n");
__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 describe your function with the __usercall calling convention.
Consider this function:
/* C code */ // eax = esi - edi int __declspec(naked) asm1() { __asm { mov eax, esi sub eax, edi ret } }
And from IDC:
auto p = ParseType("int __usercall asm1<eax>(int a<esi>, int b<edi>);", 0); auto r = Appcall(LocByName("_asm1"), p, 5, 2); Message("The result is: %d\n", r);
Variable argument functions
In C:
int va_altsum(int n1, ...) { va_list va; va_start(va, n1); int r = n1; int alt = 1; while ( (n1 = va_arg(va, int)) != 0 ) { r += n1*alt; alt *= -1; } va_end(va); return r; }
And in IDC:
auto result = va_altsum(5, 4, 2, 1, 6, 9, 0);
Calling functions that can cause exceptions
Exceptions may occur during an Appcall. To capture them, you can use the try/catch in IDC:
auto e; try { AppCall(some_func_addr, func_type, arg1, arg2); // Or equally: // some_func_name(arg1, arg2); } catch (e) { // Exception occured ..... }
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
- func: The IDC function name where the exception happened.
- line: The line number in the script
- qerrno: The internal code of last error occured
For example, you could get something like this:
description: "Appcall: The instruction at 0x401F93 referenced memory at 0x5. The memory could not be read" file: "<internal>" func: "___idc0" line: 4 qerrno: 92
In some cases the exception object will contain more information.
Specifying Appcall options
Appcall can be configured with SetAppcallOptions(), by passing the following option(s):
- APPCALL_MANUAL: Only set up the appcall, do not run it (you should call CleanupAppcall() when finished). Please Refer to 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 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 GetAppcallOptions().
Please note that Appcall option is 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, trace the function
- Finally, one calls CleanupAppcall() to restore the execution context
To illustrate, let us take the ref1 function and call it with an invalid pointer:
- SetAppcallOptions(APPCALL_MANUAL); // Set manual Appcall mode
- ref1(6); // 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:
When we reach the end of the function:
and trace beyond the return instruction, we expect to see something like this:
This is the control code that we use to determine the end of an Appcall. It is at this point that one should call CleanupAppcall() to return to the previous 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:
auto e; try { SetAppcallOptions(APPCALL_DEBEV); // Enable debug event capturing ref1(6); } catch (e) { // Exception occured. This time "e" is populated with debug_event_t fields (check idd.hpp) }
And in this case, if we dump the exception object’s contents, we get these attributes:
can_cont: 1 code: C0000005h ea: 401F93h eid: 40h (from idd.hpp: EXCEPTION = 0x00000040 Exception) file: "" func: "___idc0" handled: 1 info: "The instruction at 0x401F93 referenced memory at 0x6. The memory could not be read" line: 4h pid: 123Ch ref: 6h tid: 1164h
Appcall and Python
The Appcall concept remains the same between IDC and Python, nonetheless Appcall/Python has a different syntax (using references, unicode strings, etc, etc…)
The Appcall mechanism is provided by idaapi module through the Appcall variable. To issue an Appcall:
Appcall.printf("Hello world!\n");
One can take a reference to an Appcall:
printf = Appcall.printf # ...later... printf("Hello world!\n");
- If you have a function with a mangled name or with characters that cannot be used as an identifier name in the Python language:
findclose = Appcall["__imp__FindClose@4"] getlasterror = Appcall["__imp__GetLastError@0"] setcurdir = Appcall["__imp__SetCurrentDirectoryA@4"]
- In case you want to redefine the prototype of a given function, then use the Appcall.proto(func_name or func_ea, prototype_string):
# pass an address name and Appcall.proto() will resolve it loadlib = Appcall.proto("__imp__LoadLibraryA@4", "int (__stdcall *LoadLibraryA)(const char *lpLibFileName);") # Pass an EA instead of a name freelib = Appcall.proto( LocByName("__imp__FreeLibrary@4"), "int (__stdcall *FreeLibrary)(int hLibModule);")
- To pass unicode strings you need to use the Appcall.unicode() function:
getmodulehandlew = Appcall.proto("__imp__GetModuleHandleW@4", "int (__stdcall *GetModuleHandleW)(LPCWSTR lpModuleName);") hmod = getmodulehandlew(Appcall.unicode("kernel32.dll"))
- To define a prototype and then later assign an address so you can issue an Appcall:
# Create a typed object (no address is associated yet) virtualalloc = Appcall.typedobj( "int __stdcall VirtualAlloc(int lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);") # Later we have an address, so we pass it: virtualalloc.ea = LocByName("kernel32_VirtualAlloc") # Now we can Appcall: ptr = virtualalloc(0, Appcall.Consts.MEM_COMMIT, 0x1000, Appcall.Consts.PAGE_EXECUTE_READWRITE)
Before we conclude (if you read so far;)), here’s a small script that can be used to initiate and terminate Appcalls using hotkeys. If you want to have this script load everytime you start IDA then put its contents in idc\ida.idc file.
Here’s a simple scenario where manual Appcalls can be handy:
- You’re debugging a program and then you require to debug another function then continue debugging the current function
- You press Ctrl-Alt-F9 to initiate a manual Appcall and you type the desired function name and arguments
- The debugger will switch to the new function and you start tracing the new function
- Once you’re done to return to your previous function you terminate the Appcall by pressing Ctrl-Alt-F10
If you want to temporary start tracing from the current cursor location then use Ctrl-Alt-F4 to start a manual Appcall. Use then Ctrl-Alt-F10 to return to previous execution context.
Remember, Appcall can do more than what is illustrated in this blog entry, make sure you refer to the Appcall manual for other advanced topics.