def emul_variable_instr(self, instr, state): if instr.name in ['get_local', 'get_global']: instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name) state.ssa_stack.append(instr) self.ssa_counter += 1 elif instr.name in ['set_local', 'set_global']: state.ssa_stack.pop() instr.ssa = SSA(method_name=instr.name) elif instr.name == 'tee_local': state.ssa_stack.append(state.ssa_stack[-1]) self.ssa_counter += 1 return False
def emul_parametric_instr(self, instr, state): if instr.name == 'drop': state.ssa_stack.pop() instr.ssa = SSA(method_name=instr.name) else: # select arg = [ state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop() ] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=arg) state.ssa_stack.append(instr) self.ssa_counter += 1 return False
def ssa_system_instruction(self, instr, state): halt = False if instr.name == 'CREATE': args = [ state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop() ] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=args) state.ssa_stack.append(instr) self.ssa_counter += 1 elif instr.name in ('CALL', 'CALLCODE', 'DELEGATECALL', 'STATICCALL'): if instr.name in ('CALL', 'CALLCODE'): gas, to, value, meminstart, meminsz, memoutstart, memoutsz = \ state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop() args = [ gas, to, value, meminstart, meminsz, memoutstart, memoutsz ] else: gas, to, meminstart, meminsz, memoutstart, memoutsz = \ state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop() args = [gas, to, meminstart, meminsz, memoutstart, memoutsz] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=args) state.ssa_stack.append(instr) self.ssa_counter += 1 elif instr.name in ['RETURN', 'REVERT']: offset, length = state.ssa_stack.pop(), state.ssa_stack.pop() instr.ssa = SSA(method_name=instr.name, args=[offset, length]) halt = True elif instr.name in ['INVALID', 'SELFDESTRUCT']: # SSA STACK instr.ssa = SSA(method_name=instr.name) halt = True return halt
def emul_sha3_instruction(self, instr, state): '''Symbolic execution of SHA3 group of opcode''' # SSA STACK s0, s1 = state.ssa_stack.pop(), state.ssa_stack.pop() instr.ssa = SSA(self.ssa_counter, instr.name, args=[s0, s1]) state.ssa_stack.append(instr) self.ssa_counter += 1
def emul_constant_instr(self, instr, state): op = int.from_bytes(instr.operand, byteorder='big') instr.ssa = SSA(self.ssa_counter, instr.name, op, instr_type=SSA_TYPE_CONSTANT) state.ssa_stack.append(instr) self.ssa_counter += 1 return False
def emul_conversion_instr(self, instr, state): arg = [state.ssa_stack.pop()] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=arg) state.ssa_stack.append(instr) self.ssa_counter += 1 return False
def ssa_block_instruction(self, instr, state): if instr.name == 'BLOCKHASH': # SSA STACK blocknumber = state.ssa_stack.pop() instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=[blocknumber]) state.ssa_stack.append(instr) self.ssa_counter += 1 elif instr.name in [ 'COINBASE', 'TIMESTAMP', 'NUMBER', 'DIFFICULTY', 'GASLIMIT' ]: instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name) state.ssa_stack.append(instr) self.ssa_counter += 1
def emul_logical_i64_instr(self, instr, state): if instr.name == 'i64.eqz': arg = [state.ssa_stack.pop()] else: arg = [state.ssa_stack.pop(), state.ssa_stack.pop()] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=arg) state.ssa_stack.append(instr) self.ssa_counter += 1 return False
def emul_arithmetic_f64_instr(self, instr, state): if instr.name in ['f64.abs', 'f64.neg']: arg = [state.ssa_stack.pop()] else: arg = [state.ssa_stack.pop(), state.ssa_stack.pop()] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=arg) state.ssa_stack.append(instr) self.ssa_counter += 1 return False
def emul_arithmetic_i32_instr(self, instr, state): if instr.name in ['i32.clz', 'i32.ctz', 'i32.popcnt']: arg = [state.ssa_stack.pop()] else: arg = [state.ssa_stack.pop(), state.ssa_stack.pop()] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=arg) state.ssa_stack.append(instr) self.ssa_counter += 1 return False
def emul_memory_instr(self, instr, state): # load if 'load' in instr.name: arg = [state.ssa_stack.pop()] instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=arg) state.ssa_stack.append(instr) self.ssa_counter += 1 elif 'store' in instr.name: arg = [state.ssa_stack.pop(), state.ssa_stack.pop()] instr.ssa = SSA(method_name=instr.name, args=arg) elif instr.name == 'current_memory': instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name) state.ssa_stack.append(instr) self.ssa_counter += 1 else: instr.ssa = SSA(method_name=instr.name) return False
def ssa_environmental_instruction(self, instr, state): if instr.name in [ 'ADDRESS', 'ORIGIN', 'CALLER', 'CALLVALUE', 'CALLDATASIZE', 'CODESIZE', 'RETURNDATASIZE', 'GASPRICE' ]: # SSA STACK instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name) state.ssa_stack.append(instr) self.ssa_counter += 1 elif instr.name in ['BALANCE', 'CALLDATALOAD', 'EXTCODESIZE']: # SSA STACK s0 = state.ssa_stack.pop() instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=[s0]) state.ssa_stack.append(instr) self.ssa_counter += 1 elif instr.name in ['CALLDATACOPY', 'CODECOPY', 'RETURNDATACOPY']: op0, op1, op2 = state.ssa_stack.pop(), state.ssa_stack.pop( ), state.ssa_stack.pop() # SSA STACK instr.ssa = SSA(method_name=instr.name, args=[op0, op1, op2]) elif instr.name == 'EXTCODECOPY': addr = state.ssa_stack.pop() start, s2, size = state.ssa_stack.pop(), state.ssa_stack.pop( ), state.ssa_stack.pop() # SSA STACK instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=[addr, start, s2, size]) state.ssa_stack.append(instr) self.ssa_counter += 1
def emul_comparaison_logic_instruction(self, instr, state): if instr.name in [ 'LT', 'GT', 'SLT', 'SGT', 'EQ', 'AND', 'OR', 'XOR', 'BYTE' ]: args = [state.ssa_stack.pop(), state.ssa_stack.pop()] elif instr.name in ['ISZERO', 'NOT']: args = [state.ssa_stack.pop()] # SSA emulation instr.ssa = SSA(self.ssa_counter, instr.name, args=args) state.ssa_stack.append(instr) self.ssa_counter += 1 # Symbolic Execution emulation if self.symbolic_exec: result = self.simplify_ssa.symbolic_dispatcher(instr.name, args) state.stack.append(result)
def emul_arithmetic_instruction(self, instr, state): if instr.name in [ 'ADD', 'SUB', 'MUL', 'DIV', 'MOD', 'SDIV', 'SMOD', 'EXP', 'SIGNEXTEND' ]: args = [state.ssa_stack.pop(), state.ssa_stack.pop()] elif instr.name in ['ADDMOD', 'MULMOD']: args = [ state.ssa_stack.pop(), state.ssa_stack.pop(), state.ssa_stack.pop() ] # SSA emulation instr.ssa = SSA(self.ssa_counter, instr.name, args=args) state.ssa_stack.append(instr) self.ssa_counter += 1 # Symbolic Execution emulation if self.symbolic_exec: result = self.simplify_ssa.symbolic_dispatcher(instr.name, args) state.stack.append(result)
def emul_control_instr(self, instr, state, depth): halt = False if instr.name == 'unreachable': instr.ssa = SSA(method_name=instr.name) halt = True elif instr.name in ['nop', 'block', 'loop', 'else']: instr.ssa = SSA(method_name=instr.name) elif instr.name == 'if': arg = [state.ssa_stack.pop()] instr.ssa = SSA(method_name=instr.name, args=arg) # TODO branch if # inst + 1 == true block # need to find offset false block using edges or basicblocks list logging.warning('SSA: branch if not yet supported') elif instr.name == 'end': instr.ssa = SSA(method_name=instr.name) # check if it's the last instructions of the function if instr.offset == self.current_f_instructions[-1].offset: logging.warning("[X] break %s" % instr.name) halt = True elif instr.name == 'br': instr.ssa = SSA(method_name=instr.name) jump_addr = instr.xref # get instruction with this value as offset target = next( filter(lambda element: element.offset == jump_addr[0], self.current_f_instructions)) if target.offset not in state.instructions_visited: # condition are True logging.warning('[X] follow br branch offset 0x%x' % (target.offset)) new_state = copy.deepcopy(state) new_state.pc = self.current_f_instructions.index(target) # follow the br self.emulate(new_state, depth=depth + 1) else: logging.warning('[X] Loop detected, skipping br 0x%x' % jump_addr[0]) halt = True halt = True elif instr.name == 'br_if': arg = [state.ssa_stack.pop()] instr.ssa = SSA(method_name=instr.name, args=arg) logging.warning('[X] follow br_if default branch offset 0x%x' % (instr.offset_end + 1)) new_state = copy.deepcopy(state) self.emulate(new_state, depth=depth + 1) # after we return from emul - restore current_basicblock self.current_basicblock = self.basicblock_per_instr[instr.offset] jump_addr = instr.xref # get instruction with this value as offset target = next( filter(lambda element: element.offset == jump_addr[0], self.current_f_instructions)) if target.offset not in state.instructions_visited: # condition are True logging.warning('[X] follow br_if branch offset 0x%x' % (target.offset)) new_state = copy.deepcopy(state) new_state.pc = self.current_f_instructions.index(target) # follow the br_if self.emulate(new_state, depth=depth + 1) else: logging.warning('[X] Loop detected, skipping br_if 0x%x' % jump_addr[0]) halt = True halt = True elif instr.name == 'br_table': arg = [state.ssa_stack.pop()] instr.ssa = SSA(method_name=instr.name, args=arg) # TODO branch br_table logging.warning('SSA: branch br_table not yet supported') elif instr.name == 'return': arg = [state.ssa_stack.pop()] instr.ssa = SSA(method_name=instr.name, args=arg) halt = True elif instr.name == 'call': f_offset = int.from_bytes(instr.operand, 'big') target_func = self.ana.func_prototypes[f_offset] name, param_str, return_str, f_type = target_func # format_func_name() instr.ssa = SSA(method_name=instr.name + '_to_' + name) if param_str: num_arg = len(param_str.split(' ')) #print(hex(state.ssa_stack[-1].offset)) arg = [state.ssa_stack.pop() for x in range(1, num_arg + 1)] instr.ssa.args = arg if return_str: instr.ssa.new_assignement = self.ssa_counter state.ssa_stack.append(instr) self.ssa_counter += 1 elif instr.name == 'call_indirect': arg = [state.ssa_stack.pop()] ''' # issue when table is imported # arg is constant if arg[0].ssa.instr_type == SSA_TYPE_CONSTANT: f_offset = int.from_bytes(instr.operand, 'big') target_func = self.ana.func_prototypes[f_offset] name, param_str, return_str, f_type = target_func # format_func_name() instr.ssa = SSA(method_name=instr.name + '_to_' + name) if param_str: num_arg = len(param_str.split(' ')) print(hex(state.ssa_stack[-1].offset)) arg = [state.ssa_stack.pop() for x in range(1, num_arg+1)] instr.ssa.args = arg if return_str: instr.ssa.new_assignement = self.ssa_counter state.ssa_stack.append(instr) self.ssa_counter += 1 else: ''' instr.ssa = SSA(method_name=instr.name, args=arg) # test if arg is constant --> do like call # else - stay like that return halt
def ssa_stack_memory_storage_flow_instruction(self, instr, state, depth): halt = False op = instr.name if op == 'POP': # SSA STACK s0 = state.ssa_stack.pop() instr.ssa = SSA(method_name=instr.name) elif op in ['MLOAD', 'SLOAD']: # SSA STACK s0 = state.ssa_stack.pop() instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=[s0]) state.ssa_stack.append(instr) self.ssa_counter += 1 elif op in ['MSTORE', 'MSTORE8', 'SSTORE']: # SSA STACK s0, s1 = state.ssa_stack.pop(), state.ssa_stack.pop() instr.ssa = SSA(method_name=instr.name, args=[s0, s1]) elif op == 'JUMP': # SSA STACK push_instr = state.ssa_stack.pop() instr.ssa = SSA(method_name=instr.name, args=[push_instr]) # get instruction with this value as offset if push_instr.ssa.is_constant: #jump_addr = int.from_bytes(push_instr.operand, byteorder='big') jump_addr = push_instr.operand_interpretation # get instruction with this value as offset target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) else: # try to resolve the SSA repr jump_addr = self.simplify_ssa.resolve_instr_ssa(push_instr) target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) if not jump_addr: logging.warning('JUMP DYNAMIC') logging.warning('[X] push_instr %x: %s ' % (push_instr.offset, push_instr.name)) logging.warning('[X] push_instr.ssa %s' % push_instr.ssa.format()) list_args = [ arg.ssa.format() for arg in push_instr.ssa.args ] logging.warning('[X] push_instr.ssa %s' % list_args) return True # depth of 1 - prevent looping #if (depth < self.max_depth): if target.name != "JUMPDEST": logging.info('[X] Bad JUMP to 0x%x' % jump_addr) return True if target.offset not in state.instructions_visited: logging.info('[X] follow JUMP branch offset 0x%x' % target.offset) new_state = copy.deepcopy(state) new_state.pc = self.instructions.index(target) #state.pc = self.instructions.index(target) # follow the JUMP self.edges.append( Edge(self.current_basicblock.name, 'block_%x' % target.offset, EDGE_UNCONDITIONAL)) self.emulate(new_state, depth=depth + 1) halt = True else: #logging.info('[X] Max depth reached, skipping JUMP 0x%x' % jump_addr) self.edges.append( Edge(self.current_basicblock.name, 'block_%x' % target.offset, EDGE_UNCONDITIONAL)) logging.info('[X] Loop detected, skipping JUMP 0x%x' % jump_addr) halt = True self.current_basicblock = self.basicblock_per_instr[instr.offset] elif op == 'JUMPI': # SSA STACK push_instr, condition = state.ssa_stack.pop(), state.ssa_stack.pop( ) instr.ssa = SSA(method_name=instr.name, args=[push_instr, condition]) logging.info('[X] follow JUMPI default branch offset 0x%x' % (instr.offset_end + 1)) new_state = copy.deepcopy(state) self.edges.append( Edge(self.current_basicblock.name, 'block_%x' % (instr.offset_end + 1), EDGE_CONDITIONAL_FALSE)) self.emulate(new_state, depth=depth + 1) self.current_basicblock = self.basicblock_per_instr[instr.offset] # get instruction with this value as offset if push_instr.ssa.is_constant: #jump_addr = int.from_bytes(push_instr.operand, byteorder='big') jump_addr = push_instr.operand_interpretation # get instruction with this value as offset target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) else: # try to resolve the SSA repr jump_addr = self.simplify_ssa.resolve_instr_ssa(push_instr) target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) if not jump_addr: logging.warning('JUMP DYNAMIC') logging.warning('[X] push_instr %x: %s ' % (push_instr.offset, push_instr.name)) logging.warning('[X] push_instr.ssa %s' % push_instr.ssa.format()) list_args = [ arg.ssa.format() for arg in push_instr.ssa.args ] logging.warning('[X] push_instr.ssa %s' % list_args) return True if target.name != "JUMPDEST": logging.info('[X] Bad JUMP to 0x%x' % jump_addr) return True if target.offset not in state.instructions_visited: # condition are True logging.info('[X] follow JUMPI branch offset 0x%x' % (target.offset)) new_state = copy.deepcopy(state) new_state.pc = self.instructions.index(target) # follow the JUMPI self.edges.append( Edge(self.current_basicblock.name, 'block_%x' % target.offset, EDGE_CONDITIONAL_TRUE)) self.emulate(new_state, depth=depth + 1) else: self.edges.append( Edge(self.current_basicblock.name, 'block_%x' % target.offset, EDGE_CONDITIONAL_TRUE)) logging.warning('[X] Loop detected, skipping JUMPI 0x%x' % jump_addr) logging.warning('[X] push_instr.ssa %s' % push_instr.ssa.format()) halt = True halt = True elif op in ['PC', 'MSIZE', 'GAS']: # SSA STACK instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name) state.ssa_stack.append(instr) self.ssa_counter += 1 elif op == 'JUMPDEST': # SSA STACK instr.ssa = SSA(method_name=instr.name) return halt
def emulate_one_instruction(self, instr, state, depth): halt = False # # 0s: Stop and Arithmetic Operations # if instr.name == 'STOP': if self.ssa: instr.ssa = SSA(method_name=instr.name) halt = True elif instr.is_arithmetic: self.emul_arithmetic_instruction(instr, state) # # 10s: Comparison & Bitwise Logic Operations # elif instr.is_comparaison_logic: self.emul_comparaison_logic_instruction(instr, state) # # 20s: SHA3 # elif instr.is_sha3: self.emul_sha3_instruction(instr, state) # # 30s: Environment Information # elif instr.is_environmental: self.ssa_environmental_instruction(instr, state) # # 40s: Block Information # elif instr.uses_block_info: self.ssa_block_instruction(instr, state) # # 50s: Stack, Memory, Storage, and Flow Information # elif instr.uses_stack_block_storage_info: halt = self.ssa_stack_memory_storage_flow_instruction( instr, state, depth) # # 60s & 70s: Push Operations # elif instr.name.startswith("PUSH"): #value = int.from_bytes(instr.operand, byteorder='big') instr.ssa = SSA(self.ssa_counter, instr.name, instr.operand_interpretation, instr_type=SSA_TYPE_CONSTANT) state.ssa_stack.append(instr) self.ssa_counter += 1 # # 80s: Duplication Operations # elif instr.name.startswith('DUP'): # DUPn (eg. DUP1: a b c -> a b c c, DUP3: a b c -> a b c a) position = instr.pops # == XX from DUPXX try: # SSA STACK instr.ssa = SSA(new_assignement=self.ssa_counter, method_name=instr.name, args=[state.ssa_stack[-position]]) state.ssa_stack.append(state.ssa_stack[-position]) self.ssa_counter += 1 halt = False except: logging.info('[-] STACK underflow') halt = True # # 90s: Swap Operations # elif instr.name.startswith('SWAP'): # SWAPn (eg. SWAP1: a b c d -> a b d c, SWAP3: a b c d -> d b c a) position = instr.pops - 1 # == XX from SWAPXX try: temp = state.ssa_stack[-position - 1] state.ssa_stack[-position - 1] = state.ssa_stack[-1] state.ssa_stack[-1] = temp instr.ssa = SSA(method_name=instr.name, args=[temp]) halt = False except: logging.warning('[-] STACK underflow') halt = True #raise ValueError('STACK underflow') # # a0s: Logging Operations # elif instr.name.startswith('LOG'): # only stack operations emulated arg = [state.ssa_stack.pop() for x in range(instr.pops)] instr.ssa = SSA(method_name=instr.name, args=arg) #state.ssa_stack.append(instr) # # f0s: System Operations # elif instr.is_system: halt = self.ssa_system_instruction(instr, state) #ssa.append(instr.name) # UNKNOWN INSTRUCTION else: logging.warning('UNKNOWN = ' + instr.name) return halt