*CTF Treasure-hunter
这道题是rwctf中出现过的一道题改编。考察的主要是它本身定义的Merkle Tree的相关性质。
利用其中给出的部分操作码可以实现一些子树的添加、合并等等操作。最终满足在添加4个叶子的情况下还能正确计算root
并且在Pre_leave也就是检查是否验证过节点时也能通过根节点验证。(key和宝箱同时获得)
剩下需要注意的就是一些小条件了 比如 address的大小排序。以及左右合并顺序。
这在我的朋友 V4nish的这篇文章中介绍的非常详细。我这里不过多赘述了。
仅仅记录下题目的思考以及exp:
首先要了解题目允许我们来做什么:
用白话文讲
第一点 可以添加节点。在添加的时候会更新Root,并把你的这个叶子节点更新。
rw那道题只需要添加一次 就没有了从左合并还是从右合并这一顾虑。
而且 rw那道题其实也帮上了不少忙。感谢r3kapig的exp,不然第一次找计算root的hash还挺麻烦的。
这道题的难点要添加四次,而且后面再更新节点时因为Opcode再操作不同高度的时候会以不同的左右顺序来合并
子树往上继续合并。所以就要考虑到这点了。如果把高度同时也设置好。同时要记录每一次enter之后的合并成Root的2个hash。
这样比较方便后续操作我们的hash。
第二点就是操作我们的opcode了。
3种:
- 第一个是能把当前的叶子添加进去。也就是你之前enter的地址。
- 第二个是把你之前的2片叶子按照你输入的高度进行合并操作 会根据高度来 选择左右顺序。
- 第三个是你自己可以输入一个hash和你之前的叶子构成的hash合并。这也是我们解题非常重要的一点了。
enter就按规则往里面加就可以了。
第二步是要把整个team压进去且验证。然后难点就是要按照高度 和 之前我们压入时他的合并方向。再利用第二个opcode推一次。
第三步 是要把team压进去但不验证。这个就很简单。因为不验证的叶子都是0.只需要记得构成ROOT的hash的2个hash。在压入完前4个并合并之后。把2个hash按顺序压进去合并就可以了。
虽然不是 “合约安全” 相关的题,但是还是挺有趣的 还有点梦回OI 并且有点像公链的一些东西。想起来了西爹之前挖的CVE。。。
exp:
from web3 import Web3,HTTPProvider
from Crypto.Util.number import *
web3=Web3(HTTPProvider("http://123.60.45.88:8545/"))
print(web3.isConnected())
acct1= web3.eth.account.from_key('0xeadb4caca8d0088121549f2bf8ee0f6ee517110b4cfe36e3b655f85dfc035c1e')
acct2= web3.eth.account.from_key('0x00a6d7a2ee9220ae82d3dfbb9f4eb471bd8eacf80c31926855b5352866f8f5ee')
acct3=web3.eth.account.from_key('0x176e21af92fbfc31568f443b64922a630d0b27137c3e3c63c6aa6df2e9ee4b3f')
acct4=web3.eth.account.from_key('0x439ee9ada9f2991ecd3c4b1b2613b0d7f9eb08fefae687f14475105c1658da23')
tmp=(web3.eth.account.create())
print(tmp.address)
def deploy(rawTx,acct):
signedTx = web3.eth.account.signTransaction(rawTx, private_key=acct.privateKey)
hashTx = web3.eth.sendRawTransaction(signedTx.rawTransaction).hex()
receipt = web3.eth.waitForTransactionReceipt(hashTx)
print(receipt)
return receipt
abi="""[
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "_proofs",
"type": "bytes32[]"
}
],
"name": "enter",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "_proofs",
"type": "bytes32[]"
}
],
"name": "findKey",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "_proofs",
"type": "bytes32[]"
}
],
"name": "leave",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "_from",
"type": "address"
}
],
"name": "FindKey",
"type": "event"
},
{
"inputs": [],
"name": "openTreasureChest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "_from",
"type": "address"
}
],
"name": "OpenTreasureChest",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "_proofs",
"type": "bytes32[]"
}
],
"name": "pickupTreasureChest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "_from",
"type": "address"
}
],
"name": "PickupTreasureChest",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "haveKey",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "haveTreasureChest",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "isSolved",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "root",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "smtMode",
"outputs": [
{
"internalType": "enum SMT.Mode",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "solved",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
}
]"""
contract_address='0x6CC5804bb2a8FeBbE9b1babd7c3Bcc93735F5A81'
contract=web3.eth.contract(abi=abi,address=contract_address)
def to_bytes32(a: int) -> bytes:
return a.to_bytes(32, 'big')
def change_it(a):
tmp=[]
for i in a:
tmp.append(to_bytes32(int(i,16)))
return tmp
if __name__ == '__main__':
data1=["0x000000000000000000000000000000000000000000000000000000000000004c", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009c", "0xe9f810898db8dc62342eaa122fd26525362f2b70bd462edef6e4e34093d66c17", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009f", "0xba13a52ab72064627701ac75ab564f7e786d093c655849458536cc689abdf8e2"]
data2=["0x000000000000000000000000000000000000000000000000000000000000004c", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009a", "0x423b9e9e1b3a1cfbd4c7d075391785c44def9c2884281cc63e0a78fcc3bb7f66", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009f", "0xba13a52ab72064627701ac75ab564f7e786d093c655849458536cc689abdf8e2"]
data3=["0x000000000000000000000000000000000000000000000000000000000000004c", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009e", "0x4e951b5440112027d135d0f1dc97bff8127bec7439f8a54ff6a6b6a164764c64", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009f", "0xba13a52ab72064627701ac75ab564f7e786d093c655849458536cc689abdf8e2"]
data4=["0x000000000000000000000000000000000000000000000000000000000000004c", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009e", "0x4b3f7ee71efd14747b6620f1919bcdc78724d8a862e987596a1179a6b460be05", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009f", "0xba13a52ab72064627701ac75ab564f7e786d093c655849458536cc689abdf8e2"]
data5=["0x000000000000000000000000000000000000000000000000000000000000004c", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009c", "0xe9f810898db8dc62342eaa122fd26525362f2b70bd462edef6e4e34093d66c17", "0x000000000000000000000000000000000000000000000000000000000000004c", "0x0000000000000000000000000000000000000000000000000000000000000048", "0x000000000000000000000000000000000000000000000000000000000000009a", "0x000000000000000000000000000000000000000000000000000000000000004c","0x0000000000000000000000000000000000000000000000000000000000000048","0x000000000000000000000000000000000000000000000000000000000000009e","0x000000000000000000000000000000000000000000000000000000000000004c","0x0000000000000000000000000000000000000000000000000000000000000048", "0x000000000000000000000000000000000000000000000000000000000000009e","0x0000000000000000000000000000000000000000000000000000000000000050","0x000000000000000000000000000000000000000000000000000000000000009f","0xba13a52ab72064627701ac75ab564f7e786d093c655849458536cc689abdf8e2"]
data6=["0x000000000000000000000000000000000000000000000000000000000000004c","0x000000000000000000000000000000000000000000000000000000000000004c","0x0000000000000000000000000000000000000000000000000000000000000048", "0x000000000000000000000000000000000000000000000000000000000000009a", "0x000000000000000000000000000000000000000000000000000000000000004c","0x0000000000000000000000000000000000000000000000000000000000000048","0x000000000000000000000000000000000000000000000000000000000000009e","0x000000000000000000000000000000000000000000000000000000000000004c","0x0000000000000000000000000000000000000000000000000000000000000048","0x000000000000000000000000000000000000000000000000000000000000009e","0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009c", "0x03f38c848024ba3006373acb4175da682ce9d4e1ade10e0472919f30f664b1e4", "0x0000000000000000000000000000000000000000000000000000000000000050", "0x000000000000000000000000000000000000000000000000000000000000009f", "0xba13a52ab72064627701ac75ab564f7e786d093c655849458536cc689abdf8e2"]
final1=change_it(data1)
final2 = change_it(data2)
final3 = change_it(data3)
final4 = change_it(data4)
final5 = change_it(data5)
final6 = change_it(data6)
enter_txn1 = contract.functions.enter(final1).buildTransaction({
'nonce': web3.eth.getTransactionCount(acct1.address),
'gas': 300000,
'gasPrice': web3.eth.gasPrice
})
signed = acct1.signTransaction(enter_txn1)
tx_id = web3.eth.sendRawTransaction(signed.rawTransaction)
rec=web3.eth.waitForTransactionReceipt(tx_id)
print(rec)
enter_txn2 = contract.functions.enter(final2).buildTransaction({
'nonce': web3.eth.getTransactionCount(acct2.address),
'gas': 300000,
'gasPrice': web3.eth.gasPrice
})
signed = acct2.signTransaction(enter_txn2)
tx_id = web3.eth.sendRawTransaction(signed.rawTransaction)
rec=web3.eth.waitForTransactionReceipt(tx_id)
print(rec)
enter_txn3 = contract.functions.enter(final3).buildTransaction({
'nonce': web3.eth.getTransactionCount(acct3.address),
'gas': 300000,
'gasPrice': web3.eth.gasPrice
})
signed = acct3.signTransaction(enter_txn3)
tx_id = web3.eth.sendRawTransaction(signed.rawTransaction)
rec=web3.eth.waitForTransactionReceipt(tx_id)
print(rec)
enter_txn4 = contract.functions.enter(final4).buildTransaction({
'nonce': web3.eth.getTransactionCount(acct4.address),
'gas': 300000,
'gasPrice': web3.eth.gasPrice
})
signed = acct4.signTransaction(enter_txn4)
tx_id = web3.eth.sendRawTransaction(signed.rawTransaction)
rec=web3.eth.waitForTransactionReceipt(tx_id)
print(rec)
pickupTreasureChest_txn = contract.functions.pickupTreasureChest(final5).buildTransaction({
'nonce': web3.eth.getTransactionCount(acct4.address),
'gas': 300000,
'gasPrice': web3.eth.gasPrice
})
signed = acct4.signTransaction(pickupTreasureChest_txn)
tx_id = web3.eth.sendRawTransaction(signed.rawTransaction)
rec=web3.eth.waitForTransactionReceipt(tx_id)
print(rec)
findKey_txn = contract.functions.findKey(final6).buildTransaction({
'nonce': web3.eth.getTransactionCount(acct4.address),
'gas': 300000,
'gasPrice': web3.eth.gasPrice
})
signed = acct4.signTransaction(findKey_txn)
tx_id = web3.eth.sendRawTransaction(signed.rawTransaction)
rec=web3.eth.waitForTransactionReceipt(tx_id)
print(rec)
openTreasureChest_txn = contract.functions.openTreasureChest().buildTransaction({
'nonce': web3.eth.getTransactionCount(acct4.address),
'gas': 300000,
'gasPrice': web3.eth.gasPrice
})
signed = acct4.signTransaction(openTreasureChest_txn)
tx_id = web3.eth.sendRawTransaction(signed.rawTransaction)
rec=web3.eth.waitForTransactionReceipt(tx_id)
print(rec)