mirror of https://github.com/ConsenSys/mythril
parent
306d190809
commit
5f75257cea
@ -0,0 +1,3 @@ |
|||||||
|
.DS_Store |
||||||
|
.python-version |
||||||
|
*.pyc |
@ -0,0 +1,22 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2015-present Dan Abramov |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
|
|
@ -1,2 +0,0 @@ |
|||||||
# mythril |
|
||||||
Assembler / Disassembler for Ethereum VM bytecode |
|
@ -0,0 +1,161 @@ |
|||||||
|
from ethereum import opcodes |
||||||
|
import re |
||||||
|
import binascii |
||||||
|
|
||||||
|
|
||||||
|
regex_PUSH = re.compile('^PUSH(\d*)$') |
||||||
|
|
||||||
|
|
||||||
|
def safe_decode(hex_encoded_string): |
||||||
|
if (hex_encoded_string.startswith("0x")): |
||||||
|
return hex_encoded_string[2:].decode("hex") |
||||||
|
else: |
||||||
|
return hex_encoded_string.decode("hex") |
||||||
|
|
||||||
|
|
||||||
|
def disassembly_to_easm(disassembly): |
||||||
|
easm = "" |
||||||
|
|
||||||
|
for instruction in disassembly: |
||||||
|
easm += instruction['opcode'] |
||||||
|
|
||||||
|
if 'argument' in instruction: |
||||||
|
easm += " 0x" + instruction['argument'] |
||||||
|
|
||||||
|
easm += "\n" |
||||||
|
|
||||||
|
return easm |
||||||
|
|
||||||
|
|
||||||
|
def easm_to_disassembly(easm): |
||||||
|
|
||||||
|
regex_CODELINE = re.compile('^([A-Z0-9]+)(?:\s+([0-9a-fA-Fx]+))?$') |
||||||
|
|
||||||
|
disassembly = [] |
||||||
|
|
||||||
|
codelines = easm.split('\n') |
||||||
|
|
||||||
|
for line in codelines: |
||||||
|
|
||||||
|
m = re.search(regex_CODELINE, line) |
||||||
|
|
||||||
|
if not m: |
||||||
|
# Invalid code line |
||||||
|
continue |
||||||
|
|
||||||
|
instruction = {} |
||||||
|
|
||||||
|
instruction['opcode'] = m.group(1) |
||||||
|
|
||||||
|
if m.group(2): |
||||||
|
instruction['argument'] = m.group(2)[2:] |
||||||
|
|
||||||
|
disassembly.append(instruction) |
||||||
|
|
||||||
|
return disassembly |
||||||
|
|
||||||
|
|
||||||
|
def get_opcode_from_name(name): |
||||||
|
|
||||||
|
for opcode, value in opcodes.opcodes.items(): |
||||||
|
|
||||||
|
if name == value[0]: |
||||||
|
|
||||||
|
return opcode |
||||||
|
|
||||||
|
raise RuntimeError("Unknown opcode") |
||||||
|
|
||||||
|
|
||||||
|
def find_opcode_sequence(pattern, disassembly): |
||||||
|
match_indexes = [] |
||||||
|
|
||||||
|
pattern_length = len(pattern) |
||||||
|
|
||||||
|
for i in range(0, len(disassembly) - pattern_length): |
||||||
|
|
||||||
|
if disassembly[i]['opcode'] == pattern[0]: |
||||||
|
|
||||||
|
matched = True |
||||||
|
|
||||||
|
for j in range(1, len(pattern)): |
||||||
|
|
||||||
|
if not (disassembly[i + j]['opcode'] == pattern[j]): |
||||||
|
matched = False |
||||||
|
break |
||||||
|
|
||||||
|
if (matched): |
||||||
|
match_indexes.append(i) |
||||||
|
|
||||||
|
return match_indexes |
||||||
|
|
||||||
|
|
||||||
|
def resolve_functions(disassembly): |
||||||
|
|
||||||
|
function_stubs = find_opcode_sequence(['PUSH4', 'EQ', 'PUSH2', 'JUMPI'], disassembly) |
||||||
|
|
||||||
|
functions = [] |
||||||
|
|
||||||
|
for index in function_stubs: |
||||||
|
func = {} |
||||||
|
|
||||||
|
func['hash'] = disassembly[index]['argument'] |
||||||
|
func['address'] = disassembly[index + 2]['argument'] |
||||||
|
|
||||||
|
functions.append(func) |
||||||
|
|
||||||
|
return functions |
||||||
|
|
||||||
|
|
||||||
|
def disassemble(encoded_bytecode): |
||||||
|
|
||||||
|
bytecode = safe_decode(encoded_bytecode) |
||||||
|
|
||||||
|
disassembly = [] |
||||||
|
i = 0 |
||||||
|
|
||||||
|
while i < len(bytecode): |
||||||
|
|
||||||
|
instruction = {} |
||||||
|
|
||||||
|
try: |
||||||
|
opcode = opcodes.opcodes[ord(bytecode[i])] |
||||||
|
except KeyError: |
||||||
|
# invalid opcode |
||||||
|
disassembly.append({'opcode': "INVALID"}) |
||||||
|
i += 1 |
||||||
|
continue |
||||||
|
|
||||||
|
instruction['opcode'] = opcode[0] |
||||||
|
|
||||||
|
m = re.search(regex_PUSH, opcode[0]) |
||||||
|
|
||||||
|
if m: |
||||||
|
argument = bytecode[i+1:i+1+int(m.group(1))] |
||||||
|
instruction['argument'] = argument.encode("hex") |
||||||
|
i += int(m.group(1)) |
||||||
|
|
||||||
|
disassembly.append(instruction) |
||||||
|
|
||||||
|
i += 1 |
||||||
|
|
||||||
|
return disassembly |
||||||
|
|
||||||
|
|
||||||
|
def assemble(disassembly): |
||||||
|
|
||||||
|
bytecode = "" |
||||||
|
|
||||||
|
for instruction in disassembly: |
||||||
|
|
||||||
|
try: |
||||||
|
opcode = get_opcode_from_name(instruction['opcode']) |
||||||
|
except RuntimeError: |
||||||
|
opcode = 0xbb |
||||||
|
|
||||||
|
bytecode += binascii.hexlify(chr(opcode)) |
||||||
|
|
||||||
|
if 'argument' in instruction: |
||||||
|
|
||||||
|
bytecode += instruction['argument'] |
||||||
|
|
||||||
|
return bytecode |
@ -0,0 +1,12 @@ |
|||||||
|
from ethjsonrpc import EthJsonRpc |
||||||
|
|
||||||
|
|
||||||
|
class EthJsonRpcWithDebug(EthJsonRpc): |
||||||
|
|
||||||
|
def getBlockRlp(self, number=0): |
||||||
|
|
||||||
|
return self._call('debug_getBlockRlp', [number]) |
||||||
|
|
||||||
|
def traceTransaction(self, txHash): |
||||||
|
|
||||||
|
return self._call('debug_traceTransaction', [txHash]) |
@ -0,0 +1,64 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
"""mythril.py: Ethereum VM bytecode assembler/ disassembler |
||||||
|
|
||||||
|
http://www.github.com/b-mueller/mythril |
||||||
|
""" |
||||||
|
|
||||||
|
from ether import asm |
||||||
|
import sys |
||||||
|
import argparse |
||||||
|
import util |
||||||
|
|
||||||
|
|
||||||
|
def exitWithError(message): |
||||||
|
print(message) |
||||||
|
sys.exit() |
||||||
|
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description='Ethereum VM bytecode assembler/ disassembler') |
||||||
|
|
||||||
|
parser.add_argument('-d', '--disassemble', action='store_true', help='disassemble, use with -c or -t') |
||||||
|
parser.add_argument('-a', '--assemble', nargs=1, help='produce bytecode from easm input file', metavar='INPUT FILE') |
||||||
|
parser.add_argument('-c', '--code', nargs=1, help='bytecode string ("6060604052...")', metavar='BYTECODE') |
||||||
|
parser.add_argument('-t', '--transaction_hash', help='id of contract creation transaction') |
||||||
|
parser.add_argument('-o', '--outfile', help='file to write disassembly output to (e.g. "test.easm")') |
||||||
|
parser.add_argument('--rpchost', nargs=1, help='RPC host') |
||||||
|
parser.add_argument('--rpcport', nargs=1, help='RPC port') |
||||||
|
|
||||||
|
args = parser.parse_args() |
||||||
|
|
||||||
|
|
||||||
|
if (args.disassemble): |
||||||
|
|
||||||
|
if (args.code): |
||||||
|
disassembly = asm.disassemble(args.code[0]) |
||||||
|
elif (args.transaction_hash): |
||||||
|
|
||||||
|
try: |
||||||
|
bytecode = util.bytecode_from_blockchain(args.transaction_hash) |
||||||
|
except Exception as e: |
||||||
|
exitWithError("Exception loading bytecode via RPC: " + str(e.message)) |
||||||
|
|
||||||
|
disassembly = asm.disassemble(bytecode) |
||||||
|
|
||||||
|
else: |
||||||
|
exitWithError("Disassembler: Pass either the -c or -t flag to specify the input bytecode") |
||||||
|
|
||||||
|
easm_text = asm.disassembly_to_easm(disassembly) |
||||||
|
|
||||||
|
if (args.outfile): |
||||||
|
util.string_to_file(args.outfile, easm_text) |
||||||
|
else: |
||||||
|
sys.stdout.write(easm_text) |
||||||
|
|
||||||
|
elif (args.assemble): |
||||||
|
|
||||||
|
easm = util.file_to_string(args.assemble[0]) |
||||||
|
|
||||||
|
disassembly = asm.easm_to_disassembly(easm) |
||||||
|
|
||||||
|
print("0x" + asm.assemble(disassembly)) |
||||||
|
|
||||||
|
else: |
||||||
|
|
||||||
|
parser.print_help() |
@ -0,0 +1,2 @@ |
|||||||
|
ethereum==2.0.4 |
||||||
|
ethjsonrpc==0.3.0 |
@ -0,0 +1,60 @@ |
|||||||
|
PUSH1 0x60 |
||||||
|
PUSH1 0x40 |
||||||
|
MSTORE |
||||||
|
CALLDATASIZE |
||||||
|
ISZERO |
||||||
|
PUSH1 0x0a |
||||||
|
JUMPI |
||||||
|
JUMPDEST |
||||||
|
PUSH1 0x5a |
||||||
|
PUSH1 0x00 |
||||||
|
DUP1 |
||||||
|
SLOAD |
||||||
|
PUSH1 0xff |
||||||
|
AND |
||||||
|
ISZERO |
||||||
|
ISZERO |
||||||
|
EQ |
||||||
|
DUP1 |
||||||
|
PUSH1 0x20 |
||||||
|
JUMPI |
||||||
|
POP |
||||||
|
CALLVALUE |
||||||
|
PUSH1 0x00 |
||||||
|
EQ |
||||||
|
JUMPDEST |
||||||
|
DUP1 |
||||||
|
PUSH1 0x52 |
||||||
|
JUMPI |
||||||
|
POP |
||||||
|
PUSH1 0x01 |
||||||
|
SLOAD |
||||||
|
PUSH20 0xffffffffffffffffffffffffffffffffffffffff |
||||||
|
AND |
||||||
|
PUSH1 0x00 |
||||||
|
CALLVALUE |
||||||
|
PUSH1 0x60 |
||||||
|
DUP3 |
||||||
|
DUP2 |
||||||
|
DUP2 |
||||||
|
DUP2 |
||||||
|
DUP6 |
||||||
|
DUP9 |
||||||
|
DUP4 |
||||||
|
CALL |
||||||
|
SWAP4 |
||||||
|
POP |
||||||
|
POP |
||||||
|
POP |
||||||
|
POP |
||||||
|
ISZERO |
||||||
|
JUMPDEST |
||||||
|
ISZERO |
||||||
|
PUSH1 0x5c |
||||||
|
JUMPI |
||||||
|
PUSH1 0x02 |
||||||
|
JUMP |
||||||
|
JUMPDEST |
||||||
|
STOP |
||||||
|
JUMPDEST |
||||||
|
JUMP |
@ -0,0 +1,37 @@ |
|||||||
|
from ether.jsonrpc import EthJsonRpcWithDebug |
||||||
|
|
||||||
|
|
||||||
|
def safe_decode(hex_encoded_string): |
||||||
|
if (hex_encoded_string.startswith("0x")): |
||||||
|
return hex_encoded_string[2:].decode("hex") |
||||||
|
else: |
||||||
|
return hex_encoded_string.decode("hex") |
||||||
|
|
||||||
|
|
||||||
|
def bytecode_from_blockchain(creation_tx_hash, rpc_host='127.0.0.1', rpc_port=8545): |
||||||
|
"""Load bytecode from a local node via |
||||||
|
creation_tx_hash = ID of transaction that created the contract. |
||||||
|
""" |
||||||
|
|
||||||
|
eth = EthJsonRpcWithDebug('127.0.0.1', 8545) |
||||||
|
|
||||||
|
# receipt = eth.eth_getTransactionReceipt(creation_tx_hash) |
||||||
|
|
||||||
|
trace = eth.traceTransaction(creation_tx_hash) |
||||||
|
|
||||||
|
if trace['returnValue']: |
||||||
|
|
||||||
|
return trace['returnValue'] |
||||||
|
|
||||||
|
raise RuntimeError("Transaction trace didn't return any bytecode") |
||||||
|
|
||||||
|
|
||||||
|
def string_to_file(filename, string): |
||||||
|
outfile = open(filename, "w") |
||||||
|
outfile.write(string) |
||||||
|
outfile.close() |
||||||
|
|
||||||
|
|
||||||
|
def file_to_string(filename): |
||||||
|
infile = open(filename, "r") |
||||||
|
return infile.read() |
Loading…
Reference in new issue