diff --git a/docs/api.rst b/docs/api.rst index c7ededc..c070986 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -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: + diff --git a/examples/evm/asm.py b/examples/evm/asm.py new file mode 100644 index 0000000..3f5c2c7 --- /dev/null +++ b/examples/evm/asm.py @@ -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') + + + diff --git a/examples/evm/experiment.py b/examples/evm/experiment.py deleted file mode 100644 index ca0d229..0000000 --- a/examples/evm/experiment.py +++ /dev/null @@ -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='', - ) diff --git a/examples/evm/seth.py b/examples/evm/seth.py index 596dcdf..2c0c9fc 100644 --- a/examples/evm/seth.py +++ b/examples/evm/seth.py @@ -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: diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index d9cb9e9..df89774 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -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=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 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< 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 - diff --git a/tests/EVM/test_EVMAssembler.py b/tests/EVM/test_EVMAssembler.py new file mode 100644 index 0000000..b1b8b71 --- /dev/null +++ b/tests/EVM/test_EVMAssembler.py @@ -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()