secure
分析
1.全局观
Wallet.sol
- ERC20Like:接口
- TokenModule:合约,仅拥有
deposit()
和withdraw()
两个方法
- Wallet:一个钱包合约,所有操作都需要owner本人,获取owner批准的白名单用户才可以操作,但是无法修改owner,拥有
delegatecall()
Setup.sol
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,我们也无法做任何操作:
我想的方式是通过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)) } }
}
|