Skip to content

Commit aa7e839

Browse files
author
Brandon Miller
authored
Merge pull request #2 from zznop/1-te-loader
Loader for Terse Executables
2 parents e19a203 + d1553b8 commit aa7e839

File tree

7 files changed

+765
-231
lines changed

7 files changed

+765
-231
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Helper plugin for analyzing UEFI firmware. This plugin contains the following fe
1111
* Locate known protocol GUIDs and assign the GUID type and a symbol
1212
* Locate global assigments in entry and initialization functions and assign types
1313
* `EFI_SYSTEM_TABLE`, `EFI_RUNTIME_SERVICES`, `EFI_BOOT_SERVICES`, etc...
14+
* Loader for Terse Executables
1415

1516
![demo bn-uefi-helper](screen.gif)
1617

__init__.py

Lines changed: 5 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -1,231 +1,7 @@
1-
"""
2-
Binary Ninja plugin that aids in analysis of UEFI PEI and DXE modules
3-
"""
4-
5-
import os
6-
import csv
7-
import glob
8-
import uuid
9-
from binaryninja import (PluginCommand, BackgroundTaskThread, SegmentFlag, SectionSemantics, BinaryReader, Symbol,
10-
SymbolType, HighLevelILOperation, BinaryView)
11-
from binaryninja.highlevelil import HighLevelILInstruction
12-
from binaryninja.types import (Type, FunctionParameter)
13-
14-
class UEFIHelper(BackgroundTaskThread):
15-
"""Class for analyzing UEFI firmware to automate GUID annotation, segment fixup, type imports, and more
16-
"""
17-
18-
def __init__(self, bv: BinaryView):
19-
BackgroundTaskThread.__init__(self, '', False)
20-
self.bv = bv
21-
self.br = BinaryReader(self.bv)
22-
self.dirname = os.path.dirname(os.path.abspath(__file__))
23-
self.guids = self._load_guids()
24-
25-
def _fix_segments(self):
26-
"""UEFI modules run during boot, without page protections. Everything is RWX despite that the PE is built with
27-
the segments not being writable. It needs to be RWX so calls through global function pointers are displayed
28-
properly.
29-
"""
30-
31-
for seg in self.bv.segments:
32-
# Make segment RWX
33-
self.bv.add_user_segment(seg.start, seg.data_length, seg.data_offset, seg.data_length,
34-
SegmentFlag.SegmentWritable|SegmentFlag.SegmentReadable|SegmentFlag.SegmentExecutable)
35-
36-
# Make section semantics ReadWriteDataSectionSemantics
37-
for section in self.bv.get_sections_at(seg.start):
38-
self.bv.add_user_section(section.name, section.end-section.start, SectionSemantics.ReadWriteDataSectionSemantics)
39-
40-
def _import_types_from_headers(self):
41-
"""Parse EDKII types from header files
42-
"""
43-
44-
hdrs_path = os.path.join(self.dirname, 'headers')
45-
headers = glob.glob(os.path.join(hdrs_path, '*.h'))
46-
for hdr in headers:
47-
_types = self.bv.platform.parse_types_from_source_file(hdr)
48-
for name, _type in _types.types.items():
49-
self.bv.define_user_type(name, _type)
50-
51-
def _set_entry_point_prototype(self):
52-
"""Apply correct prototype to the module entry point
53-
"""
54-
55-
_start = self.bv.get_function_at(self.bv.entry_point)
56-
_start.function_type = "EFI_STATUS ModuleEntryPoint(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)"
57-
58-
def _load_guids(self):
59-
"""Read known GUIDs from CSV and convert string GUIDs to bytes
60-
61-
:return: Dictionary containing GUID bytes and associated names
62-
"""
63-
64-
guids_path = os.path.join(self.dirname, 'guids.csv')
65-
with open(guids_path) as f:
66-
reader = csv.reader(f, skipinitialspace=True)
67-
guids = dict(reader)
68-
69-
# Convert to bytes for faster lookup
70-
guid_bytes = dict()
71-
for guid, name in guids.items():
72-
guid_bytes[name] = uuid.UUID(guid).bytes_le
73-
74-
return guid_bytes
75-
76-
def _apply_guid_name_if_data(self, name: str, address: int):
77-
"""Check if there is a function at the address. If not, then apply the EFI_GUID type and name it
78-
79-
:param name: Name/symbol to apply to the GUID
80-
:param address: Address of the GUID
81-
"""
82-
83-
print(f'Found {name} at 0x{hex(address)} ({uuid.UUID(bytes_le=self.guids[name])})')
84-
85-
# Just to avoid a unlikely false positive and screwing up disassembly
86-
if self.bv.get_functions_at(address) != []:
87-
print(f'There is code at {address}, not applying GUID type and name')
88-
return
89-
90-
self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, address, 'g'+name))
91-
t = self.bv.parse_type_string("EFI_GUID")
92-
self.bv.define_user_data_var(address, t[0])
93-
94-
def _find_known_guids(self):
95-
"""Search for known GUIDs and apply names to matches not within a function
96-
"""
97-
98-
names_list = list(self.guids.keys())
99-
guids_list = list(self.guids.values())
100-
def _check_guid_and_get_name(guid):
101-
try:
102-
return names_list[guids_list.index(guid)]
103-
except ValueError:
104-
return None
105-
106-
for seg in self.bv.segments:
107-
for i in range(seg.start, seg.end):
108-
self.br.seek(i)
109-
data = self.br.read(16)
110-
if not data or len(data) != 16:
111-
continue
112-
113-
found_name = _check_guid_and_get_name(data)
114-
if found_name:
115-
self._apply_guid_name_if_data(found_name, i)
116-
117-
def _set_if_uefi_core_type(self, instr: HighLevelILInstruction):
118-
"""Using HLIL, scrutinize the instruction to determine if it's a move of a local variable to a global variable.
119-
If it is, check if the source operand type is a UEFI core type and apply the type to the destination global
120-
variable.
121-
122-
:param instr: High level IL instruction object
123-
"""
124-
125-
if instr.operation != HighLevelILOperation.HLIL_ASSIGN:
126-
return
127-
128-
if instr.dest.operation != HighLevelILOperation.HLIL_DEREF:
129-
return
130-
131-
if instr.dest.src.operation != HighLevelILOperation.HLIL_CONST_PTR:
132-
return
133-
134-
if instr.src.operation != HighLevelILOperation.HLIL_VAR:
135-
return
136-
137-
_type = instr.src.var.type
138-
if len(_type.tokens) == 1 and str(_type.tokens[0]) == 'EFI_HANDLE':
139-
self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gImageHandle'))
140-
elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_BOOT_SERVICES':
141-
self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gBS'))
142-
elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_RUNTIME_SERVICES':
143-
self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gRS'))
144-
elif len(_type.tokens) > 2 and str(_type.tokens[2]) == 'EFI_SYSTEM_TABLE':
145-
self.bv.define_user_symbol(Symbol(SymbolType.DataSymbol, instr.dest.src.constant, 'gST'))
146-
else:
147-
return
148-
149-
self.bv.define_user_data_var(instr.dest.src.constant, instr.src.var.type)
150-
print(f'Found global assignment - offset:0x{hex(instr.dest.src.constant)} type:{instr.src.var.type}')
151-
152-
def _check_and_prop_types_on_call(self, instr: HighLevelILInstruction):
153-
"""Most UEFI modules don't assign globals in the entry function and instead call a initialization routine and
154-
pass the system table to it where global assignments are made. This function ensures that the types are applied
155-
to the initialization function params so that we can catch global assignments outside of the module entry
156-
157-
:param instr: High level IL instruction object
158-
"""
159-
160-
if instr.operation not in [HighLevelILOperation.HLIL_TAILCALL, HighLevelILOperation.HLIL_CALL]:
161-
return
162-
163-
if instr.dest.operation != HighLevelILOperation.HLIL_CONST_PTR:
164-
return
165-
166-
argv_is_passed = False
167-
for arg in instr.params:
168-
if 'ImageHandle' in str(arg) or 'SystemTable' in str(arg):
169-
argv_is_passed = True
170-
break
171-
172-
if not argv_is_passed:
173-
return
174-
175-
func = self.bv.get_function_at(instr.dest.constant)
176-
old = func.function_type
177-
call_args = instr.params
178-
new_params = []
179-
for arg, param in zip(call_args, old.parameters):
180-
if hasattr(arg, 'var'):
181-
new_type = arg.var.type
182-
else:
183-
new_type = param.type
184-
new_type.confidence = 256
185-
new_params.append(FunctionParameter(new_type, param.name))
186-
187-
# TODO: this is a hack to account for odd behavior. func.function_type should be able to set directly to
188-
# Type.Function(...). However, during testing this isn't the case. I am only able to get it to work if I
189-
# set function_type to a string and update analysis.
190-
gross_hack = str(
191-
Type.function(old.return_value, new_params, old.calling_convention, old.has_variable_arguments, old.stack_adjustment)
192-
).replace('(', '{}('.format(func.name))
193-
func.function_type = gross_hack
194-
self.bv.update_analysis_and_wait()
195-
196-
def _set_global_variables(self):
197-
"""On entry, UEFI modules usually set global variables for EFI_BOOT_SERVICES, EFI_RUNTIME_SERIVCES, and
198-
EFI_SYSTEM_TABLE. This function attempts to identify these assignments and apply types.
199-
"""
200-
201-
func = self.bv.get_function_at(self.bv.entry_point)
202-
for block in func.high_level_il:
203-
for instr in block:
204-
self._check_and_prop_types_on_call(instr)
205-
206-
for func in self.bv.functions:
207-
for block in func.high_level_il:
208-
for instr in block:
209-
self._set_if_uefi_core_type(instr)
210-
211-
def run(self):
212-
"""Run the task in the background
213-
"""
214-
215-
self.progress = "UEFI Helper: Fixing up segments, applying types, and searching for known GUIDs ..."
216-
self._fix_segments()
217-
self._import_types_from_headers()
218-
self._set_entry_point_prototype()
219-
self._find_known_guids()
220-
self.progress = "UEFI Helper: searching for global assignments for UEFI core services ..."
221-
self._set_global_variables()
222-
print('UEFI Helper completed successfully!')
223-
224-
def run_uefi_helper(bv: BinaryView):
225-
"""Run UEFI helper utilities in the background
226-
"""
227-
228-
task = UEFIHelper(bv)
229-
task.start()
1+
from binaryninja import PluginCommand
2+
from .helper import run_uefi_helper
3+
from .teloader import TerseExecutableView
2304

2315
PluginCommand.register('UEFI Helper', 'Run UEFI Helper analysis', run_uefi_helper)
6+
TerseExecutableView.register()
7+

0 commit comments

Comments
 (0)