Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 111 additions & 33 deletions wdf_plugin/wdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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...
Expand All @@ -93,8 +98,6 @@ def apply_driver_globals() -> bool:
idaapi.warning("Failed to apply PWDF_DRIVERGLOBALS type")
return False



return True


Expand Down Expand Up @@ -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")
Expand All @@ -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...
Expand Down Expand Up @@ -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()
Expand All @@ -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:
Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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

Expand All @@ -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)
Expand Down