237 words
1 minutes
BITSCTF 2026 - safe not safe - Reverse Engineering Writeup

Category: Reverse Engineering
Flag: BITSCTF{7h15_41n7_53cur3_571ll_n07_p47ch1ng_17}

Challenge Description#

Given dist.zip with ARM kernel image. Remote: nc 135.235.195.203 3000. Flag on /dev/vda.

Analysis#

Challenge binary /challenge/lock_app is SUID root. Core math:

m1 = ((uint32_t)(a * 0x7A69) + b) % 1_000_000
m2 = (a ^ b) % 1_000_000
challenge = u ^ m1
response  = u ^ m2

response = challenge ^ m1 ^ m2

Binary uses glibc random_r with predictable state based on startup time.

Solution#

Reproduce RNG behavior with ctypes, rebuild S-box from time-derived seed:

#!/usr/bin/env python3
import ctypes
import re
import socket
import time

HOST = "135.235.195.203"
PORT = 3000


class RandomData(ctypes.Structure):
    _fields_ = [
        ("fptr", ctypes.c_void_p),
        ("rptr", ctypes.c_void_p),
        ("state", ctypes.c_void_p),
        ("rand_type", ctypes.c_int),
        ("rand_deg", ctypes.c_int),
        ("rand_sep", ctypes.c_int),
        ("end_ptr", ctypes.c_void_p),
    ]


libc = ctypes.CDLL("libc.so.6")


class GlibcRandomR:
    def __init__(self):
        self.rd = RandomData()
        self.statebuf = (ctypes.c_char * 128)()
        libc.initstate_r(1, ctypes.cast(self.statebuf, ctypes.c_char_p), 
                         ctypes.sizeof(self.statebuf), ctypes.byref(self.rd))

    def reseed(self, seed: int):
        libc.srandom_r(ctypes.c_uint(seed).value, ctypes.byref(self.rd))

    def next_u32(self) -> int:
        out = ctypes.c_int()
        libc.random_r(ctypes.byref(self.rd), ctypes.byref(out))
        return ctypes.c_uint32(out.value).value


def build_sbox(start_seed):
    rng = GlibcRandomR()
    rng.reseed(start_seed)
    rng.next_u32()
    rng.next_u32()
    s = list(range(256))
    for i in range(255, 0, -1):
        j = rng.next_u32() % (i + 1)
        s[i], s[j] = s[j], s[i]
    return s


def compute_response(challenge, sbox, challenge_seed):
    rng = GlibcRandomR()
    rng.reseed(challenge_seed)
    a = transform32(rng.next_u32(), sbox)
    b = transform32(rng.next_u32(), sbox)
    m1 = ((((a * 0x7A69) & 0xFFFFFFFF) + b) & 0xFFFFFFFF) % 1_000_000
    m2 = (a ^ b) % 1_000_000
    return (challenge ^ m1 ^ m2) & 0xFFFFFFFF


# Parse time, rebuild S-box, request challenge, compute response, submit
# ... (connection handling) ...
BITSCTF 2026 - safe not safe - Reverse Engineering Writeup
https://blog.rei.my.id/posts/48/bitsctf-2026-safe-not-safe-reverse-engineering-writeup/
Author
Reidho Satria
Published at
2026-02-22
License
CC BY-NC-SA 4.0