├── .gitignore ├── README.md ├── SolveCFF.py ├── Symex.py └── Utilities.py /.gitignore: -------------------------------------------------------------------------------- 1 | # The following command works for downloading when using Git for Windows: 2 | # curl -LOf http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore 3 | # 4 | # Download this file using PowerShell v3 under Windows with the following comand: 5 | # Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore 6 | # 7 | # or wget: 8 | # wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.sln.docstates 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Rr]elease/ 18 | x64/ 19 | [Bb]in/ 20 | [Oo]bj/ 21 | # build folder is nowadays used for build scripts and should not be ignored 22 | #build/ 23 | 24 | # NuGet Packages 25 | *.nupkg 26 | # The packages folder can be ignored because of Package Restore 27 | **/packages/* 28 | # except build/, which is used as an MSBuild target. 29 | !**/packages/build/ 30 | # Uncomment if necessary however generally it will be regenerated when needed 31 | #!**/packages/repositories.config 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | *_i.c 38 | *_p.c 39 | *.ilk 40 | *.meta 41 | *.obj 42 | *.pch 43 | *.pdb 44 | *.pgc 45 | *.pgd 46 | *.rsp 47 | *.sbr 48 | *.tlb 49 | *.tli 50 | *.tlh 51 | *.tmp 52 | *.tmp_proj 53 | *.log 54 | *.vspscc 55 | *.vssscc 56 | .builds 57 | *.pidb 58 | *.log 59 | *.scc 60 | *.exe 61 | *.bin 62 | *.pdb 63 | 64 | # OS generated files # 65 | .DS_Store* 66 | Icon? 67 | 68 | # Visual C++ cache files 69 | ipch/ 70 | *.aps 71 | *.ncb 72 | *.opensdf 73 | *.sdf 74 | *.cachefile 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | *.vspx 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | 88 | # TeamCity is a build add-in 89 | _TeamCity* 90 | 91 | # DotCover is a Code Coverage Tool 92 | *.dotCover 93 | 94 | # NCrunch 95 | *.ncrunch* 96 | .*crunch*.local.xml 97 | 98 | # Installshield output folder 99 | [Ee]xpress/ 100 | 101 | # DocProject is a documentation generator add-in 102 | DocProject/buildhelp/ 103 | DocProject/Help/*.HxT 104 | DocProject/Help/*.HxC 105 | DocProject/Help/*.hhc 106 | DocProject/Help/*.hhk 107 | DocProject/Help/*.hhp 108 | DocProject/Help/Html2 109 | DocProject/Help/html 110 | 111 | # Click-Once directory 112 | publish/ 113 | 114 | # Publish Web Output 115 | *.Publish.xml 116 | 117 | # Windows Azure Build Output 118 | csx 119 | *.build.csdef 120 | 121 | # Windows Store app package directory 122 | AppPackages/ 123 | 124 | # Others 125 | *.Cache 126 | ClientBin/ 127 | [Ss]tyle[Cc]op.* 128 | ~$* 129 | *~ 130 | *.dbmdl 131 | *.[Pp]ublish.xml 132 | *.pfx 133 | *.publishsettings 134 | modulesbin/ 135 | tempbin/ 136 | 137 | # EPiServer Site file (VPP) 138 | AppData/ 139 | 140 | # RIA/Silverlight projects 141 | Generated_Code/ 142 | 143 | # Backup & report files from converting an old project file to a newer 144 | # Visual Studio version. Backup files are not needed, because we have git ;-) 145 | _UpgradeReport_Files/ 146 | Backup*/ 147 | UpgradeLog*.XML 148 | UpgradeLog*.htm 149 | 150 | # vim 151 | *.txt~ 152 | *.swp 153 | *.swo 154 | 155 | # Temp files when opening LibreOffice on ubuntu 156 | .~lock.* 157 | 158 | # svn 159 | .svn 160 | 161 | # CVS - Source Control 162 | **/CVS/ 163 | 164 | # Remainings from resolving conflicts in Source Control 165 | *.orig 166 | 167 | # SQL Server files 168 | **/App_Data/*.mdf 169 | **/App_Data/*.ldf 170 | **/App_Data/*.sdf 171 | 172 | 173 | #LightSwitch generated files 174 | GeneratedArtifacts/ 175 | _Pvt_Extensions/ 176 | ModelManifest.xml 177 | 178 | # ========================= 179 | # Windows detritus 180 | # ========================= 181 | 182 | # Windows image file caches 183 | Thumbs.db 184 | ehthumbs.db 185 | 186 | # Folder config file 187 | Desktop.ini 188 | 189 | # Recycle Bin used on file shares 190 | $RECYCLE.BIN/ 191 | 192 | # Mac desktop service store files 193 | .DS_Store 194 | 195 | # SASS Compiler cache 196 | .sass-cache 197 | 198 | # Visual Studio 2014 CTP 199 | **/*.sln.ide 200 | 201 | # Visual Studio temp something 202 | .vs/ 203 | 204 | # dotnet stuff 205 | project.lock.json 206 | 207 | # VS 2015+ 208 | *.vc.vc.opendb 209 | *.vc.db 210 | 211 | # Rider 212 | .idea/ 213 | 214 | # Visual Studio Code 215 | .vscode/ 216 | 217 | # Output folder used by Webpack or other FE stuff 218 | **/node_modules/* 219 | **/wwwroot/* 220 | 221 | # SpecFlow specific 222 | *.feature.cs 223 | *.feature.xlsx.* 224 | *.Specs_*.html 225 | 226 | ##### 227 | # End of core ignore list, below put you custom 'per project' settings (patterns or path) 228 | ##### 229 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Symex 2 | 3 | A symbolic executor for Binary Ninja's MLIL. This was used to successfully solve a control flow flattening challenge. 4 | - Supports SSA form 5 | - Supports state forking 6 | 7 | Additional info: 8 | - Memory is symbolized on load / store 9 | - Z3 is required 10 | -------------------------------------------------------------------------------- /SolveCFF.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from typing import List 3 | import numpy as np 4 | import importlib 5 | import z3 6 | from binaryninja import * 7 | 8 | import symex 9 | import symex.SymbolicEmulator 10 | import symex.utilities 11 | importlib.reload(symex) 12 | importlib.reload(symex.SymbolicEmulator) 13 | importlib.reload(symex.utilities) 14 | from symex.SymbolicEmulator import* 15 | from symex.utilities import* 16 | 17 | def calculate_case_address(addr : np.uint32) -> np.uint64: 18 | # Add the integers. 19 | eax : np.uint32 = addr + np.uint32(0x7CD7D69E) 20 | 21 | # For some reason, numpy ignores integer overflows. 22 | # To handle overflows, we have to instantiate a *new* uint32. 23 | eax = np.uint32(eax) 24 | 25 | # If eax > 0xA, then we jump to the default case. 26 | # Otherwise, compute the switch case value normally. 27 | if eax > np.uint32(0xA): 28 | return np.uint64(0xC2F) 29 | 30 | # Zero extend eax to 64 bits. 31 | # TODO: Stop hardcoding table entries 32 | index = np.uint64(eax) 33 | cases : List[np.uint64] = ( 34 | np.uint64(0xffff9656), 35 | np.uint64(0xffff9a04), 36 | np.uint64(0xffff9b19), 37 | np.uint64(0xffff9b5b), 38 | np.uint64(0xffff9c80), 39 | np.uint64(0xffff9d1f), 40 | np.uint64(0xffff9d73), 41 | np.uint64(0xffff9d79), 42 | np.uint64(0xffff9d9c), 43 | np.uint64(0xffff9fbf), 44 | np.uint64(0xffffa071), 45 | ) 46 | 47 | rax = np.uint32(cases[index]) 48 | rdx = np.uint64(0x75d8) 49 | rdx = rax + rdx 50 | 51 | rdx = rdx - np.uint64(0x100000000) 52 | print("rdx: " + hex(rdx)) 53 | return rdx 54 | 55 | def execute_block(sym: SymbolicEmulator, block: MediumLevelILBasicBlock) -> List[int]: 56 | result : List[int] = [] 57 | for instr in block: 58 | if(instr.operation == MediumLevelILOperation.MLIL_GOTO): 59 | goto : MediumLevelILGoto = instr 60 | result.append(goto.dest) 61 | print(f"Encountered goto: exiting block with dest: {hex(goto.dest)}") 62 | return result 63 | 64 | elif(instr.operation == MediumLevelILOperation.MLIL_IF): 65 | cond : MediumLevelILIf = instr 66 | result.append(cond.operands[1]) 67 | result.append(cond.operands[2]) 68 | print(f"Encountered if operation with dests: {hex(cond.operands[1])}. {hex(cond.operands[2])}") 69 | return result 70 | 71 | elif(instr.operation == MediumLevelILOperation.MLIL_CALL_SSA): 72 | print("Encountered call instruction: skipping.") 73 | continue 74 | 75 | sym.execute_instruction(instr) 76 | return None 77 | 78 | 79 | def initialize(sym : SymbolicEmulator): 80 | execute_block(sym, get_block_by_address(mlil, 0xb19)) 81 | 82 | def run_dispatcher(sym : SymbolicEmulator): 83 | execute_block(sym, get_block_by_address(mlil, 0xbfb)) 84 | 85 | def get_outgoing_edges(sym: SymbolicEmulator, bb : MediumLevelILBasicBlock) -> List[int]: 86 | dests = execute_block(sym, bb) 87 | return dests 88 | 89 | # Symbolically executes all block corresponding a switch case. 90 | def execute_case(sym: SymbolicEmulator, block_address) -> List[int]: 91 | outgoing = get_outgoing_edges(sym, get_block_by_address(mlil, block_address)) 92 | while True: 93 | next = [] 94 | for edge in outgoing: 95 | # Exit if we encounter the dispatcher. 96 | if(edge == 32 or edge == 767): 97 | print("Encountered dispatcher.") 98 | return 99 | 100 | # Collect all pairs of outgoing edges. 101 | for d in get_outgoing_edges(sym, get_block_by_index(mlil, edge)): 102 | if d not in next: 103 | next.append(d) 104 | 105 | # Queue the outgoing edges for execution. 106 | outgoing = next 107 | 108 | # Traverse the symbolic mapping bottom up, 109 | # until we encounter the latest assignment of the state variable. 110 | def get_state_var_name(sym : SymbolicEmulator): 111 | next_name = None 112 | for assignment in sym.expressions[::-1]: 113 | if("var_5c#" in assignment): 114 | next_name = assignment 115 | break 116 | 117 | if next_name == None: 118 | raise ValueError("Failed to find symbolic name: var_5c#") 119 | return next_name 120 | 121 | # Gets all branch destinations which have not been explored. 122 | def get_new_edges(solutions : List[int], incoming_block_addr : int) -> List[int]: 123 | results : List[int] = [] 124 | if incoming_block_addr not in explored_edges: 125 | explored_edges[incoming_block_addr] = [] 126 | 127 | for sol in solutions: 128 | if calculate_case_address(sol) not in explored_edges[incoming_block_addr]: 129 | results.append(sol) 130 | return results 131 | 132 | 133 | # Load the module 134 | bv : BinaryView = BinaryViewType.get_view_of_file("C:\\Users\\colton\\Downloads\\challenge3", update_analysis=True) 135 | 136 | # Get the flattened function. 137 | func : Function = bv.get_function_at(0xB0E) 138 | 139 | # Get the MLIL function in SSA form. 140 | mlil : MediumLevelILFunction = func.mlil.ssa_form 141 | 142 | # Generate SSA form for the function. 143 | mlil.generate_ssa_form() 144 | 145 | # Collect all ssa vars. 146 | ssa_vars : List[SSAVariable] = mlil.ssa_vars 147 | 148 | # Create a symbolic executor. 149 | symbolic_executor = symex.SymbolicEmulator.SymbolicEmulator(func, mlil) 150 | 151 | # Initialize the symbolic executor. 152 | initialize(symbolic_executor) 153 | 154 | # Setup state. 155 | explored_edges : Dict[int, Set[int]] = {} 156 | execution_queue = [] 157 | first_address = 0x1259 158 | 159 | # Perform a DFS, traversing every possible unique path. 160 | while(True): 161 | # Execute the next switch statement case. 162 | print(f"Exploring block {hex(first_address)}") 163 | run_dispatcher(symbolic_executor) 164 | execute_case(symbolic_executor, first_address) 165 | 166 | # Get the state variable's symbolic expression. 167 | if first_address not in explored_edges: 168 | explored_edges[first_address] = [] 169 | next_name = get_state_var_name(symbolic_executor) 170 | next_state = symbolic_executor.variable_mapping[next_name].z3Variable 171 | 172 | # Query all possible values of the state variable. 173 | sols = [] 174 | if(first_address != 0x134b): 175 | sols = solve_for_variable(next_state) 176 | next_solution = None 177 | 178 | # Log all possible values. 179 | print("solutions after executing block: " + hex(first_address)) 180 | for sol in sols: 181 | print(" " + hex(sol)) 182 | 183 | # Query all edges which have not been explored. 184 | new_edges = get_new_edges(sols, first_address) 185 | 186 | l = len(new_edges) 187 | if(l > 2): 188 | raise ValueError("Flattened blocks cannot have more than two outgoing edges.") 189 | elif l == 2: 190 | # Fork & queue the execution state if there are more possible paths to explore. 191 | execution_queue.append(first_address) 192 | execution_queue.append(symbolic_executor.fork()) 193 | next_solution = new_edges[0] 194 | elif l == 1: 195 | # Explore the only viable path. 196 | next_solution = new_edges[0] 197 | else: 198 | # If our current path hits a deadend, then backtrack to another forked state. 199 | print("Searching for unexplored states.") 200 | while True: 201 | if(len(execution_queue) == 0): 202 | raise ValueError("Finished execution: all possible states have been explored.") 203 | 204 | # Pop the latest state fork. 205 | symbolic_executor = execution_queue.pop() 206 | 207 | # Pop the incoming address. 208 | first_address = execution_queue.pop() 209 | 210 | # Pull the latest expression of the state variable. 211 | next_name = get_state_var_name(symbolic_executor) 212 | 213 | # Solve for all possible values of the state variable. 214 | sols = solve_for_variable(symbolic_executor.variable_mapping[next_name].z3Variable) 215 | 216 | # Discard the previous outgoing destination. 217 | next_solution = None 218 | 219 | # Log all possible state variable values. 220 | print("Potential outgoing edges:") 221 | for s in sols: 222 | print(" " + hex(calculate_case_address(s))) 223 | 224 | # Query all outgoin values for the state fork. 225 | new_edges = get_new_edges(sols, first_address) 226 | 227 | # Try to execute the forked state. 228 | l = len(new_edges) 229 | if(l > 1): 230 | raise ValueError("Forked states cannot have more than one unexplored path.") 231 | elif l == 1: 232 | print("Successfully found state fork to pursue.") 233 | next_solution = new_edges[0] 234 | break 235 | else: # this means you hit zero 236 | print("Discarding fork: outgoing edges have already been explored.") 237 | continue 238 | 239 | # Concretize the state variable, and setup state for the next case execution. 240 | explored_edges[first_address].append(calculate_case_address(next_solution)) 241 | symbolic_executor.variable_mapping[next_name] = Z3Variable(next_name, next_solution) 242 | first_address = calculate_case_address(next_solution) 243 | -------------------------------------------------------------------------------- /Symex.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from binaryninja import * 3 | import z3 4 | 5 | nameCount = 0 6 | symbolic_memory_count = 0 7 | phi_count = 0 8 | 9 | def getName(): 10 | global nameCount 11 | text = "generated_" + str(nameCount) 12 | nameCount = nameCount + 1 13 | return text 14 | 15 | def getSymbolicMemoryName(): 16 | global symbolic_memory_count 17 | text = "symbolic_memory" + str(symbolic_memory_count) 18 | symbolic_memory_count = symbolic_memory_count + 1 19 | return text 20 | 21 | def getPhiName(): 22 | global phi_count 23 | text = "phi#" + str(phi_count) 24 | phi_count = phi_count + 1 25 | return text 26 | 27 | class Z3Variable: 28 | def __init__(self, name : str, z3Variable): 29 | self.name : str = name 30 | self.z3Variable = z3Variable 31 | 32 | class SymbolicExecutionEngine: 33 | def __init__(self, func : Function, mlil : MediumLevelILFunction): 34 | self.func = func 35 | self.mlil = mlil 36 | self.solver = z3.Solver() 37 | self.variable_mapping = dict() 38 | self.expressions = [] 39 | 40 | def fork(self): 41 | copy = SymbolicExecutionEngine(self.func, self.mlil) 42 | copy.solver = z3.Solver() 43 | copy.variable_mapping = dict(self.variable_mapping) 44 | copy.expressions = self.expressions.copy() 45 | 46 | return copy 47 | 48 | def execute_instruction(self, instr: MediumLevelILInstruction): 49 | print(f"Executing instruction (address: {hex(instr.address)}) and (operation: {str(instr.operation)})") 50 | 51 | if instr.operation == MediumLevelILOperation.MLIL_SET_VAR_SSA: 52 | return self.handle_set_var_ssa(instr) 53 | elif instr.operation == MediumLevelILOperation.MLIL_SET_VAR_SSA_FIELD: 54 | return self.handle_set_var_ssa_field(instr) 55 | elif instr.operation == MediumLevelILOperation.MLIL_VAR_SSA: 56 | return self.handle_var_ssa(instr) 57 | elif instr.operation == MediumLevelILOperation.MLIL_VAR_SSA_FIELD: 58 | return self.handle_var_ssa_field(instr) 59 | elif instr.operation == MediumLevelILOperation.MLIL_VAR_PHI: 60 | return self.handle_var_phi(instr) 61 | elif instr.operation == MediumLevelILOperation.MLIL_MEM_PHI: 62 | return self.handle_mem_phi(instr) 63 | elif instr.operation == MediumLevelILOperation.MLIL_LOAD_SSA: 64 | return self.handle_load_ssa(instr) 65 | elif instr.operation == MediumLevelILOperation.MLIL_STORE_SSA: 66 | return self.handle_store_ssa(instr) 67 | elif instr.operation == MediumLevelILOperation.MLIL_LOW_PART: 68 | return self.handle_low_part(instr) 69 | elif instr.operation == MediumLevelILOperation.MLIL_SX: 70 | return self.handle_sign_extend(instr) 71 | elif instr.operation == MediumLevelILOperation.MLIL_ZX: 72 | return self.handle_zero_extend(instr) 73 | elif instr.operation == MediumLevelILOperation.MLIL_CONST_PTR: 74 | return self.handle_const_ptr(instr) 75 | elif instr.operation == MediumLevelILOperation.MLIL_CONST: 76 | return self.handle_const(instr) 77 | elif instr.operation == MediumLevelILOperation.MLIL_CMP_NE: 78 | return self.handle_cmp_ne(instr) 79 | elif instr.operation == MediumLevelILOperation.MLIL_CMP_E: 80 | return self.handle_cmp_e(instr) 81 | elif instr.operation == MediumLevelILOperation.MLIL_ADD: 82 | return self.handle_add(instr) 83 | elif instr.operation == MediumLevelILOperation.MLIL_SUB: 84 | return self.handle_sub(instr) 85 | elif instr.operation == MediumLevelILOperation.MLIL_MUL: 86 | return self.handle_mul(instr) 87 | elif instr.operation == MediumLevelILOperation.MLIL_XOR: 88 | return self.handle_xor(instr) 89 | elif instr.operation == MediumLevelILOperation.MLIL_OR: 90 | return self.handle_or(instr) 91 | elif instr.operation == MediumLevelILOperation.MLIL_AND: 92 | return self.handle_and(instr) 93 | elif instr.operation == MediumLevelILOperation.MLIL_NOT: 94 | return self.handle_not(instr) 95 | elif instr.operation == MediumLevelILOperation.MLIL_LSL: 96 | return self.handle_lsl(instr) 97 | elif instr.operation == MediumLevelILOperation.MLIL_LSR: 98 | return self.handle_lsr(instr) 99 | elif instr.operation == MediumLevelILOperation.MLIL_ASR: 100 | return self.handle_asr(instr) 101 | elif instr.operation == MediumLevelILOperation.MLIL_RET: 102 | return self.handle_ret(instr) 103 | else: 104 | text = f"Cannot handle instruction: {str(type(instr))}" 105 | raise ValueError(text) 106 | 107 | def handle_set_var_ssa(self, instr: MediumLevelILSetVarSsa): 108 | z3_dest = self.get_z3_var(instr.dest) 109 | z3_src = self.execute_instruction(instr.src) 110 | self.variable_mapping[z3_dest.name] = z3_src 111 | self.expressions.append(z3_dest.name) 112 | return 113 | 114 | def handle_set_var_ssa_field(self, instr: MediumLevelILSetVarSsaField): 115 | # Get Z3 asts for the operands. 116 | z3_dest = self.get_z3_var(instr.dest) 117 | z3_prev = self.get_z3_var(instr.prev) 118 | z3_src = self.execute_instruction(instr.src) 119 | 120 | # Extract a selection from the source variable, using the provided offset. 121 | extractSize = instr.size * 8 122 | extractOffset = instr.offset * 8 123 | high = (extractOffset) + (extractSize - 1) 124 | low = 0 if extractOffset == 0 else extractOffset - 1 125 | extractedField = z3.Extract(high, low, z3_src.z3Variable) 126 | 127 | # Extract any extra bits above the selection. 128 | extractedLow = None 129 | if low != 0: 130 | extractedLow = z3.Extract(low - 1, 0, z3_prev.z3Variable) 131 | 132 | # Extract any extra bits below the selection. 133 | extractedHigh = None 134 | if high != instr.src.size - 1: 135 | extractedHigh = z3.Extract(instr.src.size * 8 - 1, high + 1, z3_prev.z3Variable) 136 | 137 | # Concatenate the extractions together, in order of lowest index to highest. 138 | finalResult = None 139 | if extractedHigh == None: 140 | finalResult = extractedField 141 | else: 142 | finalResult = z3.Concat(extractedHigh, extractedField) 143 | 144 | # Compute the complete expression. 145 | if extractedLow != None: 146 | finalResult = z3.Concat(finalResult, extractedLow) 147 | 148 | # Store the mapping. 149 | self.variable_mapping[z3_dest.name] = Z3Variable(z3_dest.name, finalResult) 150 | self.expressions.append(z3_dest.name) 151 | return 152 | 153 | def handle_var_ssa(self, instr: MediumLevelILVarSsa): 154 | return self.get_z3_var(instr.src) 155 | 156 | def handle_var_ssa_field(self, instr: MediumLevelILVarSsaField): 157 | # Get a z3 AST for the source variable. 158 | z3_src = self.get_z3_var(instr.src) 159 | 160 | # Extract out a portion of the SSA field. 161 | finalResult = z3.Extract( 162 | ((instr.size + instr.offset) * 8) - 1, 163 | instr.offset * 8, 164 | z3_src.z3Variable 165 | ) 166 | 167 | # Store and return the generated value. 168 | name = getName() 169 | var = Z3Variable(name, finalResult) 170 | self.variable_mapping[name] = var 171 | return var 172 | 173 | def handle_var_phi(self, instr: MediumLevelILVarPhi): 174 | z3_dest = self.get_z3_var(instr.dest) 175 | var : SSAVariable = None 176 | 177 | phi_values = [] 178 | 179 | # Traverse the expression list backwards, 180 | # until we find the latest assignment to the PHI node. 181 | has_found = False 182 | for item in self.expressions[::-1]: 183 | for var in instr.src: 184 | if has_found: 185 | continue 186 | 187 | name = f"{var.name}#{str(var.version)}" 188 | if name not in self.variable_mapping: 189 | continue 190 | 191 | if item == name: 192 | has_found = True 193 | z3Var = self.get_z3_var(var).z3Variable 194 | if var.type.width * 8 > instr.dest.type.width * 8: 195 | z3Var = z3.Extract((instr.dest.type.width * 8) - 1, 0, z3Var) 196 | elif var.type.width * 8 < instr.dest.type.width * 8: 197 | z3Var = z3.ZeroExt((instr.dest.type.width * 8) - (var.type.width * 8), z3Var) 198 | phi_values.append(z3Var) 199 | continue 200 | 201 | if name == z3_dest.name: 202 | continue 203 | 204 | 205 | # If none of the phi node sources have expressions, 206 | # then symbolize the phi node completely. 207 | if len(phi_values) == 0: 208 | print("Symbolizing empty phi variable.") 209 | return z3_dest 210 | 211 | phiName = getPhiName() 212 | symbolic_phi_selector : z3.Int = z3.Int(phiName) 213 | self.variable_mapping[phiName] = Z3Variable(z3_dest.name, symbolic_phi_selector) 214 | 215 | phi_expr = phi_values[0] 216 | if len(phi_values) > 1: 217 | phi_expr = self.build_nested_ite(symbolic_phi_selector, phi_values, 0) 218 | 219 | 220 | self.variable_mapping[z3_dest.name] = Z3Variable(z3_dest.name, phi_expr) 221 | return z3_dest 222 | 223 | def build_nested_ite(self, inputNode, phi_values, index): 224 | count = len(phi_values) 225 | if index + 1 == count: 226 | print("Creating ITE with single value." + str(index)) 227 | return z3.If(inputNode == z3.IntVal(index), phi_values[index], phi_values[index]) 228 | 229 | otherIte = self.build_nested_ite(inputNode, phi_values, index + 1) 230 | return z3.If(inputNode == z3.IntVal(index), phi_values[index], otherIte) 231 | 232 | def handle_mem_phi(self, instr: MediumLevelILMemPhi): 233 | # Note: all memory is symbolic. 234 | return None 235 | 236 | def handle_load_ssa(self, instr: MediumLevelILLoadSsa): 237 | z3_src = self.execute_instruction(instr.src) 238 | 239 | name = getSymbolicMemoryName() 240 | 241 | # For now, we symbolize *all* memory variables. 242 | symbolic_memory = z3.BitVec(name, instr.size * 8) 243 | 244 | result = Z3Variable(name, symbolic_memory) 245 | self.variable_mapping[name] = result 246 | return result 247 | 248 | def handle_store_ssa(self, instr: MediumLevelILStoreSsa): 249 | # Since all memory is symbolized, we do not need to implement stores. 250 | return 251 | 252 | def handle_low_part(self, instr: MediumLevelILLowPart): 253 | z3_input = self.execute_instruction(instr.src) 254 | extract = z3.Extract((instr.size * 8) - 1, 0, z3_input.z3Variable) 255 | name = getName() 256 | result = Z3Variable(name, extract) 257 | self.variable_mapping[name] = result 258 | return result 259 | 260 | def handle_const_ptr(self, instr: MediumLevelILConstPtr): 261 | name = f"const_ptr_{str(instr.constant)}" 262 | z3Integer = z3.BitVecVal(instr.constant, instr.size * 8) 263 | result = Z3Variable(name, z3Integer) 264 | self.variable_mapping[name] = result 265 | return result 266 | 267 | def handle_const(self, instr: MediumLevelILConst): 268 | name = f"const_{str(instr.constant)}" 269 | z3Integer = z3.BitVecVal(instr.constant, instr.size * 8) 270 | result = Z3Variable(name, z3Integer) 271 | self.variable_mapping[name] = result 272 | return result 273 | 274 | def handle_cmp_ne(self, instr: MediumLevelILCmpNe): 275 | z3_left = self.execute_instruction(instr.left) 276 | z3_right = self.execute_instruction(instr.right) 277 | cmpne = z3.If(z3_left.z3Variable != z3_right.z3Variable, z3.BitVecVal(1, instr.size * 8), z3.BitVecVal(0, instr.size * 8)) 278 | name = getName() 279 | result = Z3Variable(name, cmpne) 280 | self.variable_mapping[name] = result 281 | return result 282 | 283 | def handle_cmp_e(self, instr: MediumLevelILCmpNe): 284 | z3_left = self.execute_instruction(instr.left) 285 | z3_right = self.execute_instruction(instr.right) 286 | cmpe = z3.If(z3_left.z3Variable == z3_right.z3Variable, z3.BitVecVal(1, instr.size * 8), z3.BitVecVal(0, instr.size * 8)) 287 | name = getName() 288 | result = Z3Variable(name, cmpe) 289 | self.variable_mapping[name] = result 290 | return result 291 | 292 | def handle_add(self, instr: MediumLevelILAdd): 293 | z3_left = self.execute_instruction(instr.left) 294 | z3_right = self.execute_instruction(instr.right) 295 | addition = z3.BitVecRef(z3.Z3_mk_bvadd(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 296 | name = getName() 297 | result = Z3Variable(name, addition) 298 | self.variable_mapping[name] = result 299 | return result 300 | 301 | def handle_sub(self, instr: MediumLevelILSub): 302 | z3_left = self.execute_instruction(instr.left) 303 | z3_right = self.execute_instruction(instr.right) 304 | sub = z3.BitVecRef(z3.Z3_mk_bvsub(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 305 | name = getName() 306 | result = Z3Variable(name, sub) 307 | self.variable_mapping[name] = result 308 | return result 309 | 310 | def handle_mul(self, instr: MediumLevelILMul): 311 | z3_left = self.execute_instruction(instr.left) 312 | z3_right = self.execute_instruction(instr.right) 313 | mul = z3.BitVecRef(z3.Z3_mk_bvmul(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 314 | name = getName() 315 | result = Z3Variable(name, mul) 316 | self.variable_mapping[name] = result 317 | return result 318 | 319 | def handle_xor(self, instr: MediumLevelILXor): 320 | z3_left = self.execute_instruction(instr.left) 321 | z3_right = self.execute_instruction(instr.right) 322 | xor = z3.BitVecRef(z3.Z3_mk_bvxor(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 323 | name = getName() 324 | result = Z3Variable(name, xor) 325 | self.variable_mapping[name] = result 326 | return result 327 | 328 | def handle_or(self, instr: MediumLevelILXor): 329 | z3_left = self.execute_instruction(instr.left) 330 | z3_right = self.execute_instruction(instr.right) 331 | bvor = z3.BitVecRef(z3.Z3_mk_bvor(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 332 | name = getName() 333 | result = Z3Variable(name, bvor) 334 | self.variable_mapping[name] = result 335 | return result 336 | 337 | def handle_and(self, instr: MediumLevelILAnd): 338 | z3_left = self.execute_instruction(instr.left) 339 | z3_right = self.execute_instruction(instr.right) 340 | bvand = z3.BitVecRef(z3.Z3_mk_bvand(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 341 | name = getName() 342 | result = Z3Variable(name, bvand) 343 | self.variable_mapping[name] = result 344 | return result 345 | 346 | def handle_not(self, instr: MediumLevelILNot): 347 | src = self.execute_instruction(instr.src) 348 | bvnot = z3.BitVecRef(z3.Z3_mk_bvnot(z3.main_ctx().ref(), src.z3Variable.as_ast())) 349 | name = getName() 350 | result = Z3Variable(name, bvnot) 351 | self.variable_mapping[name] = result 352 | return result 353 | 354 | def handle_sign_extend(self, instr: MediumLevelILSx): 355 | z3_input = self.execute_instruction(instr.src) 356 | bvsx = z3.SignExt((instr.size * 8) - (instr.src.size * 8), z3_input.z3Variable) 357 | name = getName() 358 | result = Z3Variable(name, bvsx) 359 | self.variable_mapping[name] = result 360 | return result 361 | 362 | def handle_zero_extend(self, instr: MediumLevelILSx): 363 | z3_input = self.execute_instruction(instr.src) 364 | bvzx = z3.ZeroExt((instr.size * 8) - (instr.src.size * 8), z3_input.z3Variable) 365 | name = getName() 366 | result = Z3Variable(name, bvzx) 367 | self.variable_mapping[name] = result 368 | return result 369 | 370 | def handle_lsl(self, instr: MediumLevelILLsl): 371 | z3_left = self.execute_instruction(instr.left) 372 | z3_right = self.execute_instruction(instr.right) 373 | lsl = z3.BitVecRef(z3.Z3_mk_bvshl(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 374 | name = getName() 375 | result = Z3Variable(name, lsl) 376 | self.variable_mapping[name] = result 377 | return result 378 | 379 | def handle_lsr(self, instr: MediumLevelILLsr): 380 | z3_left = self.execute_instruction(instr.left) 381 | z3_right = self.execute_instruction(instr.right) 382 | lshr = z3.LShR(z3_left.z3Variable, z3_right.z3Variable) 383 | name = getName() 384 | result = Z3Variable(name, lshr) 385 | self.variable_mapping[name] = result 386 | return result 387 | 388 | def handle_asr(self, instr: MediumLevelILAsr): 389 | z3_left = self.execute_instruction(instr.left) 390 | z3_right = self.execute_instruction(instr.right) 391 | asr = z3.BitVecRef(z3.Z3_mk_bvashr(z3.main_ctx().ref(), z3_left.z3Variable.as_ast(), z3_right.z3Variable.as_ast())) 392 | name = getName() 393 | result = Z3Variable(name, asr) 394 | self.variable_mapping[name] = result 395 | return result 396 | 397 | def handle_ret(self, instr: MediumLevelILRet): 398 | print("Ignoring ret.") 399 | return 400 | 401 | def get_z3_var(self, var : SSAVariable): 402 | name = f"{var.name}#{str(var.version)}" 403 | 404 | # If we have already declared a symbolic variable, then grab it from the mapping. 405 | if name in self.variable_mapping: 406 | retValue = self.variable_mapping[name] 407 | return retValue 408 | 409 | # Otherwise, a placeholder symbolic value. 410 | result: Z3Variable = None 411 | typeof = type(var.type) 412 | if typeof == PointerType : 413 | z3Pointer : z3.BitVecRef = z3.BitVec(name, var.type.width * 8) 414 | result = Z3Variable(name, z3Pointer) 415 | self.variable_mapping[name] = result 416 | 417 | elif typeof == IntegerType: 418 | z3Integer = z3.BitVec(name, var.type.width * 8) 419 | result = Z3Variable(name, z3Integer) 420 | self.variable_mapping[name] = result 421 | else: 422 | text = f"Cannot convert the following type to a z3 var: {str(typeof)}" 423 | print(text) 424 | raise ValueError(text) 425 | 426 | print("Created z3 variable: " + name) 427 | return result 428 | 429 | def get_mem_z3_var(self, index, size): 430 | name = f"mem#{str(index)}" 431 | 432 | # If we have already declared a symbolic variable, then grab it from the mapping. 433 | if name in self.variable_mapping: 434 | retValue = self.variable_mapping[name] 435 | return retValue 436 | 437 | z3MemPointer : z3.BitVecRef = z3.BitVec(name, size) 438 | result : Z3Variable = Z3Variable(name, z3MemPointer) 439 | self.variable_mapping[name] = result 440 | return result 441 | -------------------------------------------------------------------------------- /Utilities.py: -------------------------------------------------------------------------------- 1 | from binaryninja import * 2 | import z3 3 | 4 | # Solves for all possible variable solutions, up to a limit of two. 5 | def solve_for_variable(z3Var) -> List[int]: 6 | solutions = [] 7 | last_solution = None 8 | s = z3.Solver() 9 | while True: 10 | if last_solution != None: 11 | s.add(z3Var != last_solution) 12 | 13 | check = str(s.check()) 14 | if(check == "unsat"): 15 | print("Found all possible solutions.") 16 | break 17 | 18 | if(len(solutions) > 2): 19 | print("Found too many solutions") 20 | break 21 | 22 | m : z3.ModelRef = s.model() 23 | evaluation : z3.BitVecNumRef = m.eval(z3Var, model_completion=True) 24 | last_solution = evaluation 25 | solutions.append(evaluation.as_long()) 26 | 27 | return solutions 28 | --------------------------------------------------------------------------------