Better tracing

pull/2/head
Bernhard Mueller 7 years ago
parent 20a6aca331
commit d1761aae51
  1. 14
      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 ethcontract import ETHContract
from ethereum import utils
from tinydb import TinyDB, Query
import codecs
import hashlib
import re
class ContractStorage:
@ -78,11 +80,21 @@ class ContractStorage:
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 instance in c['instances']:
if ('balance' in instance):
contract = ETHContract(c['code'], instance['balance'])
if (contract.matches_expression(expression)):

@ -2,14 +2,15 @@ from ether import asm, util
import re
class ETHContract:
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
def matches_expression(self, expression):
str_eval = ""
@ -22,7 +23,7 @@ class ETHContract:
str_eval += token
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):
code = m.group(1).replace(",", "\\n")
@ -30,9 +31,17 @@ class ETHContract:
continue
m = re.match(r'^balance\s*[=><]+\s*\d+$', token)
if (m):
str_eval += "self." + m.group(0)
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)

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

@ -4,9 +4,10 @@ from ethereum.slogging import get_logger
from logging import StreamHandler
import sys
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']
@ -17,12 +18,14 @@ def trace(code):
log_vm_op.setLevel("TRACE")
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()
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
"""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
"""
@ -7,8 +7,8 @@
from ether import asm,evm,util
from contractstorage import ContractStorage
import sys
import codecs
import argparse
from rpc.client import EthJsonRpc
def exitWithError(message):
@ -16,15 +16,16 @@ def exitWithError(message):
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('-t', '--trace', action='store_true', help='trace bytecode provided via the -c argument')
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, use with -c or -a and --data (optional)')
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('--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('--search', help='search contract database')
parser.add_argument('--rpchost', default='127.0.0.1', help='RPC host')
parser.add_argument('--rpcport', type=int, default=8545, help='RPC port')
@ -38,10 +39,14 @@ if (args.disassemble):
if (args.code):
encoded_bytecode = args.code
elif (args.id):
elif (args.address):
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:
exitWithError("Exception loading bytecode via RPC: " + str(e))
@ -68,14 +73,24 @@ if (args.disassemble):
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:
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):

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