├── .gitignore
├── LICENSE
├── README.md
├── decompiler
├── decompiler
│ ├── __init__.py
│ ├── bnilvisitor.py
│ ├── condition_visitor.py
│ ├── constraint_visitor.py
│ ├── debug.py
│ ├── if_else_visitor.py
│ ├── linear_mlil.py
│ ├── mlil_ast.py
│ ├── nodes.py
│ └── token_visitor.py
└── tests
│ ├── for_test
│ ├── for_test.c
│ ├── if_test
│ ├── if_test.c
│ ├── switch_test
│ └── switch_test.c
├── emulator
├── emulator
│ ├── __init__.py
│ ├── errors.py
│ ├── executor.py
│ └── state.py
├── emulatorui
│ ├── __init__.py
│ ├── binja_emulator.py
│ ├── buttons.py
│ ├── emulatorui.py
│ ├── hooks.py
│ ├── memory.py
│ ├── registers.py
│ └── stack.py
├── setup.py
└── tests
│ ├── instruction_emulator.py
│ └── test_binja.py
├── ep2-callgraph
├── README.md
└── callgraph.py
├── ep3-vm-arch
├── README.md
├── ram.bin
├── vm1.bndb
└── vm_arch.py
├── ep4-emulator
├── README.md
└── vm_visitor.py
├── function_types
├── test.c
└── test_function_types.py
├── typelib
└── __init__.py
└── unlock
└── unlock
├── __init__.py
├── analysis
├── __init__.py
├── analyze_exception_handler.py
├── analyze_folding.py
├── analyze_indirect_jump.py
├── analyze_return.py
├── analyze_unconditional_jump.py
└── analyze_unwind.py
├── bnilvisitor.py
├── exceptionvisitor.py
├── logging.py
├── state.py
└── unlockvisitor.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
106 | # Other stuff
107 | .DS_Store
108 | .vscode
109 | ignored
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Josh Watson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # f-ing-around-with-binaryninja
2 | This is a repository of code I've written during my twitch stream, ["F'ing Around with Binary Ninja"](https://twitch.tv/syrillian/).
3 |
4 | # Episodes
5 | **[Episode 1](https://www.twitch.tv/videos/356248502):** Callgraph Plugin, part 1
6 | **[Episode 2](https://www.twitch.tv/videos/358093527):** [Callgraph Plugin, part 2](ep2-callgraph/)
7 | **[Episode 3](https://www.twitch.tv/videos/358962183):** [Architecture Plugin!](ep3-vm-arch/)
8 | **[Episode 4](https://www.twitch.tv/videos/366032780):** [Emulator, and Deobfuscation!](ep4-emulator)
9 | **[Episode 5](https://www.twitch.tv/videos/366240933):** [Automated Deobfuscation!](ep5-pelock)
10 | **[Episode 6](https://www.twitch.tv/videos/364544095):** [Automating Deobfuscation!](ep6-pelock-2)
11 | **[Episode 7](https://www.twitch.tv/videos/365523417):** [Actually Automating Things!](ep7-pelock-3)
12 |
--------------------------------------------------------------------------------
/decompiler/decompiler/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = ["linear_mlil"]
2 |
3 | try:
4 | from binaryninjaui import ViewType
5 |
6 | from .linear_mlil import LinearMLILViewType
7 |
8 | # ViewType.registerViewType(MlilLinearViewType())
9 | # Register the view type so that it can be chosen by the user
10 | ViewType.registerViewType(LinearMLILViewType())
11 | except ImportError as e:
12 | print(f"Import Error {e}")
13 |
--------------------------------------------------------------------------------
/decompiler/decompiler/bnilvisitor.py:
--------------------------------------------------------------------------------
1 | from binaryninja import log_debug
2 |
3 |
4 | class BNILVisitor(object):
5 | def __init__(self, **kw):
6 | super(BNILVisitor, self).__init__()
7 |
8 | def visit(self, expression):
9 | method_name = "visit_{}".format(expression.operation.name)
10 | if hasattr(self, method_name):
11 | value = getattr(self, method_name)(expression)
12 | else:
13 | log_debug(f"{repr(expression.operation)}")
14 | value = None
15 | return value
16 |
--------------------------------------------------------------------------------
/decompiler/decompiler/condition_visitor.py:
--------------------------------------------------------------------------------
1 | from z3 import (
2 | UGT,
3 | ULE,
4 | ULT,
5 | UGE,
6 | And,
7 | Array,
8 | BitVec,
9 | BitVecSort,
10 | BitVecVal,
11 | BoolVal,
12 | Extract,
13 | Not,
14 | Or,
15 | Tactic,
16 | ZeroExt
17 | )
18 |
19 | from binaryninja import BinaryView, Variable, VariableSourceType, log_info, log_debug, TypeClass
20 |
21 | from .bnilvisitor import BNILVisitor
22 |
23 |
24 | def make_variable(var: Variable):
25 | if var.name == "":
26 | if var.source_type == VariableSourceType.RegisterVariableSourceType:
27 | var.name = var.function.arch.get_reg_by_index(var.storage)
28 | else:
29 | var.name = f'var_{abs(var.storage):x}'
30 | return BitVec(var.name, var.type.width * 8)
31 |
32 |
33 | class ConditionVisitor(BNILVisitor):
34 | def __init__(self, view: BinaryView):
35 | self.view = view
36 | super().__init__()
37 | addr_size = self.view.address_size
38 | self.mem = {
39 | 1: Array("mem1", BitVecSort(addr_size*8), BitVecSort(8)),
40 | 2: Array('mem2', BitVecSort(addr_size*8), BitVecSort(16)),
41 | 4: Array('mem4', BitVecSort(addr_size*8), BitVecSort(32)),
42 | 8: Array('mem8', BitVecSort(addr_size*8), BitVecSort(64)),
43 | 16: Array('mem16', BitVecSort(addr_size*8), BitVecSort(128))
44 | }
45 |
46 | def simplify(self, condition):
47 | visit_result = self.visit(condition)
48 |
49 | if visit_result.sort().name() != "Bool":
50 | return visit_result
51 |
52 | result = Tactic("ctx-solver-simplify")(visit_result)[0]
53 |
54 | if len(result) == 0:
55 | return BoolVal(True)
56 |
57 | if len(result) < 2:
58 | return result[0]
59 |
60 | return And(*result)
61 |
62 | def visit_MLIL_CMP_E(self, expr):
63 | left = self.visit(expr.left)
64 | right = self.visit(expr.right)
65 | if right.size() != left.size():
66 | right = ZeroExt(left.size() - right.size(), right)
67 | return left == right
68 |
69 | def visit_MLIL_CMP_NE(self, expr):
70 | left = self.visit(expr.left)
71 | right = self.visit(expr.right)
72 | if right.size() != left.size():
73 | right = ZeroExt(left.size() - right.size(), right)
74 | return left != right
75 |
76 | def visit_MLIL_CMP_SLE(self, expr):
77 | left, right = self.visit_both_sides(expr)
78 | if right.size() != left.size():
79 | right = ZeroExt(left.size() - right.size(), right)
80 | return left <= right
81 |
82 | def visit_MLIL_CMP_SLT(self, expr):
83 | left, right = self.visit_both_sides(expr)
84 | if right.size() != left.size():
85 | right = ZeroExt(left.size() - right.size(), right)
86 | return left < right
87 |
88 | def visit_MLIL_CMP_SGT(self, expr):
89 | left, right = self.visit_both_sides(expr)
90 | if right.size() != left.size():
91 | right = ZeroExt(left.size() - right.size(), right)
92 | return left > right
93 |
94 | def visit_MLIL_CMP_SGE(self, expr):
95 | left, right = self.visit_both_sides(expr)
96 | if right.size() != left.size():
97 | right = ZeroExt(left.size() - right.size(), right)
98 | return left >= right
99 |
100 | def visit_MLIL_CMP_UGT(self, expr):
101 | left, right = self.visit_both_sides(expr)
102 | if right.size() != left.size():
103 | right = ZeroExt(left.size() - right.size(), right)
104 | return UGT(left, right)
105 |
106 | def visit_MLIL_CMP_UGE(self, expr):
107 | left, right = self.visit_both_sides(expr)
108 | if right.size() != left.size():
109 | right = ZeroExt(left.size() - right.size(), right)
110 | return UGE(left, right)
111 |
112 | def visit_MLIL_CMP_ULE(self, expr):
113 | left, right = self.visit_both_sides(expr)
114 | if right.size() != left.size():
115 | right = ZeroExt(left.size() - right.size(), right)
116 | return ULE(left, right)
117 |
118 | def visit_MLIL_CMP_ULT(self, expr):
119 | left, right = self.visit_both_sides(expr)
120 | if right.size() != left.size():
121 | right = ZeroExt(left.size() - right.size(), right)
122 | return ULT(left, right)
123 |
124 | def visit_MLIL_LOAD(self, expr):
125 | src = self.visit(expr.src)
126 |
127 | if src is not None:
128 | log_debug(f'{expr.src.size} {src.sort()}')
129 | return self.mem[expr.src.size][src]
130 |
131 | def visit_MLIL_VAR_FIELD(self, expr):
132 | src = make_variable(expr.src)
133 | offset = expr.offset
134 | size = expr.size
135 |
136 | if expr.src.type.type_class == TypeClass.ArrayTypeClass:
137 | element_width = expr.src.type.element_type.width
138 | index = element_width // offset
139 | return BitVec(f'{expr.src.name}[{index}]', size * 8)
140 |
141 | if expr.src.type.type_class == TypeClass.StructureTypeClass:
142 | raise NotImplementedError()
143 |
144 | elif (expr.src.source_type ==
145 | VariableSourceType.RegisterVariableSourceType):
146 | sub_register = next(
147 | (
148 | name
149 | for name, r in expr.src.function.arch.regs.items()
150 | if (r.full_width_reg == expr.src.name and
151 | r.size == size and
152 | r.offset == offset)
153 | ),
154 | None
155 | )
156 | if sub_register is None:
157 | # This means that the variable was probably renamed, and it's
158 | # still just a register access.
159 | sub_register = expr.src.name
160 |
161 | return BitVec(sub_register, size * 8)
162 |
163 | else:
164 | # TODO: change this to var field name instead of extracting
165 | # because we don't actually care about this
166 | return Extract(((offset + size) * 8) - 1, (offset * 8), src)
167 |
168 | def visit_MLIL_VAR(self, expr):
169 | return make_variable(expr.src)
170 |
171 | def visit_MLIL_CONST(self, expr):
172 | if expr.size == 0 and expr.constant in (0, 1):
173 | return BoolVal(True) if expr.constant else BoolVal(False)
174 | return BitVecVal(expr.constant, expr.size * 8)
175 |
176 | def visit_MLIL_NOT(self, expr):
177 | return Not(self.visit(expr.src))
178 |
179 | def visit_MLIL_AND(self, expr):
180 | left, right = self.visit_both_sides(expr)
181 |
182 | if right.size() != left.size():
183 | right = ZeroExt(left.size() - right.size(), right)
184 |
185 | return left & right
186 |
187 | def visit_MLIL_OR(self, expr):
188 | left, right = self.visit_both_sides(expr)
189 |
190 | if right.size() != left.size():
191 | right = ZeroExt(left.size() - right.size(), right)
192 |
193 | return left | right
194 |
195 | def visit_MLIL_ADD(self, expr):
196 | left, right = self.visit_both_sides(expr)
197 | if right.size() != left.size():
198 | right = ZeroExt(left.size() - right.size(), right)
199 | return left + right
200 |
201 | def visit_MLIL_ADDRESS_OF(self, expr):
202 | if expr.src.name:
203 | var_name = expr.src.name
204 | elif (expr.src.source_type ==
205 | VariableSourceType.StackVariableSourceType):
206 | var_name = f'var_{abs(expr.src.storage):x}'
207 | else:
208 | var_name = expr.function.arch.get_reg_by_index(expr.src.storage)
209 |
210 | log_debug(f'var_name: {repr(var_name)}')
211 | return BitVec(
212 | f"&{var_name}",
213 | (expr.size * 8)
214 | if expr.size
215 | else expr.function.source_function.view.address_size * 8,
216 | )
217 |
218 | def visit_MLIL_LSL(self, expr):
219 | left, right = self.visit_both_sides(expr)
220 |
221 | if right.size() != left.size():
222 | right = ZeroExt(left.size() - right.size(), right)
223 |
224 | return left << right
225 |
226 | def visit_both_sides(self, expr):
227 | return self.visit(expr.left), self.visit(expr.right)
228 |
229 | visit_MLIL_CONST_PTR = visit_MLIL_CONST
230 |
--------------------------------------------------------------------------------
/decompiler/decompiler/constraint_visitor.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | Function,
3 | InstructionTextToken,
4 | InstructionTextTokenType,
5 | TypeClass,
6 | VariableSourceType,
7 | log_debug,
8 | log_info,
9 | log_warn
10 | )
11 |
12 | negations = {
13 | "<=": ">",
14 | "==": "!=",
15 | ">": "<=",
16 | ">=": "<",
17 | "<": ">=",
18 | "u<=": "u>",
19 | "u>": "u<=",
20 | "u>=": "u<",
21 | "u<": "u>="
22 | }
23 |
24 | unsigned_ops = {
25 | 'ULE': 'u<=',
26 | 'ULT': 'u<',
27 | 'UGE': 'u>=',
28 | 'UGT': 'u>'
29 | }
30 |
31 |
32 | class ConstraintVisitor:
33 | def __init__(self, function: Function):
34 | self._function = function
35 | self._in_not = []
36 |
37 | def visit(self, expression):
38 | method_name = f"visit_{expression.__class__.__name__}"
39 | if hasattr(self, method_name):
40 | value = getattr(self, method_name)(expression)
41 | else:
42 | log_debug(f"visit_{method_name} missing")
43 | value = None
44 | return value
45 |
46 | def visit_BoolRef(self, expr):
47 | if expr.num_args() == 2:
48 | orig_operation = f'{expr.decl()!s}'
49 | if orig_operation.startswith('U'):
50 | orig_operation = unsigned_ops[orig_operation]
51 |
52 | if self._in_not and self._in_not[-1]:
53 | operation = negations.get(orig_operation)
54 | if operation is None:
55 | operation = orig_operation
56 | self._in_not[-1] = False
57 | else:
58 | operation = orig_operation
59 |
60 | left = self.visit(expr.arg(0))
61 | right = self.visit(expr.arg(1))
62 |
63 | return (
64 | (
65 | [
66 | InstructionTextToken(
67 | InstructionTextTokenType.TextToken,
68 | "("
69 | )
70 | ]
71 | if expr.decl().name() in ("and", "or")
72 | else []
73 | )
74 | + left
75 | + [
76 | InstructionTextToken(
77 | InstructionTextTokenType.TextToken, f" {operation} "
78 | )
79 | ]
80 | + right
81 | + (
82 | [
83 | InstructionTextToken(
84 | InstructionTextTokenType.TextToken,
85 | ")"
86 | )
87 | ]
88 | if expr.decl().name() in ("and", "or")
89 | else []
90 | )
91 | )
92 |
93 | elif expr.num_args() == 1:
94 | if expr.decl().name() == "not":
95 | self._in_not.append(True)
96 |
97 | arg = self.visit(expr.arg(0))
98 | result = (
99 | (
100 | [
101 | InstructionTextToken(
102 | InstructionTextTokenType.TextToken,
103 | "!("
104 | )
105 | ]
106 | if self._in_not and not self._in_not[-1]
107 | else []
108 | )
109 | + arg
110 | + (
111 | [
112 | InstructionTextToken(
113 | InstructionTextTokenType.TextToken,
114 | ")"
115 | )
116 | ]
117 | if self._in_not and not self._in_not[-1]
118 | else []
119 | )
120 | )
121 |
122 | self._in_not.pop() if self._in_not else None
123 | return result
124 |
125 | elif expr.num_args() > 2:
126 | result = [
127 | InstructionTextToken(
128 | InstructionTextTokenType.TextToken,
129 | "("
130 | )
131 | ]
132 | for arg in range(expr.num_args()):
133 | result += self.visit(expr.arg(arg))
134 | if arg < expr.num_args() - 1:
135 | result.append(
136 | InstructionTextToken(
137 | InstructionTextTokenType.TextToken,
138 | f" {expr.decl()!s} "
139 | )
140 | )
141 |
142 | result.append(
143 | InstructionTextToken(
144 | InstructionTextTokenType.TextToken,
145 | ")"
146 | )
147 | )
148 |
149 | return result
150 |
151 | else:
152 | return [
153 | InstructionTextToken(
154 | InstructionTextTokenType.IntegerToken,
155 | f"{expr.decl().name()}",
156 | value=1 if expr.decl().name() == "true" else 0,
157 | )
158 | ]
159 |
160 | def visit_BitVecNumRef(self, expr):
161 | return [
162 | InstructionTextToken(
163 | InstructionTextTokenType.IntegerToken,
164 | str(expr.as_long()),
165 | expr.as_long(),
166 | size=expr.size() // 8,
167 | )
168 | ]
169 |
170 | def visit_BitVecRef(self, expr):
171 | member = None
172 | var = None
173 |
174 | if expr.decl().name() == 'bvadd':
175 | left = self.visit(expr.arg(0))
176 | right = self.visit(expr.arg(1))
177 |
178 | return (
179 | left +
180 | [
181 | InstructionTextToken(
182 | InstructionTextTokenType.TextToken,
183 | ' + '
184 | )
185 | ] +
186 | right
187 | )
188 |
189 | if expr.decl().name() == "extract":
190 | end, start = expr.params()
191 | size = (end - start + 1) // 8
192 | var_name = expr.arg(0).decl().name()
193 |
194 | var = next(
195 | (v for v in self._function.vars if v.name == var_name),
196 | 0
197 | )
198 |
199 | if var == 0:
200 | return self.visit(expr.arg(0))
201 |
202 | type_ = var.type
203 |
204 | if type_.type_class == TypeClass.NamedTypeReferenceClass:
205 | type_ = self._function.view.types[
206 | type_.named_type_reference.name
207 | ]
208 |
209 | if type_.type_class == TypeClass.StructureTypeClass:
210 | member = next(
211 | (m for m in var.structure.members if m.offset == start),
212 | None
213 | )
214 | member_name = member.name
215 |
216 | elif (var.source_type ==
217 | VariableSourceType.RegisterVariableSourceType):
218 | member = next(
219 | (
220 | subregister
221 | for subregister in self._function.arch.regs.values()
222 | if (
223 | subregister.full_width_reg
224 | == self._function.arch.get_reg_name(var.storage)
225 | and subregister.size == size
226 | and subregister.offset == start
227 | )
228 | ),
229 | None,
230 | )
231 |
232 | if member is not None:
233 | member_name = self._function.arch.get_reg_name(
234 | member.index
235 | )
236 |
237 | if member is None:
238 | mask = ((1 << (end+1)) - 1) ^ ((1 << (start)) - 1)
239 |
240 | return [
241 | InstructionTextToken(
242 | InstructionTextTokenType.LocalVariableToken,
243 | var.name,
244 | var.identifier
245 | ),
246 | InstructionTextToken(
247 | InstructionTextTokenType.TextToken,
248 | ' & '
249 | ),
250 | InstructionTextToken(
251 | InstructionTextTokenType.IntegerToken,
252 | hex(mask),
253 | mask
254 | )
255 | ]
256 |
257 | elif expr.decl().name() == 'select':
258 | log_debug(f"{expr.arg(0)}[{expr.arg(1)}]")
259 | return (
260 | [
261 | InstructionTextToken(
262 | InstructionTextTokenType.TextToken,
263 | '*('
264 | )
265 | ] + self.visit(expr.arg(1)) +
266 | [
267 | InstructionTextToken(
268 | InstructionTextTokenType.TextToken,
269 | ')'
270 | )
271 | ]
272 | )
273 |
274 | elif expr.decl().name() == 'concat':
275 | log_debug(f'{expr.num_args()}')
276 |
277 | if expr.num_args() > 2:
278 | raise NotImplementedError(
279 | f"I don't know how to handle this: {expr}"
280 | )
281 |
282 | left, right = expr.arg(0), expr.arg(1)
283 |
284 | max_size = expr.size()
285 |
286 | shift = right.size()
287 |
288 | left_size = left.size()
289 |
290 | end, start = left.params()
291 |
292 | if left_size + shift != max_size:
293 | raise NotImplementedError(
294 | (
295 | f'This should never happen! '
296 | f'{left_size} + {shift} != {max_size}'
297 | )
298 | )
299 |
300 | if start != 0:
301 | left_tokens = self.visit(left)
302 | else:
303 | left_tokens = self.visit(left.arg(0))
304 |
305 | return (
306 | left_tokens +
307 | [
308 | InstructionTextToken(
309 | InstructionTextTokenType.TextToken,
310 | ' << '
311 | ),
312 | InstructionTextToken(
313 | InstructionTextTokenType.IntegerToken,
314 | str(shift),
315 | shift
316 | )
317 | ]
318 | )
319 |
320 | else:
321 | var_name = expr.decl().name()
322 | if var_name[0] == '&':
323 | var_name = var_name[1:]
324 | var = next(
325 | (
326 | v
327 | for v in self._function.vars
328 | if v.name == var_name
329 | ),
330 | None
331 | )
332 |
333 | if var is None:
334 | log_warn(f"var is None: {expr.decl().name()}")
335 |
336 | return [
337 | InstructionTextToken(
338 | InstructionTextTokenType.TextToken,
339 | '' if not expr.decl().name()
340 | else expr.decl().name()
341 | )
342 | ]
343 |
344 | return (
345 | [
346 | InstructionTextToken(
347 | InstructionTextTokenType.TextToken,
348 | '&'
349 | )
350 | ]
351 | if expr.decl().name()[0] == '&'
352 | else []
353 | ) + (
354 | [
355 | InstructionTextToken(
356 | InstructionTextTokenType.LocalVariableToken,
357 | var.name,
358 | var.identifier
359 | )
360 | ]
361 | ) + (
362 | [
363 | InstructionTextToken(InstructionTextTokenType.TextToken, "."),
364 | InstructionTextToken(
365 | InstructionTextTokenType.RegisterToken,
366 | member_name,
367 | var.identifier
368 | ),
369 | ]
370 | if member is not None
371 | else []
372 | )
373 |
--------------------------------------------------------------------------------
/decompiler/decompiler/debug.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 | from typing import Dict, List
3 |
4 | from binaryninja import (BinaryView, BranchType, FlowGraph, FlowGraphNode,
5 | FlowGraphReport, ReportCollection, show_graph_report,
6 | BasicBlockEdge, MediumLevelILBasicBlock, Settings)
7 |
8 | from . import mlil_ast
9 | from .nodes import MediumLevelILAstNode
10 |
11 |
12 | def generate_graph(
13 | view: BinaryView,
14 | region: MediumLevelILAstNode,
15 | collection: ReportCollection = None,
16 | title: str = ''
17 | ):
18 | if not Settings().get_bool('linearmlil.debug'):
19 | return
20 |
21 | graph = FlowGraph()
22 |
23 | def add_children(node: MediumLevelILAstNode) -> FlowGraphNode:
24 | node_node = FlowGraphNode(graph)
25 | graph.append(node_node)
26 |
27 | node_line = node.type
28 |
29 | if node.type == 'block':
30 | node_line += f': {node.block}'
31 | if node.type == 'break':
32 | node_line += f': {node.start}'
33 | elif node.type in ('seq', 'case'):
34 | node_line += f': {node.start}'
35 | for child in node.nodes:
36 | child_node = add_children(child)
37 | node_node.add_outgoing_edge(
38 | BranchType.UnconditionalBranch,
39 | child_node
40 | )
41 | elif node.type == 'cond':
42 | node_line += f': {node.condition}'
43 | child = add_children(node[True])
44 | node_node.add_outgoing_edge(
45 | BranchType.TrueBranch,
46 | child
47 | )
48 | if node[False] is not None:
49 | child = add_children(node[False])
50 | node_node.add_outgoing_edge(
51 | BranchType.FalseBranch,
52 | child
53 | )
54 | elif node.type == 'switch':
55 | for child in node.cases:
56 | child_node = add_children(child)
57 | node_node.add_outgoing_edge(
58 | BranchType.UnconditionalBranch,
59 | child_node
60 | )
61 | elif node.type == 'loop':
62 | node_line += f': {node.loop_type} {node.condition}'
63 | child_node = add_children(node.body)
64 | node_node.add_outgoing_edge(
65 | BranchType.UnconditionalBranch,
66 | child_node
67 | )
68 |
69 | node_node.lines = [node_line]
70 |
71 | return node_node
72 |
73 | # iterate over regions and create nodes for them
74 | # in the AST
75 | add_children(region)
76 |
77 | if collection is not None:
78 | if not title:
79 | title = f' {region.type}: {region.start}'
80 | report = FlowGraphReport(title, graph, view)
81 | collection.append(report)
82 | else:
83 | show_graph_report('Current AST', graph)
84 |
85 |
86 | def graph_slice(
87 | view: BinaryView,
88 | ns: MediumLevelILBasicBlock,
89 | ne: MediumLevelILBasicBlock,
90 | slice: List[List[BasicBlockEdge]],
91 | collection: ReportCollection,
92 | title: str = '',
93 | ):
94 | if not Settings().get_bool('linearmlil.debug'):
95 | return
96 |
97 | graph = FlowGraph()
98 |
99 | ns_node = FlowGraphNode(graph)
100 | ns_node.lines = [f'Start: {ns.start}']
101 |
102 | ne_node = FlowGraphNode(graph)
103 | ne_node.lines = [f'End: {ne.start}']
104 |
105 | nodes = {ns.start: ns_node, ne.start: ne_node}
106 |
107 | graph.append(ns_node)
108 | graph.append(ne_node)
109 |
110 | for path in slice:
111 | for edge in path:
112 | source = edge.source
113 | if source.start in nodes:
114 | source_node = nodes[source.start]
115 | else:
116 | source_node = FlowGraphNode(graph)
117 | source_node.lines = [f'Block: {source.start}']
118 | nodes[source.start] = source_node
119 | graph.append(source_node)
120 |
121 | target = edge.target
122 |
123 | if target.start in nodes:
124 | target_node = nodes[target.start]
125 | else:
126 | target_node = FlowGraphNode(graph)
127 | target_node.lines = [f'Block: {target.start}']
128 | nodes[target.start] = target_node
129 | graph.append(target_node)
130 |
131 | if next(
132 | (
133 | e for e in source_node.outgoing_edges
134 | if e.target == target_node
135 | ),
136 | None
137 | ):
138 | continue
139 |
140 | source_node.add_outgoing_edge(
141 | edge.type,
142 | target_node
143 | )
144 |
145 | if collection is not None:
146 | if not title:
147 | title = f'Slice: {ns}->{ne}'
148 | report = FlowGraphReport(title, graph, view)
149 | collection.append(report)
150 | else:
151 | show_graph_report('Graph Slice', graph)
152 |
--------------------------------------------------------------------------------
/decompiler/decompiler/if_else_visitor.py:
--------------------------------------------------------------------------------
1 | from .bnilvisitor import BNILVisitor
2 |
3 |
4 | class IfVisitor(BNILVisitor):
5 | def __init__(self, original_condition):
6 | self.to_visit = original_condition
7 |
8 | def find_else(self, other):
9 | while self.to_visit is not None:
10 | current_expr, self.to_visit = self.visit(self.to_visit)
11 | if current_expr is not None:
12 | visitor = ElseVisitor(current_expr)
13 |
14 | match = visitor.visit(other)
15 | if match is not None:
16 | return match
17 |
18 | def visit_MLIL_AND(self, expr):
19 | left = self.visit(expr.left)
20 | right = self.visit(expr.right)
21 |
22 | if left is not None:
23 | return left[0], expr.right
24 |
25 | if right is not None:
26 | return right[0], expr.left
27 |
28 | return None, None
29 |
30 | visit_MLIL_OR = visit_MLIL_AND
31 |
32 | def visit_MLIL_NOT(self, expr):
33 | return expr.src.expr_index, None
34 |
35 | def visit_MLIL_CMP_E(self, expr):
36 | return expr.expr_index, None
37 |
38 | visit_MLIL_CMP_NE = visit_MLIL_CMP_E
39 | visit_MLIL_CMP_UGT = visit_MLIL_CMP_E
40 | visit_MLIL_CMP_ULE = visit_MLIL_CMP_E
41 | visit_MLIL_CMP_UGE = visit_MLIL_CMP_E
42 | visit_MLIL_CMP_ULT = visit_MLIL_CMP_E
43 | visit_MLIL_CMP_SGT = visit_MLIL_CMP_E
44 | visit_MLIL_CMP_SLE = visit_MLIL_CMP_E
45 | visit_MLIL_CMP_SGE = visit_MLIL_CMP_E
46 | visit_MLIL_CMP_SLT = visit_MLIL_CMP_E
47 |
48 | def visit_MLIL_CONST(self, expr):
49 | return expr.expr_index, None
50 |
51 |
52 | class ElseVisitor(BNILVisitor):
53 | def __init__(self, expr_to_find):
54 | self.expr_to_find = expr_to_find
55 |
56 | def visit_MLIL_AND(self, expr):
57 | left = self.visit(expr.left)
58 | right = self.visit(expr.right)
59 |
60 | if left is not None:
61 | return left
62 |
63 | if right is not None:
64 | return right
65 |
66 | visit_MLIL_OR = visit_MLIL_AND
67 |
68 | def visit_MLIL_NOT(self, expr):
69 | if expr.src.expr_index == self.expr_to_find:
70 | return expr.expr_index
71 |
72 | def visit_MLIL_CMP_E(self, expr):
73 | if expr.expr_index == self.expr_to_find:
74 | return expr.expr_index
75 |
76 | visit_MLIL_CMP_NE = visit_MLIL_CMP_E
77 | visit_MLIL_CMP_UGT = visit_MLIL_CMP_E
78 | visit_MLIL_CMP_ULE = visit_MLIL_CMP_E
79 | visit_MLIL_CMP_UGE = visit_MLIL_CMP_E
80 | visit_MLIL_CMP_ULT = visit_MLIL_CMP_E
81 | visit_MLIL_CMP_SGT = visit_MLIL_CMP_E
82 | visit_MLIL_CMP_SLE = visit_MLIL_CMP_E
83 | visit_MLIL_CMP_SGE = visit_MLIL_CMP_E
84 | visit_MLIL_CMP_SLT = visit_MLIL_CMP_E
85 |
--------------------------------------------------------------------------------
/decompiler/decompiler/nodes.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from functools import reduce
4 |
5 | from z3 import And, Bool, Tactic, BoolVal
6 |
7 | from binaryninja import (
8 | MediumLevelILBasicBlock,
9 | MediumLevelILInstruction,
10 | MediumLevelILOperation,
11 | log_debug,
12 | log_info
13 | )
14 |
15 | from . import mlil_ast
16 |
17 | _true_condition = BoolVal(True)
18 |
19 |
20 | class MediumLevelILAstNode(object):
21 | def __init__(self, ast: mlil_ast.MediumLevelILAst):
22 | self._type = None
23 | self._ast = ast
24 |
25 | @property
26 | def type(self):
27 | return self._type
28 |
29 | @property
30 | def ast(self):
31 | return self._ast
32 |
33 | @property
34 | def start(self):
35 | return None
36 |
37 | @property
38 | def block(self):
39 | return None
40 |
41 | def __lt__(self, other):
42 | result = (
43 | True
44 | if (
45 | self._ast.reaching_conditions.get(
46 | (self.start, other.start)
47 | ) is not None and
48 | self._ast.reaching_conditions.get(
49 | (other.start, self.start)
50 | ) is None
51 | ) else True
52 | if self.start < other.start
53 | else False
54 | )
55 | log_debug(f'{self} < {other} == {result}')
56 | return result
57 |
58 | def __le__(self, other):
59 | log_debug("__le__")
60 | return (
61 | True
62 | if self._ast.reaching_conditions.get((self.start, other.start)) is not None
63 | else False
64 | )
65 |
66 | def __gt__(self, other):
67 | log_debug("__gt__")
68 | return (
69 | True
70 | if self._ast.reaching_conditions.get((other.start, self.start)) is not None
71 | else False
72 | )
73 |
74 | def __ge__(self, other):
75 | log_debug("__ge__")
76 | return (
77 | True
78 | if self._ast.reaching_conditions.get((other.start, self.start)) is not None
79 | else False
80 | )
81 |
82 | def __eq__(self, other):
83 | log_debug("__eq__")
84 | if not isinstance(other, type(self)):
85 | return False
86 |
87 | return (
88 | True
89 | if self._type == other._type and self.start == other.start
90 | else False
91 | )
92 |
93 | def __ne__(self, other):
94 | return not isinstance(other, type(self)) or self.start != other.start
95 |
96 |
97 | class MediumLevelILAstSeqNode(MediumLevelILAstNode):
98 | def __init__(self, ast: mlil_ast.MediumLevelILAst, nodes: list = None):
99 | super().__init__(ast)
100 | self._type = "seq"
101 | self._nodes: list = nodes if nodes is not None else []
102 |
103 | self.flatten_sequence()
104 |
105 | def flatten_sequence(self):
106 | log_debug("flatten_sequence")
107 |
108 | if not len(self._nodes):
109 | return
110 |
111 | flattened_nodes = []
112 | for node in self.nodes:
113 | if node.type == "seq":
114 | flattened_nodes += node.nodes
115 | else:
116 | flattened_nodes.append(node)
117 | self._nodes = flattened_nodes
118 |
119 | @property
120 | def start(self):
121 | if self._nodes:
122 | return self._nodes[0].start
123 | else:
124 | return 0
125 |
126 | @property
127 | def address(self):
128 | if self._nodes:
129 | return self._nodes[0].address
130 | return None
131 |
132 | @property
133 | def block(self):
134 | if self._nodes:
135 | return self._nodes[0].block
136 | return None
137 |
138 | @property
139 | def nodes(self):
140 | return list(self._nodes)
141 |
142 | def append(self, value):
143 | self._nodes.append(value)
144 |
145 | def pop(self, idx=-1):
146 | self._nodes.pop(idx)
147 |
148 | def __str__(self):
149 | return f""
150 |
151 | def __repr__(self):
152 | return str(self)
153 |
154 | def __eq__(self, other):
155 | if not isinstance(other, type(self)):
156 | return False
157 |
158 | return self.start == other.start
159 |
160 | def __hash__(self):
161 | return hash(self.start)
162 |
163 |
164 | class MediumLevelILAstCaseNode(MediumLevelILAstSeqNode):
165 | def __init__(
166 | self, ast: mlil_ast.MediumLevelILAst, value, nodes: list = None
167 | ):
168 | super().__init__(ast, nodes)
169 | self._value = value
170 | self._type = "case"
171 |
172 | @property
173 | def value(self):
174 | return self._value
175 |
176 | def __lt__(self, other):
177 | log_debug(f'{self} < {other}')
178 | if self._value == ["default"]:
179 | log_debug(f'False')
180 | return False
181 |
182 | if other._value == ['default']:
183 | return True
184 |
185 | log_debug(f'{super().__lt__(other)}')
186 |
187 | return super().__lt__(other)
188 |
189 | def __str__(self):
190 | return f""
191 |
192 |
193 | class MediumLevelILAstCondNode(MediumLevelILAstNode):
194 | def __init__(
195 | self,
196 | ast: mlil_ast.MediumLevelILAst,
197 | condition: Bool,
198 | condition_il: MediumLevelILInstruction,
199 | true: MediumLevelILAstNode,
200 | false: MediumLevelILAstNode = None,
201 | ):
202 | if condition is None:
203 | raise NotImplementedError("condition should not be None")
204 | self._condition = condition
205 | super().__init__(ast)
206 | self._type = "cond"
207 | self._condition_il = condition_il
208 | self._true = true
209 | self._false = false
210 |
211 | self._flatten_conditions()
212 |
213 | def _flatten_conditions(self):
214 | log_debug(f"_flatten_conditions {self.condition} {self._condition_il!r}")
215 | if self[True] is None:
216 | return
217 |
218 | nodes = [
219 | n
220 | for n in self[True].nodes
221 | if n.type != "block"
222 | or n.block[0].operation
223 | not in (
224 | MediumLevelILOperation.MLIL_IF,
225 | MediumLevelILOperation.MLIL_GOTO,
226 | )
227 | ]
228 |
229 | if any(n.type != "cond" for n in nodes):
230 | for node in nodes:
231 | log_debug(f"- {node}")
232 | return
233 |
234 | new_conditions = []
235 |
236 | for node in nodes:
237 | if node[False] is not None:
238 | return
239 | log_debug(f"+ {node}")
240 | node._condition = reduce(
241 | And,
242 | Tactic("ctx-solver-simplify")(
243 | And(self._condition, node._condition)
244 | )[0],
245 | )
246 | log_debug(f"flattened condition: {node} -> {node._condition}")
247 | new_conditions.append(node)
248 |
249 | self.__class__ = MediumLevelILAstSeqNode
250 | self._type = "seq"
251 | self._nodes = sorted(new_conditions)
252 |
253 | @property
254 | def start(self) -> int:
255 | return self._condition_il.instr_index
256 |
257 | @property
258 | def address(self) -> int:
259 | return self._condition_il.address
260 |
261 | @property
262 | def block(self) -> MediumLevelILBasicBlock:
263 | return self[True].block
264 |
265 | @property
266 | def condition(self) -> Bool:
267 | return self._condition
268 |
269 | def __eq__(self, other):
270 | if not isinstance(other, MediumLevelILAstCondNode):
271 | return False
272 |
273 | return self[True] == other[True]
274 |
275 | def __repr__(self):
276 | return (
277 | f" "
278 | f"({self._true} | {self._false})>"
279 | )
280 |
281 | def __hash__(self):
282 | return hash(self.start)
283 |
284 | def __getitem__(self, key):
285 | if key:
286 | return self._true
287 | else:
288 | return self._false
289 |
290 | def __setitem__(self, key, value):
291 | if key:
292 | self._true = value
293 | else:
294 | self._false = value
295 |
296 |
297 | class MediumLevelILAstElseNode(MediumLevelILAstNode):
298 | def __init__(self, ast, address):
299 | super().__init__(ast)
300 | self._type = "else"
301 | self._address = address
302 |
303 | @property
304 | def address(self):
305 | return self._address
306 |
307 |
308 | class MediumLevelILAstBreakNode(MediumLevelILAstNode):
309 | def __init__(self, ast, start, address):
310 | super().__init__(ast)
311 | self._address = address
312 | self._start = start
313 | self._type = "break"
314 |
315 | @property
316 | def address(self):
317 | return self._address
318 |
319 | @property
320 | def start(self):
321 | return self._start
322 |
323 | def __repr__(self):
324 | return f""
325 |
326 | def __iter__(self):
327 | return iter([])
328 |
329 | def __hash__(self):
330 | return hash(self.start)
331 |
332 |
333 | class MediumLevelILAstLoopNode(MediumLevelILAstNode):
334 | def __init__(
335 | self,
336 | ast: mlil_ast.MediumLevelILAst,
337 | body: MediumLevelILAstSeqNode = None,
338 | condition=_true_condition,
339 | loop_type: str = "endless",
340 | ):
341 | super().__init__(ast)
342 | self._body = body
343 | self._condition = condition
344 | self._type = "loop"
345 | self._loop_type = loop_type
346 |
347 | @property
348 | def start(self):
349 | return self._body.start
350 |
351 | @property
352 | def block(self):
353 | return self._body.block
354 |
355 | @property
356 | def body(self):
357 | return self._body
358 |
359 | @property
360 | def address(self):
361 | return self.body.address
362 |
363 | @property
364 | def condition(self):
365 | return self._condition
366 |
367 | @condition.setter
368 | def condition(self, value):
369 | self._condition = value
370 |
371 | @property
372 | def loop_type(self):
373 | return self._loop_type
374 |
375 | @loop_type.setter
376 | def loop_type(self, value: str):
377 | if value in ("endless", "while", "dowhile", "for"):
378 | self._loop_type = value
379 | else:
380 | raise ValueError(
381 | "Type should be 'endless', 'while', 'for', or 'dowhile'."
382 | )
383 |
384 | def __hash__(self):
385 | return hash(self.start)
386 |
387 | def __repr__(self):
388 | return f""
389 |
390 |
391 | class MediumLevelILAstSwitchNode(MediumLevelILAstNode):
392 | def __init__(
393 | self,
394 | ast: mlil_ast.MediumLevelILAst,
395 | switch,
396 | il: MediumLevelILInstruction,
397 | ):
398 | self._switch = switch
399 | self._cases = []
400 | super().__init__(ast)
401 | self._type = "switch"
402 | self._il = il
403 | self._block = il.il_basic_block
404 | self._start = il.instr_index
405 | self._address = il.address
406 |
407 | @property
408 | def block(self):
409 | return self._block
410 |
411 | @property
412 | def cases(self):
413 | return list(self._cases)
414 |
415 | @property
416 | def switch(self):
417 | return self._switch
418 |
419 | @property
420 | def il(self):
421 | return self._il
422 |
423 | @property
424 | def start(self):
425 | return self._start
426 |
427 | @property
428 | def address(self):
429 | return self._address
430 |
431 | def __repr__(self):
432 | return f""
433 |
434 | def __hash__(self):
435 | return hash(self.start)
436 |
437 | def append(self, node):
438 | self._cases.append(node)
439 |
440 | def remove(self, node):
441 | self._cases.remove(node)
442 |
443 |
444 | class MediumLevelILAstBasicBlockNode(MediumLevelILAstNode):
445 | def __init__(
446 | self, ast: mlil_ast.MediumLevelILAst, bb: MediumLevelILBasicBlock
447 | ):
448 | super().__init__(ast)
449 | self._bb = bb
450 | self._type = "block"
451 |
452 | @property
453 | def block(self) -> MediumLevelILBasicBlock:
454 | return self._bb
455 |
456 | @property
457 | def start(self) -> int:
458 | return self._bb.start
459 |
460 | @property
461 | def address(self) -> int:
462 | return self._bb[0].address
463 |
464 | def __lt__(self, other):
465 | result = (
466 | True
467 | if (
468 | self._ast.reaching_conditions.get(
469 | (self.start, other.start)
470 | ) is not None and
471 | self._ast.reaching_conditions.get(
472 | (other.start, self.start)
473 | ) is None
474 | ) else False
475 | if self._ast.reaching_conditions.get(
476 | (self.start, other.start)
477 | ) is None
478 | else True
479 | if self.start < other.start
480 | else False
481 | )
482 | log_debug(f'{self} < {other} == {result}')
483 | return result or (self.start == other.start and other.type == "cond")
484 |
485 | def __gt__(self, other):
486 | result = (
487 | True
488 | if (
489 | self._ast.reaching_conditions.get(
490 | (other.start, self.start)
491 | ) is not None and
492 | self._ast.reaching_conditions.get(
493 | (self.start, other.start)
494 | ) is None
495 | ) else False
496 | if self._ast.reaching_conditions.get(
497 | (other.start, self.start)
498 | ) is None
499 | else True
500 | if self.start > other.start
501 | else False
502 | )
503 | log_debug(f'{self} > {other} == {result}')
504 | return result or (self.start == other.start and self.type == 'cond')
505 |
506 | def __eq__(self, other):
507 | if isinstance(other, MediumLevelILBasicBlock):
508 | return self.block == other
509 |
510 | if not isinstance(other, type(self)):
511 | return False
512 |
513 | return self.block == other.block
514 |
515 | def __hash__(self):
516 | return hash(self.block)
517 |
518 | def __repr__(self):
519 | return f""
520 |
--------------------------------------------------------------------------------
/decompiler/decompiler/token_visitor.py:
--------------------------------------------------------------------------------
1 | from itertools import chain
2 |
3 | from binaryninja import (InstructionTextToken, InstructionTextTokenType,
4 | MediumLevelILOperation, SymbolType, TypeClass,
5 | Variable, log)
6 |
7 | from .bnilvisitor import BNILVisitor
8 |
9 |
10 | class TokenVisitor(BNILVisitor):
11 | def visit(self, expr):
12 | value = super().visit(expr)
13 |
14 | if value is None:
15 | return expr.tokens
16 | else:
17 | return value
18 |
19 | def visit_MLIL_STORE(self, expr):
20 | tokens = ArrayTokenVisitor().visit(expr.dest)
21 |
22 | if not isinstance(tokens, list):
23 | dest_tokens = self.visit(expr.dest)
24 | # Add the '*'
25 | tokens = [
26 | InstructionTextToken(InstructionTextTokenType.TextToken, "*")
27 | ]
28 |
29 | if len(dest_tokens) == 1:
30 | tokens.extend(dest_tokens)
31 | else:
32 | tokens.extend(
33 | [
34 | InstructionTextToken(
35 | InstructionTextTokenType.TextToken, "("
36 | ),
37 | *dest_tokens,
38 | InstructionTextToken(
39 | InstructionTextTokenType.TextToken, ")"
40 | ),
41 | ]
42 | )
43 |
44 | src_tokens = self.visit(expr.src)
45 |
46 | tokens.extend(
47 | [
48 | InstructionTextToken(
49 | InstructionTextTokenType.TextToken, " = "
50 | ),
51 | *src_tokens,
52 | ]
53 | )
54 |
55 | return tokens
56 |
57 | def visit_MLIL_LOAD(self, expr):
58 | src_tokens = ArrayTokenVisitor().visit(expr.src)
59 |
60 | if isinstance(src_tokens, list):
61 | return src_tokens
62 |
63 | src_tokens = self.visit(expr.src)
64 |
65 | tokens = [
66 | InstructionTextToken(InstructionTextTokenType.TextToken, "*")
67 | ]
68 |
69 | if len(src_tokens) == 1:
70 | tokens.extend(src_tokens)
71 | else:
72 | tokens.extend(
73 | [
74 | InstructionTextToken(
75 | InstructionTextTokenType.TextToken, "("
76 | ),
77 | *src_tokens,
78 | InstructionTextToken(
79 | InstructionTextTokenType.TextToken, ")"
80 | ),
81 | ]
82 | )
83 |
84 | return tokens
85 |
86 | def visit_MLIL_SET_VAR(self, expr):
87 | src_tokens = self.visit(expr.src)
88 |
89 | return [
90 | InstructionTextToken(
91 | InstructionTextTokenType.LocalVariableToken,
92 | expr.dest.name,
93 | expr.dest.identifier
94 | ),
95 | InstructionTextToken(
96 | InstructionTextTokenType.TextToken,
97 | ' = '
98 | ),
99 | *src_tokens
100 | ]
101 |
102 | def visit_MLIL_SET_VAR_FIELD(self, expr):
103 | src_tokens = self.visit(expr.src)
104 |
105 | dest = expr.dest
106 | offset = expr.offset
107 | size = expr.size
108 |
109 | if dest.type.width == size and offset == 0:
110 | return [
111 | InstructionTextToken(
112 | InstructionTextTokenType.LocalVariableToken,
113 | expr.dest.name,
114 | expr.dest.identifier
115 | ),
116 | InstructionTextToken(
117 | InstructionTextTokenType.TextToken,
118 | ' = '
119 | ),
120 | *src_tokens
121 | ]
122 |
123 | def visit_MLIL_VAR_FIELD(self, expr):
124 | src = expr.src
125 | offset = expr.offset
126 | size = expr.size
127 |
128 | if src.type.width == size and offset == 0:
129 | return [
130 | InstructionTextToken(
131 | InstructionTextTokenType.LocalVariableToken,
132 | expr.src.name,
133 | expr.src.identifier
134 | )
135 | ]
136 |
137 | def visit_MLIL_CALL(self, expr):
138 | log.log_debug(f'visit_MLIL_CALL: {expr}')
139 | output = [
140 | InstructionTextToken(
141 | InstructionTextTokenType.LocalVariableToken,
142 | v.name,
143 | v.identifier
144 | )
145 | for v in expr.output
146 | ]
147 | dest = self.visit(expr.dest)
148 | params = [self.visit(p) for p in expr.params]
149 |
150 | for p in params[:-1]:
151 | p.append(
152 | InstructionTextToken(
153 | InstructionTextTokenType.TextToken,
154 | ', '
155 | )
156 | )
157 |
158 | log.log_debug(f'output: {output}')
159 | log.log_debug(f'dest: {dest}')
160 | log.log_debug(f'params: {list(chain(*params))}')
161 |
162 | return [
163 | *output,
164 | InstructionTextToken(
165 | InstructionTextTokenType.TextToken,
166 | ' = ' if output else ''
167 | ),
168 | *dest,
169 | InstructionTextToken(
170 | InstructionTextTokenType.TextToken,
171 | '('
172 | ),
173 | *chain(*params),
174 | InstructionTextToken(
175 | InstructionTextTokenType.TextToken,
176 | ')'
177 | )
178 | ]
179 |
180 | def visit_MLIL_MUL(self, expr):
181 | left = self.visit(expr.left)
182 | right = self.visit(expr.right)
183 |
184 | return [
185 | *left,
186 | InstructionTextToken(
187 | InstructionTextTokenType.TextToken,
188 | ' * '
189 | ),
190 | *right
191 | ]
192 |
193 | def visit_MLIL_ZX(self, expr):
194 | return self.visit(expr.src)
195 |
196 | def visit_MLIL_CONST_PTR(self, expr):
197 | log.log_debug(f'MLIL_CONST_PTR: {expr.constant:x}')
198 | view = expr.function.source_function.view
199 | symbol = view.get_symbol_at(expr.constant)
200 | string = view.get_string_at(expr.constant)
201 |
202 | if string is not None:
203 | return [
204 | InstructionTextToken(
205 | InstructionTextTokenType.StringToken,
206 | repr(string.value),
207 | string.start
208 | )
209 | ]
210 |
211 | elif symbol is not None:
212 | NormalSymbols = (SymbolType.FunctionSymbol, SymbolType.DataSymbol)
213 | ImportSymbols = (
214 | SymbolType.ImportedFunctionSymbol,
215 | SymbolType.ImportedDataSymbol
216 | )
217 |
218 | return [
219 | InstructionTextToken(
220 | (
221 | InstructionTextTokenType.CodeSymbolToken
222 | if symbol.type in NormalSymbols
223 | else InstructionTextTokenType.ImportToken
224 | if symbol.type in ImportSymbols
225 | else InstructionTextTokenType.PossibleAddressToken
226 | ),
227 | symbol.short_name,
228 | expr.constant,
229 | size=expr.size,
230 | address=expr.address
231 | )
232 | ]
233 |
234 | visit_MLIL_CONST = visit_MLIL_CONST_PTR
235 | visit_MLIL_IMPORT = visit_MLIL_CONST_PTR
236 |
237 |
238 | class ArrayTokenVisitor(BNILVisitor):
239 | def visit_MLIL_CONST(self, expr):
240 | return expr.constant
241 |
242 | visit_MLIL_CONST_PTR = visit_MLIL_CONST
243 |
244 | def visit_MLIL_VAR(self, expr):
245 | return expr.src
246 |
247 | def visit_MLIL_VAR_FIELD(self, expr):
248 | # TODO this is not going to work potentially
249 | return expr.src
250 |
251 | def visit_MLIL_LSL(self, expr):
252 | return self.visit(expr.left), self.visit(expr.right)
253 |
254 | def visit_MLIL_ADDRESS_OF(self, expr):
255 | return expr.src
256 |
257 | def visit_MLIL_ADD(self, expr):
258 | left = self.visit(expr.left)
259 | right = self.visit(expr.right)
260 |
261 | if (
262 | not isinstance(left, Variable) or
263 | (
264 | left.type.type_class != TypeClass.ArrayTypeClass and
265 | left.type.type_class != TypeClass.PointerTypeClass and
266 | expr.left.operation != MediumLevelILOperation.MLIL_ADDRESS_OF
267 | )
268 | ):
269 | return
270 |
271 | if isinstance(right, int):
272 | element_width = left.type.element_type.width
273 | index = element_width // right
274 | elif isinstance(right, tuple):
275 | index_shift = right[1]
276 | index = right[0]
277 |
278 | return [
279 | InstructionTextToken(
280 | InstructionTextTokenType.LocalVariableToken,
281 | left.name,
282 | left.identifier
283 | ),
284 | InstructionTextToken(
285 | InstructionTextTokenType.TextToken,
286 | '['
287 | ),
288 | InstructionTextToken(
289 | InstructionTextTokenType.LocalVariableToken,
290 | index.name,
291 | index.identifier
292 | ) if isinstance(index, Variable)
293 | else InstructionTextToken(
294 | InstructionTextTokenType.IntegerToken,
295 | str(index),
296 | index
297 | ),
298 | InstructionTextToken(
299 | InstructionTextTokenType.TextToken,
300 | ']'
301 | )
302 | ]
303 |
--------------------------------------------------------------------------------
/decompiler/tests/for_test:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshwatson/f-ing-around-with-binaryninja/2f7e907952a15305541820ac11ff0a86645c631d/decompiler/tests/for_test
--------------------------------------------------------------------------------
/decompiler/tests/for_test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | void simple_for_loop()
6 | {
7 | for (int i = 0; i < 80; i++)
8 | {
9 | printf("*");
10 | fflush(stdout);
11 | sleep(1);
12 | }
13 | }
14 |
15 | void for_with_break()
16 | {
17 | for (int i = 0; i < 35; i++)
18 | {
19 | if (i == 24)
20 | {
21 | printf("i is %d\n", i);
22 | break;
23 | }
24 | }
25 |
26 | printf("After the for loop");
27 | }
28 |
29 | int main(int argc, char** argv)
30 | {
31 | simple_for_loop();
32 | for_with_break();
33 |
34 | return 0;
35 | }
--------------------------------------------------------------------------------
/decompiler/tests/if_test:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshwatson/f-ing-around-with-binaryninja/2f7e907952a15305541820ac11ff0a86645c631d/decompiler/tests/if_test
--------------------------------------------------------------------------------
/decompiler/tests/if_test.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | // TODO: figure out why this isn't rendering correctly
5 | int main(int argc, char** argv)
6 | {
7 | int64_t a, b, c;
8 |
9 | printf("Enter a: ");
10 | scanf("%lld", &a);
11 |
12 | printf("Enter b: ");
13 | scanf("%lld", &b);
14 |
15 | printf("Enter c: ");
16 | scanf("%lld", &c);
17 |
18 | if (a > 0)
19 | {
20 | printf("a > 0\n");
21 | }
22 | else
23 | {
24 | printf("a <= 0\n");
25 | }
26 |
27 | if (b > 0 && c != 2)
28 | {
29 | printf("b > 0 and c != 2\n");
30 | }
31 | else
32 | {
33 | if (b <= 0 && c != 2)
34 | {
35 | printf("b <= 0 and c != 2\n");
36 | }
37 | else
38 | {
39 | printf("c == 2, b is....whatever\n");
40 | }
41 | }
42 |
43 |
44 | return 0;
45 | }
--------------------------------------------------------------------------------
/decompiler/tests/switch_test:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshwatson/f-ing-around-with-binaryninja/2f7e907952a15305541820ac11ff0a86645c631d/decompiler/tests/switch_test
--------------------------------------------------------------------------------
/decompiler/tests/switch_test.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int main(int argc, char** argv)
4 | {
5 | switch(argc)
6 | {
7 | case 0:
8 | printf("No args?!\n");
9 | break;
10 | case 1:
11 | printf("1 arg\n");
12 | case 20:
13 | printf("fallthrough to 20\n");
14 | break;
15 | case 2:
16 | printf("2 args\n");
17 | default:
18 | printf("lots of args");
19 | }
20 |
21 | puts("Outside the switch statement");
22 | return 0;
23 | }
--------------------------------------------------------------------------------
/emulator/emulator/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = [
2 | 'Executor',
3 | 'State',
4 | 'UninitializedRegisterError',
5 | 'InvalidMemoryError',
6 | 'InvalidInstructionError'
7 | ]
8 |
9 | from .executor import Executor
10 | from .state import State
11 | from .errors import UninitializedRegisterError, InvalidMemoryError, InvalidInstructionError
12 |
--------------------------------------------------------------------------------
/emulator/emulator/errors.py:
--------------------------------------------------------------------------------
1 | class UninitializedRegisterError(Exception):
2 | def __init__(self, reg):
3 | self.reg = reg
4 | super().__init__()
5 |
6 |
7 | class UnimplementedOperationError(Exception):
8 | def __init__(self, op):
9 | self.op = op
10 | super().__init__()
11 |
12 |
13 | class InvalidInstructionError(Exception):
14 | def __init__(self, instr):
15 | self.instr = instr
16 | super().__init__()
17 |
18 |
19 | class InvalidMemoryError(Exception):
20 | def __init__(self, addr, size):
21 | self.addr = addr
22 | self.size = size
23 |
--------------------------------------------------------------------------------
/emulator/emulator/executor.py:
--------------------------------------------------------------------------------
1 | import abc
2 |
3 | from .errors import UnimplementedOperationError
4 |
5 | from binaryninja import (ILRegister, LowLevelILInstruction,
6 | LowLevelILOperation, LowLevelILOperationAndSize)
7 |
8 |
9 | class Executor:
10 | @abc.abstractmethod
11 | def read_register(self, reg: str) -> int:
12 | pass
13 |
14 | @abc.abstractmethod
15 | def write_register(self, reg: str, value: int) -> None:
16 | pass
17 |
18 | @abc.abstractmethod
19 | def read_flag(self, flag: str) -> int:
20 | pass
21 |
22 | @abc.abstractmethod
23 | def write_flag(self, flag: str, value: int) -> None:
24 | pass
25 |
26 | @abc.abstractmethod
27 | def read_memory(self, address: int, size: int) -> int:
28 | pass
29 |
30 | @abc.abstractmethod
31 | def write_memory(self, address: int, value: int, size: int) -> None:
32 | pass
33 |
34 | @abc.abstractmethod
35 | def set_next_instr_index(self, il: LowLevelILInstruction, instr_index: int) -> None:
36 | pass
37 |
38 | @abc.abstractmethod
39 | def invoke_call(self, il: LowLevelILInstruction, dest: int) -> None:
40 | pass
41 |
42 | @abc.abstractmethod
43 | def invoke_return(self, target: int) -> None:
44 | pass
45 |
46 | def execute(self, il: LowLevelILInstruction):
47 | stack1 = il.prefix_operands
48 | stack2 = []
49 |
50 | while stack1:
51 | # print(f'stack1: {stack1}')
52 | # print(f'stack2: {stack2}')
53 | op: LowLevelILOperationAndSize = stack1.pop()
54 |
55 | if not isinstance(op, LowLevelILOperationAndSize):
56 | stack2.append(op)
57 | continue
58 |
59 | if op.operation == LowLevelILOperation.LLIL_SET_REG:
60 | dest = stack2.pop()
61 | value, _ = stack2.pop()
62 | self.write_register(dest.name, value)
63 |
64 | elif op.operation == LowLevelILOperation.LLIL_SET_FLAG:
65 | dest = stack2.pop()
66 | value, _ = stack2.pop()
67 | self.write_flag(dest.name, value)
68 |
69 | elif op.operation == LowLevelILOperation.LLIL_FLAG:
70 | flag = stack2.pop()
71 | value = self.read_flag(flag.name)
72 | stack2.append((value, op.size))
73 |
74 | elif op.operation in (
75 | LowLevelILOperation.LLIL_CONST,
76 | LowLevelILOperation.LLIL_CONST_PTR,
77 | ):
78 | # nothing to do here, because the top of the stack
79 | # is an integer
80 | assert isinstance(stack2[-1], int)
81 | constant = stack2.pop()
82 | stack2.append((constant, op.size))
83 |
84 | elif op.operation == LowLevelILOperation.LLIL_REG:
85 | assert isinstance(stack2[-1], ILRegister)
86 | reg = stack2.pop()
87 | value = self.read_register(reg.name)
88 | stack2.append((value, op.size))
89 |
90 | elif op.operation == LowLevelILOperation.LLIL_LOAD:
91 | src, size = stack2.pop()
92 | result = self.read_memory(src, op.size)
93 | stack2.append((result, op.size))
94 |
95 | elif op.operation == LowLevelILOperation.LLIL_STORE:
96 | dest, size = stack2.pop()
97 | src, size = stack2.pop()
98 | self.write_memory(dest, src, op.size)
99 |
100 | elif op.operation == LowLevelILOperation.LLIL_SUB:
101 | left, _ = stack2.pop()
102 | right, _ = stack2.pop()
103 | result = (left - right) & ((1 << (op.size * 8)) - 1)
104 | stack2.append((result, op.size))
105 |
106 | elif op.operation == LowLevelILOperation.LLIL_ADD:
107 | left, _ = stack2.pop()
108 | right, _ = stack2.pop()
109 | result = (left + right) & ((1 << (op.size * 8)) - 1)
110 | stack2.append((result, op.size))
111 |
112 | elif op.operation == LowLevelILOperation.LLIL_PUSH:
113 | value, _ = stack2.pop()
114 | sp = self.read_register(
115 | il.function.source_function.arch.stack_pointer
116 | )
117 | self.write_register(
118 | il.function.source_function.arch.stack_pointer,
119 | sp - op.size
120 | )
121 | self.write_memory(sp - op.size, value, op.size)
122 |
123 | elif op.operation == LowLevelILOperation.LLIL_POP:
124 | sp = self.read_register(
125 | il.function.source_function.arch.stack_pointer
126 | )
127 | result = self.read_memory(sp, op.size)
128 |
129 | stack2.append((result, op.size))
130 |
131 | elif op.operation == LowLevelILOperation.LLIL_CALL:
132 | dest, _ = stack2.pop()
133 | self.invoke_call(il, dest)
134 |
135 | elif op.operation == LowLevelILOperation.LLIL_GOTO:
136 | dest = stack2.pop()
137 | self.set_next_instr_index(il.function, dest)
138 |
139 | elif op.operation == LowLevelILOperation.LLIL_CMP_SGE:
140 | left, _ = stack2.pop()
141 | if left & (1 << ((op.size - 1) * 8)):
142 | left += -(1 << (op.size * 8))
143 | right, _ = stack2.pop()
144 | if right & (1 << ((op.size - 1) * 8)):
145 | right += -(1 << (op.size * 8))
146 | result = left >= right
147 | stack2.append((result, op.size))
148 |
149 | elif op.operation == LowLevelILOperation.LLIL_CMP_E:
150 | left, _ = stack2.pop()
151 | if left & (1 << ((op.size - 1) * 8)):
152 | left += -(1 << (op.size * 8))
153 | right, _ = stack2.pop()
154 | if right & (1 << ((op.size - 1) * 8)):
155 | right += -(1 << (op.size * 8))
156 | result = left == right
157 | stack2.append((result, op.size))
158 |
159 | elif op.operation == LowLevelILOperation.LLIL_CMP_NE:
160 | left, _ = stack2.pop()
161 | if left & (1 << ((op.size - 1) * 8)):
162 | left += -(1 << (op.size * 8))
163 | right, _ = stack2.pop()
164 | if right & (1 << ((op.size - 1) * 8)):
165 | right += -(1 << (op.size * 8))
166 | result = left != right
167 | stack2.append((result, op.size))
168 |
169 | elif op.operation == LowLevelILOperation.LLIL_IF:
170 | condition, _ = stack2.pop()
171 | true = stack2.pop()
172 | false = stack2.pop()
173 | if condition:
174 | self.set_next_instr_index(il.function, true)
175 | else:
176 | self.set_next_instr_index(il.function, false)
177 |
178 | elif op.operation == LowLevelILOperation.LLIL_AND:
179 | left, _ = stack2.pop()
180 | right, _ = stack2.pop()
181 |
182 | result = left & right
183 | stack2.append((result, op.size))
184 |
185 | elif op.operation == LowLevelILOperation.LLIL_OR:
186 | left, _ = stack2.pop()
187 | right, _ = stack2.pop()
188 | result = left | right
189 | stack2.append((result, op.size))
190 |
191 | elif op.operation == LowLevelILOperation.LLIL_SX:
192 | src, size = stack2.pop()
193 |
194 | if src & (1 << (size * 8 - 1)):
195 | src |= ((1 << (op.size * 8)) - 1) ^ ((1 << (size * 8)) - 1)
196 |
197 | stack2.append((src, op.size))
198 |
199 | elif op.operation == LowLevelILOperation.LLIL_ROL:
200 | left, size = stack2.pop()
201 | right, size = stack2.pop()
202 |
203 | result = (left << right) & ((1 << op.size * 8) - 1)
204 | result |= (left & (((1 << op.size * 8) - 1) ^ ((1 << right) - 1))) >> ((op.size * 8) - right)
205 |
206 | stack2.append((result, op.size))
207 |
208 | elif op.operation == LowLevelILOperation.LLIL_RET:
209 | sp = self.read_register(
210 | il.function.source_function.arch.stack_pointer
211 | )
212 | return_address = self.read_memory(
213 | sp, il.function.source_function.arch.address_size
214 | )
215 |
216 | self.invoke_return(return_address)
217 |
218 | else:
219 | raise UnimplementedOperationError(op)
--------------------------------------------------------------------------------
/emulator/emulator/state.py:
--------------------------------------------------------------------------------
1 | from dataclasses import dataclass
2 | from typing import Dict, Tuple
3 |
4 | from binaryninja import BinaryView
5 |
6 |
7 | @dataclass
8 | class State:
9 | view: BinaryView
10 | regs: Dict[str, int]
11 | memory: Dict[Tuple[int, int], bytes]
12 |
--------------------------------------------------------------------------------
/emulator/emulatorui/__init__.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | BinaryView,
3 | LowLevelILFunction,
4 | LowLevelILInstruction,
5 | PluginCommand,
6 | )
7 | from binaryninjaui import DockHandler, LinearView
8 |
9 | from . import hooks
10 | from . import emulatorui
11 | from . import memory
12 | from .memory import EmulatorMemoryModel, rewrite_segments
13 | from .stack import EmulatorStackModel
14 |
15 | emulatorui.addDockWidget()
16 | memory.addDockWidget()
17 |
18 |
19 | def load_emulator(view, il):
20 | emulator = view.session_data.get("emulator")
21 | if emulator is None:
22 | return
23 |
24 | dock_handler = DockHandler.getActiveDockHandler()
25 | if dock_handler is None:
26 | return
27 |
28 | view.session_data["emulator.memory.view"] = rewrite_segments(view)
29 | model = EmulatorMemoryModel(view)
30 | view.session_data["emulator.memory.model"] = model
31 | view.session_data["emulator.memory.widget"].setModel(model)
32 |
33 | model = EmulatorStackModel(view)
34 | view.session_data['emulator.stack.widget'].setModel(model)
35 |
36 | memory_dock_widget = view.session_data['emulator.memory.dockWidget']
37 | memory_dock_widget.linear_view = LinearView(
38 | view.session_data['emulator.memory.view'], None
39 | )
40 | memory_dock_widget.layout.addWidget(memory_dock_widget.linear_view)
41 |
42 | dock_handler.setVisible("BNIL Emulator", True)
43 |
44 |
45 | def add_hook(view: BinaryView, instruction: LowLevelILInstruction) -> None:
46 | emulator = view.session_data.get("emulator")
47 | if emulator is None:
48 | return
49 |
50 | hooks.add_hook(emulator, instruction)
51 |
52 |
53 | def add_function_hook(view: BinaryView, function: LowLevelILFunction) -> None:
54 | emulator = view.session_data.get("emulator")
55 | if emulator is None:
56 | return
57 |
58 | hooks.add_function_hook(emulator, function)
59 |
60 |
61 | def remove_hook(view: BinaryView, instruction: LowLevelILInstruction) -> None:
62 | emulator = view.session_data.get("emulator")
63 | if emulator is None:
64 | return
65 |
66 | hooks.remove_hook(emulator, instruction)
67 |
68 |
69 | def remove_function_hook(
70 | view: BinaryView, function: LowLevelILFunction
71 | ) -> None:
72 | emulator = view.session_data.get("emulator")
73 | if emulator is None:
74 | return
75 |
76 | hooks.remove_function_hook(emulator, function)
77 |
78 |
79 | PluginCommand.register_for_low_level_il_function(
80 | "Emulator\\Load", "Load Emulator", load_emulator
81 | )
82 |
83 | PluginCommand.register_for_low_level_il_instruction(
84 | "Emulator\\Add Hook",
85 | "Add an emulator hook for this LLIL instruction",
86 | add_hook,
87 | )
88 |
89 | PluginCommand.register_for_low_level_il_function(
90 | "Emulator\\Add Function Hook",
91 | "Add an emulator hook for this LLIL function",
92 | add_function_hook,
93 | )
94 |
95 | PluginCommand.register_for_low_level_il_instruction(
96 | "Emulator\\Remove Hook",
97 | "Remove an emulator hook for this LLIL instruction",
98 | remove_hook,
99 | )
100 |
101 | PluginCommand.register_for_low_level_il_function(
102 | "Emulator\\Remove Function Hook",
103 | "Remove an emulator hook for this LLIL function",
104 | remove_function_hook,
105 | )
106 |
107 |
108 | def map_memory(view, start, length, flags):
109 | emulator = view.session_data.get("emulator")
110 | if emulator is None:
111 | return
112 |
113 | emulator.map_memory(start, length, flags)
114 |
--------------------------------------------------------------------------------
/emulator/emulatorui/binja_emulator.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (BinaryView, Endianness, HighlightStandardColor,
2 | ImplicitRegisterExtend, LowLevelILFunction,
3 | LowLevelILInstruction, RegisterInfo, SegmentFlag,
4 | execute_on_main_thread_and_wait)
5 | from binaryninjaui import UIContext
6 | from emulator import (Executor, InvalidInstructionError, InvalidMemoryError,
7 | UninitializedRegisterError)
8 |
9 |
10 | class BinaryNinjaEmulator(Executor):
11 | def __init__(self, view: BinaryView, ui_widget):
12 | self.view = view
13 | self.ui_widget = ui_widget
14 | self.view.session_data["emulator"] = self
15 | self.current_instr_index = None
16 | self.current_highlight = None
17 | self.current_function = None
18 | self.hooks = {}
19 | self.return_stack = []
20 | self.flags = {}
21 | self.breakpoints = set()
22 |
23 | def read_register(self, reg_name: str) -> int:
24 | regs = dict(self.view.session_data.get("emulator.registers", {}))
25 |
26 | if reg_name.startswith('temp'):
27 | register = RegisterInfo(reg_name, self.view.address_size)
28 | else:
29 | register = self.view.arch.regs.get(reg_name)
30 |
31 | if register is None:
32 | raise UninitializedRegisterError(register)
33 |
34 | full_width_reg = register.full_width_reg
35 |
36 | if reg_name == full_width_reg:
37 | return regs.get(reg_name, 0)
38 |
39 | offset = register.offset
40 | size = register.size
41 |
42 | mask = (1 << (offset * 8)) - 1
43 | mask ^= (1 << ((size + offset) * 8)) - 1
44 |
45 | value = regs.get(full_width_reg, 0)
46 |
47 | value &= mask
48 |
49 | value >>= offset * 8
50 |
51 | return value
52 |
53 | def write_register(self, reg_name: str, value: int):
54 | registers = self.view.session_data.get("emulator.registers", [])
55 | if not registers:
56 | self.view.session_data["emulator.registers"] = registers
57 |
58 | regs = {
59 | r[0]: (i, r[1])
60 | for i, r in enumerate(
61 | self.view.session_data.get("emulator.registers", [])
62 | )
63 | }
64 |
65 | if reg_name.startswith('temp'):
66 | register = RegisterInfo(reg_name, self.view.address_size)
67 | else:
68 | register = self.view.arch.regs[reg_name]
69 |
70 | size = register.size
71 | offset = register.offset
72 | extend = register.extend
73 | full_width_reg = register.full_width_reg
74 |
75 | if full_width_reg == reg_name:
76 | if not regs or reg_name.startswith('temp'):
77 | regs[reg_name] = (0, None)
78 | execute_on_main_thread_and_wait(
79 | self.view.session_data["emulator.registers.model"].startUpdate
80 | )
81 | registers[regs[reg_name][0]] = (reg_name, value)
82 | execute_on_main_thread_and_wait(
83 | self.view.session_data["emulator.registers.model"].endUpdate
84 | )
85 |
86 | if reg_name == self.view.arch.stack_pointer:
87 | execute_on_main_thread_and_wait(
88 | lambda: self.view.session_data[
89 | 'emulator.stack.model'
90 | ].update(value)
91 | )
92 | return
93 |
94 | full_width_value = self.read_register(full_width_reg)
95 |
96 | mask = (1 << (offset * 8)) - 1
97 | mask ^= (1 << ((size + offset) * 8)) - 1
98 | shifted_value = value << (offset * 8)
99 | masked_value = shifted_value & mask
100 |
101 | full_width_size = self.view.arch.regs[full_width_reg].size
102 |
103 | full_width_mask = (1 << (full_width_size * 8)) - 1
104 | full_width_mask ^= mask
105 |
106 | if extend == ImplicitRegisterExtend.NoExtend:
107 | full_width_value = masked_value | (
108 | full_width_mask & full_width_value
109 | )
110 |
111 | elif extend == ImplicitRegisterExtend.ZeroExtendToFullWidth:
112 | full_width_value = masked_value | (
113 | full_width_value & ((1 << ((size + offset) * 8)) - 1)
114 | )
115 |
116 | elif extend == ImplicitRegisterExtend.SignExtendToFullWidth:
117 | sign_bit = shifted_value & (1 << ((size + offset - 1) * 8))
118 | full_width_value = masked_value | (
119 | full_width_value & ((1 << ((size + offset) * 8)) - 1)
120 | )
121 | if sign_bit:
122 | full_width_value |= full_width_mask ^ (
123 | (1 << ((size + offset) * 8)) - 1
124 | )
125 |
126 | if not regs:
127 | regs[full_width_reg] = (full_width_reg, full_width_value)
128 |
129 | execute_on_main_thread_and_wait(
130 | self.view.session_data["emulator.registers.model"].startUpdate
131 | )
132 | registers[regs[full_width_reg][0]] = (full_width_reg, full_width_value)
133 | execute_on_main_thread_and_wait(
134 | self.view.session_data["emulator.registers.model"].endUpdate
135 | )
136 |
137 | def read_flag(self, flag_name: str) -> int:
138 | flag = self.flags.get(flag_name, 0)
139 |
140 | return flag
141 |
142 | def write_flag(self, flag_name: str, value: int) -> None:
143 | self.flags[flag_name] = value
144 |
145 | def read_memory(self, address: int, size: int) -> int:
146 | memory = self.view.session_data.get("emulator.memory.view")
147 | if memory is None:
148 | raise KeyError("Memory View not found")
149 |
150 | value = memory.read(address, size)
151 |
152 | if value is None or len(value) < size:
153 | raise InvalidMemoryError(address, size)
154 |
155 | return int.from_bytes(
156 | value,
157 | (
158 | "little"
159 | if self.view.endianness == Endianness.LittleEndian
160 | else "big"
161 | ),
162 | )
163 |
164 | def write_memory(self, address: int, value: int, size: int) -> None:
165 | memory = self.view.session_data.get("emulator.memory.view")
166 | if memory is None:
167 | raise KeyError("Memory View not found")
168 |
169 | value_bytes = value.to_bytes(
170 | size,
171 | (
172 | "little"
173 | if self.view.endianness == Endianness.LittleEndian
174 | else "big"
175 | ),
176 | )
177 |
178 | if memory.write(address, value_bytes) != len(value_bytes):
179 | raise InvalidMemoryError(address, len(value_bytes))
180 |
181 | def map_memory(self, start: int, length: int, flags: SegmentFlag) -> bool:
182 | memory = self.view.session_data.get("emulator.memory.view")
183 | if memory is None:
184 | raise KeyError("Memory View not found")
185 |
186 | data_offset = len(memory.parent_view)
187 | memory.parent_view.write(data_offset, bytes(length))
188 | memory.add_user_segment(start, length, data_offset, length, flags)
189 | return True
190 |
191 | def unmap_memory(self, start: int, length: int) -> None:
192 | memory: BinaryView = self.view.session_data.get("emulator.memory.view")
193 | if memory is None:
194 | raise KeyError("Memory View not found")
195 |
196 | # TODO
197 | # Implement page tables oh god
198 | # Otherwise we're gonna blow up memory every time we map
199 | # something and unmap it
200 | execute_on_main_thread_and_wait(
201 | self.view.session_data["emulator.memory.model"].beginResetModel
202 | )
203 | memory.remove_user_segment(start, length)
204 | execute_on_main_thread_and_wait(
205 | self.view.session_data["emulator.memory.model"].endResetModel
206 | )
207 |
208 | def execute(self, il: LowLevelILInstruction):
209 | function = self.hooks.get(il.function, {})
210 | hook = function.get(il.instr_index)
211 |
212 | if hook is None:
213 | super().execute(il)
214 |
215 | else:
216 | ctx = UIContext.contextForWidget(
217 | self.view.session_data['emulator.memory.widget']
218 | )
219 |
220 | handler = ctx.contentActionHandler()
221 | handler.executeAction(hook)
222 |
223 | if self.current_instr_index == il.instr_index:
224 | self.set_next_instr_index(il.function, il.instr_index + 1)
225 |
226 | def set_next_instr_index(
227 | self, llil: LowLevelILFunction, instr_index: int
228 | ) -> None:
229 | self.current_instr_index = instr_index
230 | self.current_function = llil
231 |
232 | if self.current_highlight is not None:
233 | function, addr = self.current_highlight
234 |
235 | function.set_user_instr_highlight(
236 | addr, HighlightStandardColor.NoHighlightColor
237 | )
238 |
239 | llil.source_function.set_user_instr_highlight(
240 | llil[instr_index].address,
241 | HighlightStandardColor.OrangeHighlightColor,
242 | )
243 | self.current_highlight = (
244 | llil.source_function, llil[instr_index].address
245 | )
246 |
247 | def invoke_call(self, il: LowLevelILInstruction, dest: int) -> None:
248 | # emulate a call:
249 | # 1. get return address
250 | # 2. decrement stack pointer by address_size
251 | # 3. store return address at stack pointer
252 | # 4. set self.current_instr_index to 0
253 |
254 | # Step 1: get return address
255 | self.return_stack.append(self.current_function[il.instr_index + 1])
256 |
257 | # Step 2: decrement the stack pointer by address_size
258 | sp = self.read_register(self.view.arch.stack_pointer)
259 | self.write_register(
260 | self.view.arch.stack_pointer, sp - self.view.arch.address_size
261 | )
262 |
263 | # Step 3: store return address at stack pointer
264 | self.write_memory(
265 | sp - self.view.arch.address_size,
266 | self.return_stack[-1].address,
267 | self.view.arch.address_size
268 | )
269 |
270 | # Step 4: set self.current_instr_index to 0
271 | self.current_instr_index = 0
272 | self.current_function = self.view.get_function_at(dest).llil
273 |
274 | self.set_next_instr_index(self.current_function, 0)
275 |
276 | def invoke_return(self, target: int) -> None:
277 | return_il = self.return_stack.pop() if self.return_stack else None
278 | if return_il is not None and return_il.address == target:
279 | self.set_next_instr_index(
280 | return_il.function, return_il.instr_index
281 | )
282 | else:
283 | # wipe the return stack since it's not correct anymore for some reason
284 | self.return_stack = []
285 | functions = self.view.get_functions_containing(target)
286 | if functions is None or len(functions) == 0:
287 | raise InvalidInstructionError(target)
288 |
289 | function = functions[0]
290 |
291 | llil = function.llil
292 |
293 | instr = function.get_low_level_il_at(target)
294 |
295 | self.set_next_instr_index(llil, instr.instr_index)
296 |
297 | # pop the return address off the stack
298 | sp = self.read_register(self.view.arch.stack_pointer)
299 | self.write_register(
300 | self.view.arch.stack_pointer, sp + self.view.arch.address_size
301 | )
302 |
303 | def add_hook(self, instruction: LowLevelILInstruction, hook: str):
304 | function = self.hooks.get(instruction.function, {})
305 | function[instruction.instr_index] = hook
306 | self.hooks[instruction.function] = function
307 |
308 | def remove_hook(self, instruction: LowLevelILInstruction) -> None:
309 | function = self.hooks.get(instruction.function, {})
310 | if instruction.instr_index in function:
311 | del function[instruction.instr_index]
312 |
313 | self.hooks[instruction.function] = function
314 |
--------------------------------------------------------------------------------
/emulator/emulatorui/buttons.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from binaryninja import (AddressField, BackgroundTaskThread, ChoiceField,
4 | HighlightStandardColor, Settings,
5 | execute_on_main_thread_and_wait, get_form_input, log)
6 | from binaryninjaui import FileContext, LinearView, UIContext, ViewFrame
7 | from emulator.errors import (UnimplementedOperationError,
8 | UninitializedRegisterError)
9 | from PySide2.QtCore import SIGNAL, QObject
10 | from PySide2.QtGui import QFont, QFontMetrics
11 | from PySide2.QtWidgets import QHBoxLayout, QPushButton, QWidget
12 |
13 | from .hooks import add_hook, remove_hook
14 | from .memory import EmulatorMemoryModel, rewrite_segments
15 | from .stack import EmulatorStackModel
16 | from .registers import RegisterEmulatorModel
17 |
18 |
19 | class EmulatorRunTaskThread(BackgroundTaskThread):
20 | def __init__(self, widget, emulator, il):
21 | self.widget = widget
22 | self.emulator = emulator
23 | self.starting_il = il
24 | super().__init__()
25 |
26 | def run(self):
27 | il = self.starting_il
28 | view = self.emulator.view
29 | self.emulator.set_next_instr_index(il.function, il.instr_index)
30 | self.widget.running = True
31 | while self.widget.running:
32 | if (il.function, il.instr_index) in self.emulator.breakpoints:
33 | il.function.source_function.set_user_instr_highlight(
34 | il.address,
35 | HighlightStandardColor.NoHighlightColor
36 | )
37 | view.navigate(view.file.view, il.address)
38 | break
39 |
40 | if self.widget.execute_one_instruction(self.emulator, il):
41 | il = self.emulator.current_function[
42 | self.emulator.current_instr_index
43 | ]
44 | else:
45 | break
46 |
47 | print('Complete')
48 |
49 |
50 | class EmulatorButton(QPushButton):
51 | def __init__(self, view, label, callback):
52 | super().__init__(label)
53 | self.callback = callback
54 | self.view = view
55 |
56 | font_name = Settings().get_string('ui.font.name')
57 | font_size = Settings().get_integer('ui.font.size')
58 | button_font = QFont(font_name, font_size)
59 | fm = QFontMetrics(button_font)
60 | self.setFont(button_font)
61 | self.setFixedWidth(fm.horizontalAdvance(label) + 10)
62 |
63 | QObject.connect(self, SIGNAL('clicked()'), self.callback)
64 |
65 |
66 | class EmulatorButtonsWidget(QWidget):
67 | def __init__(self, parent, view):
68 | super().__init__(parent)
69 | self.view = view
70 | self.view.session_data['emulator.buttons.widget'] = self
71 | self.running = False
72 |
73 | self.reset_button = EmulatorButton(view, '♻️', self.reset)
74 | self.reset_button.setToolTip('Reset emulator')
75 | self.run_button = EmulatorButton(view, '▶️', self.run)
76 | self.run_button.setToolTip('Run emulator')
77 | self.run_to_button = EmulatorButton(view, '⏭', self.run_to)
78 | self.run_to_button.setToolTip('Run to set location')
79 | self.set_stop_button = EmulatorButton(view, '⏹', self.set_stop)
80 | self.set_stop_button.setToolTip('Set stop location on address')
81 | self.pause_button = EmulatorButton(view, '⏸', self.pause)
82 | self.pause_button.setToolTip('Pause emulator')
83 | self.step_button = EmulatorButton(view, '⏯', self.step)
84 | self.step_button.setToolTip('Step one disassembly instruction')
85 | self.map_memory_button = EmulatorButton(view, '🗺', self.map_memory)
86 | self.map_memory_button.setToolTip('Map virtual memory')
87 | self.unmap_memory_button = EmulatorButton(view, '🚮', self.unmap_memory)
88 | self.unmap_memory_button.setToolTip('Unmap virtual memory')
89 | self.view_memory_button = EmulatorButton(view, '📈', self.view_memory)
90 | self.view_memory_button.setToolTip('Open memory view')
91 | self.add_hook_button = EmulatorButton(view, '🎣', self.add_hook)
92 | self.add_hook_button.setToolTip('Add instruction hook')
93 | self.remove_hook_button = EmulatorButton(view, '🐟', self.remove_hook)
94 | self.remove_hook_button.setToolTip('Remove instruction hook')
95 |
96 | self.button_layout = QHBoxLayout(self)
97 | self.button_layout.addWidget(self.reset_button)
98 | self.button_layout.addWidget(self.run_button)
99 | self.button_layout.addWidget(self.pause_button)
100 | self.button_layout.addWidget(self.run_to_button)
101 | self.button_layout.addWidget(self.set_stop_button)
102 | self.button_layout.addWidget(self.step_button)
103 | self.button_layout.addWidget(self.map_memory_button)
104 | self.button_layout.addWidget(self.unmap_memory_button)
105 | self.button_layout.addWidget(self.view_memory_button)
106 | self.button_layout.addWidget(self.add_hook_button)
107 | self.button_layout.addWidget(self.remove_hook_button)
108 |
109 | def get_context(self):
110 | ctx = self.parent().view_frame.actionContext()
111 |
112 | if ctx.lowLevelILFunction is not None:
113 | function = ctx.lowLevelILFunction
114 | if ctx.instrIndex == 0xffffffffffffffff:
115 | il = function[0]
116 | else:
117 | il = function[ctx.instrIndex]
118 | elif ctx.mediumLevelILFunction is not None:
119 | if ctx.instrIndex == 0xffffffffffffffff:
120 | il = ctx.mediumLevelILFunction[0].llil.non_ssa_form
121 | else:
122 | il = ctx.mediumLevelILFunction[
123 | ctx.instrIndex
124 | ].llil.non_ssa_form
125 | elif ctx.function is not None:
126 | function = ctx.function
127 | il = function.get_low_level_il_at(ctx.address)
128 |
129 | return il
130 |
131 | def run(self):
132 | emulator = self.view.session_data['emulator']
133 |
134 | il = self.get_context()
135 |
136 | task = EmulatorRunTaskThread(self, emulator, il)
137 | task.start()
138 |
139 | def pause(self):
140 | self.running = False
141 |
142 | def run_to(self):
143 | pass
144 |
145 | def set_stop(self):
146 | il = self.get_context()
147 |
148 | emulator = self.view.session_data['emulator']
149 |
150 | emulator.breakpoints.add((il.function, il.instr_index))
151 |
152 | il.function.source_function.set_auto_instr_highlight(
153 | il.address,
154 | HighlightStandardColor.RedHighlightColor
155 | )
156 |
157 | def reset(self):
158 | self.running = False
159 | emulator = self.view.session_data['emulator']
160 | if (emulator.current_function is not None and
161 | emulator.current_instr_index is not None):
162 | current_il = emulator.current_function[
163 | emulator.current_instr_index
164 | ]
165 |
166 | emulator.current_function.source_function.set_auto_instr_highlight(
167 | current_il.address,
168 | HighlightStandardColor.NoHighlightColor
169 | )
170 |
171 | self.view.session_data["emulator.memory.view"] = rewrite_segments(
172 | self.view
173 | )
174 | model = EmulatorMemoryModel(self.view)
175 | self.view.session_data["emulator.memory.model"] = model
176 | self.view.session_data["emulator.memory.widget"].setModel(model)
177 |
178 | model = EmulatorStackModel(self.view)
179 | self.view.session_data['emulator.stack.widget'].setModel(model)
180 |
181 | model = RegisterEmulatorModel(self.view)
182 | self.view.session_data['emulator.registers.widget'].setModel(model)
183 | self.view.session_data['emulator.registers.widget'].update()
184 |
185 | def step(self):
186 | ctx = self.parent().view_frame.actionContext()
187 | emulator = self.parent().emulator
188 |
189 | if ctx.lowLevelILFunction is not None:
190 | function = ctx.lowLevelILFunction
191 | if ctx.instrIndex == 0xffffffffffffffff:
192 | il = function[0]
193 | else:
194 | il = function[ctx.instrIndex]
195 | elif ctx.mediumLevelILFunction is not None:
196 | if ctx.instrIndex == 0xffffffffffffffff:
197 | il = ctx.mediumLevelILFunction[0].llil.non_ssa_form
198 | else:
199 | il = ctx.mediumLevelILFunction[
200 | ctx.instrIndex
201 | ].llil.non_ssa_form
202 | elif ctx.function is not None:
203 | function = ctx.function
204 | il = function.get_low_level_il_at(ctx.address)
205 |
206 | emulator.set_next_instr_index(
207 | il.function, il.instr_index
208 | )
209 |
210 | il_start = il.instr_index
211 | exits = il.function.source_function.get_low_level_il_exits_at(
212 | il.address
213 | )
214 | il_exit = max(
215 | exits
216 | ) if exits else il_start
217 |
218 | next_il = il
219 | while (il.function == emulator.current_function and
220 | il_start <= emulator.current_instr_index <= il_exit):
221 | if not self.execute_one_instruction(emulator, next_il):
222 | break
223 | if emulator.current_instr_index < len(emulator.current_function):
224 | next_il = emulator.current_function[
225 | emulator.current_instr_index
226 | ]
227 | else:
228 | emulator.view.navigate(emulator.view.file.view, next_il.address)
229 |
230 | def execute_one_instruction(self, emulator, il):
231 | try:
232 | emulator.execute(il)
233 | except UninitializedRegisterError as e:
234 | print(f'UninitializedRegisterError: {e.reg}')
235 | return False
236 | except UnimplementedOperationError as e:
237 | print(f'UnimplementedOperationError: {e.op!r}')
238 | return False
239 |
240 | return True
241 |
242 | def map_memory(self):
243 | start = AddressField('Start (hex):')
244 | length = AddressField('Length (hex):')
245 | flags = ChoiceField(
246 | 'Flags',
247 | [
248 | '---',
249 | '--x',
250 | '-w-',
251 | '-wx',
252 | 'r--',
253 | 'r-x',
254 | 'rw-',
255 | 'rwx'
256 | ]
257 | )
258 | get_form_input([start, length, flags], 'Map Memory')
259 | self.parent().emulator.map_memory(
260 | start.result,
261 | length.result,
262 | flags.result
263 | )
264 |
265 | def unmap_memory(self):
266 | start = AddressField('Start (hex):')
267 | length = AddressField('Length (hex):')
268 | get_form_input([start, length], 'Unmap Memory')
269 |
270 | self.parent().emulator.unmap_memory(start.result, length.result)
271 |
272 | def view_memory(self):
273 | memory_view = self.parent().view.session_data['emulator.memory.view']
274 |
275 | ctx = UIContext.activeContext()
276 | linear_view = LinearView(memory_view, None)
277 | memory_view.register_notification(linear_view)
278 | ctx.createTabForWidget('Emulator Memory', linear_view)
279 |
280 | def add_hook(self):
281 | emulator = self.parent().view.session_data['emulator']
282 |
283 | ctx = UIContext.activeContext()
284 |
285 | content = ctx.contentActionHandler()
286 | action_context = content.actionContext()
287 |
288 | llil = action_context.lowLevelILFunction
289 | instr_index = action_context.instrIndex
290 |
291 | if None in (llil, instr_index) or instr_index == 0xffffffffffffffff:
292 | log.log_alert('LLIL Function/Instruction not selected!')
293 | return
294 |
295 | add_hook(emulator, llil[instr_index])
296 |
297 | def remove_hook(self):
298 | emulator = self.parent().view.session_data['emulator']
299 |
300 | ctx = UIContext.activeContext()
301 |
302 | content = ctx.contentActionHandler()
303 | action_context = content.actionContext()
304 |
305 | llil = action_context.lowLevelILFunction
306 | instr_index = action_context.instrIndex
307 |
308 | if None in (llil, instr_index) or instr_index == 0xffffffffffffffff:
309 | log.log_alert('LLIL Function/Instruction not selected!')
310 | return
311 |
312 | remove_hook(emulator, llil[instr_index])
313 |
--------------------------------------------------------------------------------
/emulator/emulatorui/emulatorui.py:
--------------------------------------------------------------------------------
1 | from binaryninjaui import DockContextHandler, DockHandler
2 | from PySide2.QtCore import Qt
3 | from PySide2.QtWidgets import QApplication, QGridLayout, QWidget, QLabel
4 |
5 | from .binja_emulator import BinaryNinjaEmulator
6 | from .buttons import EmulatorButtonsWidget
7 | from .memory import EmulatorMemoryView
8 | from .registers import RegisterEmulatorView
9 | from .stack import EmulatorStackView
10 |
11 |
12 | class EmulatorDockWidget(QWidget, DockContextHandler):
13 | def __init__(self, parent, name, view):
14 | try:
15 | QWidget.__init__(self, parent)
16 | DockContextHandler.__init__(self, self, name)
17 |
18 | layout = QGridLayout(self)
19 | self.registers_label = QLabel(None)
20 | self.registers_label.setText('Registers')
21 | self.registers_view = RegisterEmulatorView(None, view)
22 |
23 | self.memory_label = QLabel(None)
24 | self.memory_label.setText('Memory Map')
25 | self.memory_view = EmulatorMemoryView(None, view)
26 |
27 | self.stack_label = QLabel(None)
28 | self.stack_label.setText('Stack View')
29 | self.stack_view = EmulatorStackView(None, view)
30 |
31 | # TODO
32 | # Implement a view that shows the top 0x100 bytes of the stack
33 | # OR....OR...let's make a "local variables" view
34 |
35 | self.button_widget = EmulatorButtonsWidget(self, view)
36 |
37 | layout.addWidget(self.button_widget, 0, 0, Qt.AlignLeft)
38 | layout.addWidget(self.memory_label, 1, 0)
39 | layout.addWidget(self.memory_view, 2, 0)
40 |
41 | layout.addWidget(self.registers_label, 1, 1)
42 | layout.addWidget(self.registers_view, 2, 1)
43 |
44 | layout.addWidget(self.stack_label, 1, 2)
45 | layout.addWidget(self.stack_view, 2, 2)
46 |
47 | self.registers_view.horizontalHeader().setStretchLastSection(True)
48 |
49 | self.view = view
50 | self.view_frame = None
51 | self.emulator = BinaryNinjaEmulator(view, self)
52 |
53 | dock_handler = DockHandler.getActiveDockHandler()
54 | dock_handler.setVisible('BNIL Emulator', False)
55 | except Exception as e:
56 | print(e)
57 |
58 | def notifyViewChanged(self, view_frame):
59 | self.view_frame = view_frame
60 |
61 | @staticmethod
62 | def create_widget(name, parent, data=None):
63 | return EmulatorDockWidget(parent, name, data)
64 |
65 |
66 | def addDockWidget():
67 | if len(QApplication.allWidgets()) == 0:
68 | return
69 |
70 | mw = QApplication.allWidgets()[0].window()
71 | dock_handler = mw.findChild(DockHandler, '__DockHandler')
72 | dock_handler.addDockWidget(
73 | "BNIL Emulator",
74 | EmulatorDockWidget.create_widget,
75 | Qt.TopDockWidgetArea,
76 | Qt.Horizontal,
77 | False
78 | )
79 |
--------------------------------------------------------------------------------
/emulator/emulatorui/hooks.py:
--------------------------------------------------------------------------------
1 | from binaryninjaui import UIContext
2 | from binaryninja import ChoiceField, get_form_input, HighlightStandardColor
3 |
4 |
5 | def add_hook(emulator, instruction):
6 | ctx = UIContext.activeContext()
7 | handler = ctx.globalActions()
8 | hook_options = [
9 | a
10 | for a in handler.getAllValidActions()
11 | if "Snippets\\" in a and "emulator" in a.lower()
12 | ]
13 | snippets = ChoiceField("Snippets:", hook_options)
14 |
15 | get_form_input([snippets], "Add Hook")
16 |
17 | choice = hook_options[snippets.result]
18 |
19 | emulator.add_hook(instruction, choice)
20 |
21 | instruction.function.source_function.set_auto_instr_highlight(
22 | instruction.address, HighlightStandardColor.BlackHighlightColor
23 | )
24 |
25 |
26 | def add_function_hook(emulator, function):
27 | ctx = UIContext.activeContext()
28 | handler = ctx.globalActions()
29 | hook_options = [
30 | a
31 | for a in handler.getAllValidActions()
32 | if "Snippets\\" in a and "emulator" in a.lower()
33 | ]
34 | snippets = ChoiceField("Snippets:", hook_options)
35 |
36 | get_form_input([snippets], "Add Function Hook")
37 |
38 | choice = hook_options[snippets.result]
39 |
40 | # TODO
41 |
42 |
43 | def remove_hook(emulator, instruction):
44 | emulator.remove_hook(instruction)
45 | instruction.function.source_function.set_auto_instr_highlight(
46 | instruction.address, HighlightStandardColor.NoHighlightColor
47 | )
48 |
49 |
50 | def remove_function_hook(emulator, function):
51 | # TODO
52 | pass
53 |
--------------------------------------------------------------------------------
/emulator/emulatorui/memory.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (BackgroundTaskThread, BinaryDataNotification,
2 | BinaryView, BinaryViewType, SegmentFlag, Settings)
3 | from PySide2.QtCore import QAbstractTableModel, Qt
4 | from PySide2.QtGui import QFont
5 | from PySide2.QtWidgets import QHeaderView, QTableView, QWidget, QHBoxLayout, QApplication
6 | from binaryninjaui import DockContextHandler, LinearView, DockHandler
7 |
8 |
9 | class EmulatorMemoryModel(QAbstractTableModel, BinaryDataNotification):
10 | def __init__(self, view: BinaryView):
11 | QAbstractTableModel.__init__(self)
12 | BinaryDataNotification.__init__(self)
13 | self.view = view
14 | self.memory_view = view.session_data.get('emulator.memory.view')
15 |
16 | self.font_name = Settings().get_string('ui.font.name')
17 | self.font_size = Settings().get_integer('ui.font.size')
18 |
19 | if self.memory_view is None:
20 | return
21 |
22 | self.memory_view.register_notification(self)
23 |
24 | if self.view.session_data.get('emulator.memory') is None:
25 | self.view.session_data['emulator.memory'] = [
26 | seg for seg in self.memory_view.segments
27 | ]
28 |
29 | def rowCount(self, parent):
30 | rows = self.view.session_data.get('emulator.memory')
31 | if rows is None:
32 | return 0
33 |
34 | return len(rows)
35 |
36 | def columnCount(self, parent):
37 | return 5
38 |
39 | def data(self, index, role=Qt.DisplayRole):
40 | if role == Qt.CheckStateRole:
41 | return None
42 |
43 | if role == Qt.FontRole:
44 | return QFont(self.font_name, self.font_size)
45 |
46 | memory = self.view.session_data.get('emulator.memory')
47 | if memory is None:
48 | return
49 |
50 | row = memory[index.row()]
51 |
52 | if index.column() == 0:
53 | return hex(row.start)
54 |
55 | elif index.column() == 1:
56 | return hex(row.end)
57 |
58 | elif index.column() == 2:
59 | return hex(row.data_offset)
60 |
61 | elif index.column() == 3:
62 | return hex(row.data_length)
63 |
64 | elif index.column() == 4:
65 | return (
66 | f'{"r" if row.readable else "-"}'
67 | f'{"w" if row.writable else "-"}'
68 | f'{"x" if row.executable else "-"}'
69 | )
70 |
71 | def headerData(self, section, orientation, role=Qt.DisplayRole):
72 | if orientation == Qt.Orientation.Vertical:
73 | return None
74 |
75 | if role != Qt.DisplayRole:
76 | return None
77 |
78 | return ['Start', 'End', 'Data Offset', 'Data Length', 'Flags'][
79 | section
80 | ]
81 |
82 | def data_inserted(self, view, offset, length):
83 | self.beginResetModel()
84 | self.view.session_data['emulator.memory'] = [
85 | seg for seg in self.memory_view.segments
86 | ]
87 | self.endResetModel()
88 | return super().data_inserted(view, offset, length)
89 |
90 | def data_removed(self, view, offset, length):
91 | self.beginResetModel()
92 | self.view.session_data['emulator.memory'] = [
93 | seg for seg in self.memory_view.segments
94 | ]
95 | self.endResetModel()
96 | return super().data_removed(view, offset, length)
97 |
98 | def data_written(self, view, offset, length):
99 | self.beginResetModel()
100 | self.view.session_data['emulator.memory'] = [
101 | seg for seg in self.memory_view.segments
102 | ]
103 | self.endResetModel()
104 | return super().data_written(view, offset, length)
105 |
106 |
107 | class EmulatorMemoryView(QTableView):
108 | def __init__(self, parent, view):
109 | super().__init__(parent)
110 | self.parent = parent
111 | self.view = view
112 | self.view.session_data['emulator.memory.widget'] = self
113 | self.verticalHeader().setVisible(False)
114 | self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
115 |
116 |
117 | def rewrite_segments(view: BinaryView):
118 | class EmulatorBackgroundTask(BackgroundTaskThread):
119 | def __init__(self, view):
120 | self.view = view
121 | super().__init__()
122 |
123 | def run(self):
124 | self.view.update_analysis_and_wait()
125 |
126 | new_raw_view = BinaryView()
127 | current_addr = 0
128 | for segment in view.segments:
129 | segment_data = view.read(segment.start, segment.data_length)
130 | segment_data += b'\x00'*(len(segment) - segment.data_length)
131 | new_raw_view.write(current_addr, segment_data)
132 | current_addr += len(segment_data)
133 |
134 | new_view = BinaryViewType['Mapped'].create(new_raw_view)
135 | new_view.remove_auto_segment(0, len(new_raw_view))
136 | t = EmulatorBackgroundTask(new_view)
137 | t.start()
138 | t.join()
139 |
140 | current_addr = 0
141 | for segment in view.segments:
142 | new_view.add_user_segment(
143 | segment.start,
144 | len(segment),
145 | current_addr,
146 | len(segment),
147 | (
148 | (SegmentFlag.SegmentReadable if segment.readable else 0) |
149 | (SegmentFlag.SegmentWritable if segment.writable else 0) |
150 | (SegmentFlag.SegmentExecutable if segment.executable else 0)
151 | )
152 | )
153 |
154 | current_addr += len(segment)
155 |
156 | return new_view
157 |
158 |
159 | class EmulatorMemoryDockWidget(QWidget, DockContextHandler):
160 | def __init__(self, parent, name, view):
161 | try:
162 | QWidget.__init__(self, parent)
163 | DockContextHandler.__init__(self, self, name)
164 |
165 | view.session_data['emulator.memory.dockWidget'] = self
166 |
167 | self.view = view
168 |
169 | self.layout = QHBoxLayout(self)
170 |
171 | dock_handler = DockHandler.getActiveDockHandler()
172 | dock_handler.setVisible('Emulator Memory View', False)
173 |
174 | except Exception as e:
175 | print(e)
176 |
177 | def notifyViewChanged(self, view_frame):
178 | self.view_frame = view_frame
179 |
180 | @staticmethod
181 | def create_widget(name, parent, data=None):
182 | return EmulatorMemoryDockWidget(parent, name, data)
183 |
184 |
185 | def addDockWidget():
186 | if len(QApplication.allWidgets()) == 0:
187 | return
188 |
189 | mw = QApplication.allWidgets()[0].window()
190 | dock_handler = mw.findChild(DockHandler, '__DockHandler')
191 | dock_handler.addDockWidget(
192 | "Emulator Memory View",
193 | EmulatorMemoryDockWidget.create_widget,
194 | Qt.RightDockWidgetArea,
195 | Qt.Horizontal,
196 | False
197 | )
198 |
--------------------------------------------------------------------------------
/emulator/emulatorui/registers.py:
--------------------------------------------------------------------------------
1 | from PySide2.QtWidgets import QTableView
2 | from PySide2.QtCore import QAbstractTableModel, Qt
3 | from PySide2.QtGui import QFont
4 | from binaryninja import Settings
5 |
6 |
7 | # TODO
8 | # Handle temp registers
9 | class RegisterEmulatorModel(QAbstractTableModel):
10 | def __init__(self, view):
11 | super().__init__()
12 | self.view = view
13 |
14 | if view.arch is None:
15 | view.session_data['emulator.registers'] = []
16 |
17 | view.session_data['emulator.registers'] = [
18 | (r, 0) for r in view.arch.full_width_regs
19 | ]
20 |
21 | view.session_data['emulator.registers.model'] = self
22 |
23 | self.font_name = Settings().get_string('ui.font.name')
24 | self.font_size = Settings().get_integer('ui.font.size')
25 |
26 | def rowCount(self, parent):
27 | if self.view.arch is None:
28 | return 0
29 | return len(self.view.arch.full_width_regs)
30 |
31 | def columnCount(self, parent):
32 | return 2
33 |
34 | def data(self, index, role=Qt.DisplayRole):
35 | if role == Qt.CheckStateRole:
36 | return None
37 |
38 | if role == Qt.FontRole:
39 | return QFont(self.font_name, self.font_size)
40 |
41 | regs = self.view.session_data['emulator.registers']
42 | if len(regs) == 0 and index.row() == 0:
43 | return None
44 |
45 | if regs[index.row()][index.column()] is None:
46 | return None
47 |
48 | elif index.column() == 0:
49 | return regs[index.row()][0]
50 | else:
51 | return hex(regs[index.row()][1])
52 |
53 | def headerData(self, section, orientation, role=Qt.DisplayRole):
54 | if orientation != Qt.Orientation.Horizontal:
55 | return None
56 |
57 | if role != Qt.DisplayRole:
58 | return None
59 |
60 | if section == 0:
61 | return 'Register'
62 | elif section == 1:
63 | return 'Value'
64 | else:
65 | return None
66 |
67 | def setData(self, index, value, role=Qt.EditRole):
68 | if self.view.arch is None:
69 | return False
70 |
71 | emulator = self.view.session_data['emulator']
72 | regs = self.view.session_data['emulator.registers']
73 |
74 | if value.startswith('0x'):
75 | try:
76 | value = int(value, 16)
77 | except ValueError:
78 | return False
79 | elif value.isnumeric():
80 | value = int(value)
81 | else:
82 | return False
83 |
84 | emulator.write_register(regs[index.row()][0], value)
85 | return True
86 |
87 | def flags(self, index):
88 | if index.column() == 1:
89 | return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
90 | elif index.row() >= len(self.view.session_data['emulator.registers']):
91 | return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
92 | else:
93 | return Qt.NoItemFlags
94 |
95 | def startUpdate(self):
96 | self.beginResetModel()
97 |
98 | def endUpdate(self):
99 | self.endResetModel()
100 |
101 |
102 | class RegisterEmulatorView(QTableView):
103 | def __init__(self, parent, view):
104 | super().__init__(parent)
105 | self.parent = parent
106 | self.view = view
107 | self.setModel(RegisterEmulatorModel(view))
108 | self.horizontalHeader().show()
109 | self.view.session_data['emulator.registers.widget'] = self
110 |
--------------------------------------------------------------------------------
/emulator/emulatorui/stack.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (BinaryDataNotification, BinaryReader,
2 | BinaryView, BinaryViewType, Settings)
3 | from PySide2.QtCore import QAbstractTableModel, Qt
4 | from PySide2.QtGui import QFont
5 | from PySide2.QtWidgets import QHeaderView, QTableView
6 |
7 |
8 | class EmulatorStackModel(QAbstractTableModel, BinaryDataNotification):
9 | def __init__(self, view: BinaryView):
10 | QAbstractTableModel.__init__(self)
11 | BinaryDataNotification.__init__(self)
12 | try:
13 | self.view = view
14 | self.view.session_data['emulator.stack.model'] = self
15 |
16 | self.memory_view = view.session_data.get('emulator.memory.view')
17 |
18 | self.font_name = Settings().get_string('ui.font.name')
19 | self.font_size = Settings().get_integer('ui.font.size')
20 |
21 | if self.memory_view is None:
22 | return
23 |
24 | self.memory_view.register_notification(self)
25 |
26 | self.br = BinaryReader(self.memory_view, self.view.endianness)
27 |
28 | if self.view.address_size == 1:
29 | self.br.read_ptr = self.br.read8
30 | elif self.view.address_size == 2:
31 | self.br.read_ptr = self.br.read16
32 | elif self.view.address_size == 4:
33 | self.br.read_ptr = self.br.read32
34 | elif self.view.address_size == 8:
35 | self.br.read_ptr = self.br.read64
36 | except Exception as e:
37 | print(e.msg)
38 |
39 | self.stack = []
40 |
41 | def rowCount(self, parent):
42 | return 0x100 / self.view.address_size
43 |
44 | def columnCount(self, parent):
45 | return 2
46 |
47 | def data(self, index, role=Qt.DisplayRole):
48 | if role == Qt.CheckStateRole:
49 | return None
50 |
51 | if role == Qt.FontRole:
52 | return QFont(self.font_name, self.font_size)
53 |
54 | size = len(self.stack)
55 |
56 | if 0 == size or size < index.row():
57 | return
58 |
59 | return hex(self.stack[index.row()][index.column()])
60 |
61 | def headerData(self, section, orientation, role=Qt.DisplayRole):
62 | if orientation == Qt.Orientation.Vertical:
63 | return None
64 |
65 | if role != Qt.DisplayRole:
66 | return None
67 |
68 | return ['Address', 'Value'][
69 | section
70 | ]
71 |
72 | def setData(self, index, value, role=Qt.EditRole):
73 | if self.view.arch is None:
74 | return False
75 |
76 | emulator = self.view.session_data['emulator']
77 |
78 | if value.startswith('0x'):
79 | try:
80 | value = int(value, 16)
81 | except ValueError:
82 | return False
83 | elif value.isnumeric():
84 | value = int(value)
85 | else:
86 | return False
87 |
88 | offset = self.stack[index.row()][0]
89 |
90 | emulator.write_memory(offset, value, self.view.address_size)
91 |
92 | return True
93 |
94 | def flags(self, index):
95 | if index.column() == 1:
96 | return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable
97 | else:
98 | return Qt.NoItemFlags
99 |
100 | def data_written(self, view: BinaryView, offset: int, length: int) -> None:
101 | sp = self.view.arch.stack_pointer
102 |
103 | emulator = self.view.session_data['emulator']
104 |
105 | try:
106 | stack_pointer = emulator.read_register(sp)
107 | except Exception as e:
108 | print(e)
109 | self.stack = []
110 | return
111 |
112 | if offset > stack_pointer + 0x100 or offset < stack_pointer:
113 | return
114 |
115 | self.update(stack_pointer)
116 |
117 | def update(self, stack_pointer):
118 | self.beginResetModel()
119 | self.br.seek(stack_pointer)
120 |
121 | self.stack = []
122 | for i in range(0, 0x100, self.view.address_size):
123 | self.stack.append((self.br.offset, self.br.read_ptr()))
124 | self.endResetModel()
125 |
126 |
127 | class EmulatorStackView(QTableView):
128 | def __init__(self, parent, view):
129 | super().__init__(parent)
130 | self.parent = parent
131 | self.view = view
132 | self.view.session_data['emulator.stack.widget'] = self
133 | self.verticalHeader().setVisible(False)
134 | self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
135 |
--------------------------------------------------------------------------------
/emulator/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 | setuptools.setup(
4 | name='emulator',
5 | version='0.0.1',
6 | author='syrillian',
7 | )
8 |
--------------------------------------------------------------------------------
/emulator/tests/instruction_emulator.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Tuple, Union
2 |
3 | from binaryninja import (Architecture, BinaryView,
4 | ImplicitRegisterExtend)
5 | from emulator import Executor, State
6 | from emulator.errors import UninitializedRegisterError
7 |
8 |
9 | class InstructionEmulator(Executor):
10 | def __init__(
11 | self,
12 | view: BinaryView,
13 | regs: Dict[str, int] = None,
14 | memory: Union[Dict[Tuple[int, int], bytes], None] = None
15 | ):
16 | if regs is None:
17 | regs = {}
18 | if memory is None:
19 | memory = {}
20 | self._state = State(view, regs, memory)
21 |
22 | def read_register(self, reg_name: str) -> int:
23 | register = self._state.view.arch.regs[reg_name]
24 |
25 | if reg_name not in self._state.regs:
26 | raise UninitializedRegisterError(register)
27 |
28 | full_width_reg = register.full_width_reg
29 |
30 | if reg_name == full_width_reg:
31 | return self._state.regs[reg_name]
32 |
33 | offset = register.offset
34 | size = register.size
35 |
36 | mask = (1 << (offset * 8)) - 1
37 | mask ^= (1 << ((size + offset) * 8)) - 1
38 |
39 | value = self._state.regs[full_width_reg]
40 |
41 | value &= mask
42 |
43 | value >>= offset * 8
44 |
45 | return value
46 |
47 | def write_register(self, reg_name: str, value: int):
48 | register = self._state.view.arch.regs[reg_name]
49 |
50 | size = register.size
51 | offset = register.offset
52 | extend = register.extend
53 | full_width_reg = register.full_width_reg
54 |
55 | if full_width_reg == reg_name:
56 | self._state.regs[reg_name] = value
57 | return
58 |
59 | full_width_value = self.read_register(full_width_reg)
60 |
61 | mask = (1 << (offset * 8)) - 1
62 | mask ^= (1 << ((size + offset) * 8)) - 1
63 | shifted_value = value << (offset * 8)
64 | masked_value = shifted_value & mask
65 |
66 | full_width_size = self._state.view.arch.regs[full_width_reg].size
67 |
68 | full_width_mask = (1 << (full_width_size * 8)) - 1
69 | full_width_mask ^= mask
70 |
71 | if extend == ImplicitRegisterExtend.NoExtend:
72 | full_width_value = (
73 | masked_value | (full_width_mask & full_width_value)
74 | )
75 |
76 | elif extend == ImplicitRegisterExtend.ZeroExtend:
77 | full_width_value = (
78 | masked_value | (
79 | full_width_value & ((1 << ((size + offset) * 8)) - 1)
80 | )
81 | )
82 |
83 | elif extend == ImplicitRegisterExtend.SignExtend:
84 | sign_bit = shifted_value & (1 << ((size + offset - 1) * 8))
85 | full_width_value = (
86 | masked_value | (
87 | full_width_value & ((1 << ((size + offset) * 8)) - 1)
88 | )
89 | )
90 | if sign_bit:
91 | full_width_value |= full_width_mask ^ ((1 << ((size + offset) * 8)) - 1)
92 |
93 | self._state.regs[full_width_reg] = full_width_value
94 |
95 |
96 | if __name__ == '__main__':
97 | bv = BinaryView()
98 |
99 | # bv.write(0, b'\x89\xd8\x90\x90\x90')
100 | # bv.write(0, b'\xb8\x01\x00\x00\x00')
101 | bv.write(0, b'\x01 \xa0\xe3')
102 |
103 | # bv.platform = Architecture['x86'].standalone_platform
104 | bv.platform = Architecture['armv7'].standalone_platform
105 |
106 | bv.create_user_function(0)
107 |
108 | bv.update_analysis_and_wait()
109 |
110 | function = bv.get_function_at(0)
111 |
112 | emu = InstructionEmulator(bv, {'r2': 1337})
113 |
114 | print(emu._state.regs)
115 |
116 | emu.execute(function.llil[0])
117 |
118 | print(emu._state.regs)
119 |
--------------------------------------------------------------------------------
/emulator/tests/test_binja.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (BinaryView, LowLevelILFunction, PluginCommand,
2 | SegmentFlag)
3 | from emulator import Executor
4 |
5 |
6 | def setup_stack(view: BinaryView, function: LowLevelILFunction) -> None:
7 | emulator = view.session_data['emulator']
8 | memory_view = view.session_data['emulator.memory.view']
9 |
10 | map_start = 0x1000
11 | map_len = 0x10000
12 |
13 | while True:
14 | while memory_view.get_segment_at(map_start) is not None:
15 | map_start += 0x1000
16 |
17 | if any(
18 | s.start > map_start and
19 | s.start < map_start + map_len
20 | for s in memory_view.segments
21 | ):
22 | map_start += 0x1000
23 | continue
24 |
25 | emulator.map_memory(
26 | map_start,
27 | map_len,
28 | SegmentFlag.SegmentReadable | SegmentFlag.SegmentWritable
29 | )
30 | break
31 |
32 | sp = map_start + map_len - view.address_size
33 | emulator.write_register(view.arch.stack_pointer, sp)
34 |
35 |
36 | PluginCommand.register_for_low_level_il_function(
37 | 'Emulator\\Setup stack',
38 | 'Setup Emulator Stack',
39 | setup_stack,
40 | lambda v, f: v.session_data.get('emulator') is not None
41 | )
42 |
--------------------------------------------------------------------------------
/ep2-callgraph/README.md:
--------------------------------------------------------------------------------
1 | # Episode 2: Callgraph Plugin, part 2
2 |
3 | In this [episode](https://www.twitch.tv/videos/358093527), I refactor the callgraph plugin from the first episode to be more robust, and a lot more colorful!
4 |
5 | The callgraph plugin from this episode can be found [here](callgraph.py).
--------------------------------------------------------------------------------
/ep2-callgraph/callgraph.py:
--------------------------------------------------------------------------------
1 | from binaryninja import *
2 | import ctypes
3 |
4 | class CallgraphTask(BackgroundTaskThread):
5 | def __init__(self, view):
6 | super(CallgraphTask, self).__init__('Generating callgraph...')
7 | self.view = view
8 |
9 | def run(self):
10 | collect_calls(self.view)
11 |
12 | def get_or_set_call_node(callgraph, function_nodes, function):
13 | # create a new node if one doesn't exist already
14 | if function not in function_nodes:
15 | node = FlowGraphNode(callgraph)
16 |
17 | function_nodes[function] = node
18 |
19 | if function.symbol.type == SymbolType.ImportedFunctionSymbol:
20 | token_type = InstructionTextTokenType.ImportToken
21 | else:
22 | token_type = InstructionTextTokenType.CodeSymbolToken
23 |
24 | # Set the node's text to be the name of the function
25 | node.lines = [
26 | DisassemblyTextLine(
27 | [
28 | InstructionTextToken(
29 | token_type,
30 | function.name,
31 | function.start
32 | )
33 | ]
34 | )
35 | ]
36 |
37 | callgraph.append(node)
38 | else:
39 | node = function_nodes[function]
40 |
41 | return node
42 |
43 | def collect_calls(view):
44 | log_info("collect_calls")
45 |
46 | # dict containing callee -> set(callers)
47 | calls = {}
48 |
49 | for function in view.functions:
50 | for ref in view.get_code_refs(function.start):
51 | caller = ref.function
52 | calls[function] = calls.get(function, set())
53 |
54 | call_il = caller.get_low_level_il_at(ref.address)
55 | if (call_il.operation in (
56 | LowLevelILOperation.LLIL_CALL,
57 | LowLevelILOperation.LLIL_TAILCALL,
58 | LowLevelILOperation.LLIL_CALL_STACK_ADJUST
59 | ) and call_il.dest.operation == LowLevelILOperation.LLIL_CONST_PTR):
60 | calls[function].add(caller)
61 |
62 | callgraph = FlowGraph()
63 | callgraph.function = view.get_function_at(view.entry_point)
64 | root_node = FlowGraphNode(callgraph)
65 | root_node.lines = ['ROOT']
66 | callgraph.append(root_node)
67 | function_nodes = {}
68 |
69 | call_queue = view.functions
70 |
71 | while call_queue:
72 | # get the next called function
73 | callee = call_queue.pop()
74 |
75 | # create a new node if one doesn't exist already
76 | callee_node = get_or_set_call_node(callgraph, function_nodes, callee)
77 |
78 | # create nodes for the callers, and add edges
79 | callers = calls.get(callee, set())
80 |
81 | if not callers:
82 | root_node.add_outgoing_edge(
83 | BranchType.FalseBranch, callee_node
84 | )
85 |
86 | for caller in callers:
87 | caller_node = get_or_set_call_node(callgraph, function_nodes, caller)
88 |
89 | # Add the edge between the caller and the callee
90 | if ctypes.addressof(callee_node.handle.contents) not in [
91 | ctypes.addressof(edge.target.handle.contents)
92 | for edge in caller_node.outgoing_edges]:
93 | caller_node.add_outgoing_edge(
94 | BranchType.TrueBranch,
95 | callee_node
96 | )
97 |
98 | callgraph.layout_and_wait()
99 | callgraph.show('Callgraph')
100 |
101 | def generate_callgraph(view):
102 | log_info("generate_callgraph")
103 | callgraph_task = CallgraphTask(view)
104 | callgraph_task.start()
105 |
106 | PluginCommand.register(
107 | 'Generate Callgraph',
108 | 'Generate a callgraph of the binary',
109 | generate_callgraph
110 | )
--------------------------------------------------------------------------------
/ep3-vm-arch/README.md:
--------------------------------------------------------------------------------
1 | # Episode 3: Architecture Plugin!
2 |
3 | In this shorter [episode](https://www.twitch.tv/videos/358962183), I decided to try to build a simple `Architecture` plugin in about 1.5 hours. Our target was a [simple VM crackme](https://www.malwaretech.com/vm1) from MalwareTech's site.
4 |
5 | The resulting disassembler and lifter can be found [here](vm_arch.py), and an annotated bndb file is [here](vm1.bndb).
6 |
7 | We didn't actually solve the crackme, though, so I've left that as an exercise to the reader!
--------------------------------------------------------------------------------
/ep3-vm-arch/ram.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshwatson/f-ing-around-with-binaryninja/2f7e907952a15305541820ac11ff0a86645c631d/ep3-vm-arch/ram.bin
--------------------------------------------------------------------------------
/ep3-vm-arch/vm1.bndb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joshwatson/f-ing-around-with-binaryninja/2f7e907952a15305541820ac11ff0a86645c631d/ep3-vm-arch/vm1.bndb
--------------------------------------------------------------------------------
/ep3-vm-arch/vm_arch.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (Architecture, InstructionInfo, InstructionTextToken,
2 | RegisterInfo, InstructionTextTokenType, BranchType, ILRegister)
3 | from collections import defaultdict
4 |
5 | opcodes = defaultdict(
6 | lambda: 'hlt',
7 | {
8 | 1: "set",
9 | 2: "get",
10 | 3: "xor"
11 | }
12 | )
13 |
14 | class VMArch(Architecture):
15 | name = "VMArch"
16 |
17 | address_size = 1
18 | default_int_size = 1
19 | max_instr_length = 3
20 |
21 | stack_pointer = 's'
22 |
23 | regs = {
24 | 'k': RegisterInfo('k', 1),
25 | 'c': RegisterInfo('c', 1),
26 | 's': RegisterInfo('s', 1)
27 | }
28 |
29 | def parse_instruction(self, data, addr):
30 | opcode, offset, value = data[:3]
31 |
32 | return opcode, offset, value, 3
33 |
34 | def get_instruction_info(self, data, addr):
35 | opcode, offset, value, length = self.parse_instruction(data, addr)
36 |
37 | info = InstructionInfo()
38 | info.length = length
39 |
40 | if opcodes[opcode] == 'hlt':
41 | info.add_branch(BranchType.FunctionReturn)
42 |
43 | return info
44 |
45 | def get_instruction_text(self, data, addr):
46 | opcode, offset, value, length = self.parse_instruction(data, addr)
47 |
48 | tokens = []
49 |
50 | op = opcodes[opcode]
51 |
52 | # create the opcode token
53 | tokens.append(
54 | InstructionTextToken(
55 | InstructionTextTokenType.InstructionToken,
56 | f'{op:<.6s}', value=opcode
57 | )
58 | )
59 |
60 | # create the offset token
61 | if op != 'hlt':
62 | tokens.append(
63 | InstructionTextToken(
64 | InstructionTextTokenType.PossibleAddressToken,
65 | f' {offset}', value=offset, size=1
66 | )
67 | )
68 |
69 | if op == 'set':
70 | tokens.append(
71 | InstructionTextToken(
72 | InstructionTextTokenType.IntegerToken,
73 | f' {value}', value=value, size=1
74 | )
75 | )
76 |
77 | return tokens, length
78 |
79 | def get_instruction_low_level_il(self, data, addr, il):
80 | opcode, offset, value, length = self.parse_instruction(data, addr)
81 |
82 | op = opcodes[opcode]
83 |
84 | # [offset].b = value
85 | if op == 'set':
86 | il.append(
87 | il.store(1, il.const(1, offset), il.const(1, value))
88 | )
89 |
90 | # c = [offset].b
91 | elif op == 'get':
92 | il.append(
93 | il.set_reg(
94 | 1, 'c',
95 | il.load(
96 | 1, il.const(1, offset)
97 | )
98 | )
99 | )
100 |
101 | # [offset].b = [offset].b ^ c
102 | elif op == 'xor':
103 | il.append(
104 | il.set_reg(
105 | 1, 'k',
106 | il.load(1, il.const(1, offset))
107 | )
108 | )
109 | il.append(
110 | il.store(
111 | 1, il.const(1, offset),
112 | il.xor_expr(
113 | 1, il.reg(1, 'k'), il.reg(1, 'c')
114 | )
115 | )
116 | )
117 | elif op == 'hlt':
118 | il.append(il.no_ret())
119 |
120 | return length
121 |
122 | VMArch.register()
--------------------------------------------------------------------------------
/ep4-emulator/README.md:
--------------------------------------------------------------------------------
1 | # Episode 4: Emulator, and Deobfuscation!
2 |
3 | In this [episode](https://www.twitch.tv/videos/366032780), we revisited last episode's VMArch `Architecture` plugin, and then implemented a simple emulator to solve the challenge for us.
4 |
5 | After that, we started looking at an obfuscated binary to see if we can automate some, if not all, of the deobfuscation process. We're off to a good start, so look for the results of that code in Episode 5!
--------------------------------------------------------------------------------
/ep4-emulator/vm_visitor.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (Architecture, BinaryReader, BinaryWriter,
2 | PluginCommand, log_alert)
3 |
4 | class BNILVisitor(object):
5 | def __init__(self, **kw):
6 | super(BNILVisitor, self).__init__()
7 |
8 | def visit(self, expression):
9 | method_name = 'visit_{}'.format(expression.operation.name)
10 | if hasattr(self, method_name):
11 | value = getattr(self, method_name)(expression)
12 | else:
13 | value = None
14 | return value
15 |
16 | class VMVisitor(BNILVisitor):
17 | def __init__(self, view):
18 | super(VMVisitor, self).__init__()
19 |
20 | self.view = view
21 | self.bw = BinaryWriter(view)
22 | self.br = BinaryReader(view)
23 |
24 | self.regs = {r: 0 for r in Architecture['VMArch'].regs}
25 |
26 | def visit_LLIL_STORE(self, expr):
27 | dest = self.visit(expr.dest)
28 | src = self.visit(expr.src)
29 |
30 | if None not in (dest, src):
31 | self.bw.seek(dest)
32 | self.bw.write8(src)
33 |
34 | def visit_LLIL_CONST(self, expr):
35 | return expr.constant
36 |
37 | def visit_LLIL_CONST_PTR(self, expr):
38 | return expr.constant
39 |
40 | def visit_LLIL_SET_REG(self, expr):
41 | dest = expr.dest.name
42 | src = self.visit(expr.src)
43 |
44 | if src is not None:
45 | self.regs[dest] = src
46 |
47 | def visit_LLIL_LOAD(self, expr):
48 | src = self.visit(expr.src)
49 |
50 | if src is not None:
51 | self.br.seek(src)
52 | return self.br.read8()
53 |
54 | def visit_LLIL_XOR(self, expr):
55 | left = self.visit(expr.left)
56 | right = self.visit(expr.right)
57 |
58 | if None not in (left, right):
59 | return left ^ right
60 |
61 | def visit_LLIL_REG(self, expr):
62 | src = expr.src
63 |
64 | return self.regs[src.name]
65 |
66 | def visit_LLIL_NORET(self, expr):
67 | log_alert("VM Halted.")
68 |
69 | def run_emulator(view):
70 | v = VMVisitor(view)
71 | for il in view.llil_instructions:
72 | v.visit(il)
73 |
74 | PluginCommand.register(
75 | 'Emulate VMArch',
76 | 'Emulate VMArch LLIL',
77 | run_emulator,
78 | lambda view: view.arch == Architecture['VMArch']
79 | )
80 |
--------------------------------------------------------------------------------
/function_types/test.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | int main(int argc, char** argv)
4 | {
5 | int a;
6 |
7 | printf("%d %s\n", a, argv[0]);
8 | printf("%p\n", argv[0]);
9 |
10 | return 0;
11 | }
--------------------------------------------------------------------------------
/function_types/test_function_types.py:
--------------------------------------------------------------------------------
1 | from binaryninja import Function, BinaryView, PluginCommand, MediumLevelILOperation, log, Type, FunctionParameter
2 |
3 | def fix_printfs(view: BinaryView):
4 | printf = view.get_symbols_by_name('_printf')
5 |
6 | if not printf:
7 | printf = view.get_symbols_by_name('printf')
8 |
9 | if not printf:
10 | return
11 |
12 | for sym in printf:
13 | function = view.get_function_at(sym.address)
14 | if not function:
15 | continue
16 |
17 | xrefs = view.get_code_refs(function.start)
18 |
19 | for xref in xrefs:
20 | caller: Function = xref.function
21 |
22 | call_mlil = caller.get_low_level_il_at(xref.address).mlil
23 | print(call_mlil)
24 | if call_mlil is None:
25 | continue
26 |
27 | fmt_operand = call_mlil.params[0]
28 | if fmt_operand.operation == MediumLevelILOperation.MLIL_VAR:
29 | log.log_warn(f"Potential format string bug: {fmt_operand.address:x}")
30 | continue
31 |
32 | elif fmt_operand.operation in (MediumLevelILOperation.MLIL_CONST_PTR, MediumLevelILOperation.MLIL_CONST):
33 | fmt_address = fmt_operand.constant
34 | fmt = view.get_ascii_string_at(fmt_address, 2)
35 |
36 | if fmt is None:
37 | continue
38 |
39 | fmt_value = fmt.value
40 |
41 | else:
42 | continue
43 |
44 | specifiers = fmt_value.split('%')
45 |
46 | param_types = []
47 |
48 | for specifier in specifiers[1:]:
49 | if not specifier:
50 | continue
51 |
52 | if specifier.startswith('d'):
53 | param_types.append(Type.int(4, sign=True))
54 | elif specifier.startswith('s'):
55 | param_types.append(Type.pointer(view.arch, Type.char()))
56 | elif specifier.startswith('p'):
57 | param_types.append(Type.pointer(view.arch, Type.void()))
58 | else:
59 | log.log_warn(f'Unknown format specifier: {specifier}; skipping')
60 | param_types.append(Type.pointer(view.arch, Type.void()))
61 |
62 | param_idx = 1
63 | params = [FunctionParameter(Type.pointer(view.arch, Type.char()), 'fmt')]
64 | for param in param_types:
65 | params.append(FunctionParameter(param, f'arg{param_idx}'))
66 | param_idx += 1
67 |
68 | caller.set_call_type_adjustment(xref.address, Type.function(Type.int(4), params))
69 |
70 |
71 | PluginCommand.register(
72 | 'Fix up printf signatures',
73 | 'Fix up printf signatures so that the variadic arguments are correctly typed',
74 | fix_printfs
75 | )
--------------------------------------------------------------------------------
/typelib/__init__.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | Architecture,
3 | FunctionParameter,
4 | Platform,
5 | QualifiedName,
6 | Type,
7 | TypeLibrary,
8 | Structure
9 | )
10 |
11 | advapi32_x86 = TypeLibrary.new(Architecture["x86"], "advapi32.dll")
12 |
13 | advapi32_x86.add_platform(Platform["windows-x86"])
14 |
15 | BOOL = Type.int(4, altname="BOOL")
16 | HCRYPTPROV_type = Type.structure_type(Structure())
17 | HCRYPTPROV = Type.named_type_from_type(
18 | "HCRYPTPROV", HCRYPTPROV_type
19 | )
20 |
21 | LPCSTR_type = Type.pointer(Architecture["x86"], Type.char())
22 | LPCSTR = Type.named_type_from_type('LPCSTR', LPCSTR_type)
23 |
24 | DWORD = Type.int(4, sign=False, altname="DWORD")
25 |
26 | advapi32_x86.add_named_type("HCRYPTPROV", HCRYPTPROV_type)
27 |
28 | CryptAcquireContextA = Type.function(
29 | BOOL,
30 | [
31 | FunctionParameter(
32 | Type.pointer(Architecture["x86"], HCRYPTPROV), "phProv"
33 | ),
34 | FunctionParameter(LPCSTR, "szContainer"),
35 | FunctionParameter(LPCSTR, "szProvider"),
36 | FunctionParameter(DWORD, "dwProvType"),
37 | FunctionParameter(DWORD, "dwFlags"),
38 | ],
39 | calling_convention=Platform['windows-x86'].stdcall_calling_convention
40 | )
41 |
42 | CryptReleaseContext = Type.function(
43 | BOOL,
44 | [
45 | FunctionParameter(
46 | HCRYPTPROV, 'hProv'
47 | ),
48 | FunctionParameter(
49 | DWORD, 'dwFlags'
50 | )
51 | ],
52 | calling_convention=Platform['windows-x86'].stdcall_calling_convention
53 | )
54 |
55 | advapi32_x86.add_named_object(
56 | QualifiedName(["CryptAcquireContextA"]), CryptAcquireContextA
57 | )
58 | advapi32_x86.add_named_object(
59 | QualifiedName(["CryptReleaseContext"]), CryptReleaseContext
60 | )
61 |
62 | advapi32_x86.finalize()
63 |
--------------------------------------------------------------------------------
/unlock/unlock/__init__.py:
--------------------------------------------------------------------------------
1 | # This script requires python 3
2 | __all__ = ["UnlockVisitor", "SEHState"]
3 |
4 | from binaryninja import (
5 | PluginCommand,
6 | BinaryView,
7 | Function,
8 | FlowGraph,
9 | FlowGraphNode,
10 | DisassemblyTextLine,
11 | BranchType,
12 | )
13 | from binaryninja import core_ui_enabled
14 | from .unlockvisitor import UnlockVisitor
15 | from .state import SEHState
16 |
17 |
18 | def run_unlock(view: BinaryView, function: Function):
19 | u = UnlockVisitor(function, function.start)
20 | u.start()
21 | if not core_ui_enabled():
22 | import time
23 | from datetime import timedelta
24 |
25 | dot_count = 0
26 | start = time.time()
27 | while not u.finished:
28 | time.sleep(1)
29 | print(
30 | f'[{timedelta(seconds=(time.time() - start))}] Running{"."*dot_count:<4s}\r',
31 | end="",
32 | )
33 | dot_count = (dot_count + 1) % 4
34 | u.join()
35 | print(f"{view.functions}")
36 |
37 |
38 | PluginCommand.register_for_function(
39 | r"Unlock\Run unlock",
40 | "Run unlock",
41 | run_unlock,
42 | is_valid=lambda v, f: "obfuscated" in v.file.filename,
43 | )
44 |
45 |
46 | def generate_graphs(view: BinaryView):
47 | for func in view.functions:
48 | bbs = {}
49 | g = FlowGraph()
50 | g.function = func
51 | n = FlowGraphNode(g)
52 | for bb in func.basic_blocks:
53 | if bb.start not in bbs:
54 | print(f"bbs[{bb.start:x}] = n")
55 | bbs[bb.start] = n
56 | else:
57 | print("g.append(n)")
58 | g.append(n)
59 | print(f"n = bbs[{bb.start:x}]")
60 | n = bbs[bb.start]
61 | current_addr = bb.start
62 | for instr in bb:
63 | if instr[0][0].text not in ("nop", "jmp") or (
64 | instr[0][0].text == "jmp" and not bb.outgoing_edges
65 | ):
66 | print(instr[0])
67 | n.lines += [DisassemblyTextLine(instr[0], address=current_addr)]
68 |
69 | current_addr += instr[1]
70 | if (
71 | instr[0][0].text == "jmp"
72 | and bb.outgoing_edges
73 | and bb.outgoing_edges[0].target.start in bbs
74 | and bb.outgoing_edges[0].target in bb.dominators
75 | ):
76 | n.add_outgoing_edge(
77 | BranchType.UnconditionalBranch,
78 | bbs[bb.outgoing_edges[0].target.start],
79 | )
80 | else:
81 | # Keep using the same FlowGraphNode
82 | pass
83 | g.append(n)
84 | g.layout_and_wait()
85 | g.show(func.name)
86 |
87 |
88 | PluginCommand.register(
89 | r"Unlock\Generate Graphs",
90 | "Generate Deobfuscated Graphs",
91 | generate_graphs,
92 | lambda v: "obfuscated" in v.file.filename,
93 | )
94 |
--------------------------------------------------------------------------------
/unlock/unlock/analysis/__init__.py:
--------------------------------------------------------------------------------
1 | #__all__ = ['analyze_return', 'analyze_unconditional_jump']
2 |
3 | #from .analyze_return import analyze_return
4 | #from .analyze_unconditional_jump import analyze_unconditional_jump
--------------------------------------------------------------------------------
/unlock/unlock/analysis/analyze_exception_handler.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | MediumLevelILInstruction,
3 | RegisterValueType,
4 | Variable,
5 | SSAVariable,
6 | VariableSourceType,
7 | MediumLevelILOperation,
8 | LowLevelILOperation,
9 | )
10 |
11 | from ..state import SEHState
12 | from ..bnilvisitor import BNILVisitor
13 | from ..logging import log_debug
14 | from .analyze_unwind import analyze_unwind
15 |
16 |
17 | class NullDerefVisitor(BNILVisitor):
18 | def visit_MLIL_SX(self, expr):
19 | return self.visit(expr.src)
20 |
21 | visit_MLIL_LOAD = visit_MLIL_SX
22 |
23 | def visit_MLIL_CONST_PTR(self, expr):
24 | return expr.constant
25 |
26 | visit_MLIL_CONST = visit_MLIL_CONST_PTR
27 |
28 |
29 | def analyze_exception_handler_set_var(
30 | self, expr: MediumLevelILInstruction
31 | ):
32 | log_debug("analyze_exception_handler_set_var")
33 | log_debug(f"{self.seh_state!r}")
34 |
35 | if self.seh_state == SEHState.NoException and self.fs in expr.src.prefix_operands:
36 | self.seh_state = SEHState.PushSeh
37 | return
38 |
39 | if self.seh_state == SEHState.Seh and self.seh:
40 | if NullDerefVisitor().visit(expr.src) == 0:
41 | self.seh_state = SEHState.InException
42 | target = self.seh.pop()
43 | if target:
44 | log_debug(f"Manipulating the stack")
45 | self.enter_location = expr.address
46 | self.view.write(
47 | expr.address,
48 | self.view.arch.assemble(
49 | f"enter 0xb4, 0\njmp 0x{target:x}", expr.address
50 | ),
51 | )
52 | self.target_queue.put(target)
53 | return True
54 |
55 | # not a null deref, so let's check the next instruction
56 | return
57 |
58 | elif self.seh_state == SEHState.LookingForPop:
59 | log_debug(f"looking for pop: {expr}")
60 | if (
61 | expr.dest.storage == self.view.arch.get_reg_index("esp")
62 | and expr.src.operation == MediumLevelILOperation.MLIL_ADDRESS_OF
63 | and expr.src.llil.non_ssa_form.operation == LowLevelILOperation.LLIL_ADD
64 | ):
65 | log_debug("Pop found")
66 | self.seh_state = SEHState.NoException
67 | self.convert_to_nop(expr.address)
68 | self.target_queue.put(expr.function[expr.instr_index + 1].address)
69 | return True
70 | else:
71 | return self.visit(expr.src)
72 |
73 |
74 | def analyze_exception_handler_store(
75 | self, expr: MediumLevelILInstruction
76 | ):
77 | log_debug("analyze_exception_handler_store")
78 | log_debug(f"{self.seh_state!r}")
79 |
80 | if self.seh_state == SEHState.PushSeh:
81 | sp = self.function.get_reg_value_at(expr.address, self.view.arch.stack_pointer)
82 | seh = self.function.get_stack_contents_at(
83 | expr.address, sp.offset + self.address_size, self.address_size
84 | )
85 | if seh.type == RegisterValueType.ConstantValue:
86 | log_debug("pushing seh {seh.value:x}")
87 | self.seh.append(seh.value)
88 | self.seh_state = SEHState.Seh
89 | else:
90 | # something else is going on here. stop so we can
91 | # examine it
92 | return False
93 |
94 | elif self.seh_state == SEHState.InException:
95 | return self.analyze_unwind(expr)
96 |
97 | elif self.seh_state == SEHState.Unwinding and self.fs in expr.dest.prefix_operands:
98 | # Change our state in the state machine
99 |
100 | # Find all of the uses of the fs segment
101 | fs_uses = expr.function.get_var_uses(self.fs)
102 |
103 | # Collect the stack offset to find where the second pointer of the
104 | # exception handler frame was created
105 | max_offset = (float("-inf"), None)
106 | for use in fs_uses:
107 | fs_use_il = expr.function[use]
108 |
109 | # Get the previous push, and any math done on that push as well
110 | # so we can remove it
111 | saved_eh = self.function.get_reg_value_at(fs_use_il.address, "esp")
112 | max_offset = max(max_offset, (saved_eh.offset, fs_use_il))
113 |
114 | saved_eh_var = Variable(
115 | self.function, VariableSourceType.StackVariableSourceType, 0, max_offset[0]
116 | )
117 | saved_eh_ssa = SSAVariable(
118 | saved_eh_var, max_offset[1].get_ssa_var_version(saved_eh_var)
119 | )
120 |
121 | saved_eh_defs = [expr.function.get_ssa_var_definition(saved_eh_ssa)]
122 | current_def = expr.function[saved_eh_defs[0]]
123 |
124 | while saved_eh_var in current_def.src.prefix_operands:
125 | saved_eh_defs.append(
126 | expr.function.get_ssa_var_definition(
127 | SSAVariable(saved_eh_var, saved_eh_ssa.version - 1)
128 | )
129 | )
130 | current_def = expr.function[saved_eh_defs[-1]]
131 |
132 | instr_to_queue = expr.function[fs_uses[0]]
133 |
134 | # NOP everything out
135 | # 1. NOP out the uses of the fs register
136 | for use in fs_uses:
137 | fs_use_il = expr.function[use]
138 | self.convert_to_nop(fs_use_il.address)
139 |
140 | # 2. NOP out the push of the saved exception handler
141 | for _def in saved_eh_defs:
142 | self.convert_to_nop(expr.function[_def].address)
143 |
144 | # 3. NOP out the enter instruction we used
145 | self.convert_to_nop(self.enter_location)
146 |
147 | # Move to the next state, where we look for the pop
148 | self.seh_state = SEHState.LookingForPop
149 |
150 | # Find the next instruction to queue
151 | self.queue_prev_block(instr_to_queue)
152 |
153 | return True
154 |
--------------------------------------------------------------------------------
/unlock/unlock/analysis/analyze_folding.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | log_debug,
3 | LowLevelILOperation,
4 | Variable,
5 | VariableSourceType,
6 | SSAVariable,
7 | SSARegister,
8 | MediumLevelILInstruction,
9 | LowLevelILInstruction,
10 | MediumLevelILOperation,
11 | ILRegister,
12 | RegisterValueType,
13 | )
14 |
15 | from ..logging import log_debug
16 |
17 |
18 | def analyze_constant_folding(self, expr):
19 | log_debug("analyze_constant_folding")
20 |
21 | if isinstance(expr, MediumLevelILInstruction):
22 | dependents, patch_value, patch_address = analyze_constant_folding_mlil(
23 | self, expr
24 | )
25 | elif isinstance(expr, LowLevelILInstruction):
26 | dependents, patch_value, patch_address = analyze_constant_folding_llil(
27 | self, expr
28 | )
29 |
30 | if None in (dependents, patch_value, patch_address):
31 | log_debug("got None back, returning")
32 | return
33 |
34 | if self.view.get_instruction_length(patch_address.address) < len(patch_value):
35 | log_debug(f"{patch_address.address:x} is too few bytes to patch")
36 | return False
37 |
38 | # First, convert to NOPs and *then* write the patch
39 | self.convert_to_nop(patch_address.address)
40 |
41 | log_debug(f"Writing patch at {patch_address.address:x}")
42 | self.view.write(patch_address.address, patch_value)
43 |
44 | log_debug("NOPPING THE SHIT OUT OF THIS THING")
45 | # Nop all of the previous assignments
46 | for addr in dependents:
47 | log_debug(f"nopping {addr:x}")
48 | self.convert_to_nop(addr)
49 | log_debug("DONE WITH ALL THAT NOPPING")
50 |
51 | # place the last address on the queue, to fold
52 | # all the NOPs and GOTOs
53 | if dependents:
54 | self.queue_prev_block(self.function.get_low_level_il_at(dependents[-1]).mmlil)
55 | return True
56 | else:
57 | return
58 |
59 |
60 | def analyze_constant_folding_llil(self, expr: LowLevelILInstruction):
61 | log_debug("analyze_constant_folding_llil")
62 |
63 | llil = expr.function.non_ssa_form
64 |
65 | reg_value = expr.value
66 |
67 | log_debug(f"folding {expr.src} into {reg_value.value:x}")
68 |
69 | if expr.operation == LowLevelILOperation.LLIL_REG_SSA:
70 | reg_ssa = expr.src
71 | reg_name = reg_ssa.reg.name
72 | reg_index = reg_ssa.reg.index
73 | elif expr.operation == LowLevelILOperation.LLIL_REG_SSA_PARTIAL:
74 | reg_ssa = expr.full_reg
75 | partial_reg_index = expr.src.index
76 | reg_index = reg_ssa.reg.index
77 | reg_name = expr.src.name
78 | else:
79 | return
80 |
81 | reg_def = llil[llil.get_ssa_reg_definition(reg_ssa)]
82 |
83 | log_debug(f"register defined at {reg_def.address:x}")
84 |
85 | dependent_regs = []
86 |
87 | next_il = reg_def
88 |
89 | while next_il:
90 | log_debug(f"{next_il}: {next_il.src.prefix_operands}")
91 | reg = next(
92 | (
93 | o
94 | for o in next_il.ssa_form.src.prefix_operands
95 | if isinstance(o, ILRegister) and o.index == partial_reg_index
96 | ),
97 | None,
98 | )
99 | ssa = next(
100 | (
101 | o
102 | for o in next_il.ssa_form.src.prefix_operands
103 | if isinstance(o, SSARegister)
104 | and o.reg.index == reg_index
105 | and len(llil.get_ssa_reg_uses(o)) == 1
106 | ),
107 | None,
108 | )
109 |
110 | if ssa is not None and reg is None:
111 | next_il = llil[llil.get_ssa_reg_definition(ssa)]
112 | dependent_regs.append(next_il.address)
113 | elif ssa is not None and reg is not None:
114 | next_il = llil[llil.get_ssa_reg_definition(ssa)]
115 | if next_il.operation == LowLevelILOperation.LLIL_SET_REG_SSA_PARTIAL:
116 | dependent_regs.append(next_il.address)
117 | else:
118 | next_il = None
119 | else:
120 | next_il = None
121 |
122 | # Convert the final one into the assignment
123 | patch_value = self.view.arch.assemble(
124 | f"mov {reg_name}, 0x{reg_value.value:x}", reg_def.address
125 | )
126 |
127 | return dependent_regs, patch_value, reg_def
128 |
129 |
130 | def analyze_constant_folding_mlil(self, expr: MediumLevelILInstruction):
131 | log_debug("analyze_constant_folding_mlil")
132 | mlil = expr.function
133 |
134 | if expr.src.storage > 0x7FFFFFFF:
135 | log_debug("this is a temp var")
136 | return
137 |
138 | var_value = mlil[expr.instr_index].src.value
139 |
140 | log_debug(f"folding {expr.src} into {var_value.value:x}")
141 |
142 | var_ssa = expr.ssa_form.src
143 |
144 | var_def = mlil[mlil.get_ssa_var_definition(var_ssa)]
145 |
146 | log_debug(f"variable defined at {var_def.address:x}")
147 |
148 | next_il = var_def
149 |
150 | dependents = [next_il]
151 |
152 | while next_il:
153 | log_debug(f"{next_il}: {next_il.src.prefix_operands}")
154 | for operand in next_il.ssa_form.src.prefix_operands:
155 | if isinstance(operand, SSAVariable) and operand.var == var_ssa.var:
156 | next_il = mlil[mlil.get_ssa_var_definition(operand)]
157 | log_debug(f"Adding {next_il} to list")
158 | dependents.append(next_il)
159 | break
160 | else:
161 | next_il = None
162 |
163 | if dependents:
164 | log_debug(f"{dependents!r}")
165 | patch_var = dependents.pop()
166 | log_debug(f"{patch_var}")
167 |
168 | if patch_var.dest.source_type == VariableSourceType.StackVariableSourceType:
169 | # Convert the final one into the assignment
170 | patch_string = f'{"push" if patch_var.llil.dest.operation == LowLevelILOperation.LLIL_SUB else "pop"} 0x{var_value.value:x}'
171 | log_debug(f"{patch_string} at {patch_var.address:x}")
172 | patch_value = self.view.arch.assemble(patch_string, patch_var.address)
173 | elif patch_var.dest.name:
174 | patch_string = f"mov {patch_var.dest.name}, 0x{var_value.value:x}"
175 | log_debug(patch_string)
176 | patch_value = self.view.arch.assemble(patch_string, patch_var.address)
177 | else:
178 | log_debug("returning None")
179 | return [], None, None
180 | else:
181 | log_debug("returning None")
182 | return [], None, None
183 |
184 | return ([i.address for i in dependents] + [expr.address], patch_value, patch_var)
185 |
186 |
187 | def analyze_goto_folding(self, expr: MediumLevelILInstruction):
188 | log_debug("analyze_goto_folding")
189 | llil = expr.function.llil
190 |
191 | llil_jump = expr.llil.non_ssa_form
192 | log_debug(f"llil_jump = {llil_jump}")
193 |
194 | if llil_jump is None:
195 | log_debug("We don't have a corresponding LLIL instr?!")
196 | return False
197 |
198 | final_target = llil[llil_jump.dest]
199 | log_debug(f"final_target = {final_target}")
200 |
201 | if self.phase < 3:
202 | jump_ops = (LowLevelILOperation.LLIL_GOTO,)
203 | else:
204 | jump_ops = (LowLevelILOperation.LLIL_GOTO, LowLevelILOperation.LLIL_JUMP_TO)
205 |
206 | while final_target.operation in jump_ops:
207 | if (
208 | final_target.operation == LowLevelILOperation.LLIL_JUMP_TO
209 | and final_target.dest.value.type == RegisterValueType.ConstantPointerValue
210 | ):
211 | final_target = self.function.get_low_level_il_at(
212 | final_target.dest.value.value
213 | )
214 | else:
215 | final_target = llil[final_target.dest]
216 | log_debug(f"final_target = {final_target.address:x}")
217 |
218 | if llil_jump.dest == final_target.instr_index:
219 | return final_target.mmlil.instr_index
220 |
221 | patch_value = self.view.arch.assemble(
222 | f"jmp 0x{final_target.address:x}", expr.address
223 | )
224 |
225 | if self.view.get_instruction_length(expr.address) < len(patch_value):
226 | log_debug(f"{expr.address:x} is too small for patch")
227 | return
228 |
229 | self.view.write(expr.address, patch_value)
230 |
231 | self.target_queue.put(final_target.address)
232 |
233 | return False
234 |
--------------------------------------------------------------------------------
/unlock/unlock/analysis/analyze_indirect_jump.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | MediumLevelILInstruction,
3 | BinaryView,
4 | Function,
5 | Type,
6 | MediumLevelILOperation,
7 | LowLevelILOperation,
8 | RegisterValueType,
9 | Variable,
10 | VariableSourceType,
11 | SSAVariable,
12 | MediumLevelILBasicBlock,
13 | BinaryDataNotification
14 | )
15 |
16 | from ..bnilvisitor import BNILVisitor
17 | from ..logging import log_debug
18 | from ..state import SEHState
19 |
20 |
21 | class JumpVisitor(BNILVisitor):
22 | def visit_MLIL_JUMP(self, expr):
23 | return self.visit(expr.dest)
24 |
25 | def visit_MLIL_LOAD(self, expr):
26 | return self.visit(expr.src)
27 |
28 | def visit_MLIL_CONST_PTR(self, expr):
29 | return expr.constant
30 |
31 | visit_MLIL_CONST = visit_MLIL_CONST_PTR
32 |
33 |
34 | def analyze_indirect_jump(self, expr: MediumLevelILInstruction):
35 | log_debug("analyze_indirect_jump")
36 | jump_value = JumpVisitor().visit(expr)
37 |
38 | if jump_value is None:
39 | log_debug("Jump target not constant")
40 | return False
41 |
42 | indirect_type = Type.int(self.view.arch.address_size, False)
43 | indirect_type.const = True
44 |
45 | if not self.view.is_offset_readable(jump_value):
46 | log_debug("Jump target is not readable")
47 | return False
48 |
49 | self.view.define_user_data_var(jump_value, indirect_type)
50 | self.target_queue.put(expr.address)
51 | return False
52 |
53 |
54 | def analyze_possible_call(self, expr: MediumLevelILInstruction):
55 | log_debug("analyze_possible_call")
56 |
57 | if self.phase == 1:
58 | return
59 |
60 | # When we're in an exception, there's extra executable addresses on the stack.
61 | # So, if we're in an exception-based obfuscation, there won't be any calls.
62 | if self.exception_visitors[self.function.start].state != SEHState.NoException:
63 | return
64 |
65 | log_debug(f'{self.function.start:x} {self.exception_visitors[self.function.start].state!r}')
66 |
67 | if expr.dest.operation != MediumLevelILOperation.MLIL_CONST_PTR:
68 | return
69 |
70 | if expr.llil.dest.operation != LowLevelILOperation.LLIL_REG_SSA:
71 | return
72 |
73 | target_reg = expr.llil.dest.src.reg.name
74 |
75 | current_esp = self.function.get_reg_value_at(expr.address, "esp")
76 |
77 | if current_esp.type != RegisterValueType.StackFrameOffset:
78 | return
79 |
80 | ret_addr = self.function.get_stack_contents_at(expr.address, current_esp.offset, 4)
81 |
82 | if ret_addr.type not in (
83 | RegisterValueType.ConstantValue,
84 | RegisterValueType.ConstantPointerValue,
85 | ):
86 | return
87 |
88 | if not self.view.is_offset_executable(ret_addr.value):
89 | return
90 |
91 | log_debug(f"{ret_addr.value:x} is executable")
92 |
93 | # If we got to here, then this is a tail call. Let's define the call
94 | patch_value = self.view.arch.assemble(f"call {target_reg}", expr.address)
95 |
96 | if self.view.get_instruction_length(expr.address) < len(patch_value):
97 | log_debug(f"{expr.address:x} is too small for call instruction")
98 | return
99 |
100 | self.view.write(expr.address, patch_value)
101 |
102 | # Find the return address variable and remove it
103 | return_addr_var = Variable(
104 | self.function, VariableSourceType.StackVariableSourceType, 0, current_esp.offset
105 | )
106 |
107 | # get the IL instructions of the definitions instead of the indices
108 | return_addr_definitions = list(
109 | map(
110 | expr.function.__getitem__,
111 | expr.function.get_var_definitions(return_addr_var),
112 | )
113 | )
114 |
115 | current_bb = next(
116 | bb
117 | for bb in expr.function.basic_blocks
118 | if bb.start <= expr.instr_index < bb.end
119 | )
120 |
121 | for instr in return_addr_definitions:
122 | # get the IL basic block of this definition
123 | instr_bb: MediumLevelILBasicBlock = next(
124 | bb
125 | for bb in expr.function.basic_blocks
126 | if bb.start <= instr.instr_index < bb.end
127 | )
128 |
129 | if instr_bb in current_bb.dominators:
130 | self.convert_to_nop(instr.address)
131 |
132 | # queue the return address for analysis
133 | self.queue_prev_block(return_addr_definitions[0])
134 |
135 | # queue the function start as well
136 | from ..exceptionvisitor import ExceptionVisitor
137 |
138 | self.exception_visitors[expr.dest.constant] = ExceptionVisitor(self)
139 | self.target_queue.put(expr.dest.constant)
140 |
141 | # Change the phase back to 1
142 | self.prev_phase = self.phase
143 | self.phase = 1
144 |
145 | return True
--------------------------------------------------------------------------------
/unlock/unlock/analysis/analyze_return.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | MediumLevelILOperation,
3 | RegisterValueType,
4 | Architecture,
5 | MediumLevelILInstruction,
6 | MediumLevelILFunction,
7 | log_warn,
8 | )
9 |
10 | from ..logging import log_debug
11 |
12 | def analyze_return(self, expr: MediumLevelILInstruction):
13 | arch: Architecture = self.view.arch
14 | mmlil: MediumLevelILFunction = self.function.llil.mapped_medium_level_il
15 |
16 | # Step 1: retrieve address of the ret
17 | ret_addr = expr.address
18 |
19 | # Step 2: calculate the address to jump to
20 | current_sp: RegisterValueType = self.function.get_reg_value_at(
21 | ret_addr, arch.stack_pointer
22 | )
23 |
24 | if current_sp.type != RegisterValueType.StackFrameOffset:
25 | log_debug(f'{current_sp.type!r} != RegisterValueType.StackFrameOffset')
26 | return False
27 |
28 | current_sp: int = current_sp.offset
29 |
30 | next_jump_value: RegisterValueType = self.function.get_stack_contents_at(
31 | ret_addr, current_sp, arch.address_size
32 | )
33 |
34 | if next_jump_value.type == RegisterValueType.ConstantValue:
35 | next_jump_addr: int = next_jump_value.value
36 | else:
37 | log_debug(f"next_jump_value is not a constant: {next_jump_value.type!r}")
38 | return False
39 |
40 | # Step 3: identify the start of this primitive – we assume that the
41 | # return address is either pushed directly onto the stack, or it is
42 | # put on the stack and then some operation is performed on it, in which
43 | # the return address will always be on the left side of said operation.
44 | ret_il_ssa = expr.ssa_form
45 | jump_variable_ssa = ret_il_ssa.dest.src
46 |
47 | jump_variable_def = mmlil.get_ssa_var_definition(jump_variable_ssa)
48 |
49 | if jump_variable_ssa is None:
50 | log_debug("wtf why is this magically None?")
51 | log_debug(f"{ret_il_ssa}")
52 | return False
53 |
54 | jump_il = mmlil[jump_variable_def]
55 | while jump_il.src.operation != MediumLevelILOperation.MLIL_CONST:
56 | new_var_ssa = jump_il.src.left.ssa_form.src
57 | jump_il = mmlil[mmlil.get_ssa_var_definition(new_var_ssa)]
58 |
59 | # Step 4: Patch the binary to jump to the return address
60 | patch_addr = jump_il.address
61 | patch_value = arch.assemble(f"jmp 0x{next_jump_addr:x}", patch_addr)
62 |
63 | # Ensure there is enough space in this primitive to patch it
64 | if (ret_addr - patch_addr) < len(patch_value):
65 | log_warn(
66 | f"Not enough space to patch {patch_addr:x}; need {len(patch_value)} bytes"
67 | )
68 | return False
69 |
70 | self.view.write(patch_addr, patch_value)
71 | log_debug(f"Patched {patch_addr:x} with new jump target")
72 |
73 | # log_debug(f"adding {next_jump_addr:x} to target queue")
74 | # self.target_queue.put(next_jump_addr)
75 |
76 | # return True
77 |
78 | return self.queue_prev_block(jump_il)
79 |
--------------------------------------------------------------------------------
/unlock/unlock/analysis/analyze_unconditional_jump.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | MediumLevelILOperation,
3 | MediumLevelILFunction,
4 | RegisterValueType,
5 | Variable,
6 | ILBranchDependence,
7 | MediumLevelILInstruction,
8 | )
9 | from itertools import chain
10 | from ..bnilvisitor import BNILVisitor
11 | from ..logging import log_debug
12 |
13 | cmp_pairs = {
14 | MediumLevelILOperation.MLIL_CMP_E: MediumLevelILOperation.MLIL_CMP_NE,
15 | MediumLevelILOperation.MLIL_CMP_NE: MediumLevelILOperation.MLIL_CMP_E,
16 | MediumLevelILOperation.MLIL_CMP_UGT: MediumLevelILOperation.MLIL_CMP_ULE,
17 | MediumLevelILOperation.MLIL_CMP_ULE: MediumLevelILOperation.MLIL_CMP_UGT,
18 | MediumLevelILOperation.MLIL_CMP_UGE: MediumLevelILOperation.MLIL_CMP_ULT,
19 | MediumLevelILOperation.MLIL_CMP_ULT: MediumLevelILOperation.MLIL_CMP_UGE,
20 | MediumLevelILOperation.MLIL_CMP_SGE: MediumLevelILOperation.MLIL_CMP_SLT,
21 | MediumLevelILOperation.MLIL_CMP_SLT: MediumLevelILOperation.MLIL_CMP_SGE,
22 | MediumLevelILOperation.MLIL_CMP_SGT: MediumLevelILOperation.MLIL_CMP_SLE,
23 | MediumLevelILOperation.MLIL_CMP_SLE: MediumLevelILOperation.MLIL_CMP_SGT,
24 | MediumLevelILOperation.MLIL_NOT: MediumLevelILOperation.MLIL_VAR,
25 | MediumLevelILOperation.MLIL_VAR: MediumLevelILOperation.MLIL_NOT,
26 | MediumLevelILOperation.MLIL_AND: MediumLevelILOperation.MLIL_OR,
27 | MediumLevelILOperation.MLIL_OR: MediumLevelILOperation.MLIL_AND,
28 | }
29 |
30 |
31 | def analyze_unconditional_jump(self, expr: MediumLevelILInstruction):
32 | log_debug("analyze_unconditional_jump")
33 |
34 | function = self.function
35 | view = self.view
36 | mmlil = expr.function
37 |
38 | seen_count = self.seen.get(expr.address, 0)
39 | log_debug(f"Analyzing {expr.address:x} : {seen_count} times")
40 | if seen_count > 20:
41 | log_debug(f"{expr.address:x} has been seen too many times")
42 | return expr.true
43 |
44 | # double check to see if it's solvable:
45 | if expr.condition.value.type == RegisterValueType.ConstantValue:
46 | if expr.condition.value.value == 0:
47 | # patch to never jump
48 | pass
49 | else:
50 | # patch to always jump
51 | pass
52 |
53 | # step 2: get our mlil basic block
54 | for bb in mmlil.basic_blocks:
55 | if bb.start <= expr.instr_index < bb.end:
56 | first_jump_bb = bb
57 | break
58 | else:
59 | log_debug("Couldn't find basic block")
60 | return False
61 |
62 | # step 3: look for all the returns
63 | returns = []
64 |
65 | for idx in range(expr.instr_index + 1, len(mmlil)):
66 | current_il = mmlil[idx]
67 | if current_il.operation in (
68 | MediumLevelILOperation.MLIL_RET,
69 | MediumLevelILOperation.MLIL_RET_HINT,
70 | MediumLevelILOperation.MLIL_UNDEF,
71 | MediumLevelILOperation.MLIL_JUMP,
72 | ) or (
73 | current_il.operation == MediumLevelILOperation.MLIL_GOTO
74 | and len(current_il.branch_dependence) > 1
75 | ):
76 | returns.append(current_il)
77 | idx += 1
78 |
79 | # step 4: find the unconditional jump
80 | # TODO: switch not_unconditional to a set and do the difference
81 | unconditional_target = (
82 | mmlil[expr.true]
83 | if expr.instr_index not in mmlil[expr.true].branch_dependence
84 | else None
85 | )
86 | not_unconditional = []
87 |
88 | for ret in returns:
89 | if ret.branch_dependence:
90 | not_unconditional.append(ret)
91 | else:
92 | unconditional_target = ret
93 |
94 | if unconditional_target is None:
95 | original_target = expr.address
96 | false_target = mmlil[expr.false].address
97 |
98 | # try to get the BinaryView to trigger analysis when we leave
99 | false_seen_target = self.seen.get(false_target, 0)
100 | if false_seen_target < 10:
101 | log_debug(f"{false_target:x} seen {false_seen_target}")
102 | self.target_queue.put(false_target)
103 | self.target_queue.put(original_target)
104 | return False
105 |
106 | log_debug(f"{false_target:x} has been seen too many times")
107 | return expr.true
108 |
109 | # get the basic block for the unconditional ret
110 | bb = get_mmlil_bb(mmlil, unconditional_target.instr_index)
111 |
112 | # make sure first jump dominates
113 | if first_jump_bb not in bb.dominators:
114 | log_debug(f"first_jump_bb not in bb.dominators for {bb[0].address:x}")
115 | return False
116 |
117 | # find the ret that is dependent on first jump and another jump
118 | # and both need to have the same type of branch
119 |
120 | for ret in not_unconditional:
121 | dependence = ret.branch_dependence
122 | second_jump = next(
123 | (
124 | mmlil[i]
125 | for i in sorted(dependence)
126 | if (
127 | i != expr.instr_index
128 | and expr.instr_index in mmlil[i].branch_dependence
129 | )
130 | ),
131 | None,
132 | )
133 | if second_jump is None:
134 | continue
135 |
136 | if expr.instr_index not in dependence:
137 | continue
138 |
139 | # same type of branch
140 | if dependence[expr.instr_index] != dependence[second_jump.instr_index]:
141 | continue
142 |
143 | bb = get_mmlil_bb(mmlil, ret.instr_index)
144 | break
145 | else:
146 | log_debug("Didn't find a ret")
147 | return False
148 |
149 | if second_jump is None:
150 | log_debug("Second Jump is None")
151 | return False
152 |
153 | if expr.condition.operation == MediumLevelILOperation.MLIL_VAR:
154 | # This could be an if (flag:o) and an if (!(flag:o))
155 | if second_jump.condition.operation != MediumLevelILOperation.MLIL_NOT:
156 | first_jump_condition = mmlil[
157 | mmlil.get_ssa_var_definition(expr.ssa_form.condition.src)
158 | ].src
159 | else:
160 | first_jump_condition = expr.condition
161 | else:
162 | first_jump_condition = expr.condition
163 |
164 | if second_jump.condition.operation == MediumLevelILOperation.MLIL_VAR:
165 | if expr.condition.operation != MediumLevelILOperation.MLIL_NOT:
166 | second_jump_condition = mmlil[
167 | mmlil.get_ssa_var_definition(second_jump.ssa_form.condition.src)
168 | ].src
169 | else:
170 | second_jump_condition = second_jump.condition
171 | else:
172 | second_jump_condition = second_jump.condition
173 |
174 | # make sure the comparisons are opposites
175 | if cmp_pairs[first_jump_condition.operation] != second_jump_condition.operation:
176 | log_debug("Comparisons didn't match")
177 | return False
178 |
179 | # make sure the operands are the same
180 | first_ops = ConditionVisitor().visit(first_jump_condition)
181 | second_ops = ConditionVisitor().visit(second_jump_condition)
182 |
183 | if isinstance(first_ops, Variable):
184 | if first_ops != second_ops:
185 | log_debug("first_ops != second_ops")
186 | return False
187 | elif not all(o in second_ops for o in first_ops):
188 | log_debug("not all(o in second_ops for o in first_ops)")
189 | return False
190 |
191 | # we have found our two jumps and the unconditional!
192 | patch_addr = expr.address
193 |
194 | patch_bb = next(bb for bb in function if bb.start <= patch_addr < bb.end)
195 |
196 | branch_type = dependence[expr.instr_index]
197 |
198 | if branch_type == ILBranchDependence.FalseBranchDependent:
199 | target = mmlil[expr.true].address
200 |
201 | patch_value = view.arch.always_branch(
202 | view.read(patch_addr, view.get_instruction_length(patch_addr)), patch_addr
203 | )
204 | else:
205 | target = mmlil[expr.false].address
206 |
207 | patch_value = view.arch.never_branch(
208 | view.read(patch_addr, view.get_instruction_length(patch_addr)), patch_addr
209 | )
210 |
211 | if (patch_bb.end - patch_addr) < len(patch_value):
212 | log_debug("not enough space", repr(patch_value))
213 | return False
214 |
215 | view.write(patch_addr, patch_value)
216 |
217 | self.target_queue.put(target)
218 |
219 | return True
220 |
221 |
222 | bb_cache = {}
223 |
224 |
225 | def get_mmlil_bb(mmlil: MediumLevelILFunction, idx: int):
226 | return next(bb for bb in mmlil.basic_blocks if bb.start <= idx < bb.end)
227 |
228 |
229 | class ConditionVisitor(BNILVisitor):
230 | def visit_MLIL_CMP_E(self, expr):
231 | left = self.visit(expr.left)
232 | right = self.visit(expr.right)
233 |
234 | if not isinstance(left, tuple):
235 | left = (left,)
236 | if not isinstance(right, tuple):
237 | right = (right,)
238 |
239 | # return a single tuple of all variables and constants
240 | return tuple(chain(left, right))
241 |
242 | visit_MLIL_CMP_NE = visit_MLIL_CMP_E
243 | visit_MLIL_CMP_UGT = visit_MLIL_CMP_E
244 | visit_MLIL_CMP_ULE = visit_MLIL_CMP_E
245 | visit_MLIL_CMP_UGE = visit_MLIL_CMP_E
246 | visit_MLIL_CMP_ULT = visit_MLIL_CMP_E
247 | visit_MLIL_CMP_SGT = visit_MLIL_CMP_E
248 | visit_MLIL_CMP_SLE = visit_MLIL_CMP_E
249 | visit_MLIL_CMP_SGE = visit_MLIL_CMP_E
250 | visit_MLIL_CMP_SLT = visit_MLIL_CMP_E
251 |
252 | def visit_MLIL_VAR(self, expr):
253 | return expr.src
254 |
255 | def visit_MLIL_NOT(self, expr):
256 | return self.visit(expr.src)
257 |
258 | def visit_MLIL_CONST(self, expr):
259 | return expr.constant
260 |
261 | def visit_MLIL_AND(self, expr):
262 | left = self.visit(expr.left)
263 | right = self.visit(expr.right)
264 |
265 | return left, right
266 |
267 | visit_MLIL_OR = visit_MLIL_AND
268 |
269 | visit_MLIL_CONST_PTR = visit_MLIL_CONST
270 |
--------------------------------------------------------------------------------
/unlock/unlock/analysis/analyze_unwind.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | MediumLevelILOperation,
3 | RegisterValueType,
4 | MediumLevelILInstruction,
5 | VariableSourceType,
6 | )
7 |
8 |
9 | from ..state import SEHState
10 | from ..bnilvisitor import BNILVisitor
11 | from ..logging import log_debug
12 |
13 |
14 | def analyze_unwind(self, expr: MediumLevelILInstruction):
15 | log_debug("analyze_unwind")
16 | log_debug(f"{self.seh_state!r}")
17 |
18 | if expr.src.value.type not in (
19 | RegisterValueType.ConstantPointerValue,
20 | RegisterValueType.ConstantValue,
21 | ):
22 | return
23 |
24 | visitor = UnwindVisitor()
25 | is_stack_var = visitor.visit(expr)
26 |
27 | if is_stack_var is not False:
28 | log_debug("Stack manipulation found; Starting unwind...")
29 | self.seh_state = SEHState.Unwinding
30 | next_il = expr.function[expr.instr_index + 1]
31 | self.convert_to_nop(is_stack_var)
32 |
33 | patch_value = self.view.arch.assemble(
34 | f"jmp 0x{expr.src.value.value:x}", next_il.address
35 | )
36 | if self.view.get_instruction_length(next_il.address) >= len(patch_value):
37 | self.view.write(next_il.address, patch_value)
38 |
39 | self.target_queue.put(next_il.address)
40 | self.convert_to_nop(expr.address)
41 |
42 | if hasattr(visitor, "nop_address"):
43 | self.convert_to_nop(visitor.nop_address)
44 |
45 | return True
46 | else:
47 | log_debug(f"{next_il.address:x} is not big enough for a patch")
48 | return False
49 |
50 | log_debug("This store does not manipulate the unwind.")
51 | return False
52 |
53 |
54 | class UnwindVisitor(BNILVisitor):
55 | def visit_MLIL_STORE(self, expr):
56 | function = expr.function
57 |
58 | if expr.dest.operation != MediumLevelILOperation.MLIL_VAR:
59 | return False
60 |
61 | dest_ssa = expr.dest.ssa_form.src
62 |
63 | return self.visit(function[function.get_ssa_var_definition(dest_ssa)])
64 |
65 | def visit_MLIL_SET_VAR(self, expr):
66 | return self.visit(expr.src)
67 |
68 | def visit_MLIL_ADD(self, expr):
69 | left = self.visit(expr.left)
70 | right = self.visit(expr.right)
71 |
72 | return left or right
73 |
74 | visit_MLIL_SUB = visit_MLIL_ADD
75 |
76 | def visit_MLIL_CONST(self, expr):
77 | if expr.constant == 0xB8:
78 | log_debug("Found the 0xb8")
79 | self.nop_address = expr.address
80 | return False
81 |
82 | def visit_MLIL_VAR(self, expr):
83 | function = expr.function
84 | var = expr.src
85 | if var.source_type == VariableSourceType.StackVariableSourceType:
86 | return expr.address
87 | else:
88 | var_ssa = expr.ssa_form.src
89 | return self.visit(function[function.get_ssa_var_definition(var_ssa)])
90 |
91 |
--------------------------------------------------------------------------------
/unlock/unlock/bnilvisitor.py:
--------------------------------------------------------------------------------
1 | from .logging import log_debug
2 |
3 | class BNILVisitor(object):
4 | def __init__(self, **kw):
5 | super(BNILVisitor, self).__init__()
6 |
7 | def visit(self, expression):
8 | method_name = "visit_{}".format(expression.operation.name)
9 | if hasattr(self, method_name):
10 | value = getattr(self, method_name)(expression)
11 | else:
12 | log_debug(f"{repr(expression.operation)}")
13 | value = None
14 | return value
--------------------------------------------------------------------------------
/unlock/unlock/exceptionvisitor.py:
--------------------------------------------------------------------------------
1 | from binaryninja import (
2 | MediumLevelILInstruction,
3 | RegisterValueType,
4 | Variable,
5 | SSAVariable,
6 | VariableSourceType,
7 | MediumLevelILOperation,
8 | LowLevelILOperation,
9 | )
10 |
11 | from .state import SEHState
12 | from .bnilvisitor import BNILVisitor
13 | from .logging import log_debug
14 |
15 | class NullDerefVisitor(BNILVisitor):
16 | def visit_MLIL_SX(self, expr):
17 | return self.visit(expr.src)
18 |
19 | visit_MLIL_LOAD = visit_MLIL_SX
20 |
21 | def visit_MLIL_CONST_PTR(self, expr):
22 | return expr.constant
23 |
24 | visit_MLIL_CONST = visit_MLIL_CONST_PTR
25 |
26 | class ExceptionVisitor(BNILVisitor):
27 | def __init__(self, unlock):
28 | self.unlock = unlock
29 | self.state = SEHState.NoException
30 | self.seh = []
31 | self.enter_location = None
32 | super().__init__()
33 |
34 | def visit_MLIL_STORE(self, expr):
35 | unlock = self.unlock
36 |
37 | log_debug("ExceptionVisitor.visit_MLIL_STORE")
38 | log_debug(f"{unlock.function.start:x} {self.state!r}")
39 |
40 | if self.state == SEHState.PushSeh:
41 | sp = unlock.function.get_reg_value_at(expr.address, unlock.view.arch.stack_pointer)
42 | seh = unlock.function.get_stack_contents_at(
43 | expr.address, sp.offset + unlock.address_size, unlock.address_size
44 | )
45 | if seh.type == RegisterValueType.ConstantValue:
46 | log_debug("pushing seh {seh.value:x}")
47 | self.seh.append(seh.value)
48 | self.state = SEHState.Seh
49 | else:
50 | # something else is going on here. stop so we can
51 | # examine it
52 | return False
53 |
54 | elif self.state == SEHState.InException:
55 | return self.visit_unwind(expr)
56 |
57 | elif self.state == SEHState.Unwinding and unlock.fs in expr.dest.prefix_operands:
58 | # Change our state in the state machine
59 |
60 | # Find all of the uses of the fs segment
61 | fs_uses = expr.function.get_var_uses(unlock.fs)
62 |
63 | # Collect the stack offset to find where the second pointer of the
64 | # exception handler frame was created
65 | max_offset = (float("-inf"), None)
66 | for use in fs_uses:
67 | fs_use_il = expr.function[use]
68 |
69 | # Get the previous push, and any math done on that push as well
70 | # so we can remove it
71 | saved_eh = unlock.function.get_reg_value_at(fs_use_il.address, "esp")
72 | max_offset = max(max_offset, (saved_eh.offset, fs_use_il))
73 |
74 | saved_eh_var = Variable(
75 | unlock.function, VariableSourceType.StackVariableSourceType, 0, max_offset[0]
76 | )
77 | saved_eh_ssa = SSAVariable(
78 | saved_eh_var, max_offset[1].get_ssa_var_version(saved_eh_var)
79 | )
80 |
81 | saved_eh_defs = [expr.function.get_ssa_var_definition(saved_eh_ssa)]
82 | current_def = expr.function[saved_eh_defs[0]]
83 |
84 | while saved_eh_var in current_def.src.prefix_operands:
85 | saved_eh_defs.append(
86 | expr.function.get_ssa_var_definition(
87 | SSAVariable(saved_eh_var, saved_eh_ssa.version - 1)
88 | )
89 | )
90 | current_def = expr.function[saved_eh_defs[-1]]
91 |
92 | instr_to_queue = expr.function[fs_uses[0]]
93 |
94 | # NOP everything out
95 | # 1. NOP out the uses of the fs register
96 | for use in fs_uses:
97 | fs_use_il = expr.function[use]
98 | unlock.convert_to_nop(fs_use_il.address)
99 |
100 | # 2. NOP out the push of the saved exception handler
101 | for _def in saved_eh_defs:
102 | unlock.convert_to_nop(expr.function[_def].address)
103 |
104 | # 3. NOP out the enter instruction we used
105 | unlock.convert_to_nop(self.enter_location)
106 |
107 | # Move to the next state, where we look for the pop
108 | self.state = SEHState.LookingForPop
109 |
110 | # Find the next instruction to queue
111 | unlock.queue_prev_block(instr_to_queue)
112 |
113 | return True
114 |
115 | def visit_MLIL_SET_VAR(self, expr):
116 | unlock = self.unlock
117 | log_debug("ExceptionVisitor.visit_MLIL_SET_VAR")
118 | log_debug(f"{unlock.function.start:x} {self.state!r}")
119 |
120 | if self.state == SEHState.NoException and unlock.fs in expr.src.prefix_operands:
121 | self.state = SEHState.PushSeh
122 | return
123 |
124 | if self.state == SEHState.Seh and self.seh:
125 | if NullDerefVisitor().visit(expr.src) == 0:
126 | self.state = SEHState.InException
127 | target = self.seh.pop()
128 | if target:
129 | log_debug(f"Manipulating the stack")
130 | self.enter_location = expr.address
131 | unlock.view.write(
132 | expr.address,
133 | unlock.view.arch.assemble(
134 | f"enter 0xb4, 0\njmp 0x{target:x}", expr.address
135 | ),
136 | )
137 | unlock.target_queue.put(target)
138 | return True
139 |
140 | # not a null deref, so let's check the next instruction
141 | return
142 |
143 | elif self.state == SEHState.LookingForPop:
144 | log_debug(f"looking for pop: {expr}")
145 | if (
146 | expr.dest.storage == unlock.view.arch.get_reg_index("esp")
147 | and expr.src.operation == MediumLevelILOperation.MLIL_ADDRESS_OF
148 | and expr.src.llil.non_ssa_form.operation == LowLevelILOperation.LLIL_ADD
149 | ):
150 | log_debug("Pop found")
151 | self.state = SEHState.NoException
152 | unlock.convert_to_nop(expr.address)
153 | unlock.target_queue.put(expr.function[expr.instr_index + 1].address)
154 | return True
155 | else:
156 | return unlock.visit(expr.src)
157 |
158 | def visit_unwind(self, expr):
159 | log_debug("ExceptionVisitor.visit_unwind")
160 | log_debug(f"{self.unlock.function.start:x} {self.state!r}")
161 |
162 | unlock = self.unlock
163 |
164 | if expr.src.value.type not in (
165 | RegisterValueType.ConstantPointerValue,
166 | RegisterValueType.ConstantValue,
167 | ):
168 | return
169 |
170 | visitor = UnwindVisitor()
171 | is_stack_var = visitor.visit(expr)
172 |
173 | if is_stack_var is not False:
174 | log_debug("Stack manipulation found; Starting unwind...")
175 | self.state = SEHState.Unwinding
176 | next_il = expr.function[expr.instr_index + 1]
177 | unlock.convert_to_nop(is_stack_var)
178 |
179 | patch_value = unlock.view.arch.assemble(
180 | f"jmp 0x{expr.src.value.value:x}", next_il.address
181 | )
182 | if unlock.view.get_instruction_length(next_il.address) >= len(patch_value):
183 | unlock.view.write(next_il.address, patch_value)
184 |
185 | unlock.target_queue.put(next_il.address)
186 | unlock.convert_to_nop(expr.address)
187 |
188 | if hasattr(visitor, "nop_address"):
189 | unlock.convert_to_nop(visitor.nop_address)
190 |
191 | return True
192 | else:
193 | log_debug(f"{next_il.address:x} is not big enough for a patch")
194 | return False
195 |
196 | log_debug("This store does not manipulate the unwind.")
197 | return False
198 |
199 |
200 | class UnwindVisitor(BNILVisitor):
201 | def visit_MLIL_STORE(self, expr):
202 | function = expr.function
203 |
204 | if expr.dest.operation != MediumLevelILOperation.MLIL_VAR:
205 | return False
206 |
207 | dest_ssa = expr.dest.ssa_form.src
208 |
209 | return self.visit(function[function.get_ssa_var_definition(dest_ssa)])
210 |
211 | def visit_MLIL_SET_VAR(self, expr):
212 | return self.visit(expr.src)
213 |
214 | def visit_MLIL_ADD(self, expr):
215 | left = self.visit(expr.left)
216 | right = self.visit(expr.right)
217 |
218 | return left or right
219 |
220 | visit_MLIL_SUB = visit_MLIL_ADD
221 |
222 | def visit_MLIL_CONST(self, expr):
223 | if expr.constant == 0xB8:
224 | log_debug("Found the 0xb8")
225 | self.nop_address = expr.address
226 | return False
227 |
228 | def visit_MLIL_VAR(self, expr):
229 | function = expr.function
230 | var = expr.src
231 | if var.source_type == VariableSourceType.StackVariableSourceType:
232 | return expr.address
233 | else:
234 | var_ssa = expr.ssa_form.src
235 | return self.visit(function[function.get_ssa_var_definition(var_ssa)])
236 |
237 |
--------------------------------------------------------------------------------
/unlock/unlock/logging.py:
--------------------------------------------------------------------------------
1 | from binaryninja import log_debug
2 |
3 | old_log_debug = log_debug
4 |
5 |
6 | def new_log_debug(msg):
7 | old_log_debug(f"[UNLOCK] {msg}")
8 |
9 |
10 | log_debug = new_log_debug
--------------------------------------------------------------------------------
/unlock/unlock/state.py:
--------------------------------------------------------------------------------
1 | from binaryninja import enum
2 |
3 | class SEHState(enum.IntEnum):
4 | NoException = 0
5 | PushSeh = 1
6 | Seh = 2
7 | InException = 3
8 | Unwinding = 4
9 | LookingForPop = 5
--------------------------------------------------------------------------------
/unlock/unlock/unlockvisitor.py:
--------------------------------------------------------------------------------
1 | # This script requires python 3
2 | import operator as op
3 | import time
4 | from functools import partial
5 | from queue import Queue
6 | from threading import Event
7 | from math import floor
8 |
9 | from binaryninja import (
10 | AnalysisCompletionEvent,
11 | Architecture,
12 | ArchitectureHook,
13 | BackgroundTaskThread,
14 | BasicBlock,
15 | BinaryDataNotification,
16 | BinaryReader,
17 | BinaryView,
18 | BranchType,
19 | Function,
20 | FunctionAnalysisSkipOverride,
21 | ILBranchDependence,
22 | InstructionBranch,
23 | InstructionInfo,
24 | LowLevelILBasicBlock,
25 | LowLevelILExpr,
26 | LowLevelILFunction,
27 | LowLevelILOperation,
28 | LowLevelILInstruction,
29 | MediumLevelILBasicBlock,
30 | MediumLevelILFunction,
31 | MediumLevelILInstruction,
32 | MediumLevelILOperation,
33 | PluginCommand,
34 | RegisterValueType,
35 | SectionSemantics,
36 | SSAVariable,
37 | Variable,
38 | VariableSourceType,
39 | enum,
40 | )
41 | from binaryninja import _binaryninjacore as core
42 | from binaryninja import log_debug, log_info, log_warn, worker_enqueue
43 |
44 | from .analysis.analyze_exception_handler import (
45 | analyze_exception_handler_set_var,
46 | analyze_exception_handler_store,
47 | )
48 | from .analysis.analyze_folding import analyze_constant_folding, analyze_goto_folding
49 | from .analysis.analyze_indirect_jump import analyze_indirect_jump, analyze_possible_call
50 | from .analysis.analyze_return import analyze_return
51 | from .analysis.analyze_unconditional_jump import analyze_unconditional_jump
52 | from .bnilvisitor import BNILVisitor
53 | from .logging import log_debug
54 | from .state import SEHState
55 | from .exceptionvisitor import ExceptionVisitor
56 |
57 |
58 | class TargetQueue(Queue):
59 | def put(self, item, block=True, timeout=None):
60 | log_debug(f"putting {item:x} in target queue")
61 | super(TargetQueue, self).put(item, block, timeout)
62 |
63 |
64 | class UnlockVisitor(BNILVisitor, BackgroundTaskThread):
65 | def __init__(self, function: Function, start: int):
66 | BNILVisitor.__init__(self)
67 | BackgroundTaskThread.__init__(self, f"Deobfuscating {start:x}", True)
68 | self._start: int = start
69 | self.function: Function = function
70 | self.view: BinaryView = function.view
71 | self.address_size = self.view.arch.address_size
72 | self.target_queue = TargetQueue()
73 | self.exception_visitors = {
74 | f.start: ExceptionVisitor(self) for f in self.view.functions
75 | }
76 | self.seen = {}
77 | self.prev_phase = 1
78 | self.num_phases = 3
79 | self.phase = 1
80 |
81 | self.target_queue.put(start)
82 |
83 | def run(self):
84 | self.run_time = time.time()
85 | while self.phase:
86 | self.start_time = time.time()
87 | while not self.target_queue.empty():
88 | self.addr = None
89 | while not self.target_queue.empty():
90 | self.addr = self.target_queue.get()
91 | if self.addr is not None:
92 | # Attempt to navigate to the location; if we
93 | # can't, then it's not a valid instruction
94 | # currently
95 | # valid = self.view.navigate(self.view.file.view, self.addr)
96 | log_debug(f"checking validity of {self.addr:x}")
97 | valid = (
98 | self.view.get_functions_containing(self.addr) is not None
99 | )
100 | if not valid:
101 | log_debug(f"{self.addr:x} is not valid")
102 | self.addr = None
103 | continue
104 | else:
105 | break
106 | else:
107 | log_debug("target queue has been exhausted")
108 | break
109 |
110 | log_debug(f"run for {self.addr:x} started")
111 |
112 | # Get a new copy of our Function object, since reanalyzing might
113 | # make dataflow stale
114 | log_debug(f"Getting new function for {self.addr:x}")
115 | self.function = next(
116 | f for f in self.view.get_functions_containing(self.addr)
117 | )
118 |
119 | self.fs = Variable(
120 | self.function,
121 | VariableSourceType.RegisterVariableSourceType,
122 | 0,
123 | self.function.arch.get_reg_index("fs"),
124 | "fs",
125 | )
126 |
127 | il = self.function.get_low_level_il_at(self.addr).mapped_medium_level_il
128 |
129 | mmlil = il.function
130 |
131 | self.progress = f"[Phase {self.phase}] {self.addr:x} in function {self.function.start:x} ({il.instr_index}/{len(list(mmlil.instructions))})"
132 |
133 | while True:
134 | log_debug(
135 | f"[{self.function.start:08x} analyzing {il.instr_index}[{il.address:08x}]: {il}"
136 | )
137 |
138 | # self.function.analysis_skipped = True
139 | self.view.begin_undo_actions()
140 | self.seen[il.address] = self.seen.get(il.address, 0) + 1
141 | process_result = self.visit(il)
142 | self.view.commit_undo_actions()
143 | # self.function.analysis_skipped = False
144 |
145 | # If it's True or False, then we've finished
146 | # processing this path and want to continue
147 | # processing other paths. If it's an integer,
148 | # then that's the next IL instruction index we
149 | # should analyze. If it's None, then just continue
150 | # on to the next instruction.
151 | if isinstance(process_result, bool):
152 | break
153 | elif isinstance(process_result, int):
154 | next_il = process_result
155 | else:
156 | next_il = il.instr_index + 1
157 |
158 | try:
159 | il = mmlil[next_il]
160 | except:
161 | break
162 |
163 | log_debug(f"analysis for {il.address:x} finished")
164 |
165 | # If process_result is True or False, then something
166 | # was modified and we should update.
167 | if process_result is not None:
168 | log_debug("waiting for analysis to finish")
169 | self.view.update_analysis_and_wait()
170 | log_debug("analysis complete")
171 |
172 | # If an analysis forces a phase change, note it
173 | if self.phase != self.prev_phase:
174 | self.end_time = time.time()
175 | log_info(
176 | f"Phase changed from {self.prev_phase} to {self.phase}; Time elapsed: {self.end_time - self.start_time}"
177 | )
178 | self.prev_phase = self.phase
179 | self.start_time = time.time()
180 |
181 | log_debug("target queue is empty")
182 | self.end_time = time.time()
183 |
184 | log_info(
185 | f"Phase {self.phase} complete; Time elapsed: {self.end_time - self.start_time}"
186 | )
187 |
188 | # Iterate the phase. If it hits 0, it will stop
189 | self.prev_phase = self.phase
190 | self.phase = (self.phase + 1) % (self.num_phases + 1)
191 |
192 | for func in self.view.functions:
193 | self.target_queue.put(func.start)
194 |
195 | print(f"Analysis complete; Time elapsed: {time.time() - self.run_time}")
196 |
197 | visit_MLIL_RET = analyze_return
198 | visit_MLIL_RET_HINT = analyze_return
199 |
200 | def visit_MLIL_JUMP(self, expr):
201 | result = self.visit(expr.dest.llil)
202 |
203 | if result is True:
204 | return result
205 |
206 | return self.analyze_indirect_jump(expr)
207 |
208 | def visit_MLIL_JUMP_TO(self, expr):
209 | if self.analyze_possible_call(expr):
210 | return True
211 |
212 | return self.visit(expr.dest.llil)
213 |
214 | visit_MLIL_GOTO = analyze_goto_folding
215 |
216 | def visit_MLIL_STORE(self, expr):
217 | return self.exception_visitors[self.function.start].visit(expr)
218 |
219 | def visit_MLIL_SET_VAR(self, expr):
220 | if self.phase == 1:
221 | return self.exception_visitors[self.function.start].visit(expr)
222 |
223 | elif self.phase > 1:
224 | if expr.src.operation == MediumLevelILOperation.MLIL_VAR and expr.dest == expr.src.src:
225 | self.convert_to_nop(expr.address)
226 | return self.queue_prev_block(expr)
227 |
228 | llil_instr = expr.llil
229 | if llil_instr.operation == LowLevelILOperation.LLIL_SET_REG_SSA:
230 | if not expr.function.llil.get_ssa_reg_uses(llil_instr.dest):
231 | if (
232 | llil_instr.non_ssa_form.operation
233 | == LowLevelILOperation.LLIL_SET_REG
234 | ):
235 | if (
236 | llil_instr.non_ssa_form.src.operation
237 | != LowLevelILOperation.LLIL_POP
238 | ):
239 | self.convert_to_nop(expr.address)
240 | return self.queue_prev_block(expr)
241 | elif expr.src.operation == MediumLevelILOperation.MLIL_VAR:
242 | pop_var = expr.src.ssa_form.src
243 | push_var_def = expr.ssa_form.function.get_ssa_var_definition(pop_var)
244 | if expr.function.get_ssa_var_uses(push_var_def.dest) == [
245 | expr
246 | ]:
247 | self.convert_to_nop(expr.address)
248 | self.convert_to_nop(push_var_def.address)
249 | return self.queue_prev_block(expr)
250 |
251 | return self.visit(expr.src)
252 |
253 | def visit_LLIL_REG_SSA(self, expr):
254 | log_debug("visit_LLIL_REG_SSA")
255 |
256 | if expr.value.type in (
257 | RegisterValueType.ConstantPointerValue,
258 | RegisterValueType.ConstantValue,
259 | ):
260 | return self.analyze_constant_folding(expr)
261 |
262 | def visit_MLIL_SET_VAR_FIELD(self, expr):
263 | if self.phase > 1:
264 | llil_instr = expr.llil
265 | if llil_instr.operation == LowLevelILOperation.LLIL_SET_REG_SSA_PARTIAL:
266 | if not expr.function.llil.get_ssa_reg_uses(llil_instr.full_reg):
267 | if (
268 | llil_instr.non_ssa_form.operation
269 | == LowLevelILOperation.LLIL_SET_REG
270 | ):
271 | if (
272 | llil_instr.non_ssa_form.src.operation
273 | != LowLevelILOperation.LLIL_POP
274 | ):
275 | self.convert_to_nop(expr.address)
276 | return self.queue_prev_block(expr)
277 | return self.visit(expr.src)
278 |
279 | def visit_MLIL_IF(self, expr):
280 | log_debug("visit_MLIL_IF")
281 |
282 | # is this a stosb or something similar? If so,
283 | # find the largest exit index and start there.
284 | exits = self.function.get_low_level_il_exits_at(expr.address)
285 | if len(exits) > 1:
286 | return max(exits) + 1
287 |
288 | return self.analyze_unconditional_jump(expr)
289 |
290 | def visit_MLIL_UNDEF(self, expr):
291 | log_debug("visit_MLIL_UNDEF")
292 | # Nothing to do down this path; just get something
293 | # else from the target queue.
294 | return False
295 |
296 | def visit_LLIL_LOAD_SSA(self, expr):
297 | return self.visit(expr.src)
298 |
299 | def visit_LLIL_ADD(self, expr):
300 | log_debug("visit_LLIL_ADD")
301 | add_value = expr.value
302 | if add_value.type in (
303 | RegisterValueType.ConstantPointerValue,
304 | RegisterValueType.ConstantValue,
305 | ):
306 | log_debug(f"add value is {add_value.value:x}")
307 | return self.analyze_constant_folding(expr.left)
308 | else:
309 | log_debug(f"add value is not constant ptr")
310 |
311 | return
312 |
313 | def visit_LLIL_SUB(self, expr):
314 | log_debug("visit_LLIL_SUB")
315 | sub_value = expr.value
316 | if sub_value.type in (
317 | RegisterValueType.ConstantPointerValue,
318 | RegisterValueType.ConstantValue,
319 | ):
320 | log_debug(f"sub value is {sub_value.value:x}")
321 | return self.analyze_constant_folding(expr.left)
322 | else:
323 | log_debug(f"sub value is not constant ptr")
324 |
325 | return
326 |
327 | def visit_MLIL_SUB(self, expr):
328 | log_debug("visit_MLIL_SUB")
329 |
330 | # This is a top level MLIL_SUB, which means it's probably a cmp instruction
331 | if expr.function[expr.instr_index].operation == MediumLevelILOperation.MLIL_SUB:
332 | self.convert_to_nop(expr.address)
333 | return self.queue_prev_block(expr)
334 |
335 | if expr.left.value.type in (
336 | RegisterValueType.UndeterminedValue,
337 | RegisterValueType.EntryValue,
338 | ):
339 | # Make sure we're not accidentally NOPing a push/pop
340 | # due to the stack being in a bad state due to a weird
341 | # loop
342 | if (
343 | expr.left.operation != MediumLevelILOperation.MLIL_VAR
344 | and expr.left.src.index != self.view.arch.get_reg_index("esp")
345 | ):
346 | self.convert_to_nop(expr.address)
347 | return self.queue_prev_block(expr)
348 |
349 | sub_value = expr.value
350 | if sub_value.type in (
351 | RegisterValueType.ConstantPointerValue,
352 | RegisterValueType.ConstantValue,
353 | ):
354 | log_debug(f"sub value is {sub_value.value:x}")
355 | return self.analyze_constant_folding(expr.left)
356 | else:
357 | log_debug("sub value is not a constant ptr")
358 |
359 | return
360 |
361 | def visit_MLIL_ADD(self, expr):
362 | log_debug("visit_MLIL_ADD")
363 |
364 | if expr.left.value.type in (
365 | RegisterValueType.UndeterminedValue,
366 | RegisterValueType.EntryValue,
367 | ):
368 | self.convert_to_nop(expr.address)
369 |
370 | return self.queue_prev_block(expr)
371 |
372 | add_value = expr.value
373 | if add_value.type in (
374 | RegisterValueType.ConstantPointerValue,
375 | RegisterValueType.ConstantValue,
376 | ):
377 | log_debug(f"add value is {add_value.value:x}")
378 | return self.analyze_constant_folding(expr.left)
379 | else:
380 | log_debug("add value is not a constant ptr")
381 |
382 | return
383 |
384 | def visit_MLIL_CONST(self, expr):
385 | log_debug("visit_MLIL_CONST")
386 |
387 | if expr.llil.operation != LowLevelILOperation.LLIL_CONST:
388 | return self.visit(expr.llil)
389 |
390 | def visit_MLIL_XOR(self, expr):
391 | log_debug("visit_MLIL_XOR")
392 |
393 | # If it's something like `ecx ^ const` and ecx isn't a known
394 | # value, then just erase it. It's not needed at all.
395 | if expr.left.value.type in (
396 | RegisterValueType.UndeterminedValue,
397 | RegisterValueType.EntryValue,
398 | ):
399 | self.convert_to_nop(expr.address)
400 |
401 | return self.queue_prev_block(expr)
402 |
403 | visit_MLIL_AND = visit_MLIL_XOR
404 |
405 | def visit_MLIL_OR(self, expr):
406 | log_debug("visit_MLIL_OR")
407 |
408 | # If it's something like `ecx | 0` then we can NOP it
409 | # and nothing of value is lost
410 | if expr.right.value.type in (
411 | RegisterValueType.ConstantPointerValue,
412 | RegisterValueType.ConstantValue
413 | ) and expr.right.value.value == 0:
414 | self.convert_to_nop(expr.address)
415 |
416 | return self.queue_prev_block(expr)
417 |
418 | def visit_MLIL_TAILCALL(self, expr):
419 | log_debug("visit_MLIL_TAIL_CALL")
420 | return self.visit(expr.dest.llil)
421 |
422 | visit_MLIL_TAILCALL_UNTYPED = visit_MLIL_TAILCALL
423 |
424 | analyze_unconditional_jump = analyze_unconditional_jump
425 | analyze_indirect_jump = analyze_indirect_jump
426 | analyze_goto_folding = analyze_goto_folding
427 | analyze_constant_folding = analyze_constant_folding
428 | analyze_possible_call = analyze_possible_call
429 |
430 | def convert_to_nop(self, address):
431 | log_debug(f"Nopping {address:x}")
432 | self.view.convert_to_nop(address)
433 |
434 | def queue_prev_block(self, expr):
435 | log_debug("queue_prev_block")
436 | if isinstance(expr, MediumLevelILInstruction):
437 | ILBasicBlock = MediumLevelILBasicBlock
438 |
439 | elif isinstance(expr, LowLevelILInstruction):
440 | ILBasicBlock = LowLevelILBasicBlock
441 |
442 | else:
443 | return
444 |
445 | current_bb: ILBasicBlock = next(
446 | bb
447 | for bb in expr.function.basic_blocks
448 | if bb.start <= expr.instr_index < bb.end
449 | )
450 |
451 | log_debug(f"current_bb has {len(current_bb.incoming_edges)} incoming edges")
452 |
453 | if len(current_bb.incoming_edges) != 1:
454 | log_debug("Incoming Edges was not 1, just continuing")
455 | self.target_queue.put(expr.address)
456 | return True
457 |
458 | prev_bb = current_bb.incoming_edges[0].source
459 |
460 | while prev_bb[0].operation in (
461 | LowLevelILOperation.LLIL_JUMP_TO,
462 | MediumLevelILOperation.MLIL_JUMP_TO,
463 | MediumLevelILOperation.MLIL_GOTO,
464 | LowLevelILOperation.LLIL_GOTO,
465 | ):
466 | if len(prev_bb.incoming_edges) != 1:
467 | log_debug("Incoming edges was not 1, stopping here")
468 | break
469 |
470 | log_debug(f"{prev_bb.incoming_edges}")
471 | if prev_bb not in prev_bb.incoming_edges[0].source.dominators:
472 | prev_bb = prev_bb.incoming_edges[0].source
473 | else:
474 | break
475 |
476 | self.target_queue.put(prev_bb.il_function[prev_bb.start].address)
477 | return True
478 |
--------------------------------------------------------------------------------