Skip to content
This repository was archived by the owner on Aug 4, 2025. It is now read-only.

Commit 0a2c9ba

Browse files
committed
Initial version
0 parents  commit 0a2c9ba

File tree

7 files changed

+512
-0
lines changed

7 files changed

+512
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.vscode

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2023 Vector 35 Inc.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# EFI Resolver Plugin for Binary Ninja
2+
3+
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.
4+
5+
## Features
6+
7+
* **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.
8+
* **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.
9+
* **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.
10+
11+
## Usage
12+
13+
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.
14+
15+
Please note that this process might take a few moments to complete, depending on the size and complexity of the binary.
16+
17+
## Limitations
18+
19+
The current version of EFI Resolver does not support vendor-specific protocols. It is focused on the core protocols defined within the UEFI specification.
20+
21+
## License
22+
23+
This project is licensed under the terms of the Apache 2.0 license.

__init__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from binaryninja import PluginCommand, BinaryView, BackgroundTaskThread, log_alert
2+
from .protocols import init_protocol_mapping, define_handle_protocol_types, define_open_protocol_types, define_locate_protocol_types
3+
from .system_table import propagate_system_table_pointer
4+
5+
def resolve_efi(bv: BinaryView):
6+
class Task(BackgroundTaskThread):
7+
def __init__(self, bv: BinaryView):
8+
super().__init__("Initializing EFI protocol mappings...", True)
9+
self.bv = bv
10+
11+
def run(self):
12+
if not init_protocol_mapping():
13+
return
14+
15+
if "EFI_SYSTEM_TABLE" not in self.bv.types:
16+
log_alert("This binary is not using the EFI platform. Use Open with Options when loading the binary to select the EFI platform.")
17+
return
18+
19+
self.bv.begin_undo_actions()
20+
try:
21+
self.progress = "Propagating EFI system table pointers..."
22+
if not propagate_system_table_pointer(self.bv, self):
23+
return
24+
25+
self.progress = "Defining types for uses of HandleProtocol..."
26+
if not define_handle_protocol_types(self.bv, self):
27+
return
28+
29+
self.progress = "Defining types for uses of OpenProtocol..."
30+
if not define_open_protocol_types(self.bv, self):
31+
return
32+
33+
self.progress = "Defining types for uses of LocateProtocol..."
34+
if not define_locate_protocol_types(self.bv, self):
35+
return
36+
finally:
37+
self.bv.commit_undo_actions()
38+
39+
Task(bv).start()
40+
41+
PluginCommand.register("Resolve EFI Protocols", "Automatically resolve usage of EFI protocols", resolve_efi)

plugin.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"pluginmetadataversion": 2,
3+
"name": "EFI Resolver",
4+
"type": [
5+
"platform"
6+
],
7+
"api": [
8+
"python3"
9+
],
10+
"description": "A Binary Ninja plugin that automatically resolves type information for EFI protocol usage.",
11+
"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.",
12+
"license": {
13+
"name": "Apache-2.0",
14+
"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."
15+
},
16+
"platforms": [
17+
"Darwin",
18+
"Linux",
19+
"Windows"
20+
],
21+
"installinstructions": {
22+
"Darwin": "no special instructions, package manager is recommended",
23+
"Linux": "no special instructions, package manager is recommended",
24+
"Windows": "no special instructions, package manager is recommended"
25+
},
26+
"dependencies": {},
27+
"version": "1.0.0",
28+
"author": "Vector 35 Inc",
29+
"minimumbinaryninjaversion": 4333
30+
}

protocols.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
from binaryninja import (BinaryView, BackgroundTask, HighLevelILCall, RegisterValueType, HighLevelILAddressOf,
2+
HighLevelILVar, Constant, Function, HighLevelILVarSsa, HighLevelILVarInitSsa,
3+
TypeFieldReference, bundled_plugin_path, log_info, log_warn, log_alert)
4+
from typing import Optional, Tuple
5+
import os
6+
import sys
7+
import struct
8+
9+
protocols = None
10+
11+
def init_protocol_mapping():
12+
# Parse EFI definitions only once
13+
global protocols
14+
if protocols is not None:
15+
return True
16+
17+
# Find the EFI type definition file within the Binary Ninja installation
18+
if sys.platform == "darwin":
19+
efi_def_path = os.path.join(bundled_plugin_path(), "..", "..", "Resources", "types", "efi.c")
20+
else:
21+
efi_def_path = os.path.join(bundled_plugin_path(), "..", "types", "efi.c")
22+
23+
# Try to read the EFI type definitions. This may not exist on older versions of Binary Ninja.
24+
try:
25+
efi_defs = open(efi_def_path, "r").readlines()
26+
except:
27+
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.")
28+
return False
29+
30+
protocols = {}
31+
32+
# Parse the GUID to protocol structure mappings out of the type definition source
33+
guids = []
34+
for line in efi_defs:
35+
if line.startswith("///@protocol"):
36+
guid = line.split("///@protocol")[1].replace("{", "").replace("}", "").strip().split(",")
37+
guid = [int(x, 16) for x in guid]
38+
guid = struct.pack("<IHHBBBBBBBB", *guid)
39+
guids.append((guid, None))
40+
elif line.startswith("///@binding"):
41+
guid_name = line.split(" ")[1]
42+
guid = line.split(" ")[2].replace("{", "").replace("}", "").strip().split(",")
43+
guid = [int(x, 16) for x in guid]
44+
guid = struct.pack("<IHHBBBBBBBB", *guid)
45+
guids.append((guid, guid_name))
46+
elif line.startswith("struct"):
47+
name = line.split(" ")[1].strip()
48+
for guid_info in guids:
49+
guid, guid_name = guid_info
50+
if guid_name is None:
51+
protocols[guid] = (name, f"{name}_GUID")
52+
else:
53+
protocols[guid] = (name, guid_name)
54+
else:
55+
guids = []
56+
57+
return True
58+
59+
def lookup_protocol_guid(guid: bytes) -> Optional[Tuple[str, str]]:
60+
global protocols
61+
if guid in protocols:
62+
return protocols[guid]
63+
return (None, None)
64+
65+
def variable_name_for_protocol(protocol: str) -> str:
66+
name = protocol
67+
if name.startswith("EFI_"):
68+
name = name[4:]
69+
if name.endswith("_GUID"):
70+
name = name[:-5]
71+
if name.endswith("_PROTOCOL"):
72+
name = name[:-9]
73+
case_str = ""
74+
first = True
75+
for c in name:
76+
if c == "_":
77+
first = True
78+
continue
79+
elif first:
80+
case_str += c.upper()
81+
first = False
82+
else:
83+
case_str += c.lower()
84+
return case_str
85+
86+
def nonconflicting_variable_name(func: Function, base_name: str) -> str:
87+
idx = 0
88+
name = base_name
89+
while True:
90+
ok = True
91+
for var in func.vars:
92+
if var.name == name:
93+
ok = False
94+
break
95+
if ok:
96+
break
97+
idx += 1
98+
name = f"{base_name}_{idx}"
99+
return name
100+
101+
def define_protocol_types_for_refs(bv: BinaryView, func_name: str, refs, guid_param: int, interface_param: int, task: BackgroundTask) -> bool:
102+
refs = list(refs)
103+
for ref in refs:
104+
if task.cancelled:
105+
return False
106+
107+
if isinstance(ref, TypeFieldReference):
108+
func = ref.func
109+
else:
110+
func = ref.function
111+
112+
llil = func.get_llil_at(ref.address, ref.arch)
113+
if not llil:
114+
continue
115+
for hlil in llil.hlils:
116+
if isinstance(hlil, HighLevelILCall):
117+
# Check for status transform wrapper function
118+
if len(hlil.params) == 1 and isinstance(hlil.params[0], HighLevelILCall):
119+
hlil = hlil.params[0]
120+
121+
# Found call to target field
122+
if len(hlil.params) <= max(guid_param, interface_param):
123+
continue
124+
125+
# Get GUID parameter and read it from the binary or the stack
126+
guid_addr = hlil.params[guid_param].value
127+
guid = None
128+
if guid_addr.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue]:
129+
guid = bv.read(guid_addr.value, 16)
130+
if not guid or len(guid) < 16:
131+
continue
132+
elif guid_addr.type == RegisterValueType.StackFrameOffset:
133+
mlil = hlil.mlil
134+
if mlil is None:
135+
continue
136+
low = mlil.get_stack_contents(guid_addr.value, 8)
137+
high = mlil.get_stack_contents(guid_addr.value + 8, 8)
138+
if low.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue]:
139+
low = low.value
140+
else:
141+
continue
142+
if high.type in [RegisterValueType.ConstantValue, RegisterValueType.ConstantPointerValue]:
143+
high = high.value
144+
else:
145+
continue
146+
guid = struct.pack("<QQ", low, high)
147+
elif isinstance(hlil.params[guid_param], HighLevelILVar):
148+
# See if GUID variable is an incoming parameter
149+
ssa = hlil.params[guid_param].ssa_form
150+
if ssa is None or not isinstance(ssa, HighLevelILVarSsa):
151+
continue
152+
if ssa.var.version != 0:
153+
incoming_def = func.hlil.get_ssa_var_definition(ssa.var)
154+
if incoming_def is None:
155+
continue
156+
incoming_def = incoming_def.ssa_form
157+
if not isinstance(incoming_def, HighLevelILVarInitSsa):
158+
continue
159+
if not isinstance(incoming_def.src, HighLevelILVarSsa):
160+
continue
161+
if incoming_def.src.var.version != 0:
162+
continue
163+
ssa = incoming_def.src
164+
165+
# Find index of incoming parameter
166+
incoming_guid_param_idx = None
167+
for i in range(len(func.parameter_vars)):
168+
if func.parameter_vars[i] == ssa.var.var:
169+
incoming_guid_param_idx = i
170+
break
171+
if incoming_guid_param_idx is None:
172+
continue
173+
174+
# See if output interface variable is an incoming parameter
175+
ssa = hlil.params[interface_param].ssa_form
176+
if ssa is None or not isinstance(ssa, HighLevelILVarSsa):
177+
continue
178+
if ssa.var.version != 0:
179+
incoming_def = func.hlil.get_ssa_var_definition(ssa.var)
180+
if incoming_def is None:
181+
continue
182+
incoming_def = incoming_def.ssa_form
183+
if not isinstance(incoming_def, HighLevelILVarInitSsa):
184+
continue
185+
if not isinstance(incoming_def.src, HighLevelILVarSsa):
186+
continue
187+
if incoming_def.src.var.version != 0:
188+
continue
189+
ssa = incoming_def.src
190+
191+
# Find index of incoming parameter
192+
incoming_interface_param_idx = None
193+
for i in range(len(func.parameter_vars)):
194+
if func.parameter_vars[i] == ssa.var.var:
195+
incoming_interface_param_idx = i
196+
break
197+
if incoming_interface_param_idx is None:
198+
continue
199+
200+
# This function is a wrapper, resolve protocols for calls to this function
201+
log_info(f"Found EFI protocol wrapper {func_name} at {hex(ref.address)}, checking references to wrapper function")
202+
if not define_protocol_types_for_refs(bv, func.name, bv.get_code_refs(func.start),
203+
incoming_guid_param_idx, incoming_interface_param_idx, task):
204+
return False
205+
continue
206+
207+
if guid is None:
208+
continue
209+
210+
# Get the protocol from the GUID
211+
protocol, guid_name = lookup_protocol_guid(guid)
212+
if protocol is None:
213+
log_warn(f"Unknown EFI protocol {guid.hex()} referenced at {hex(ref.address)}")
214+
continue
215+
216+
# Rename the GUID with the protocol name
217+
sym = bv.get_symbol_at(guid_addr.value)
218+
name = guid_name
219+
if sym is not None:
220+
name = sym.name
221+
bv.define_user_data_var(guid_addr.value, "EFI_GUID", name)
222+
223+
# Get interface pointer parameter and set it to the type of the protocol
224+
dest = hlil.params[interface_param]
225+
if isinstance(dest, HighLevelILAddressOf):
226+
dest = dest.src
227+
if isinstance(dest, HighLevelILVar):
228+
dest = dest.var
229+
log_info(f"Setting type {protocol}* for local variable in {func_name} call at {hex(ref.address)}")
230+
name = nonconflicting_variable_name(func, variable_name_for_protocol(guid_name))
231+
func.create_user_var(dest, f"{protocol}*", name)
232+
elif isinstance(dest, Constant):
233+
dest = dest.constant
234+
log_info(f"Setting type {protocol}* for global variable at {hex(dest)} in {func_name} call at {hex(ref.address)}")
235+
sym = bv.get_symbol_at(dest)
236+
name = f"{variable_name_for_protocol(guid_name)}_{dest:x}"
237+
if sym is not None:
238+
name = sym.name
239+
bv.define_user_data_var(dest, f"{protocol}*", name)
240+
241+
bv.update_analysis_and_wait()
242+
return True
243+
244+
def define_protocol_types(bv: BinaryView, field: str, guid_param: int, interface_param: int, task: BackgroundTask) -> bool:
245+
boot_services = bv.types["EFI_BOOT_SERVICES"]
246+
offset = None
247+
for member in boot_services.members:
248+
if member.name == field:
249+
offset = member.offset
250+
break
251+
if offset is None:
252+
log_warn(f"Could not find {field} member in EFI_BOOT_SERVICES")
253+
return True
254+
return define_protocol_types_for_refs(bv, field, bv.get_code_refs_for_type_field("EFI_BOOT_SERVICES", offset),
255+
guid_param, interface_param, task)
256+
257+
def define_handle_protocol_types(bv: BinaryView, task: BackgroundTask) -> bool:
258+
return define_protocol_types(bv, "HandleProtocol", 1, 2, task)
259+
260+
def define_open_protocol_types(bv: BinaryView, task: BackgroundTask) -> bool:
261+
return define_protocol_types(bv, "OpenProtocol", 1, 2, task)
262+
263+
def define_locate_protocol_types(bv: BinaryView, task: BackgroundTask) -> bool:
264+
return define_protocol_types(bv, "LocateProtocol", 0, 2, task)

0 commit comments

Comments
 (0)