#pwn #blockchain #solana Part of the [[ALLES! 2021]] CTF. # Description It's rumored that you can get a free flag on the Solana blockchain, but it's locked behind a secret! Can you still obtain it? The contract is deployed at: `Secret1111111111111111111111111111111111111` This challenge has the same setup as all Solana Smart Contract challenges: a validator running in a docker container that you have to interact with via RPC. This challenge can be solved by just using the store-cli and solana cli. If you got the secret, you can get the flag by calling ```bash store-cli -k ./keys/rich-boi.json get-flag <flag_depot_address> <secret> ``` We recommend using the [Solana PoC Framework](https://github.com/neodyme-labs/solana-poc-framework) which facilitates fast exploit development. Alternatively you can also use the official [rust api](https://docs.rs/solana-client/1.7.10/solana_client/rpc_client/struct.RpcClient.html), the official [js api](https://solana-labs.github.io/solana-web3.js/) or any other way you can think of interacting with the RPC server. Solana also has a multitude of [cli tools](https://docs.solana.com/cli/install-solana-cli-tools). Please note however that due to setup limitations, the TPU port of the validator is not exposed, which means the `solana program deploy` command will not work. The Solana PoC Framework has a [function](https://docs.rs/poc-framework/0.1.0/poc_framework/trait.Environment.html#method.deploy_program) for this that only uses the rpc endpoint and will work. The [solana explorer](https://explorer.solana.com/) works with any cluster your browser can reach. Just click on the `Mainnet Beta` button and enter the url of the RPC endpoint into the `Custom` text field. Checking the `Enable custom url param` checkbox might also be useful for collaboration. The explorer allows you to inspect accounts and transactions and has a bunch of useful features. The goal of these challenges is to obtain a flag-token (mint `F1agMint11111111111111111111111111111111111`). After you got one, you have to call the flag contract `F1ag111111111111111111111111111111111111111`. The instruction data is ignored, the first account has to be a spl-token account that contains a flag token and the second account has to be the owner of the token account. The second account needs to sign the transaction, to proof that you really got the flag. A good starting point is the Solana documentation: - [https://docs.solana.com/developing/programming-model/overview](https://docs.solana.com/developing/programming-model/overview) - [https://spl.solana.com/token#operational-overview](https://spl.solana.com/token#operational-overview) - [https://docs.solana.com/developing/clients/jsonrpc-api](https://docs.solana.com/developing/clients/jsonrpc-api) Files: [secret-store.zip](https://mega.nz/file/nttkgBTT#3Zp29kFaoAhPrxI5tnR087nejL10EFrRB0JVGEfKrrE) # Initial Understanding Before we attempt this challenge it is important to have an understanding of how the Solana blockchain works so here are some of the key points as I understand them: - An account on Solana is identified by its public key and is a store of both SOL currency and data. - An account's data can be marked as executable, at which point it becomes a program that can be executed on the blockchain (like an Ethereum smart contract). - Every account is owned by a program. Only this program can modify the account's data. - There are two types of account: - An account with a public key and a private key. These accounts can sign transactions using their private key. - An account that is derived from another account using ECC magic. These accounts only have a public key and "sign" transactions by proving the derivation and having the account from which they were derived sign it. - As executable accounts are immutable, programs will often use derived accounts to store data. - When executing a program you pass it accounts and data. Every account that the program interacts with must be passed. If the program has multiple functions then the first byte of data is often used to define which is being called. - The system program is very important. You interact with it to create accounts and send transactions. Accounts that just hold SOL will generally be owned by it. Knowing these points makes both this challenge and [[Legit Bank]] far easier. # Analysis The provided zip file allows us to start a local instance of the challenge using Docker. We will exposed port 1024 as that is where the RPC server is running: ```bash unzip secret-store.zip cd secret-store sudo docker build -t ss . sudo docker run -d -p 1024:1024 ss ``` The challenge description tells us that the `Secret...` program can give us the `F1agMint...` token. Luckily we are given the source code for this program in `program/src`. The `lib.rs` file contains definitions used by `processor.rs` which holds the contract's functions. We'll start by looking at `lib.rs` as it has very good documentation: ```rust /// General information about a single bank #[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Store { // simple secret pub secret: u64, } pub const STORE_LEN: u64 = 8; /// Instructions that this program supports #[derive(Debug, BorshDeserialize, BorshSerialize)] pub enum StoreInstruction { /// Initialize the Store /// /// To protect from scams, only a single bank is supported. /// Thus, the address of the bank account must be the program-derived address with empty seed. /// /// Passed accounts: /// /// (1) Store account /// (2) Flag account, pays for creation of secret account (must sign) /// (3) Rent sysvar Initialize { secret: u64 }, /// Get the Flag /// /// Gives you flag if you give secret :) /// /// Passed accounts: /// /// (1) Store account GetFlag { secret: u64 }, } pub mod processor; use processor::process_instruction; entrypoint!(process_instruction); ``` We can infer that the `Store` `struct` is used to hold the program's data. It has a single member, `secret`, which is probably the `secret` we need to give `store-cli`. The `StoreInstruction` `enum` tells us what functions the program has and gives some documentation on each. Using this information we can guess what each function does: - `Initialize` creates a derived account for the `Store` `struct` and populates it with the `secret` argument. - `GetFlag` compares the `secret` argument to the value in the derived account. If they match then we get the `F1agMint...` token. Our theory can be partially confirmed using Solana Explorer. Looking at the `Secret...` program shows one transaction. The data for the only instruction in the transaction is `00c600cc547abcee71` (nine bytes). This makes perfect sense given that one byte is needed for the function identifier and the remaining eight should be the `secret` argument. The inner instructions give even more information. A new account, `4Wqg...`, is created with eight bytes of space (the length of `secret`). The Solana command line tools let us see the data in this account: ```bash solana -u http://localhost:1024 account 4WqgwsyU8WautoDMPQZFrnJ26d7UWrY39GHyz3qWtFQN ``` It is the same `c600cc547abcee71` as the argument. The program uses Borsh to serialize data which means it is little-endian. We can use Python to convert it back to an integer: ```python unpack("<Q", bytes.fromhex("c600cc547abcee71")) ``` Which gives `8209706404337680582`. # Solution In order to avoid compiling `store-cli` we will do everything in the the docker container from now on: ```bash sudo docker exec -it <container_id> /bin/bash ``` We have worked out that the `secret` argument `8209706404337680582` but we still need the `flag_depot_address`. We have been given a `flag-depot.json` keypair in `/keys` and can get the address with: ```bash solana-keygen pubkey /keys/flag-depot.json ``` Which gives `Bfkx...`. Sadly trying this with `store-cli` doesn't work because the `Secret...` address isn't initialized for receiving `F1agMint...` SPL tokens. We can create and initialize a new derived account from the command-line with: ```bash solana config set -u http://localhost:1024 -k /keys/flag-depot.json spl-token create-account F1agMint11111111111111111111111111111111111 ``` Which tells us that the derived address is `HYkP...`. Let's try running `store-cli` with that address: ```bash store-cli -k ./keys/rich-boi.json get-flag HYkP8hLxL1eXSRcX6EGZ9bMFJ1csmyBvJGcWeGU6Syh3 8209706404337680582 ``` It works! We get the placeholder flag `ALLES!{placeholder}`. We can follow the same process to recover the remote secret and get the real flag.