diff --git a/wdf_plugin/wdf.py b/wdf_plugin/wdf.py index 5339d6b..8725f24 100644 --- a/wdf_plugin/wdf.py +++ b/wdf_plugin/wdf.py @@ -3,6 +3,8 @@ import ida_bytes import ida_funcs import ida_nalt +import ida_ida +import ida_idp import ida_typeinf import idaapi import idautils @@ -39,6 +41,8 @@ def log(message: str, file=None, level=LOG_INFO, callee=LOG_DEBUG): def apply_driver_globals() -> bool: + log("Applying driver globals", level=LOG_INFO) + names = idautils.Names() version_bind_ea = None @@ -49,6 +53,8 @@ def apply_driver_globals() -> bool: if version_bind_ea is None: return False + + log(f"Found WdfVersionBind at 0x{version_bind_ea:x}", level=LOG_INFO) args_addrs = None for xref in idautils.XrefsTo(version_bind_ea): @@ -62,25 +68,24 @@ def apply_driver_globals() -> bool: if args_addrs is None: return False + log(f"Found args_addrs: {args_addrs}", level=LOG_INFO) + if len(args_addrs) != 4: return False - insn = idautils.DecodeInstruction( - args_addrs[3] - ) # DriverGlobals is the 4th argument driver_globals = None - for op in insn.ops: - # I need to check both values and addr because IDA uses either one - # in 32 bit or 64 bit - if op.addr != 0: # Registers and direct values don't have addresses - driver_globals = op.addr - elif op.value != 0: # Registers and direct values don't have values - driver_globals = op.value - break + for xref in idautils.XrefsFrom(args_addrs[3]): + if xref.type == idaapi.ida_xref.fl_F: # Fallthrough is irrelevant + continue + + driver_globals = xref.to + break if driver_globals is None: return False + log(f"Found driver globals at 0x{driver_globals:x}", level=LOG_INFO) + ok = idc.set_name(driver_globals, "WdfDriverGlobals", flags=idc.SN_NOCHECK|idc.SN_NON_WEAK) if not ok: # We need to unset a type because it seems to be wrong... @@ -93,8 +98,6 @@ def apply_driver_globals() -> bool: idaapi.warning("Failed to apply PWDF_DRIVERGLOBALS type") return False - - return True @@ -133,6 +136,8 @@ def apply_function_xrefs(wdf_functions_ea, wdffunctions_tid): def apply_wdf_functions() -> bool: sid_found = False + log("Applying WDF functions", level=LOG_INFO) + # Since ida 8.4, the _WDF_BIND_INFO structure is not imported automatically so we have to import it manually idc.import_type(-1, "_WDF_BIND_INFO") tid = idc.import_type(-1, "WDFFUNCTIONS") @@ -158,8 +163,14 @@ def apply_wdf_functions() -> bool: offset = soff break + log(f"Found offset of FuncTable at 0x{offset:x}", level=LOG_INFO) + for xref in xrefs: + log(f"Found xref: 0x{xref.to:x} 0x{xref.frm:x}", level=LOG_INFO) ptr = idaapi.get_qword(xref.frm + offset) + + log(f"Found WdfFunctions at 0x{ptr:x}", level=LOG_INFO) + ok = idc.set_name(ptr, "WdfFunctions", flags=idc.SN_NOCHECK|idc.SN_NON_WEAK) if not ok: # We need to unset a type because it seems to be wrong... @@ -190,6 +201,11 @@ def apply_wdf() -> bool: argument types. """ + # We need to do it again here for some weird IDA reason... + if ida_idp.get_idp_name() == "arm": + log("Manually setting ARM64 IDA pointer size to 64") + ida_ida.inf_set_cc_cm(ida_typeinf.CM_N64) + ok = apply_driver_globals() if ok: apply_wdf_functions() @@ -215,13 +231,36 @@ def find_wdf_version_via_str() -> str: return None, None for xref in xrefs: - ptr_size = 8 if idaapi.inf_is_64bit() else 4 - major = idaapi.get_dword(xref.frm + ptr_size) - minor = idaapi.get_dword(xref.frm + ptr_size + 4) - bind_info = xref.frm - ptr_size - break # There should be only one ref in WDF_BIND_INFO + bind_info = xref.frm - return f"{major}-{minor}", bind_info + if not is_ea_wdf_bind_info(xref.frm): + # If the address is untyped, the xref will be to + # the Component member of the WDF_BIND_INFO structure. + bind_info -= 8 if idaapi.inf_is_64bit() else 4 + + major, minor = get_wdf_version_from_bind_info(bind_info) + if major is None or minor is None: + continue + + return f"{major}-{minor}", bind_info + + return None, None + + +def is_ea_wdf_bind_info(ea) -> bool: + """ + Check if the given address already has the _WDF_BIND_INFO type applied. + Returns True if the address is already typed as _WDF_BIND_INFO, False otherwise. + """ + tinfo = ida_typeinf.tinfo_t() + if not idaapi.get_tinfo(tinfo, ea): + return False + + type_name = tinfo.get_type_name() + if type_name is None: + return False + + return type_name in ("_WDF_BIND_INFO", "WDF_BIND_INFO") def get_version_from_call(frm) -> str: @@ -236,19 +275,17 @@ def get_version_from_call(frm) -> str: if args_addrs == None or len(args_addrs) != 4: return None, None - insn = idautils.DecodeInstruction(args_addrs[2]) # BindInfo is the 3rd argument - bind_info = None - for op in insn.ops: - if op.addr != 0: # Registers and direct values don't have addresses - bind_info = op.addr - break + for xref in idautils.XrefsFrom(args_addrs[2]): + if xref.type == idaapi.ida_xref.fl_F: # Fallthrough is irrelevant + continue + + bind_info = xref.to + break if bind_info is None: return None, None - - ptr_size = 8 if idaapi.inf_is_64bit() else 4 - major = idaapi.get_dword(bind_info + ptr_size * 2) - minor = idaapi.get_dword(bind_info + ptr_size * 2 + 4) + + major, minor = get_wdf_version_from_bind_info(bind_info) return f"{major}-{minor}", bind_info @@ -293,6 +330,41 @@ def find_wdf_version_via_version_bind() -> str: return version, bind_info +def get_member_offset(struc_name, member): + """ + Return offset of `member` inside struct `struc_name` (or None). + Accepts either the tag name `_WDF_BIND_INFO` or the typedef `WDF_BIND_INFO`. + """ + sid = idc.get_struc_id(struc_name) + if sid == idc.BADADDR: + # try the typedef form if the tag wasn't found (or vice-versa) + alt = struc_name[1:] if struc_name.startswith( + '_') else f'_{struc_name}' + sid = idc.get_struc_id(alt) + if sid == idc.BADADDR: + return None + + return idc.get_member_offset(sid, member) + + +def get_wdf_version_from_bind_info(bind_info_ea): + ptr_size = 8 if idaapi.inf_is_64bit() else 4 + + offset = get_member_offset("_WDF_BIND_INFO", "Version") + if offset is None: + offset = ptr_size * 2 + + major_offset = get_member_offset("_WDF_VERSION", "Major") + if major_offset is None: + major_offset = 0 + + minor_offset = get_member_offset("_WDF_VERSION", "Minor") + if minor_offset is None: + minor_offset = 4 + + return idaapi.get_dword(bind_info_ea + offset + major_offset), idaapi.get_dword(bind_info_ea + offset + minor_offset) + + def find_wdf_version(): """ Tries to find the WDF version used via two ways. @@ -317,6 +389,10 @@ def load_wdf() -> bool: Find the version of WDF and load the associated .til if it exists. """ + if ida_idp.get_idp_name() == "arm": + log("Manually setting ARM64 IDA pointer size to 64") + ida_ida.inf_set_cc_cm(ida_typeinf.CM_N64) + wdf_version, bind_info_ea = find_wdf_version() if wdf_version is None: log("Could not find WDF version. Is this a WDF driver?", level=LOG_INFO) @@ -329,7 +405,6 @@ def load_wdf() -> bool: ok = ida_typeinf.add_til(til_path, ida_typeinf.ADDTIL_DEFAULT) if ok != ida_typeinf.ADDTIL_OK: - log("Failed to load WDF til", level=LOG_ERROR) return False @@ -340,13 +415,16 @@ def load_wdf() -> bool: ok = idc.set_name(bind_info_ea, "BindInfo", flags=idc.SN_NOCHECK|idc.SN_NON_WEAK) if not ok: # We need to unset a type because it seems to be wrong... + idaapi.warning("Failed to set name to BindInfo") to_unset = ida_bytes.prev_head(bind_info_ea, 0) ida_bytes.del_items(to_unset, 0, 1, None) idc.set_name(bind_info_ea, "BindInfo", flags=idc.SN_NOCHECK|idc.SN_NON_WEAK) - ok = ida_typeinf.apply_cdecl(None, bind_info_ea, "struct _WDF_BIND_INFO;", ida_typeinf.TINFO_DEFINITE) - if not ok: - idaapi.warning("Failed to apply WDF_BIND_INFO type") + # For some reason, false is returned even when the type is applied + ida_typeinf.apply_cdecl(None, bind_info_ea, "struct _WDF_BIND_INFO;", ida_typeinf.TINFO_DEFINITE) + if not is_ea_wdf_bind_info(bind_info_ea): + idaapi.warning(f"Failed to apply WDF_BIND_INFO type (0x{bind_info_ea:x})") + return False # Set bind info type in force to be sure the plugin will work log("WDF til successfully loaded", level=LOG_INFO)