├── .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 | --------------------------------------------------------------------------------