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

Commit 953047f

Browse files
authored
feat: support parsing PEI files and PPI protocols (#10)
Changes: 1. support parsing PEI services 2. support defining PEI descriptors 1. support defining platform specific PEI services pointers 2. add module strings and function strings 3. add a utils.py 4. rename functions (e.g. propagate_system_table_pointer -> propagate_function_param_types) 5. fix the log error when opening TE files 6. add a typorary solution for vector35/binaryninja#749 7. add exception handling code for accessing references, in case of involving infinite loops 8. add support for previously created BNDB files, parse type from both bv.types and platform types
1 parent 29275b4 commit 953047f

6 files changed

Lines changed: 446 additions & 73 deletions

File tree

__init__.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from binaryninja import PluginCommand, BinaryView, BackgroundTaskThread, log_alert
1+
from binaryninja import PluginCommand, BinaryView, BackgroundTaskThread, log_alert, log_warn
22
from .moduletype import identify_efi_module_type, set_efi_module_entry_type, EFIModuleType
33
from .protocols import (
44
init_protocol_mapping,
@@ -13,18 +13,35 @@
1313
define_smm_handle_protocol_types
1414
)
1515

16-
from .system_table import propagate_system_table_pointers
16+
from .system_table import propagate_function_param_types, set_windows_bootloader_type
1717
from .guid_renderer import EfiGuidDataRenderer
18+
from .ppi import define_pei_descriptor, define_locate_ppi_types, define_pei_pointers
19+
1820

1921
def resolve_efi(bv: BinaryView):
2022
class Task(BackgroundTaskThread):
2123
def __init__(self, bv: BinaryView):
2224
super().__init__("Initializing EFI protocol mappings...", True)
2325
self.bv = bv
2426

27+
def _resolve_pei(self):
28+
self.progress = "Defining platform specific PEI pointers..."
29+
if not define_pei_pointers(self.bv, self):
30+
log_warn("Failed to define PEI pointers")
31+
self.progress = "Propagating PEI services..."
32+
if not propagate_function_param_types(self.bv, self):
33+
return
34+
if not define_pei_descriptor(self.bv, self):
35+
return
36+
if not define_locate_ppi_types(self.bv, self):
37+
return
38+
2539
def _resolve_dxe(self):
2640
self.progress = "Propagating EFI system table pointers..."
27-
if not propagate_system_table_pointers(self.bv, self):
41+
if not propagate_function_param_types(self.bv, self):
42+
return
43+
44+
if not set_windows_bootloader_type(self.bv):
2845
return
2946

3047
self.progress = "Defining types for uses of HandleProtocol..."
@@ -44,19 +61,19 @@ def _resolve_dxe(self):
4461
if "EFI_MM_SYSTEM_TABLE" in self.bv.types:
4562
self.progress = "Defining types for SMM/MM system tables..."
4663
if not define_locate_mm_system_table_types(self.bv, self) or not define_locate_smm_system_table_types(
47-
self.bv, self
64+
self.bv, self
4865
):
4966
return
5067

5168
self.progress = "Defining types for uses of SMM/MM LocateProtocol..."
5269
if not define_mm_locate_protocol_types(self.bv, self) or not define_smm_locate_protocol_types(
53-
self.bv, self
70+
self.bv, self
5471
):
5572
return
5673

5774
self.progress = "Defining types for uses of SMM/MM HandleProtocol..."
5875
if not define_mm_handle_protocol_types(self.bv, self) or not define_smm_handle_protocol_types(
59-
self.bv, self
76+
self.bv, self
6077
):
6178
return
6279

@@ -65,7 +82,8 @@ def run(self):
6582
return
6683

6784
if "EFI_SYSTEM_TABLE" not in self.bv.types:
68-
log_alert("This binary is not using the EFI platform. Use Open with Options when loading the binary to select the EFI platform.")
85+
log_alert(
86+
"This binary is not using the EFI platform. Use Open with Options when loading the binary to select the EFI platform.")
6987
return
7088

7189
module_type = identify_efi_module_type(self.bv)
@@ -85,10 +103,11 @@ def run(self):
85103
if module_type == EFIModuleType.DXE:
86104
self._resolve_dxe()
87105
elif module_type == EFIModuleType.PEI:
88-
log_alert("TE PEI modules are not yet supported.")
106+
self._resolve_pei()
89107
finally:
90108
self.bv.commit_undo_actions()
91109

92110
Task(bv).start()
93111

112+
94113
PluginCommand.register("Resolve EFI Protocols", "Automatically resolve usage of EFI protocols", resolve_efi)

moduletype.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ def identify_efi_module_type(bv: BinaryView) -> EFIModuleType:
3737
cases, and is the convention used by most UEFI tooling.
3838
"""
3939

40-
if bv.get_view_of_type("PE"):
40+
if bv.view_type == "PE":
4141
return EFIModuleType.DXE
4242

43-
if bv.get_view_of_type("TE"):
43+
if bv.view_type == "TE":
4444
return EFIModuleType.PEI
4545

4646
return EFIModuleType.UNKNOWN

ppi.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
"""
2+
This module contains functions related to resolving PEI services and PPI protocols
3+
"""
4+
from typing import Optional
5+
from binaryninja import BinaryView, MediumLevelILCall, MediumLevelILTailcall, MediumLevelILLoadStruct, PointerType, \
6+
MediumLevelILConstPtr, BackgroundTask, HighLevelILAssign, HighLevelILIntrinsic, ILIntrinsic, log_warn, \
7+
MediumLevelILIntrinsic, MediumLevelILConst, NamedTypeReferenceType, MediumLevelILSetVar, HighLevelILVarInit, \
8+
MediumLevelILIntrinsicSsa, ILException, Type
9+
from .protocols import define_protocol_types, lookup_and_define_guid
10+
from .system_table import propagate_function_param_types
11+
from .utils import non_conflicting_symbol_name, remove_type_prefix_suffix, get_var_name_from_type, get_type
12+
13+
14+
def define_pei_pointers(bv: BinaryView, task: BackgroundTask) -> bool:
15+
"""
16+
EFI_PEI_SERVICES can be retrieved by inline assembly code snippets, resolve EFI_PEI_SERVICES pointers according to
17+
platforms.
18+
"""
19+
if bv.arch.name in ['x86', 'x86_64']:
20+
return define_pei_idt(bv, task)
21+
if bv.arch.name == 'aarch64':
22+
return define_pei_mrs(bv, task)
23+
if bv.arch.name in ['thumb2', 'armv7']:
24+
return define_pei_mrc(bv, task)
25+
log_warn(f"Resolving Assembly PEI Services pointers not supported for {bv.arch.name}.")
26+
return False
27+
28+
29+
def define_pei_mrc(bv: BinaryView, task: BackgroundTask) -> bool:
30+
"""
31+
For ARMv7, the EFI_PEI_SERVICES** is stored in the TPIDRURW read/write Software Thread ID register
32+
defined in the ARMv7-A Architectural Reference Manual.
33+
"""
34+
pei_service_pointer = Type.pointer(bv.arch, Type.pointer(bv.arch, get_type(bv, "EFI_PEI_SERVICES")))
35+
for instr in bv.mlil_instructions:
36+
if task.cancelled:
37+
return False
38+
if isinstance(instr, MediumLevelILIntrinsic):
39+
if not instr.intrinsic.name == 'Coproc_GetOneWord':
40+
continue
41+
if not len(instr.params) == 5:
42+
continue
43+
# the parameter should be 0xf, 0, 0xd, 0, 2
44+
value = [0xf, 0, 0xd, 0, 2]
45+
mrc = True
46+
for idx in range(5):
47+
param = instr.params[idx]
48+
if not isinstance(param, MediumLevelILConst):
49+
continue
50+
if param.constant != value[idx]:
51+
mrc = False
52+
break
53+
if not mrc:
54+
continue
55+
instr.output[0].type = pei_service_pointer
56+
return True
57+
58+
59+
def define_pei_mrs(bv: BinaryView, task):
60+
"""
61+
For AARCH64, the PEI_SERVICES pointer is stored in TPIDREL0 register, mark return type of `_ReadStatusReg` to
62+
`EFI_PEI_SERVICES**`.
63+
"""
64+
pei_service_pointer = Type.pointer(bv.arch, Type.pointer(bv.arch, get_type(bv, "EFI_PEI_SERVICES")))
65+
for instr in bv.mlil_instructions:
66+
if task.cancelled:
67+
return False
68+
if isinstance(instr, MediumLevelILIntrinsic):
69+
if not instr.intrinsic.name == '_ReadStatusReg':
70+
continue
71+
# if it reading tpidr_el0
72+
if not instr.params[0].var.name == 'tpidr_el0':
73+
continue
74+
# set the type
75+
instr.output[0].type = pei_service_pointer
76+
return True
77+
78+
79+
def define_pei_idt(bv: BinaryView, task: BackgroundTask) -> bool:
80+
"""
81+
This function will define EFI_PEI_SERVICES pointers in x86 and x86_64 platform.
82+
According to UEFI PI Specification, for x86 and x86_64, EFI_PEI_SERVICES can be accessed via `IDTR`
83+
"""
84+
intrinsic_target_name = {
85+
# For X86 processors, the EFI_PEI_SERVICES** is stored in the 4 bytes immediately preceding the
86+
# Interrupt Descriptor Table.
87+
'x86': ('__sidt_mems', 'IDTR32'),
88+
# For x64 processors, the EFI_PEI_SERVICES** is stored in eight bytes immediately preceding the
89+
# Interrupt Descriptor Table.
90+
'x86_64': ('__sidt_mems', 'IDTR64'),
91+
}
92+
93+
operand_name, intrinsic_type = intrinsic_target_name[bv.arch.name]
94+
intrinsic_type = get_type(bv, intrinsic_type)
95+
if not intrinsic_type:
96+
log_warn("Cannot find IDTR type, Your version of Binary Ninja may be out of date, please consider manually adding those definition or updating to new version.")
97+
return False
98+
for instr in bv.hlil_instructions:
99+
if task.cancelled:
100+
return False
101+
102+
if isinstance(instr, HighLevelILAssign):
103+
if (
104+
not isinstance(instr.src, HighLevelILIntrinsic)
105+
or not instr.src.operands
106+
or not isinstance(instr.src.operands[0], ILIntrinsic)
107+
or not instr.src.operands[0].name == operand_name
108+
or not instr.dest.vars
109+
):
110+
continue
111+
instr.dest.vars[0].type = intrinsic_type
112+
113+
elif isinstance(instr, HighLevelILVarInit):
114+
mlils = instr.mlils
115+
for mlil in mlils:
116+
if (
117+
isinstance(mlil, MediumLevelILIntrinsicSsa)
118+
and mlil.intrinsic.name == operand_name
119+
and len(mlil.output) > 0
120+
):
121+
mlil.output[0].var.type = intrinsic_type
122+
123+
# TODO there is a type propagation issue (vector35/binaryninja#759) related to indirect struct access in core,
124+
# manually fix it now, the following should be removed after the bug is fixed.
125+
for ref in bv.get_code_refs_for_type("EFI_PEI_SERVICES"):
126+
try:
127+
instr = ref.mlil
128+
except ILException:
129+
log_warn(f"mlil not available at {hex(ref.address)}")
130+
continue
131+
if not isinstance(instr, MediumLevelILSetVar):
132+
continue
133+
if not isinstance(instr.src, MediumLevelILLoadStruct):
134+
continue
135+
# manually propagate the type
136+
instr.dest.type = instr.src.expr_type
137+
138+
return True
139+
140+
141+
def _define_descriptor(bv: BinaryView, task: BackgroundTask, descriptor_type, param) -> Optional[bool]:
142+
"""
143+
Define the descriptor type at `param`'s address. The `param` should be a ConstPtr parameter.
144+
If the analysis got cancelled or encountered an error, return False. If it doesn't meet conditions, return None.
145+
"""
146+
if not isinstance(param, MediumLevelILConstPtr):
147+
return
148+
149+
var_addr = param.constant
150+
151+
var = bv.get_data_var_at(var_addr)
152+
if var:
153+
if descriptor_type == var.type:
154+
# already defined
155+
return
156+
sym = bv.get_symbol_at(var_addr)
157+
158+
if sym is not None:
159+
var_name = sym.name
160+
else:
161+
var_name = None
162+
163+
bv.define_user_data_var(var_addr, descriptor_type, var_name)
164+
bv.update_analysis_and_wait()
165+
166+
var = bv.get_data_var_at(var_addr)
167+
notify_descriptor = var.value
168+
169+
if not isinstance(notify_descriptor, dict):
170+
return
171+
if "Guid" not in notify_descriptor:
172+
return
173+
174+
# define types for guid and notify entrypoint
175+
protocol_name = lookup_and_define_guid(bv, notify_descriptor["Guid"])
176+
if protocol_name is False:
177+
return False
178+
179+
if "Notify" in notify_descriptor:
180+
notify_entrypoint = notify_descriptor['Notify']
181+
func = bv.get_function_at(notify_entrypoint)
182+
if not func:
183+
return
184+
if not protocol_name:
185+
func_name = non_conflicting_symbol_name(bv, "UnknownNotify")
186+
else:
187+
func_name = non_conflicting_symbol_name(bv, f"Notify{get_var_name_from_type(protocol_name)}")
188+
func.type = f"EFI_STATUS {func_name}(EFI_PEI_SERVICES **PeiServices, EFI_PEI_NOTIFY_DESCRIPTOR* NotifyDescriptor, VOID* Ppi)"
189+
bv.update_analysis_and_wait()
190+
return propagate_function_param_types(bv, task, func)
191+
192+
return True
193+
194+
195+
def define_pei_descriptor(bv: BinaryView, task: BackgroundTask) -> bool:
196+
"""
197+
Defines PEI related descriptors, currently this function will only define EFI_PEI_NOTIFY_DESCRIPTOR
198+
and EFI_PEI_PPI_DESCRIPTOR, which may be used by PEI_SERVICES. Protocol related descriptors are not
199+
supported yet.
200+
"""
201+
descriptor_types_names = ["EFI_PEI_NOTIFY_DESCRIPTOR", "EFI_PEI_PPI_DESCRIPTOR"]
202+
descriptor_types = [get_type(bv, des) for des in descriptor_types_names]
203+
for descriptor_type in descriptor_types:
204+
refs = list(bv.get_code_refs_for_type(descriptor_type))
205+
for ref in refs:
206+
if task.cancelled:
207+
return False
208+
try:
209+
instr = ref.mlil
210+
except ILException:
211+
log_warn(f"mlil unavailable for ref: {hex(ref.address)}")
212+
continue
213+
214+
if (
215+
not isinstance(instr, (MediumLevelILCall, MediumLevelILTailcall))
216+
or not isinstance(instr.dest, MediumLevelILLoadStruct)
217+
or not isinstance(instr.dest.expr_type, PointerType)
218+
):
219+
continue
220+
221+
function_type = instr.dest.expr_type.target
222+
params = function_type.parameters
223+
target_param = None
224+
for param in params:
225+
type_name = str(param.type)
226+
if (
227+
not isinstance(param.type, PointerType)
228+
or not isinstance(param.type.target, NamedTypeReferenceType)
229+
):
230+
continue
231+
if remove_type_prefix_suffix(type_name) in descriptor_types_names:
232+
target_param = params.index(param)
233+
break
234+
if target_param is None:
235+
continue
236+
237+
if _define_descriptor(bv, task, descriptor_type, instr.params[target_param]) is False:
238+
return False
239+
return True
240+
241+
242+
def define_locate_ppi_types(bv: BinaryView, task: BackgroundTask) -> bool:
243+
"""
244+
define PPI protocols by resolving invocations of LocatePpi
245+
"""
246+
return define_protocol_types(bv, "EFI_PEI_SERVICES", "LocatePpi", 1, 4, task)

0 commit comments

Comments
 (0)