02.secure
2023-09-18 11:10:14 # 19.Paradigm CTF 2021

secure

分析

1.全局观

  • Wallet.sol

    • ERC20Like:接口
    • TokenModule:合约,仅拥有deposit()withdraw()两个方法
    • Wallet:一个钱包合约,所有操作都需要owner本人,获取owner批准的白名单用户才可以操作,但是无法修改owner,拥有delegatecall()
  • Setup.sol

    • WETH9:接口
    • Setup:在构造器初始化题目

2.任务

将本合约的余额0修改为50

1
2
3
function isSolved() public view returns (bool) {
return WETH.balanceOf(address(this)) == WANT;
}

3.详细分析

所有内容都在Setup的构造器中初始化:本来Setup合约有50WETH,然后转出去了,变为0WETH,完成题目的要求是让Setup合约再次拥有50WETH。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  constructor() public payable {
// 合约创建的时候传入了50 ether
require(msg.value == WANT);

// 新建一个 TokenModule 合约
address tokenModule = address(new TokenModule());

// 新建一个 Wallet 合约,本合约是 Wallet 合约的owner
wallet = new Wallet 合约();
// wallet 将 tokenModule 合约添加进白名单
wallet.allowModule(tokenModule);

// 本合约在 WETH 合约拥有50ETH
WETH.deposit.value(msg.value)();
// wallet 可以操作本合约在 WETH 的50ETH
WETH.approve(address(wallet), uint(-1));

// wallet 在 WETH 合约拥有50ETH
wallet.execModule(tokenModule, abi.encodeWithSelector(TokenModule(0x00).deposit.selector, WETH, address(this), msg.value));
}

经过分析,wallet合约我的账户无法做任何操作,因为不是白名单中。wallet 将 tokenModule 合约添加进白名单 是关键,因为这个合约进入了白名单。

TokenModule合约的两个函数,如果token是WETH,我们也无法做任何操作:

  • transferFrom():没有任何人approve()给TokenModule合约

  • transfer():TokenModule合约都没有钱,更加无法操作

我想的方式是通过TokenModule合约调用wallet合约的execModule()从而转钱,因为wallet合约有钱嘛。但是行不通,因为TokenModule合约根本没有办法调用execModule()

1
2
3
4
5
6
7
8
9
contract TokenModule {
function deposit(ERC20Like token, address from, uint amount) public {
token.transferFrom(from, address(this), amount);
}

function withdraw(ERC20Like token, address to, uint amount) public {
token.transfer(to, amount);
}
}

我本来认为考点就是我上面分析的那样,把一开始的50ether转回去,让Setup合约重新拥有50ether完成题目。其实在Paradigm里面,每个用户每个关卡都拥有5000ether,我们只要充值50ether给WETH,然后转给Setup即可完成题目,实在无语。。。。

解题

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
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.0;

import "forge-std/Test.sol";
import "./interface.sol";

contract attackTest is Test {

string constant Setup_Artifact = 'out/Setup.sol/Setup.json';
string constant weth9_Artifact = 'out/helper_WETH9.sol/WETH9.json';

ISetup level;
IWETH9 weth9;

function setUp() public payable{
// 创建我们的WETH9合约
weth9 = IWETH9(deployHelper_weth(weth9_Artifact));
// 初始化题目合约,并传入WETH9合约的地址,因为复现的原因,WETH9的地址修改了一下,题目源码修改了一点,但是效果是差不多一样的
level = ISetup(this.deployHelper_Setup{value:50 ether}(Setup_Artifact,address(weth9)));
}

function test_isComplete() public{
// 因为在Paradigm 2021中,每个用户初始化拥有5000个ETH
weth9.deposit{value: 50 ether}();
weth9.transfer(address(level), 50 ether);

// 检查是否完成
assertEq(level.isSolved(), true);
}

function deployHelper_weth(string memory what) public returns (address addr) {
bytes memory bytecode = vm.getCode(what);
assembly {
addr := create(0, add(bytecode, 0x20), mload(bytecode))
}
}
function deployHelper_Setup(string memory what,address _addr) public payable returns (address addr) {
bytes memory bytecode = vm.getCode(what);
// 构造器有参数
bytes memory bytecode_withConstructor = abi.encodePacked(bytecode,abi.encode(address(_addr)));
assembly {
addr := create(50000000000000000000, add(bytecode_withConstructor, 0x20), mload(bytecode_withConstructor))
}
}

}