[ << ALL_FEED ]

Static resolution of imports

More in Reverse engineering

Static resolution of imports 👨‍💻

Dynamic resolution of imports by hash sums in malware is a well-worn topic, but to perform static analysis it is necessary to label API names and prototypes. To get from what is shown in screenshot 1 to what is in screenshot 2, without struggling with manual labeling, you can write an IDAPython script.

😠 Using DodgeBox as an example, let’s look at resolution that consists of computing the address of an API function and placing it into a global structure. The implementation is in screenshot 3.

We’ll skip the hashing algorithm, since it isn’t that important here. Before starting, it’s necessary to prepare a dictionary with WinAPI function names and their hash sums. To do this, we’ll select the libraries used in the binary. Here the DLL names are in plaintext, but sometimes there are only hash sums — in that case you can build a dictionary from all system DLLs. Our dictionary is in screenshot 4.

Next, we make sure that the members in the structure are filled in sequentially, so that we can automatically extract the hash sums and module names from the code. Note — the first member of the structure is skipped. We create a structure of the required size (screenshot 5) so that it can hold all the functions.

To extract the hash sums and library names, we write a function that will iterate over all calls to get_proc_by_hash and extract its arguments.

def get_hashes(resolve_API_addr, get_proc_by_hash_addr):
  result = []
  func: ida_funcs.func_t
  func = ida_funcs.get_func(resolve_API_addr)

  cur = func.start_ea
  while cur < func.end_ea:
    if get_operand_value(cur, 0) == get_proc_by_hash_addr:
      result.append(get_args(cur))
    cur = ida_search.find_code(cur, SEARCH_DOWN)

  return resultCode language: Python (python)


The get_args function goes up several steps from the call instruction and extracts the arguments.

def get_args(call_addr):
  func_name_hash = None
  lib_name = None

  cur = call_addr
  True:
    if print_insn_mnem(cur) == "mov" and print_operand(cur, 0) == "r8d":
      func_name_hash = get_operand_value(cur, 1) & 0xFFFFFFFF
    elif print_insn_mnem(cur) == "lea" and print_operand(cur, 0) == "rcx":
      lib_name = ida_bytes.get_strlit_contents(get_operand_value(cur, 1), -1, STRTYPE_C_16).decode()

    if func_name_hash and lib_name:
      return lib_name, func_name_hash
        
    cur = ida_search.find_code(cur, SEARCH_UP)Code language: Intel x86 Assembly (x86asm)


The output of get_hashes will be a list that we use to fill in the API structure. The main function will look like this:

struc: ida_struct.struc_t = ida_struct.get_struc(ida_struct.get_struc_id("API"))

funcs = get_hashes(0x180007A90, 0x1800078E0)
for i in range(1, len(funcs) + 1):

  lib_name, func_name_hash = funcs[i - 1]
  member: ida_struct.member_t = struc.members[i]

  func_name = get_func_name(lib_name, func_name_hash, winapi_hashes_dict)
  if func_name:
    ida_struct.set_member_name(struc, member.soff, func_name)
  func_tinfo = get_func_tinfo(func_name)
  if func_tinfo:
    ida_struct.set_member_tinfo(struc, member, 0, func_tinfo, 0)Code language: YAML (yaml)


The get_func_name function is simple to implement — it finds the API name in the dictionary by hash sum. But get_func_tinfo is more interesting: it creates an object containing the function prototype, which we also apply to the structure member.

def get_func_tinfo(func_name):
  tinfo = ida_typeinf.get_named_type(None, func_name, 0)
  if tinfo:
    type_s = tinfo[1]
    field_s = tinfo[2]
    t = ida_typeinf.tinfo_t()
    t.deserialize(None, type_s, field_s)
    t.create_ptr(t)
    return t
  else:
    return NoneCode language: Python (python)


The ida_typeinf.get_named_type function retrieves information about a type contained in the Type Library (*.til). The call looks like this:

Python>get_func_tinfo("GetWindowsDirectoryW")
UINT (__stdcall *)(LPWSTR lpBuffer, UINT uSize)Code language: plaintext (plaintext)


However, the function actually returns an object of type ida_typeinf.tinfo_t.

The API structure after running the script is shown in screenshot 6. If you apply it to a global variable, the resolution turns into what is visible in screenshot 7, and you can conveniently analyze the binary statically without launching a debugger.

There is, of course, a temptation to write a make_beautifully function that itself subtracts the offsets, creates the structure and the members inside it, but that’s for another time.


#tip #reverse #idapython
@ptescalator

More from global_author

More from global_author

More in Reverse engineering