EVM assembler/disassembler doc and cleanup (#563)
* 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 * Doc and cleanups in evm assembler * EVMInstruction -> Instruction * cleanups * typo * deepcopy in Constant * Better EVM-asm api and doc * some docs * More evm asm docs * Fix import * * typo * newline between text and param * similar phrasing to all the other flags * typo * typo * fix function name in comment * sphinx newline * documentation fixes * documentation fixes * EVMAssembler to EVMAsm * Fix evm @hook signature * EVMAsm * EVMasm refactor
This commit is contained in:
@@ -36,3 +36,12 @@ Models
|
||||
.. function:: strlen
|
||||
|
||||
.. function:: strcmp
|
||||
|
||||
EVM
|
||||
---
|
||||
.. automodule:: manticore.platforms.evm
|
||||
.. autoclass:: manticore.platforms.evm::EVMAsm.Instruction
|
||||
:members:
|
||||
.. autoclass:: manticore.platforms.evm.EVMAsm
|
||||
:members:
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# EVM disassembler
|
||||
from manticore.platforms.evm import EVMAsm as ea
|
||||
|
||||
def printi(instruction):
|
||||
print 'Instruction: %s'% instruction
|
||||
print '\tdescription:', instruction.description
|
||||
print '\tgroup:', instruction.group
|
||||
print '\taddress:', instruction.offset
|
||||
print '\tsize:', instruction.size
|
||||
print '\thas_operand:', instruction.has_operand
|
||||
print '\toperand_size:', instruction.operand_size
|
||||
print '\toperand:', instruction.operand
|
||||
print '\tsemantics:', instruction.semantics
|
||||
print '\tpops:', instruction.pops
|
||||
print '\tpushes:', instruction.pushes
|
||||
print '\tbytes:', '0x'+instruction.bytes.encode('hex')
|
||||
print '\twrites to stack:', instruction.writes_to_stack
|
||||
print '\treads from stack:', instruction.reads_from_stack
|
||||
print '\twrites to memory:', instruction.writes_to_memory
|
||||
print '\treads from memory:', instruction.reads_from_memory
|
||||
print '\twrites to storage:', instruction.writes_to_storage
|
||||
print '\treads from storage:', instruction.reads_from_storage
|
||||
print '\tis terminator', instruction.is_terminator
|
||||
|
||||
|
||||
instruction = ea.disassemble_one('\x60\x10')
|
||||
printi(instruction)
|
||||
|
||||
instruction = ea.assemble_one('PUSH1 0x10')
|
||||
printi(instruction)
|
||||
|
||||
for instruction in ea.disassemble_all('\x30\x31'):
|
||||
printi(instruction)
|
||||
|
||||
for instruction in ea.assemble_all('ADDRESS\nBALANCE'):
|
||||
printi(instruction)
|
||||
|
||||
|
||||
#High level simple assembler/disassembler
|
||||
print ea.assemble_hex(
|
||||
"""PUSH1 0x60
|
||||
BLOCKHASH
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x100
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
print ea.disassemble_hex('0x606040526002610100')
|
||||
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
from seth import *
|
||||
|
||||
seth = ManticoreEVM()
|
||||
seth.verbosity(3)
|
||||
user_account = seth.create_account(balance=1000)
|
||||
|
||||
bytecode = '`'
|
||||
#Initialize contract
|
||||
contract_account = seth.create_contract(owner=user_account,
|
||||
balance=0,
|
||||
init=bytecode)
|
||||
|
||||
def explore(state):
|
||||
pass
|
||||
|
||||
seth.add_hook(None, explore)
|
||||
seth.transaction( caller=user_account,
|
||||
address=contract_account,
|
||||
value=None,
|
||||
data='',
|
||||
)
|
||||
@@ -2,7 +2,6 @@ from manticore import Manticore
|
||||
from manticore.core.smtlib import ConstraintSet, Operators, solver, issymbolic, Array, Expression, Constant
|
||||
from manticore.core.smtlib.visitors import arithmetic_simplifier
|
||||
from manticore.platforms import evm
|
||||
from manticore.platforms.evm import pack_msb
|
||||
from manticore.core.state import State
|
||||
import tempfile
|
||||
from subprocess import Popen, PIPE
|
||||
@@ -371,7 +370,7 @@ class ManticoreEVM(Manticore):
|
||||
assert ty == 'CREATE_CONTRACT'
|
||||
world.create_contract(caller=caller, address=address, balance=value, init=data)
|
||||
|
||||
def will_execute_instruction_callback(self, state, instruction):
|
||||
def will_execute_instruction_callback(self, state, pc, instruction):
|
||||
assert state.constraints == state.platform.constraints
|
||||
assert state.platform.constraints == state.platform.current.constraints
|
||||
|
||||
@@ -473,7 +472,6 @@ class ManticoreEVM(Manticore):
|
||||
else:
|
||||
print "\t", hex(address), account['balance'],"wei"
|
||||
|
||||
|
||||
if state.platform.logs:
|
||||
print "LOGS:"
|
||||
for address, memlog, topics in state.platform.logs:
|
||||
|
||||
+528
-203
@@ -1,14 +1,9 @@
|
||||
'''
|
||||
Solidity / Smart contract VM
|
||||
Implements the yellow paper: http://gavwood.com/paper.pdf
|
||||
Get example contracts from here:
|
||||
https://ethereum.github.io/browser-solidity/#version=soljson-latest.js
|
||||
'''
|
||||
''' Symbolic EVM implementation based on the yellow paper: http://gavwood.com/paper.pdf '''
|
||||
import random, copy
|
||||
from ..utils.helpers import issymbolic, memoized
|
||||
from ..platforms.platform import *
|
||||
from ..core.smtlib import solver, TooManySolutions, Expression, Bool, BitVec, Array, Operators, Constant, BitVecConstant, ConstraintSet
|
||||
from ..core.state import ForkState, TerminateState
|
||||
from ..utils.helpers import issymbolic
|
||||
from ..utils.event import Eventful
|
||||
from ..core.smtlib.visitors import pretty_print, arithmetic_simplifier, translate_to_smtlib
|
||||
from ..core.state import Concretize,TerminateState
|
||||
@@ -30,26 +25,25 @@ def ceil32(x):
|
||||
return Operators.ITEBV(256, (x % 32) == 0, x , x + 32 - (x % 32))
|
||||
|
||||
def to_signed(i):
|
||||
return Operators.ITEBV(256, i<TT255, i, i-TT256) #i if i < TT255 else i - TT256
|
||||
|
||||
def pack_msb(value, size=32):
|
||||
'''takes an int and packs it into a 32 byte string, msb first'''
|
||||
assert size >=1
|
||||
bytes = []
|
||||
for position in range(size):
|
||||
bytes.append( Operators.EXTRACT(value, position*8, 8) )
|
||||
chars = map(Operators.CHR, bytes)
|
||||
return ''.join(reversed(chars))
|
||||
return Operators.ITEBV(256, i<TT255, i, i-TT256)
|
||||
|
||||
class EVMMemory(object):
|
||||
'''
|
||||
The EVM symbolic memory manager.
|
||||
'''
|
||||
def __init__(self, constraints, address_size=256, value_size=8, *args, **kwargs):
|
||||
'''
|
||||
Builds a memory.
|
||||
A symbolic memory manager for EVM.
|
||||
This is internally used to provide memory to an Ethereum Virtual Machine.
|
||||
It maps address_size bits wide bitvectors to value_size wide bitvectors.
|
||||
Normally BitVec(256) -> BitVec(8)
|
||||
|
||||
:param constraints: a set of constraints
|
||||
Example use::
|
||||
cs = ConstraintSet()
|
||||
mem = EVMMemory(cs)
|
||||
mem[16] = 0x41
|
||||
assert (mem.allocated == 1)
|
||||
assert (mem[16] == 0x41)
|
||||
|
||||
:param constraints: a set of constraints
|
||||
:type constraints: ConstraintSet
|
||||
:param address_size: address bit width
|
||||
:param values_size: value bit width
|
||||
'''
|
||||
@@ -62,12 +56,14 @@ class EVMMemory(object):
|
||||
self._allocated = 0
|
||||
|
||||
def __copy__(self):
|
||||
''' Makes a copy of itself '''
|
||||
new_mem = EVMMemory(self._constraints, self._address_size, self._value_size)
|
||||
new_mem._memory = dict(self._memory)
|
||||
new_mem._symbols = dict(self._symbols)
|
||||
return new_mem
|
||||
|
||||
def __reduce__(self):
|
||||
''' Implements serialization/pickle '''
|
||||
return (self.__class__, (self._constraints, self._address_size, self._value_size), {'_symbols':self._symbols, '_memory':self._memory, '_allocated': self._allocated } )
|
||||
|
||||
@property
|
||||
@@ -79,6 +75,10 @@ class EVMMemory(object):
|
||||
self._constraints = constraints
|
||||
|
||||
def _get_size(self, index):
|
||||
''' Calculates the size of a slice
|
||||
:param index: a slice
|
||||
:type index: slice
|
||||
'''
|
||||
size = index.stop - index.start
|
||||
if isinstance(size, BitVec):
|
||||
size = arithmetic_simplifier(size)
|
||||
@@ -150,6 +150,10 @@ class EVMMemory(object):
|
||||
def __len__(self):
|
||||
return self._allocated
|
||||
|
||||
@property
|
||||
def allocated(self):
|
||||
return self._allocated
|
||||
|
||||
def _allocate(self, address):
|
||||
'''
|
||||
Allocate more memory
|
||||
@@ -168,14 +172,15 @@ class EVMMemory(object):
|
||||
|
||||
def read(self, address, size):
|
||||
'''
|
||||
Read a stream of potentially symbolic items from a potentially symbolic
|
||||
address
|
||||
Read size items from address.
|
||||
Address can by a symbolic value.
|
||||
The result is a sequence the requested size.
|
||||
Resultant items can by symbolic.
|
||||
|
||||
:param address: Where to read from
|
||||
:param size: How many items
|
||||
:rtype: list
|
||||
'''
|
||||
#size = self._get_size(size)
|
||||
assert not issymbolic(size)
|
||||
self._allocate(address+size)
|
||||
|
||||
@@ -188,7 +193,6 @@ class EVMMemory(object):
|
||||
except TooManySolutions as e:
|
||||
m, M = solver.minmax(self.constraints, address)
|
||||
logger.debug('Got TooManySolutions on a symbolic read. Range [%x, %x]. Not crashing!', m, M)
|
||||
#INCOMPLETE Result! Using the 0x100 values sampled before
|
||||
logger.info('INCOMPLETE Result! Using the sampled solutions we have as result')
|
||||
condition = False
|
||||
for base in e.solutions:
|
||||
@@ -237,7 +241,7 @@ class EVMMemory(object):
|
||||
:param address: The address at which to write
|
||||
:type address: int or long or Expression
|
||||
:param value: Bytes to write
|
||||
:type value: str or list
|
||||
:type value: tuple or list
|
||||
'''
|
||||
size = len(value)
|
||||
self._allocate(address+size)
|
||||
@@ -261,101 +265,287 @@ class EVMMemory(object):
|
||||
if address+offset in self._symbols:
|
||||
del self._symbols[address+offset]
|
||||
self._concrete_write(address+offset, value[offset])
|
||||
|
||||
|
||||
class EVMInstruction(object):
|
||||
'''This represents an EVM instruction '''
|
||||
def __init__(self, opcode, name, operand_size, pops, pushes, fee, description, operand=None):
|
||||
self._opcode = opcode
|
||||
self._name = name
|
||||
self._operand_size = operand_size
|
||||
self._pops = pops
|
||||
self._pushes = pushes
|
||||
self._fee = fee
|
||||
self._description = description
|
||||
self._operand = operand #Immediate operand if any
|
||||
|
||||
def parse_operand(self, buf):
|
||||
operand = 0
|
||||
for _ in range(self.operand_size):
|
||||
try:
|
||||
operand <<= 8
|
||||
operand |= ord(next(buf))
|
||||
except:
|
||||
raise TerminateState("Operand has insufficient bytes")
|
||||
self._operand = operand
|
||||
|
||||
@property
|
||||
def operand_size(self):
|
||||
return self._operand_size
|
||||
|
||||
@property
|
||||
def has_operand(self):
|
||||
return self.operand_size > 0
|
||||
|
||||
@property
|
||||
def operand(self):
|
||||
return self._operand
|
||||
|
||||
@property
|
||||
def pops(self):
|
||||
return self._pops
|
||||
|
||||
@property
|
||||
def pushes(self):
|
||||
return self._pushes
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self._operand_size + 1
|
||||
|
||||
@property
|
||||
def fee(self):
|
||||
return self._fee
|
||||
|
||||
def __len__(self):
|
||||
return self.size
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self._name == 'PUSH':
|
||||
return 'PUSH%d'%self.operand_size
|
||||
elif self._name == 'DUP':
|
||||
return 'DUP%d'%self.pops
|
||||
elif self._name == 'SWAP':
|
||||
return 'SWAP%d'%(self.pops-1)
|
||||
elif self._name == 'LOG':
|
||||
return 'LOG%d'%(self.pops-2)
|
||||
return self._name
|
||||
|
||||
def __str__(self):
|
||||
bytes = self.bytes.encode('hex')
|
||||
output = '<%s> '%bytes + self.name + (' 0x%x'%self.operand if self.has_operand else '')
|
||||
output += ' '*(80-len(output))+self.description
|
||||
return output
|
||||
|
||||
@property
|
||||
def semantics(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def bytes(self):
|
||||
bytes = []
|
||||
bytes.append(chr(self._opcode))
|
||||
for offset in reversed(xrange(self.operand_size)):
|
||||
c = (self.operand >> offset*8 ) & 0xff
|
||||
bytes.append(chr(c))
|
||||
return ''.join(bytes)
|
||||
|
||||
|
||||
class EVMDecoder(object):
|
||||
class EVMAsm(object):
|
||||
'''
|
||||
EVM Instruction factory
|
||||
|
||||
Example use::
|
||||
|
||||
>>> from manticore.platforms.evm import EVMAsm
|
||||
>>> EVMAsm.disassemble_one('\\x60\\x10')
|
||||
Instruction(0x60, 'PUSH', 1, 0, 1, 0, 'Place 1 byte item on stack.', 16, 0)
|
||||
>>> EVMAsm.assemble_one('PUSH1 0x10')
|
||||
Instruction(0x60, 'PUSH', 1, 0, 1, 0, 'Place 1 byte item on stack.', 16, 0)
|
||||
>>> tuple(EVMAsm.disassemble_all('\\x30\\x31'))
|
||||
(Instruction(0x30, 'ADDRESS', 0, 0, 1, 2, 'Get address of currently executing account.', None, 0),
|
||||
Instruction(0x31, 'BALANCE', 0, 1, 1, 20, 'Get balance of the given account.', None, 1))
|
||||
>>> tuple(EVMAsm.assemble_all('ADDRESS\\nBALANCE'))
|
||||
(Instruction(0x30, 'ADDRESS', 0, 0, 1, 2, 'Get address of currently executing account.', None, 0),
|
||||
Instruction(0x31, 'BALANCE', 0, 1, 1, 20, 'Get balance of the given account.', None, 1))
|
||||
>>> EVMAsm.assemble_hex(
|
||||
... """PUSH1 0x60
|
||||
... BLOCKHASH
|
||||
... MSTORE
|
||||
... PUSH1 0x2
|
||||
... PUSH2 0x100
|
||||
... """
|
||||
... )
|
||||
'0x606040526002610100'
|
||||
>>> EVMAsm.disassemble_hex('0x606040526002610100')
|
||||
'PUSH1 0x60\\nBLOCKHASH\\nMSTORE\\nPUSH1 0x2\\nPUSH2 0x100'
|
||||
'''
|
||||
class Instruction(object):
|
||||
def __init__(self, opcode, name, operand_size, pops, pushes, fee, description, operand=None, offset=0):
|
||||
'''
|
||||
This represents an EVM instruction.
|
||||
EVMAsm will create this for you.
|
||||
|
||||
:param opcode: the opcode value
|
||||
:param name: instruction name
|
||||
:param operand_size: immediate operand size in bytes
|
||||
:param pops: number of items popped from the stack
|
||||
:param pushes: number of items pushed into the stack
|
||||
:param fee: gas fee for the instruction
|
||||
:param description: textual description of the instruction
|
||||
:param operand: optional immediate operand
|
||||
:param offset: optional offset of this instruction in the program
|
||||
|
||||
Example use::
|
||||
|
||||
instruction = EVMAsm.assemble_one('PUSH1 0x10')
|
||||
print 'Instruction: %s'% instruction
|
||||
print '\tdescription:', instruction.description
|
||||
print '\tgroup:', instruction.group
|
||||
print '\taddress:', instruction.offset
|
||||
print '\tsize:', instruction.size
|
||||
print '\thas_operand:', instruction.has_operand
|
||||
print '\toperand_size:', instruction.operand_size
|
||||
print '\toperand:', instruction.operand
|
||||
print '\tsemantics:', instruction.semantics
|
||||
print '\tpops:', instruction.pops
|
||||
print '\tpushes:', instruction.pushes
|
||||
print '\tbytes:', '0x'+instruction.bytes.encode('hex')
|
||||
print '\twrites to stack:', instruction.writes_to_stack
|
||||
print '\treads from stack:', instruction.reads_from_stack
|
||||
print '\twrites to memory:', instruction.writes_to_memory
|
||||
print '\treads from memory:', instruction.reads_from_memory
|
||||
print '\twrites to storage:', instruction.writes_to_storage
|
||||
print '\treads from storage:', instruction.reads_from_storage
|
||||
print '\tis terminator', instruction.is_terminator
|
||||
|
||||
|
||||
'''
|
||||
self._opcode = opcode
|
||||
self._name = name
|
||||
self._operand_size = operand_size
|
||||
self._pops = pops
|
||||
self._pushes = pushes
|
||||
self._fee = fee
|
||||
self._description = description
|
||||
self._operand = operand #Immediate operand if any
|
||||
if operand_size != 0 and operand is not None:
|
||||
mask = (1<<operand_size*8)-1
|
||||
if ~mask & operand:
|
||||
raise ValueError("operand should be %d bits long"%(operand_size*8))
|
||||
self._offset=offset
|
||||
|
||||
def __eq__(self, other):
|
||||
''' Instructions are equal if all features match '''
|
||||
return self._opcode == other._opcode and\
|
||||
self._name == other._name and\
|
||||
self._operand == other._operand and\
|
||||
self._operand_size == other._operand_size and\
|
||||
self._pops == other._pops and\
|
||||
self._pushes == other._pushes and\
|
||||
self._fee == other._fee and\
|
||||
self._offset == other._offset and\
|
||||
self._description == other._description
|
||||
|
||||
def __repr__(self):
|
||||
output = 'Instruction(0x%x, %r, %d, %d, %d, %d, %r, %r, %r)'%(self._opcode, self._name, self._operand_size, self._pops, self._pushes, self._fee, self._description, self._operand, self._offset)
|
||||
return output
|
||||
|
||||
|
||||
def __str__(self):
|
||||
output = self.name + (' 0x%x'%self.operand if self.has_operand else '')
|
||||
return output
|
||||
|
||||
@property
|
||||
def opcode(self):
|
||||
''' The opcode as an integer '''
|
||||
return self._opcode
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
''' The instruction name/mnemonic '''
|
||||
if self._name == 'PUSH':
|
||||
return 'PUSH%d'%self.operand_size
|
||||
elif self._name == 'DUP':
|
||||
return 'DUP%d'%self.pops
|
||||
elif self._name == 'SWAP':
|
||||
return 'SWAP%d'%(self.pops-1)
|
||||
elif self._name == 'LOG':
|
||||
return 'LOG%d'%(self.pops-2)
|
||||
return self._name
|
||||
|
||||
def parse_operand(self, buf):
|
||||
''' Parses an operand from buf
|
||||
|
||||
:param buf: a buffer
|
||||
:type buf: iterator/generator/string
|
||||
'''
|
||||
buf = iter(buf)
|
||||
try:
|
||||
operand = 0
|
||||
for _ in range(self.operand_size):
|
||||
operand <<= 8
|
||||
operand |= ord(next(buf))
|
||||
self._operand = operand
|
||||
except StopIteration:
|
||||
raise Exception("Not enough data for decoding")
|
||||
|
||||
@property
|
||||
def operand_size(self):
|
||||
''' The immediate operand size '''
|
||||
return self._operand_size
|
||||
|
||||
@property
|
||||
def has_operand(self):
|
||||
''' True if the instruction uses an immediate operand'''
|
||||
return self.operand_size > 0
|
||||
|
||||
@property
|
||||
def operand(self):
|
||||
''' The immediate operand '''
|
||||
return self._operand
|
||||
|
||||
@property
|
||||
def pops(self):
|
||||
'''Number words popped from the stack'''
|
||||
return self._pops
|
||||
|
||||
@property
|
||||
def pushes(self):
|
||||
'''Number words pushed to the stack'''
|
||||
return self._pushes
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
''' Size of the encoded instruction '''
|
||||
return self._operand_size + 1
|
||||
|
||||
@property
|
||||
def fee(self):
|
||||
''' The basic gas fee of the instruction '''
|
||||
return self._fee
|
||||
|
||||
@property
|
||||
def semantics(self):
|
||||
''' Canonical semantics '''
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
''' Coloquial description of the instruction '''
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def bytes(self):
|
||||
''' Encoded instruction '''
|
||||
bytes = []
|
||||
bytes.append(chr(self._opcode))
|
||||
for offset in reversed(xrange(self.operand_size)):
|
||||
c = (self.operand >> offset*8 ) & 0xff
|
||||
bytes.append(chr(c))
|
||||
return ''.join(bytes)
|
||||
|
||||
@property
|
||||
def offset(self):
|
||||
'''Location in the program (optional)'''
|
||||
return self._offset
|
||||
|
||||
@property
|
||||
def group(self):
|
||||
'''Instruction classification as per the yellow paper'''
|
||||
classes = {
|
||||
0: 'Stop and Arithmetic Operations',
|
||||
1: 'Comparison & Bitwise Logic Operations',
|
||||
2: 'SHA3',
|
||||
3: 'Environmental Information',
|
||||
4: 'Block Information',
|
||||
5: 'Stack, Memory, Storage and Flow Operations',
|
||||
6: 'Push Operations',
|
||||
7: 'Push Operations',
|
||||
8: 'Duplication Operations',
|
||||
9: 'Exchange Operations',
|
||||
0xa: 'Logging Operations',
|
||||
0xf: 'System operations'
|
||||
}
|
||||
return classes.get(self.opcode>>4, 'Invalid instruction')
|
||||
|
||||
|
||||
@property
|
||||
def reads_from_stack(self):
|
||||
''' True if the instruction reads from stack '''
|
||||
return self.pops > 0
|
||||
|
||||
@property
|
||||
def writes_to_stack(self):
|
||||
''' True if the instruction writes to the stack '''
|
||||
return self.pushes > 0
|
||||
|
||||
@property
|
||||
def reads_from_memory(self):
|
||||
''' True if the instruction reads from memory '''
|
||||
return self.semantics in ('MLOAD','CREATE', 'CALL', 'CALLCODE', 'RETURN', 'DELEGATECALL', 'REVERT')
|
||||
|
||||
@property
|
||||
def writes_to_memory(self):
|
||||
''' True if the instruction writes to memory '''
|
||||
return self.semantics in ('MSTORE', 'MSTORE8', 'CALLDATACOPY', 'CODECOPY', 'EXTCODECOPY')
|
||||
|
||||
@property
|
||||
def reads_from_memory(self):
|
||||
''' True if the instruction reads from memory '''
|
||||
return self.semantics in ('MLOAD','CREATE', 'CALL', 'CALLCODE', 'RETURN', 'DELEGATECALL', 'REVERT')
|
||||
|
||||
@property
|
||||
def writes_to_storage(self):
|
||||
''' True if the instruction writes to the storage '''
|
||||
return self.semantics in ('SSTORE')
|
||||
|
||||
@property
|
||||
def reads_from_storage(self):
|
||||
''' True if the instruction reads from the storage '''
|
||||
return self.semantics in ('SLOAD')
|
||||
|
||||
@property
|
||||
def is_terminator(self):
|
||||
''' True if the instruction is a basic block terminator '''
|
||||
return self.semantics in ('RETURN', 'STOP', 'INVALID', 'JUMP', 'JUMPI', 'SELFDESTRUCT', 'REVERT')
|
||||
|
||||
@property
|
||||
def is_branch(self):
|
||||
''' True if the instruction is a jump'''
|
||||
return self.semantics in ('JUMP', 'JUMPI')
|
||||
|
||||
@property
|
||||
def is_environmental(self):
|
||||
''' True if the instruction access enviromental data '''
|
||||
return self.group == 'Environmental Information'
|
||||
|
||||
@property
|
||||
def is_system(self):
|
||||
''' True if the instruction is a system operation '''
|
||||
return self.group == 'System operations'
|
||||
|
||||
@property
|
||||
def uses_block_info(self):
|
||||
''' True if the instruction access block information'''
|
||||
return self.group == 'Block Information'
|
||||
|
||||
|
||||
#from http://gavwood.com/paper.pdf
|
||||
_table = {#opcode: (name, immediate_operand_size, pops, pushes, gas, description)
|
||||
0x00: ('STOP', 0, 0, 0, 0, 'Halts execution.'),
|
||||
@@ -500,35 +690,238 @@ class EVMDecoder(object):
|
||||
0xff: ('SELFDESTRUCT', 0, 1, 0, 5000, 'Halt execution and register account for later deletion.')
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@memoized
|
||||
def _get_reverse_table():
|
||||
''' Build an internal table used in the assembler '''
|
||||
reverse_table = {}
|
||||
for (opcode, (name, immediate_operand_size, pops, pushes, gas, description)) in EVMAsm._table.items():
|
||||
mnemonic = name
|
||||
if name == 'PUSH':
|
||||
mnemonic = '%s%d'%(name, (opcode&0x1f) + 1)
|
||||
elif name in ('SWAP', 'LOG'):
|
||||
mnemonic = '%s%d'%(name, (opcode&0xf) + 1)
|
||||
|
||||
reverse_table[mnemonic] = opcode, name, immediate_operand_size, pops, pushes, gas, description
|
||||
return reverse_table
|
||||
|
||||
@staticmethod
|
||||
def decode_one(bytecode):
|
||||
def assemble_one(assembler, offset=0):
|
||||
''' Assemble one EVM instruction from its textual representation.
|
||||
|
||||
:param assembler: assembler code for one instruction
|
||||
:param offset: offset of the instruction in the bytecode (optional)
|
||||
:return: An Instruction object
|
||||
|
||||
Example use::
|
||||
|
||||
>>> print evm.EVMAsm.assemble_one('LT')
|
||||
|
||||
|
||||
'''
|
||||
try:
|
||||
_reverse_table = EVMAsm._get_reverse_table()
|
||||
assembler = assembler.strip().split(' ')
|
||||
opcode, name, operand_size, pops, pushes, gas, description = _reverse_table[assembler[0].upper()]
|
||||
if operand_size > 0:
|
||||
assert len(assembler) == 2
|
||||
operand = int(assembler[1],0)
|
||||
else:
|
||||
assert len(assembler) == 1
|
||||
operand = None
|
||||
|
||||
return EVMAsm.Instruction(opcode, name, operand_size, pops, pushes, gas, description, operand=operand, offset=offset)
|
||||
except:
|
||||
raise Exception("Something wron at offset %d"%offset)
|
||||
|
||||
@staticmethod
|
||||
def assemble_all(assembler, offset=0):
|
||||
''' Assemble a sequence of textual representation of EVM instructions
|
||||
|
||||
:param assembler: assembler code for any number of instructions
|
||||
:param offset: offset of the first instruction in the bytecode(optional)
|
||||
:return: An generator of Instruction objects
|
||||
|
||||
Example use::
|
||||
|
||||
>>> evm.EVMAsm.encode_one("""PUSH1 0x60
|
||||
PUSH1 0x40
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x108
|
||||
PUSH1 0x0
|
||||
POP
|
||||
SSTORE
|
||||
PUSH1 0x40
|
||||
MLOAD
|
||||
""")
|
||||
|
||||
'''
|
||||
if isinstance(assembler, str):
|
||||
assembler = assembler.split('\n')
|
||||
assembler = iter(assembler)
|
||||
for line in assembler:
|
||||
if not line.strip():
|
||||
continue
|
||||
instr = EVMAsm.assemble_one(line, offset=offset)
|
||||
yield instr
|
||||
offset += instr.size
|
||||
|
||||
@staticmethod
|
||||
def disassemble_one(bytecode, offset=0):
|
||||
''' Decode a single instruction from a bytecode
|
||||
|
||||
:param bytecode: the bytecode stream
|
||||
:param offset: offset of the instruction in the bytecode(optional)
|
||||
:type bytecode: iterator/sequence/str
|
||||
:return: an Instruction object
|
||||
|
||||
Example use::
|
||||
|
||||
>>> print EVMAsm.assemble_one('PUSH1 0x10')
|
||||
|
||||
'''
|
||||
bytecode = iter(bytecode)
|
||||
opcode = ord(next(bytecode))
|
||||
invalid = ('INVALID', 0, 0, 0, 0, 'Unknown opcode')
|
||||
name, operand_size, pops, pushes, gas, description = EVMDecoder._table.get(opcode, invalid)
|
||||
instruction = EVMInstruction(opcode, name, operand_size, pops, pushes, gas, description)
|
||||
name, operand_size, pops, pushes, gas, description = EVMAsm._table.get(opcode, invalid)
|
||||
instruction = EVMAsm.Instruction(opcode, name, operand_size, pops, pushes, gas, description, offset=offset)
|
||||
if instruction.has_operand:
|
||||
instruction.parse_operand(bytecode)
|
||||
|
||||
return instruction
|
||||
|
||||
@staticmethod
|
||||
def decode_all(bytecode):
|
||||
def disassemble_all(bytecode, offset=0):
|
||||
''' Decode all instructions in bytecode
|
||||
|
||||
:param bytecode: an evm bytecode (binary)
|
||||
:param offset: offset of the first instruction in the bytecode(optional)
|
||||
:type bytecode: iterator/sequence/str
|
||||
:return: An generator of Instruction objects
|
||||
|
||||
Example use::
|
||||
|
||||
>>> for inst in EVMAsm.decode_all(bytecode):
|
||||
... print inst
|
||||
|
||||
...
|
||||
PUSH1 0x60
|
||||
PUSH1 0x40
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x108
|
||||
PUSH1 0x0
|
||||
POP
|
||||
SSTORE
|
||||
PUSH1 0x40
|
||||
MLOAD
|
||||
|
||||
|
||||
'''
|
||||
|
||||
bytecode = iter(bytecode)
|
||||
while True:
|
||||
yield EVMDecoder.decode_one(bytecode)
|
||||
instr = EVMAsm.disassemble_one(bytecode, offset=offset)
|
||||
offset += instr.size
|
||||
yield instr
|
||||
|
||||
@staticmethod
|
||||
def disassemble(bytecode):
|
||||
output = ''
|
||||
address = 0
|
||||
for i in EVMDecoder.decode_all(bytecode) :
|
||||
output += "0x%04x %s\n"%(address, i)
|
||||
address += i.size
|
||||
return output
|
||||
def disassemble(bytecode, offset=0):
|
||||
''' Disassemble an EVM bytecode
|
||||
|
||||
:param bytecode: binary representation of an evm bytecode (hexadecimal)
|
||||
:param offset: offset of the first instruction in the bytecode(optional)
|
||||
:type bytecode: str
|
||||
:return: the text representation of the aseembler code
|
||||
|
||||
Example use::
|
||||
|
||||
>>> EVMAsm.disassemble("\x60\x60\x60\x40\x52\x60\x02\x61\x01\x00")
|
||||
...
|
||||
PUSH1 0x60
|
||||
BLOCKHASH
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x100
|
||||
|
||||
'''
|
||||
return '\n'.join(map(str, EVMAsm.disassemble_all(bytecode, offset=offset)))
|
||||
|
||||
@staticmethod
|
||||
def assemble(asmcode, offset=0):
|
||||
''' Assemble an EVM program
|
||||
|
||||
:param asmcode: an evm assembler program
|
||||
:param offset: offset of the first instruction in the bytecode(optional)
|
||||
:type asmcode: str
|
||||
:return: the hex representation of the bytecode
|
||||
|
||||
Example use::
|
||||
|
||||
>>> EVMAsm.assemble( """PUSH1 0x60
|
||||
BLOCKHASH
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x100
|
||||
"""
|
||||
)
|
||||
...
|
||||
"\x60\x60\x60\x40\x52\x60\x02\x61\x01\x00"
|
||||
'''
|
||||
return ''.join(map(lambda x:x.bytes, EVMAsm.assemble_all(asmcode, offset=offset)))
|
||||
|
||||
@staticmethod
|
||||
def disassemble_hex(bytecode, offset=0):
|
||||
''' Disassemble an EVM bytecode
|
||||
|
||||
:param bytecode: canonical representation of an evm bytecode (hexadecimal)
|
||||
:param int offset: offset of the first instruction in the bytecode(optional)
|
||||
:type bytecode: str
|
||||
:return: the text representation of the aseembler code
|
||||
|
||||
Example use::
|
||||
|
||||
>>> EVMAsm.disassemble_hex("0x6060604052600261010")
|
||||
...
|
||||
PUSH1 0x60
|
||||
BLOCKHASH
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x100
|
||||
|
||||
'''
|
||||
if bytecode.startswith('0x'):
|
||||
bytecode = bytecode[2:]
|
||||
bytecode = bytecode.decode('hex')
|
||||
return EVMAsm.disassemble(bytecode, offset=offset)
|
||||
|
||||
@staticmethod
|
||||
def assemble_hex(asmcode, offset=0):
|
||||
''' Assemble an EVM program
|
||||
|
||||
:param asmcode: an evm assembler program
|
||||
:param offset: offset of the first instruction in the bytecode(optional)
|
||||
:type asmcode: str
|
||||
:return: the hex representation of the bytecode
|
||||
|
||||
Example use::
|
||||
|
||||
>>> EVMAsm.assemble_hex( """PUSH1 0x60
|
||||
BLOCKHASH
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x100
|
||||
"""
|
||||
)
|
||||
...
|
||||
"0x6060604052600261010"
|
||||
'''
|
||||
return '0x' + EVMAsm.assemble(asmcode, offset=offset).encode('hex')
|
||||
|
||||
|
||||
|
||||
#Exceptions...
|
||||
|
||||
class EVMException(Exception):
|
||||
pass
|
||||
@@ -568,7 +961,6 @@ class Call(EVMException):
|
||||
def __reduce__(self):
|
||||
return (self.__class__, (self.gas, self.to, self.value, self.data, self.out_offset, self.out_size) )
|
||||
|
||||
|
||||
class Create(Call):
|
||||
def __init__(self, value, offset, size):
|
||||
super(Create, self).__init__(gas=None, to=None, value=value, data='')
|
||||
@@ -619,10 +1011,7 @@ class EVM(Eventful):
|
||||
from position 0), and the stack contents. The memory
|
||||
contents are a series of zeroes of bitsize 256
|
||||
'''
|
||||
|
||||
_published_events = {'read_code', 'decode_instruction', 'execute_instruction', 'concrete_sha3', 'symbolic_sha3'} # _published_events = {'write_register', 'read_register', 'write_memory', 'read_memory', 'decode_instruction'}
|
||||
|
||||
|
||||
_published_events = {'read_code', 'decode_instruction', 'execute_instruction', 'concrete_sha3', 'symbolic_sha3'}
|
||||
def __init__(self, constraints, address, origin, price, data, caller, value, code, header, global_storage=None, depth=0, gas=1000000, **kwargs):
|
||||
'''
|
||||
Builds a Ethereum Virtual Machine instance
|
||||
@@ -728,10 +1117,6 @@ class EVM(Eventful):
|
||||
|
||||
#Memory related
|
||||
def _allocate(self, address):
|
||||
#print pretty_print (address)
|
||||
#if address > 100000:
|
||||
# raise NotEnoughGas()
|
||||
|
||||
if address > self.memory._allocated:
|
||||
GMEMORY = 3
|
||||
GQUADRATICMEMDENOM = 512 # 1 gas per 512 quadwords
|
||||
@@ -746,8 +1131,6 @@ class EVM(Eventful):
|
||||
def _store(self, address, value):
|
||||
#CHECK ADDRESS IS A 256 BIT INT OR BITVEC
|
||||
#CHECK VALUE IS A 256 BIT INT OR BITVEC
|
||||
#if address > 100000:
|
||||
# raise NotEnoughGas()
|
||||
self._allocate(address)
|
||||
self.memory.write(address, [value])
|
||||
|
||||
@@ -776,7 +1159,7 @@ class EVM(Eventful):
|
||||
return value
|
||||
|
||||
def disassemble(self):
|
||||
return EVMDecoder.disassemble(self.bytecode)
|
||||
return EVMAsm.disassemble(self.bytecode)
|
||||
|
||||
|
||||
@property
|
||||
@@ -800,7 +1183,7 @@ class EVM(Eventful):
|
||||
while True:
|
||||
yield '\x00'
|
||||
|
||||
return EVMDecoder.decode_one(getcode())
|
||||
return EVMAsm.disassemble_one(getcode())
|
||||
|
||||
#auxiliar funcs
|
||||
#Stack related
|
||||
@@ -843,7 +1226,7 @@ class EVM(Eventful):
|
||||
last_pc = self.pc
|
||||
current = self.instruction
|
||||
|
||||
self._publish( 'will_execute_instruction', current)
|
||||
self._publish( 'will_execute_instruction', self.pc, current)
|
||||
#Consume some gas
|
||||
self._consume(current.fee)
|
||||
|
||||
@@ -855,10 +1238,10 @@ class EVM(Eventful):
|
||||
arguments = []
|
||||
if self.instruction.has_operand:
|
||||
arguments.append(current.operand)
|
||||
|
||||
for _ in range(current.pops):
|
||||
arguments.append(self._pop())
|
||||
|
||||
self._publish( 'did_execute_instruction', last_pc, self.pc, current)
|
||||
|
||||
#simplify stack arguments
|
||||
for i in range(len(arguments)):
|
||||
@@ -1881,61 +2264,3 @@ class EVMWorld(Platform):
|
||||
|
||||
self.current._push(value)
|
||||
self.current.pc += self.current.instruction.size
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bytecode='60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680635ec01e4d146044578063e1c7392a146067575bfe5b3415604b57fe5b60516076565b6040518082815260200191505060405180910390f35b3415606e57fe5b60746080565b005b6000600490505b90565b5b5600a165627a7a723058201ee3d4d835c10d46b09531c20dcdfe17b2dcef676a2666d66d0b3dde4969f6e00029'.decode ('hex')
|
||||
#print EVMDecoder.disassemble(bytecode)
|
||||
|
||||
instructions = list(EVMDecoder.decode_all(bytecode))
|
||||
|
||||
BBs = {}
|
||||
EDGES = {}
|
||||
current_bb = []
|
||||
address = 0
|
||||
for i in instructions:
|
||||
i.address = address
|
||||
|
||||
current_bb.append(i)
|
||||
if i.name in ['JUMPI', 'JUMP', 'STOP', 'INVALID', 'RETURN', 'SELFDESTRUCT', 'REVERT']:
|
||||
BBs[current_bb[0].address] = tuple(current_bb)
|
||||
if i.name in ['JUMP', 'JUMPI']:
|
||||
source = current_bb[0].address
|
||||
if len(current_bb) >= 2:
|
||||
if current_bb[-2].name.startswith('PUSH'):
|
||||
dest = current_bb[-2].operand
|
||||
EDGES.setdefault(source, set()).add(dest)
|
||||
if i.name == 'JUMPI':
|
||||
EDGES[source].add(address + i.size)
|
||||
|
||||
current_bb = list()
|
||||
address += i.size
|
||||
|
||||
|
||||
for addr in sorted(BBs.keys()):
|
||||
print hex(addr), ":", map(hex, sorted(list(EDGES.get(addr,set()))))
|
||||
print '\n'.join(map(lambda x: " "+str(x), BBs[addr]))
|
||||
|
||||
address=0x414141414141
|
||||
origin=0x424242424242
|
||||
price=1
|
||||
data='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
|
||||
sender=0x434343434343
|
||||
value=1
|
||||
header={'timestamp':1}
|
||||
depth=0
|
||||
|
||||
|
||||
from manticore.core.smtlib.constraints import ConstraintSet
|
||||
constraints = ConstraintSet()
|
||||
memory = constraints.new_array(256, 'MEM_%d'%depth)
|
||||
evm = EVM(memory, address, origin, price, data, sender, value, bytecode, header, depth)
|
||||
print evm
|
||||
import pickle
|
||||
a = pickle.dumps(evm)
|
||||
evm = pickle.loads(a)
|
||||
while True:
|
||||
evm.execute()
|
||||
print evm
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
import struct
|
||||
import unittest
|
||||
import json
|
||||
from manticore.platforms import evm
|
||||
from manticore.core import state
|
||||
from manticore.core.smtlib import Operators, ConstraintSet
|
||||
import os
|
||||
|
||||
|
||||
class EVMTest_Assembler(unittest.TestCase):
|
||||
_multiprocess_can_split_ = True
|
||||
maxDiff=None
|
||||
|
||||
def test_ADD_1(self):
|
||||
instruction = evm.EVMAsm.disassemble_one('\x60\x10')
|
||||
self.assertEqual( evm.EVMAsm.Instruction(0x60, 'PUSH', 1, 0, 1, 0, 'Place 1 byte item on stack.', 16, 0),
|
||||
instruction)
|
||||
|
||||
|
||||
instruction = evm.EVMAsm.assemble_one('PUSH1 0x10')
|
||||
evm.EVMAsm.Instruction(0x60, 'PUSH', 1, 0, 1, 0, 'Place 1 byte item on stack.', 16, 0)
|
||||
|
||||
instructions1 = evm.EVMAsm.disassemble_all('\x30\x31')
|
||||
instructions2 = evm.EVMAsm.assemble_all('ADDRESS\nBALANCE')
|
||||
self.assertTrue( all(a == b for a,b in zip(instructions1, instructions2)))
|
||||
|
||||
#High level simple assembler/disassembler
|
||||
|
||||
bytecode = evm.EVMAsm.assemble_hex(
|
||||
"""PUSH1 0x60
|
||||
BLOCKHASH
|
||||
MSTORE
|
||||
PUSH1 0x2
|
||||
PUSH2 0x100
|
||||
"""
|
||||
)
|
||||
self.assertEqual(bytecode, '0x606040526002610100')
|
||||
|
||||
asmcode = evm.EVMAsm.disassemble_hex('0x606040526002610100')
|
||||
self.assertEqual(asmcode, '''PUSH1 0x60\nBLOCKHASH\nMSTORE\nPUSH1 0x2\nPUSH2 0x100''')
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user