def trace(code, calldata=""): logHandlers = [ 'eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage' ] output = StringIO() streamHandler = StreamHandler(output) for handler in logHandlers: log_vm_op = get_logger(handler) log_vm_op.setLevel("TRACE") log_vm_op.addHandler(streamHandler) addr = codecs.decode('0123456789ABCDEF0123456789ABCDEF01234567', 'hex_codec') state = State() ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) message = vm.Message(addr, addr, 0, 21000, calldata) res, gas, dat = vm.vm_execute(ext, message, util.safe_decode(code)) streamHandler.flush() ret = output.getvalue() return ret
def trace(code, calldata=""): log_handlers = [ "eth.vm.op", "eth.vm.op.stack", "eth.vm.op.memory", "eth.vm.op.storage", ] output = StringIO() stream_handler = StreamHandler(output) for handler in log_handlers: log_vm_op = get_logger(handler) log_vm_op.setLevel("TRACE") log_vm_op.addHandler(stream_handler) addr = bytes.fromhex("0123456789ABCDEF0123456789ABCDEF01234567") state = State() ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) message = vm.Message(addr, addr, 0, 21000, calldata) vm.vm_execute(ext, message, util.safe_decode(code)) stream_handler.flush() ret = output.getvalue() lines = ret.split("\n") state_trace = [] for line in lines: m = re.search(r"pc=b\'(\d+)\'.*op=([A-Z0-9]+)", line) if m: pc = m.group(1) op = m.group(2) m = re.match(r".*stack=(\[.*?\])", line) if m: stackitems = re.findall(r"b\'(\d+)\'", m.group(1)) stack = "[" if len(stackitems): for i in range(0, len(stackitems) - 1): stack += hex(int(stackitems[i])) + ", " stack += hex(int(stackitems[-1])) stack += "]" else: stack = "[]" if re.match(r"^PUSH.*", op): val = re.search(r"pushvalue=(\d+)", line).group(1) pushvalue = hex(int(val)) state_trace.append({ "pc": pc, "op": op, "stack": stack, "pushvalue": pushvalue }) else: state_trace.append({"pc": pc, "op": op, "stack": stack}) return state_trace
def __init__(self, code: str, enable_online_lookup: bool = False): self.bytecode = code self.instruction_list = asm.disassemble(util.safe_decode(code)) self.func_hashes = [] self.function_name_to_address = {} self.address_to_function_name = {} signatures = SignatureDb( enable_online_lookup=enable_online_lookup ) # control if you want to have online signature hash lookups try: signatures.open() # open from default locations except FileNotFoundError: logging.info( "Missing function signature file. Resolving of function names from signature file disabled." ) # Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing jump_table_indices = asm.find_op_code_sequence( [("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ", )], self.instruction_list) for index in jump_table_indices: function_hash, jump_target, function_name = get_function_info( index, self.instruction_list, signatures) self.func_hashes.append(function_hash) if jump_target is not None and function_name is not None: self.function_name_to_address[function_name] = jump_target self.address_to_function_name[jump_target] = function_name signatures.write( ) # store resolved signatures (potentially resolved online)
def __init__(self, code, enable_online_lookup=True): self.instruction_list = asm.disassemble(util.safe_decode(code)) self.func_hashes = [] self.func_to_addr = {} self.addr_to_func = {} self.bytecode = code signatures = SignatureDb( enable_online_lookup=enable_online_lookup ) # control if you want to have online sighash lookups try: signatures.open() # open from default locations except FileNotFoundError: logging.info( "Missing function signature file. Resolving of function names from signature file disabled." ) # Parse jump table & resolve function names # Need to take from PUSH1 to PUSH4 because solc seems to remove excess 0s at the beginning for optimizing jmptable_indices = asm.find_opcode_sequence( [("PUSH1", "PUSH2", "PUSH3", "PUSH4"), ("EQ", )], self.instruction_list) for i in jmptable_indices: func_hash = self.instruction_list[i]['argument'] # Append with missing 0s at the beginning func_hash = "0x" + func_hash[2:].rjust(8, "0") self.func_hashes.append(func_hash) try: # tries local cache, file and optional online lookup # may return more than one function signature. since we cannot probe for the correct one we'll use the first func_names = signatures.get(func_hash) if len(func_names) > 1: # ambigious result func_name = "**ambiguous** %s" % func_names[ 0] # return first hit but note that result was ambiguous else: # only one item func_name = func_names[0] except KeyError: func_name = "_function_" + func_hash try: offset = self.instruction_list[i + 2]['argument'] jump_target = int(offset, 16) self.func_to_addr[func_name] = jump_target self.addr_to_func[jump_target] = func_name except: continue signatures.write( ) # store resolved signatures (potentially resolved online)
def get_xrefs(self): disassembly = asm.disassemble(util.safe_decode(self.code)) xrefs = [] for instruction in disassembly: if instruction['opcode'] == "PUSH20": if instruction['argument']: addr = instruction['argument'].decode("utf-8") if (re.match(r'^[a-zA-Z0-9]{40}$', addr) and addr != "ffffffffffffffffffffffffffffffffffffffff"): if addr not in xrefs: xrefs.append(addr) return xrefs
def __init__(self, code): self.instruction_list = asm.disassemble(util.safe_decode(code)) self.xrefs = [] self.func_to_addr = {} self.addr_to_func = {} self.bytecode = code try: mythril_dir = os.environ['MYTHRIL_DIR'] except KeyError: mythril_dir = os.path.join(os.path.expanduser('~'), ".mythril") # Load function signatures signatures_file = os.path.join(mythril_dir, 'signatures.json') if not os.path.exists(signatures_file): logging.info( "Missing function signature file. Resolving of function names disabled." ) signatures = {} else: with open(signatures_file) as f: signatures = json.load(f) # Parse jump table & resolve function names jmptable_indices = asm.find_opcode_sequence(["PUSH4", "EQ"], self.instruction_list) for i in jmptable_indices: func_hash = self.instruction_list[i]['argument'] try: func_name = signatures[func_hash] except KeyError: func_name = "_function_" + func_hash try: offset = self.instruction_list[i + 2]['argument'] jump_target = int(offset, 16) self.func_to_addr[func_name] = jump_target self.addr_to_func[jump_target] = func_name except: continue
def assemble(instruction_list): bytecode = b"" for instruction in instruction_list: try: opcode = get_opcode_from_name(instruction["opcode"]) except RuntimeError: opcode = 0xBB bytecode += opcode.to_bytes(1, byteorder="big") if "argument" in instruction: bytecode += util.safe_decode(instruction["argument"]) return bytecode
def assemble(instruction_list): bytecode = b"" for instruction in instruction_list: try: opcode = get_opcode_from_name(instruction['opcode']) except RuntimeError: opcode = 0xbb bytecode += opcode.to_bytes(1, byteorder='big') if 'argument' in instruction: bytecode += util.safe_decode(instruction['argument']) return bytecode
def trace(code, calldata=""): logHandlers = [ 'eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage' ] output = StringIO() streamHandler = StreamHandler(output) for handler in logHandlers: log_vm_op = get_logger(handler) log_vm_op.setLevel("TRACE") log_vm_op.addHandler(streamHandler) addr = bytes.fromhex('0123456789ABCDEF0123456789ABCDEF01234567') state = State() ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) message = vm.Message(addr, addr, 0, 21000, calldata) res, gas, dat = vm.vm_execute(ext, message, util.safe_decode(code)) streamHandler.flush() ret = output.getvalue() lines = ret.split("\n") trace = [] for line in lines: m = re.search(r'pc=b\'(\d+)\'.*op=([A-Z0-9]+)', line) if m: pc = m.group(1) op = m.group(2) m = re.match(r'.*stack=(\[.*?\])', line) if (m): stackitems = re.findall(r'b\'(\d+)\'', m.group(1)) stack = "[" if (len(stackitems)): for i in range(0, len(stackitems) - 1): stack += hex(int(stackitems[i])) + ", " stack += hex(int(stackitems[-1])) stack += "]" else: stack = "[]" if (re.match(r'^PUSH.*', op)): val = re.search(r'pushvalue=(\d+)', line).group(1) pushvalue = hex(int(val)) trace.append({ 'pc': pc, 'op': op, 'stack': stack, 'pushvalue': pushvalue }) else: trace.append({'pc': pc, 'op': op, 'stack': stack}) return trace
def __init__(self, code): self.instruction_list = asm.disassemble(util.safe_decode(code)) self.blocks = [] self.xrefs = [] self.func_to_addr = {} self.addr_to_func = {} # Parse jump table & resolve function names script_dir = os.path.dirname(os.path.realpath(__file__)) signature_file = os.path.join(script_dir, 'signatures.json') with open(signature_file) as f: signatures = json.load(f) jmptable_indices = asm.find_opcode_sequence(["PUSH4", "EQ"], self.instruction_list) for i in jmptable_indices: func_hash = self.instruction_list[i]['argument'] try: func_name = signatures[func_hash] except KeyError: func_name = "UNK_" + func_hash try: offset = self.instruction_list[i+2]['argument'] jump_target = int(offset, 16) self.func_to_addr[func_name] = jump_target self.addr_to_func[jump_target] = func_name except: continue # Parse instructions into basic blocks current_block = Block(0, 0, "PROLOGUE") index = 0 blocklen = 0 blocknumber = 1 for instruction in self.instruction_list: if (instruction['opcode'] == "JUMPDEST"): try: func_name = "- FUNCTION " + self.addr_to_func[instruction['address']] + " -" except KeyError: func_name = "- JUMPDEST_UNK -" current_block.update_length(blocklen) self.blocks.append(current_block) current_block = Block(blocknumber, index, func_name) blocklen = 0 blocknumber += 1 current_block.instruction_list.append(instruction) blocklen += 1 index += 1 # Add the last block current_block.update_length(blocklen) self.blocks.append(current_block) # Resolve cross-references for block in self.blocks: jmp_indices = asm.find_opcode_sequence(["JUMP"], block.instruction_list) jmp_indices += asm.find_opcode_sequence(["JUMPI"], block.instruction_list) for i in jmp_indices: try: dest_hex = block.instruction_list[i - 1]['argument'] dest = int(dest_hex[2:], 16) except: continue j = 0 try: while(self.blocks[j].end_addr < dest): j += 1 except IndexError: continue if not (block.id, self.blocks[j].id) in self.xrefs: self.xrefs.append((block.id, self.blocks[j].id)) # if the last instruction isn't an unconditional jump or halt, also add a reference to the following block try: if (block.id < len(self.blocks)) and (block.instruction_list[block.length - 1]['opcode'] not in ['JUMP', 'STOP', 'THROW', 'REVERT', 'INVALID']): if not (block.id, self.blocks[j].id) in self.xrefs: self.xrefs.append((block.id, block.id + 1)) except UnboundLocalError: # quickfix continue
def get_easm(self): return asm.disassembly_to_easm(asm.disassemble(util.safe_decode(self.code)))
def get_disassembly(self): return asm.disassemble(util.safe_decode(self.code))