IDA Tricks - Handling dynamic imports

published 2018-05-16

Intro

A common thing you encounter when analyzing code is targets that resolve imports manually at runtime in order to cloak their doings. API calls then usually look like this:

Call Site

If you know your WinAPI, that looks like a Reg* call because of the first parameter, but other than that we know little. The code has lost all glance value - you can't easily scan over larger parts of code and get a general grasp of what it does.

So of course you analyze the import resolving algorithm. Let's assume we know how imports are resolved and created a struct that contains the offsets matched to API names like this:

API struct

Having this struct at hand, we can go to each call site, put the cursor on the line with the call, press T to bring up IDA's structure offset selector and we get this:

Named Call

That makes life easier but not good enough for me. While we know the APIs, what we still lack is IDA's excellent function argument propagation. Let's fix this.

Structure members with type information

Now while we have a structure with the import names and their corresponding offsets, we unfortunately didn't automatically get the function prototype set as would happen if we named a DWORD as RegOpenKeyExA. But IDA actually supports type information for structure members.

So let's look up RegOpenKeyExA' function prototype:

LSTATUS __stdcall RegOpenKeyExA(
    HKEY hKey,
    LPCSTR lpSubKey,
    DWORD ulOptions,
    REGSAM samDesired,
    PHKEY phkResult);

If you try to paste this (without the line wrapping of course) into the struct member type declaration window (open it with Y while the cursor is on the member), you get an error.

Which makes sense, because what we actually have inside the struct is a bunch of function pointers. Understanding that, we can turn the above function prototype into a C function pointer:

LSTATUS (__stdcall *RegOpenKeyExA)(
    HKEY hKey,
    LPCSTR lpSubKey,
    DWORD ulOptions,
    REGSAM samDesired,
    PHKEY phkResult);

And if we try to paste that into the type declaration window, it works:

Call with argument propagation

That's much better - we now have all the functionality we would have had if the imports were static. We can find xrefs on struct members to find all call sites, we have the name instead of an ambiguous offset, and we have argument propagation.

Automation

Looking up the function prototype, turning it into a function pointer and adding it to the struct members is tedious, so let's automate the task.

We need a function to retrieve type information given a name, just like IDA does when we name DWORDs for example.

Unfortunately, there seems to be no clean way to do this, but it is doable nontheless.

First we use ida_typeinf.get_named_type() to lookup type information for a string like GetProcAddress:

Python>ida_typeinf.get_named_type(None,"GetProcAddress",0)
(2, '\x0cS=\x08FARPROC\x03=\x08HMODULE=\x07LPCSTR', '\x08hModule\x0blpProcName',
 None, None, 0, 0L)

That looks ugly, but it contains what we need. The 2nd item in that tuple is the serialized type information, and the 3rd item contains the argument names also in serialized form.

We can make them usable by deserializing them:

ret = ida_typeinf.get_named_type(None,name,0)
if not ret:
    return None

type_str = ret[1]
field_str = ret[2]
t = ida_typeinf.tinfo_t()
t.deserialize(None,type_str,field_str)
proto = str(t)

which returns the following in proto:

FARPROC __stdcall(HMODULE hModule, LPCSTR lpProcName)

Pretty good, but we need one last fix - this is not a function pointer. So I just used text replacement to turn it into a function pointer by replacing __stdcall with (__stdcall*) - that's not pretty but all WinAPI functions are __stdcall so it works for this use case.

The full function then is:

def type_for_name(name):
    ret = ida_typeinf.get_named_type(None,name,0)
    if not ret:
        return None

    type_str = ret[1]
    field_str = ret[2]
    t = ida_typeinf.tinfo_t()
    t.deserialize(None,type_str,field_str)
    typeinfo = str(t)
    typeinfo = typeinfo.replace("__stdcall","(__stdcall*)")
    return typeinfo

Now we just need to programmatically set the type information for struct members.

That works like this:

def set_member_type_info(struc,member,decl):
    ti = idaapi.tinfo_t()
    idaapi.parse_decl2(None,decl,ti,0)
    idaapi.set_member_tinfo(struc,member,0,ti,0):

We create a tinfo_t type, use parse_decl2() to have it parse the type string and then just set the member type to it.

For convenience I also wrote code to just iterate over my API structure looking up names for every struct member and adding type information if it finds them.

def get_struct(name):
    sid = idaapi.get_struc_id(name)
    return idaapi.get_struc(sid)

def enum_members(struc):
    idx = 0
    while idx != -1:
        member = struc.get_member(idx)
        yield member
        idx = idaapi.get_next_member_idx(struc,member.soff)

struc = get_struct("apis")

for mem in enum_members(struc):
    name = idaapi.get_member_name(mem.id)

    typeinfo = type_for_name(name)
    if typeinfo:
        print typeinfo
        set_member_type_info(struc,mem,typeinfo + ";")

That's it. Now we have complete type information for all APIs in the struct and can read the code as if it had proper imports. The solution is hacky but good enough I would say as it makes reading code much easier.

IDAPython, in my opinion, is what makes IDA so powerful and truly interactive.