-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 0a2c9ba
Showing
7 changed files
with
512 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright 2023 Vector 35 Inc. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# EFI Resolver Plugin for Binary Ninja | ||
|
||
EFI Resolver is a Binary Ninja plugin developed to enhance your UEFI reverse engineering workflow. The plugin automatically resolves type information for EFI protocol usage, making it easier to understand and analyze EFI binaries. | ||
|
||
## Features | ||
|
||
* **Automatic EFI Protocol Typing**: EFI Resolver intelligently identifies instances where EFI protocols are used and automatically applies the appropriate type information. EFI Resolver looks for references to the boot services protocol functions and applies type information according to the GUID passed to these functions. | ||
* **Global Variable Propagation**: The plugin propagates pointers to the system table, boot services, and runtime services to any global variables where they are stored. This streamlines the process of tracking these vital system components across a binary. | ||
* **Comprehensive UEFI Specification Support**: The plugin fully supports all core protocols within the UEFI specification. However, please note that vendor-specific protocols are not currently supported. | ||
|
||
## Usage | ||
|
||
To use the EFI Resolver plugin, open a UEFI binary in Binary Ninja. Then, navigate to the `Plugins` menu, and choose `Resolve EFI Protocols`. The plugin will automatically analyze the binary and apply type information. | ||
|
||
Please note that this process might take a few moments to complete, depending on the size and complexity of the binary. | ||
|
||
## Limitations | ||
|
||
The current version of EFI Resolver does not support vendor-specific protocols. It is focused on the core protocols defined within the UEFI specification. | ||
|
||
## License | ||
|
||
This project is licensed under the terms of the Apache 2.0 license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
from binaryninja import PluginCommand, BinaryView, BackgroundTaskThread, log_alert | ||
from .protocols import init_protocol_mapping, define_handle_protocol_types, define_open_protocol_types, define_locate_protocol_types | ||
from .system_table import propagate_system_table_pointer | ||
|
||
def resolve_efi(bv: BinaryView): | ||
class Task(BackgroundTaskThread): | ||
def __init__(self, bv: BinaryView): | ||
super().__init__("Initializing EFI protocol mappings...", True) | ||
self.bv = bv | ||
|
||
def run(self): | ||
if not init_protocol_mapping(): | ||
return | ||
|
||
if "EFI_SYSTEM_TABLE" not in self.bv.types: | ||
log_alert("This binary is not using the EFI platform. Use Open with Options when loading the binary to select the EFI platform.") | ||
return | ||
|
||
self.bv.begin_undo_actions() | ||
try: | ||
self.progress = "Propagating EFI system table pointers..." | ||
if not propagate_system_table_pointer(self.bv, self): | ||
return | ||
|
||
self.progress = "Defining types for uses of HandleProtocol..." | ||
if not define_handle_protocol_types(self.bv, self): | ||
return | ||
|
||
self.progress = "Defining types for uses of OpenProtocol..." | ||
if not define_open_protocol_types(self.bv, self): | ||
return | ||
|
||
self.progress = "Defining types for uses of LocateProtocol..." | ||
if not define_locate_protocol_types(self.bv, self): | ||
return | ||
finally: | ||
self.bv.commit_undo_actions() | ||
|
||
Task(bv).start() | ||
|
||
PluginCommand.register("Resolve EFI Protocols", "Automatically resolve usage of EFI protocols", resolve_efi) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"pluginmetadataversion": 2, | ||
"name": "EFI Resolver", | ||
"type": [ | ||
"platform" | ||
], | ||
"api": [ | ||
"python3" | ||
], | ||
"description": "A Binary Ninja plugin that automatically resolves type information for EFI protocol usage.", | ||
"longdescription": "EFI Resolver is a Binary Ninja plugin that automates the task of resolving EFI protocol type information. It propagates pointers to system table, boot services, and runtime services to any global variables where they are stored. The plugin also identifies references to the boot services protocol functions and applies type information according to the GUID passed to these functions. The plugin supports all of the core UEFI specification, but does not support vendor protocols.", | ||
"license": { | ||
"name": "Apache-2.0", | ||
"text": "Copyright 2023 Vector 35 Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License." | ||
}, | ||
"platforms": [ | ||
"Darwin", | ||
"Linux", | ||
"Windows" | ||
], | ||
"installinstructions": { | ||
"Darwin": "no special instructions, package manager is recommended", | ||
"Linux": "no special instructions, package manager is recommended", | ||
"Windows": "no special instructions, package manager is recommended" | ||
}, | ||
"dependencies": {}, | ||
"version": "1.0.0", | ||
"author": "Vector 35 Inc", | ||
"minimumbinaryninjaversion": 4333 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,264 @@ | ||
from binaryninja import (BinaryView, BackgroundTask, HighLevelILCall, RegisterValueType, HighLevelILAddressOf, | ||
HighLevelILVar, Constant, Function, HighLevelILVarSsa, HighLevelILVarInitSsa, | ||
TypeFieldReference, bundled_plugin_path, log_info, log_warn, log_alert) | ||
from typing import Optional, Tuple | ||
import os | ||
import sys | ||
import struct | ||
|
||
protocols = None | ||
|
||
def init_protocol_mapping(): | ||
# Parse EFI definitions only once | ||
global protocols | ||
if protocols is not None: | ||
return True | ||
|
||
# Find the EFI type definition file within the Binary Ninja installation | ||
if sys.platform == "darwin": | ||
efi_def_path = os.path.join(bundled_plugin_path(), "..", "..", "Resources", "types", "efi.c") | ||
else: | ||
efi_def_path = os.path.join(bundled_plugin_path(), "..", "types", "efi.c") | ||
|
||
# Try to read the EFI type definitions. This may not exist on older versions of Binary Ninja. | ||
try: | ||
efi_defs = open(efi_def_path, "r").readlines() | ||
except: | ||
log_alert(f"Could not open EFI type definition file at '{efi_def_path}'. Your version of Binary Ninja may be out of date. Please update to version 3.5.4331 or higher.") | ||
return False | ||
|
||
protocols = {} | ||
|
||
# Parse the GUID to protocol structure mappings out of the type definition source | ||
guids = [] | ||
for line in efi_defs: | ||
if line.startswith("///@protocol"): | ||
guid = line.split("///@protocol")[1].replace("{", "").replace("}", "").strip().split(",") | ||
guid = [int(x, 16) for x in guid] | ||
guid = struct.pack("<IHHBBBBBBBB", *guid) | ||
guids.append((guid, None)) | ||
elif line.startswith("///@binding"): | ||
guid_name = line.split(" ")[1] | ||
guid = line.split(" ")[2].replace("{", "").replace("}", "").strip().split(",") | ||
guid = [int(x, 16) for x in guid] | ||
guid = struct.pack("<IHHBBBBBBBB", *guid) | ||
guids.append((guid, guid_name)) | ||
elif line.startswith("struct"): | ||
name = line.split(" ")[1].strip() | ||
for guid_info in guids: | ||
guid, guid_name = guid_info | ||
if guid_name is None: | ||
protocols[guid] = (name, f"{name}_GUID") | ||
else: | ||
protocols[guid] = (name, guid_name) | ||
else: | ||
guids = [] | ||
|
||
return True | ||
|
||
def lookup_protocol_guid(guid: bytes) -> Optional[Tuple[str, str]]: | ||
global protocols | ||
if guid in protocols: | ||
return protocols[guid] | ||
return (None, None) | ||
|
||
def variable_name_for_protocol(protocol: str) -> str: | ||
name = protocol | ||
if name.startswith("EFI_"): | ||
name = name[4:] | ||
if name.endswith("_GUID"): | ||
name = name[:-5] | ||
if name.endswith("_PROTOCOL"): | ||
name = name[:-9] | ||
case_str = "" | ||
first = True | ||
for c in name: | ||
if c == "_": | ||
first = True | ||
continue | ||
elif first: | ||
case_str += c.upper() | ||
first = False | ||
else: | ||
case_str += c.lower() | ||
return case_str | ||
|
||
def nonconflicting_variable_name(func: Function, base_name: str) -> str: | ||
idx = 0 | ||
name = base_name | ||
while True: | ||
ok = True | ||
for var in func.vars: | ||
if var.name == name: | ||
ok = False | ||
break | ||
if ok: | ||
break | ||
idx += 1 | ||
name = f"{base_name}_{idx}" | ||
return name | ||
|
||
def define_protocol_types_for_refs(bv: BinaryView, func_name: str, refs, guid_param: int, interface_param: int, task: BackgroundTask) -> bool: | ||
refs = list(refs) | ||
for ref in refs: | ||
if task.cancelled: | ||
return False | ||
|
||
if isinstance(ref, TypeFieldReference): | ||
func = ref.func | ||
else: | ||
func = ref.function | ||
|
||
llil = func.get_llil_at(ref.address, ref.arch) | ||
if not llil: | ||
continue | ||
for hlil in llil.hlils: | ||
if isinstance(hlil, HighLevelILCall): | ||
# Check for status transform wrapper function | ||
if len(hlil.params) == 1 and isinstance(hlil.params[0], HighLevelILCall): | ||
hlil = hlil.params[0] | ||
|
||
# Found call to target field | ||
if len(hlil.params) <= max(guid_param, interface_param): | ||
continue | ||
|
||
# Get GUID parameter and read it from the binary or the stack | ||
guid_addr = hlil.params[guid_param].value | ||
guid = None | ||
if guid_addr.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue]: | ||
guid = bv.read(guid_addr.value, 16) | ||
if not guid or len(guid) < 16: | ||
continue | ||
elif guid_addr.type == RegisterValueType.StackFrameOffset: | ||
mlil = hlil.mlil | ||
if mlil is None: | ||
continue | ||
low = mlil.get_stack_contents(guid_addr.value, 8) | ||
high = mlil.get_stack_contents(guid_addr.value + 8, 8) | ||
if low.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue]: | ||
low = low.value | ||
else: | ||
continue | ||
if high.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue]: | ||
high = high.value | ||
else: | ||
continue | ||
guid = struct.pack("<QQ", low, high) | ||
elif isinstance(hlil.params[guid_param], HighLevelILVar): | ||
# See if GUID variable is an incoming parameter | ||
ssa = hlil.params[guid_param].ssa_form | ||
if ssa is None or not isinstance(ssa, HighLevelILVarSsa): | ||
continue | ||
if ssa.var.version != 0: | ||
incoming_def = func.hlil.get_ssa_var_definition(ssa.var) | ||
if incoming_def is None: | ||
continue | ||
incoming_def = incoming_def.ssa_form | ||
if not isinstance(incoming_def, HighLevelILVarInitSsa): | ||
continue | ||
if not isinstance(incoming_def.src, HighLevelILVarSsa): | ||
continue | ||
if incoming_def.src.var.version != 0: | ||
continue | ||
ssa = incoming_def.src | ||
|
||
# Find index of incoming parameter | ||
incoming_guid_param_idx = None | ||
for i in range(len(func.parameter_vars)): | ||
if func.parameter_vars[i] == ssa.var.var: | ||
incoming_guid_param_idx = i | ||
break | ||
if incoming_guid_param_idx is None: | ||
continue | ||
|
||
# See if output interface variable is an incoming parameter | ||
ssa = hlil.params[interface_param].ssa_form | ||
if ssa is None or not isinstance(ssa, HighLevelILVarSsa): | ||
continue | ||
if ssa.var.version != 0: | ||
incoming_def = func.hlil.get_ssa_var_definition(ssa.var) | ||
if incoming_def is None: | ||
continue | ||
incoming_def = incoming_def.ssa_form | ||
if not isinstance(incoming_def, HighLevelILVarInitSsa): | ||
continue | ||
if not isinstance(incoming_def.src, HighLevelILVarSsa): | ||
continue | ||
if incoming_def.src.var.version != 0: | ||
continue | ||
ssa = incoming_def.src | ||
|
||
# Find index of incoming parameter | ||
incoming_interface_param_idx = None | ||
for i in range(len(func.parameter_vars)): | ||
if func.parameter_vars[i] == ssa.var.var: | ||
incoming_interface_param_idx = i | ||
break | ||
if incoming_interface_param_idx is None: | ||
continue | ||
|
||
# This function is a wrapper, resolve protocols for calls to this function | ||
log_info(f"Found EFI protocol wrapper {func_name} at {hex(ref.address)}, checking references to wrapper function") | ||
if not define_protocol_types_for_refs(bv, func.name, bv.get_code_refs(func.start), | ||
incoming_guid_param_idx, incoming_interface_param_idx, task): | ||
return False | ||
continue | ||
|
||
if guid is None: | ||
continue | ||
|
||
# Get the protocol from the GUID | ||
protocol, guid_name = lookup_protocol_guid(guid) | ||
if protocol is None: | ||
log_warn(f"Unknown EFI protocol {guid.hex()} referenced at {hex(ref.address)}") | ||
continue | ||
|
||
# Rename the GUID with the protocol name | ||
sym = bv.get_symbol_at(guid_addr.value) | ||
name = guid_name | ||
if sym is not None: | ||
name = sym.name | ||
bv.define_user_data_var(guid_addr.value, "EFI_GUID", name) | ||
|
||
# Get interface pointer parameter and set it to the type of the protocol | ||
dest = hlil.params[interface_param] | ||
if isinstance(dest, HighLevelILAddressOf): | ||
dest = dest.src | ||
if isinstance(dest, HighLevelILVar): | ||
dest = dest.var | ||
log_info(f"Setting type {protocol}* for local variable in {func_name} call at {hex(ref.address)}") | ||
name = nonconflicting_variable_name(func, variable_name_for_protocol(guid_name)) | ||
func.create_user_var(dest, f"{protocol}*", name) | ||
elif isinstance(dest, Constant): | ||
dest = dest.constant | ||
log_info(f"Setting type {protocol}* for global variable at {hex(dest)} in {func_name} call at {hex(ref.address)}") | ||
sym = bv.get_symbol_at(dest) | ||
name = f"{variable_name_for_protocol(guid_name)}_{dest:x}" | ||
if sym is not None: | ||
name = sym.name | ||
bv.define_user_data_var(dest, f"{protocol}*", name) | ||
|
||
bv.update_analysis_and_wait() | ||
return True | ||
|
||
def define_protocol_types(bv: BinaryView, field: str, guid_param: int, interface_param: int, task: BackgroundTask) -> bool: | ||
boot_services = bv.types["EFI_BOOT_SERVICES"] | ||
offset = None | ||
for member in boot_services.members: | ||
if member.name == field: | ||
offset = member.offset | ||
break | ||
if offset is None: | ||
log_warn(f"Could not find {field} member in EFI_BOOT_SERVICES") | ||
return True | ||
return define_protocol_types_for_refs(bv, field, bv.get_code_refs_for_type_field("EFI_BOOT_SERVICES", offset), | ||
guid_param, interface_param, task) | ||
|
||
def define_handle_protocol_types(bv: BinaryView, task: BackgroundTask) -> bool: | ||
return define_protocol_types(bv, "HandleProtocol", 1, 2, task) | ||
|
||
def define_open_protocol_types(bv: BinaryView, task: BackgroundTask) -> bool: | ||
return define_protocol_types(bv, "OpenProtocol", 1, 2, task) | ||
|
||
def define_locate_protocol_types(bv: BinaryView, task: BackgroundTask) -> bool: | ||
return define_protocol_types(bv, "LocateProtocol", 0, 2, task) |
Oops, something went wrong.