Better tracing

pull/2/head
Bernhard Mueller 7 years ago
parent 20a6aca331
commit d1761aae51
  1. 20
      contractstorage.py
  2. 15
      ethcontract.py
  3. 17
      ether/asm.py
  4. 13
      ether/evm.py
  5. 41
      mythril
  6. 60
      signatures.json
  7. 29
      utils/download_sigs.py

@ -1,8 +1,10 @@
from rpc.client import EthJsonRpc from rpc.client import EthJsonRpc
from ethcontract import ETHContract from ethcontract import ETHContract
from ethereum import utils
from tinydb import TinyDB, Query from tinydb import TinyDB, Query
import codecs import codecs
import hashlib import hashlib
import re
class ContractStorage: class ContractStorage:
@ -78,13 +80,23 @@ class ContractStorage:
all_contracts = self.db.all() all_contracts = self.db.all()
matches = re.findall(r'func\[([a-zA-Z0-9\s,()]+)\]', expression)
for m in matches:
# Pre-calculate function signature hashes
sign_hash = utils.sha3(m)[:4].hex()
print(sign_hash)
expression = expression.replace(m, sign_hash)
for c in all_contracts: for c in all_contracts:
for instance in c['instances']: for instance in c['instances']:
if ('balance' in instance): contract = ETHContract(c['code'], instance['balance'])
contract = ETHContract(c['code'], instance['balance'])
if (contract.matches_expression(expression)): if (contract.matches_expression(expression)):
print("Found contract:" + instance['address']) print("Found contract:" + instance['address'])

@ -2,14 +2,15 @@ from ether import asm, util
import re import re
class ETHContract: class ETHContract:
def __init__(self, code = "", balance = 0): def __init__(self, code = "", balance = 0):
self.easm_code = asm.disassembly_to_easm(asm.disassemble(util.safe_decode(code))) self.disassembly = asm.disassemble(util.safe_decode(code))
self.easm_code = asm.disassembly_to_easm(self.disassembly)
self.balance = balance self.balance = balance
def matches_expression(self, expression): def matches_expression(self, expression):
str_eval = "" str_eval = ""
@ -22,7 +23,7 @@ class ETHContract:
str_eval += token str_eval += token
continue continue
m = re.match(r'^code\(([a-zA-Z0-9\s,]+)\)$', token) m = re.match(r'^code\[([a-zA-Z0-9\s,]+)\]$', token)
if (m): if (m):
code = m.group(1).replace(",", "\\n") code = m.group(1).replace(",", "\\n")
@ -30,9 +31,17 @@ class ETHContract:
continue continue
m = re.match(r'^balance\s*[=><]+\s*\d+$', token) m = re.match(r'^balance\s*[=><]+\s*\d+$', token)
if (m): if (m):
str_eval += "self." + m.group(0) str_eval += "self." + m.group(0)
continue continue
m = re.match(r'^func\[([a-zA-Z0-9\s,()]+)\]$', token)
if (m):
str_eval += "\"" + m.group(1) + "\" in self.easm_code"
continue
return eval(str_eval) return eval(str_eval)

@ -83,23 +83,6 @@ def find_opcode_sequence(pattern, disassembly):
return match_indexes 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(bytecode): def disassemble(bytecode):
disassembly = [] disassembly = []

@ -4,9 +4,10 @@ from ethereum.slogging import get_logger
from logging import StreamHandler from logging import StreamHandler
import sys import sys
import codecs import codecs
from .util import safe_decode
def trace(code): def trace(code, address = "", calldata = ""):
logHandlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage'] logHandlers = ['eth.vm.op', 'eth.vm.op.stack', 'eth.vm.op.memory', 'eth.vm.op.storage']
@ -17,12 +18,14 @@ def trace(code):
log_vm_op.setLevel("TRACE") log_vm_op.setLevel("TRACE")
log_vm_op.addHandler(streamHandler) log_vm_op.addHandler(streamHandler)
addr = codecs.decode('0123456789ABCDEF0123456789ABCDEF01234567', 'hex_codec') addr_from = codecs.decode('0123456789ABCDEF0123456789ABCDEF01234567', 'hex_codec')
addr_to = safe_decode(address)
data = safe_decode(calldata)
state = State() state = State()
ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr, 0, addr)) ext = messages.VMExt(state, transactions.Transaction(0, 0, 21000, addr_from, 0, addr_to))
msg = vm.Message(addr, addr) message = vm.Message(addr_from, addr_to, 0, 21000, data, code_address=addr_to)
vm.vm_execute(ext, msg, code) res, gas, dat = vm.vm_execute(ext, message, code)

@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
"""mythril.py: Smart contract bug hunting on the Ethereum blockchain """mythril.py: Bug hunting on the Ethereum blockchain
http://www.github.com/b-mueller/mythril http://www.github.com/b-mueller/mythril
""" """
@ -7,8 +7,8 @@
from ether import asm,evm,util from ether import asm,evm,util
from contractstorage import ContractStorage from contractstorage import ContractStorage
import sys import sys
import codecs
import argparse import argparse
from rpc.client import EthJsonRpc
def exitWithError(message): def exitWithError(message):
@ -16,15 +16,16 @@ def exitWithError(message):
sys.exit() sys.exit()
parser = argparse.ArgumentParser(description='Smart contract bug hunting on the Ethereum blockchain') parser = argparse.ArgumentParser(description='Bug hunting on the Ethereum blockchain')
parser.add_argument('-d', '--disassemble', action='store_true', help='disassemble, use with -c or --id') parser.add_argument('-d', '--disassemble', action='store_true', help='disassemble, use with -c or -a')
parser.add_argument('-t', '--trace', action='store_true', help='trace bytecode provided via the -c argument') parser.add_argument('-t', '--trace', action='store_true', help='trace, use with -c or -a and --data (optional)')
parser.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE') parser.add_argument('-c', '--code', help='hex-encoded bytecode string ("6060604052...")', metavar='BYTECODE')
parser.add_argument('-a', '--address', default='0x0123456789ABCDEF0123456789ABCDEF01234567', help='contract address')
parser.add_argument('-o', '--outfile') parser.add_argument('-o', '--outfile')
parser.add_argument('--id', help='contract id') parser.add_argument('--data', help='message call input data for tracing')
parser.add_argument('--search', help='search the contract database')
parser.add_argument('--init-db', action='store_true', help='Initialize the contract database') parser.add_argument('--init-db', action='store_true', help='Initialize the contract database')
parser.add_argument('--search', help='search contract database')
parser.add_argument('--rpchost', default='127.0.0.1', help='RPC host') parser.add_argument('--rpchost', default='127.0.0.1', help='RPC host')
parser.add_argument('--rpcport', type=int, default=8545, help='RPC port') parser.add_argument('--rpcport', type=int, default=8545, help='RPC port')
@ -38,10 +39,14 @@ if (args.disassemble):
if (args.code): if (args.code):
encoded_bytecode = args.code encoded_bytecode = args.code
elif (args.id): elif (args.address):
try: try:
encoded_bytecode = storage.get_contract_code_by_address(args.id) # encoded_bytecode = storage.get_contract_code_by_address(args.id)
eth = EthJsonRpc(args.rpchost, args.rpcport)
encoded_bytecode = eth.eth_getCode(args.address)
except Exception as e: except Exception as e:
exitWithError("Exception loading bytecode via RPC: " + str(e)) exitWithError("Exception loading bytecode via RPC: " + str(e))
@ -68,14 +73,24 @@ if (args.disassemble):
elif (args.trace): elif (args.trace):
if args.code: if (args.code):
encoded_bytecode = args.code
elif (args.address):
eth = EthJsonRpc(args.rpchost, args.rpcport)
encoded_bytecode = eth.eth_getCode(args.address)
else:
exitWithError("Disassembler: Provide the input bytecode via -c BYTECODE or --id ID")
bytecode = util.safe_decode(args.code) if (args.data):
evm.trace(util.safe_decode(encoded_bytecode), args.address, args.data)
else: else:
exitWithError("Trace: Provide the input bytecode using -c BYTECODE or -f INPUT_FILE") evm.trace(util.safe_decode(encoded_bytecode), args.address)
evm.trace(bytecode)
elif (args.search): elif (args.search):

File diff suppressed because one or more lines are too long

@ -0,0 +1,29 @@
import requests
from requests.adapters import HTTPAdapter
JSON_MEDIA_TYPE = 'application/json'
MAX_RETRIES = 3
session = requests.Session()
session.mount("www.4byte.directory", HTTPAdapter(max_retries=MAX_RETRIES))
result_list = []
headers = {'Content-Type': JSON_MEDIA_TYPE}
for page in range(1,60):
print("retrieving page " + str(page))
url = 'https://www.4byte.directory/api/v1/signatures/?format=json&page=' + str(page)
try:
r = session.get(url, headers=headers)
response = r.json()
except ValueError:
raise BadJsonError(r.text)
result_list += response['results']
print(result_list)
Loading…
Cancel
Save