def enumerate_basicblocks_edges(instructions): """ Return a list of basicblock after statically parsing given instructions """ basicblocks = list() edges = list() xrefs = enumerate_xref(instructions) # create the first block new_block = True for inst in instructions: if new_block: block = BasicBlock(start_offset=inst.offset, start_instr=inst, name='block_%x' % inst.offset) new_block = False # add current instruction to the basicblock block.instructions.append(inst) # next instruction in xrefs list if (inst.offset_end + 1) in xrefs: # absolute JUMP if inst.is_branch_unconditional: edges.append( Edge(block.name, 'block_%x' % xref_of_instr(inst), EDGE_UNCONDITIONAL)) # conditionnal JUMPI / JUMPIF / ... elif inst.is_branch_conditional: edges.append( Edge(block.name, 'block_%x' % xref_of_instr(inst), EDGE_CONDITIONAL_TRUE)) edges.append( Edge(block.name, 'block_%x' % (inst.offset_end + 1), EDGE_CONDITIONAL_FALSE)) # Halt instruction : RETURN, STOP, RET, ... elif inst.is_halt: pass # just falls to the next instruction else: edges.append( Edge(block.name, 'block_%x' % (inst.offset_end + 1), EDGE_FALLTHROUGH)) block.end_offset = inst.offset_end block.end_instr = inst basicblocks.append(block) new_block = True # add the last block basicblocks.append(block) edges = list(set(edges)) return (basicblocks, edges)
def get_functions_call_edges(self): nodes = list() edges = list() if not self.analyzer: self.analyzer = WasmModuleAnalyzer(self.module_bytecode) if not self.functions: self.functions = enum_func(self.module_bytecode) # create nodes for name, param_str, return_str in self.analyzer.func_prototypes: nodes.append(format_func_name(name, param_str, return_str)) log.info('nodes: %s', nodes) # create edges tmp_edges = enum_func_call_edges(self.functions, len(self.analyzer.imports_func)) # tmp_edges = [(node_from, node_to), (...), ...] for node_from, node_to in tmp_edges: # node_from name, param, ret = self.analyzer.func_prototypes[node_from] from_final = format_func_name(name, param, ret) # node_to name, param, ret = self.analyzer.func_prototypes[node_to] to_final = format_func_name(name, param, ret) edges.append(Edge(from_final, to_final, EDGE_CALL)) log.info('edges: %s', edges) return (nodes, edges)
def enumerate_edges_statically(basicblocks): edges = list() for block in basicblocks: # JMP if block.is_unconditional: jmp_instr = block.end_instr edges.append(Edge(block.name, 'block_%x' % xref_of_instr(jmp_instr), EDGE_UNCONDITIONAL)) # JMPIF, JMPIFNOT elif block.is_conditional: jmp_instr = block.end_instr edges.append(Edge(block.name, 'block_%x' % xref_of_instr(jmp_instr), EDGE_CONDITIONAL_TRUE)) edges.append(Edge(block.name, 'block_%x' % (block.end_offset + 1), EDGE_CONDITIONAL_FALSE)) elif block.is_fallthrough: edges.append(Edge(block.name, 'block_%x' % (block.end_offset + 1), EDGE_FALLTHROUGH)) edges = list(set(edges)) return edges
def emulate(self, state=EthereumVMstate(), depth=0): # create fake stack for test state.symbolic_stack = list(range(1000)) # get current instruction instr = self.reverse_instructions[state.pc] # create the first basicblock of this branch # print('%d : %s' % (instr.offset, instr.name)) self.current_basicblock = self.basicblock_per_instr[instr.offset] # beginning of a function if instr in self.functions_start_instr: # retrive matching function self.current_function = next( filter(lambda f: f.start_instr == instr, self.functions)) self.ssa_counter = 0 logging.info("[+] Entering function:" + self.current_function.prefered_name + ' at ' + str(hex(self.current_function.start_offset))) # associate function to basicblock self.current_basicblock.function_name = self.current_function.prefered_name # associate basicblock to function self.current_function.basicblocks.append(self.current_basicblock) # halt variable use to catch end branch halt = False while not halt: # get current instruction instr = self.reverse_instructions[state.pc] # handle fall-thrown due to JUMPDEST if instr.name == 'JUMPDEST': # doesn't match new block that start with JUMPDEST if self.current_basicblock.start_offset != instr.offset: self.edges.append( Edge(self.current_basicblock.name, 'block_%x' % instr.offset, EDGE_FALLTHROUGH)) # get current basicblock self.current_basicblock = self.basicblock_per_instr[instr.offset] # add this instruction to his functions self.current_function.instructions.append(instr) # Save instruction and state state.instr = instr self.states[self.states_total] = state state = copy.deepcopy(state) self.states_total += 1 state.pc += 1 # execute single instruction halt = self.emulate_one_instruction(instr, state, depth) state.instructions_visited.append(instr.offset) logging.info("[X] Returning from basicblock %s" % self.current_basicblock.name) # automatic remove doublon edges self.edges = list(set(self.edges))
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.ssaoptimizer.resolve_instr_ssa(push_instr) target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) if not jump_addr: logging.warning('JUMP DYNAMIC TODO') 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) # 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.ssaoptimizer.resolve_instr_ssa(push_instr) target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) if not jump_addr: logging.warning('JUMP DYNAMIC TODO') 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 JUMP 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 enum_blocks_edges(function_id, instructions): """ Return a list of basicblock after statically parsing given instructions """ basicblocks = list() edges = list() branches = [] xrefs = [] intent = 0 blocks_tmp = [] blocks_list = [] # remove last instruction that is 'end' for the funtion tt = instructions[:-1] for index, inst in enumerate(tt): if inst.is_block_terminator: start, name = blocks_tmp.pop() blocks_list.append((intent, start, inst.offset_end, name)) intent -= 1 if inst.is_block_starter: # in ['block', 'loop']: blocks_tmp.append((inst.offset, inst.name)) intent += 1 if inst.is_branch: branches.append((intent, inst)) blocks_list = sorted(blocks_list, key=lambda tup: tup[1]) for depth, inst in branches: d2 = int(inst.operand_interpretation.split(' ')[1]) rep = next(((i, s, e, n) for i, s, e, n in blocks_list if (i == (depth - d2) and s < inst.offset and e > inst.offset_end)), None) if rep: i, start, end, name = rep if name == 'loop': value = start # else name == 'block' elif name == 'block': value = end else: value = None inst.xref = value xrefs.append(value) # remove "block" instruction - not usefull graphicaly # instructions = [x for x in instructions if x.name not in ['block', 'loop']] # enumerate blocks new_block = True for index, inst in enumerate(instructions): # creation of a block if new_block: block = BasicBlock(inst.offset, inst, name=format_bb_name(function_id, inst.offset)) new_block = False # add current instruction to the basicblock block.instructions.append(inst) # next instruction is a jump target if index < (len(instructions) - 1) and \ instructions[index + 1].offset in xrefs: new_block = True # absolute jump - br elif inst.is_branch_unconditional: new_block = True # conditionnal jump - br_if elif inst.is_branch_conditional: new_block = True # end of a block elif index < (len(instructions) - 1) and \ inst.name in ['end']: # is_block_terminator new_block = True elif index < (len(instructions) - 1) and \ instructions[index + 1].name == 'else': # is_block_terminator new_block = True # start of a block elif index < (len(instructions) - 1) and \ instructions[index + 1].is_block_starter: new_block = True # last instruction of the bytecode elif inst.offset == instructions[-1].offset: new_block = True if new_block: block.end_offset = inst.offset_end block.end_instr = inst basicblocks.append(block) new_block = True # TODO: detect and remove end instruction that end loop # enumerate edges for index, block in enumerate(basicblocks): # get the last instruction inst = block.end_instr # unconditional jump - br if inst.is_branch_unconditional: edges.append(Edge(block.name, format_bb_name(function_id, inst.xref), EDGE_UNCONDITIONAL)) # conditionnal jump - br_if elif inst.is_branch_conditional: if inst.name == 'if': edges.append(Edge(block.name, format_bb_name(function_id, inst.offset_end + 1), EDGE_CONDITIONAL_TRUE)) # if 'else' in [i.name for i in basicblocks[index + 1].instructions]: # edges.append(Edge(block.name, format_bb_name(function_id, basicblocks[index + 2].start_instr.offset), EDGE_CONDITIONAL_FALSE)) edges.append(Edge(block.name, format_bb_name(function_id, basicblocks[index + 2].start_instr.offset), EDGE_CONDITIONAL_FALSE)) else: edges.append(Edge(block.name, format_bb_name(function_id, inst.xref), EDGE_CONDITIONAL_TRUE)) edges.append(Edge(block.name, format_bb_name(function_id, inst.offset_end + 1), EDGE_CONDITIONAL_FALSE)) elif inst.offset != instructions[-1].offset: # EDGE_FALLTHROUGH edges.append(Edge(block.name, format_bb_name(function_id, inst.offset_end + 1), EDGE_FALLTHROUGH)) # prevent duplicate edges edges = list(set(edges)) return basicblocks, edges
def enumerate_basicblocks_edges(function_id, instructions): """ Return a list of basicblock after statically parsing given instructions """ basicblocks = list() edges = list() labels = [] intent = 0 # find label first for index, inst in enumerate(instructions): if inst.is_block_starter: if inst.name in ['block', 'loop']: intent += 1 elif inst.name in ['end']: if intent > 0: intent -= 1 #tmp_labels.append({'intent': intent, # 'offset': inst.offset_end}) labels.append(inst.offset_end) # print(function_id) # print(labels) ''' #e = [{'intent': 0, 'offset': 15}, {'intent': 1, 'offset': 17}, {'intent': 0, 'offset': 56}] tmp_list = [] for index, x in enumerate(tmp_labels): if x.get('intent') == 0: labels += tmp_list[::-1] tmp_list = [] tmp_list.append(x.get('offset')) labels += tmp_list[::-1] print(labels) ''' # remove "block" instruction - not usefull graphicaly instructions = [x for x in instructions if x.name not in ['block', 'loop']] # create the first block new_block = False end_block = False block = BasicBlock(instructions[0].offset, instructions[0], name=format_bb_name(function_id, instructions[0].offset)) for index, inst in enumerate(instructions): if new_block: block = BasicBlock(inst.offset, inst, name=format_bb_name(function_id, inst.offset)) new_block = False # add current instruction to the basicblock block.instructions.append(inst) # absolute jump - br # br is *always* followed by end instruction if inst.is_branch_unconditional: end_block = True jump_offset = int(inst.operand_interpretation.split(' ')[1]) if instructions[index + 1].name == 'end': end_block = False edges.append( Edge(block.name, format_bb_name(function_id, labels[jump_offset] + 1), EDGE_UNCONDITIONAL)) # conditionnal jump - br_if elif inst.is_branch_conditional: end_block = True jump_offset = int(inst.operand_interpretation.split(' ')[1]) edges.append( Edge(block.name, format_bb_name(function_id, labels[jump_offset] + 1), EDGE_CONDITIONAL_TRUE)) edges.append( Edge( block.name, format_bb_name(function_id, instructions[index + 1].offset), EDGE_CONDITIONAL_FALSE)) # end of a block elif index < (len(instructions) - 1) and \ inst.name in ['end', 'else']: # is_block_terminator end_block = True if not instructions[index - 1].is_branch_unconditional: edges.append( Edge( block.name, format_bb_name(function_id, instructions[index + 1].offset), EDGE_FALLTHROUGH)) # start of a block elif index < (len(instructions) - 1) and \ instructions[index + 1].is_block_starter: end_block = True edges.append( Edge( block.name, format_bb_name(function_id, instructions[index + 1].offset), EDGE_FALLTHROUGH)) # last instruction of the entire bytecode elif inst == instructions[-1]: end_block = True if end_block: block.end_offset = inst.offset_end block.end_instr = inst basicblocks.append(block) new_block = True end_block = False edges = list(set(edges)) return basicblocks, edges
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') # get instruction with this value as offset target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) # depth of 1 - prevent looping #if (depth < self.max_depth): if target.name == "JUMPDEST": if target.offset not in state.instructions_visited: #if (depth < self.max_depth): logging.info('[X] follow JUMP branch offset 0x%x' % target.offset) new_state = copy.deepcopy(state) new_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 else: logging.info('[X] Bad JUMP to 0x%x' % jump_addr) # modif current basicblock because jump is not valid self.current_basicblock.type = 'terminal' halt = True else: logging.warning('JUMP DYNAMIC TODO') logging.warning('[X] push_instr %s push_instr %x' % (push_instr.name, push_instr.offset)) #raise Exception('JUMP DYNAMIC TODO') 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]) # depth of 1 - prevent looping #if (depth < self.max_depth): #if (instr.offset_end + 1) not in state.instructions_visited: 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 #push_instr = next(filter(lambda element: element.offset == jump_addr, self.instructions)) if push_instr.ssa.is_constant: jump_addr = int.from_bytes(push_instr.operand, byteorder='big') # get instruction with this value as offset target = next( filter(lambda element: element.offset == jump_addr, self.instructions)) if target.name == "JUMPDEST": if target.offset not in state.instructions_visited: # condition are True # and target not in state.instructions_visited: 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.info('[X] Loop detected, skipping JUMP 0x%x' % jump_addr) halt = True else: logging.info('[X] Bad JUMP to 0x%x' % jump_addr) self.current_basicblock.type = 'terminal' else: # tricks to exit properly this function #depth = self.max_depth logging.warning('JUMP DYNAMIC TODO') logging.warning('[X] push_instr %s push_instr %x' % (push_instr.name, push_instr.offset)) #raise Exception('JUMP DYNAMIC TODO') 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