← ALL WRITEUPS

RESEARCHNODE / WRITEUP

bitdebit2 dctf 2026 writeup

DefCamp D-CTF BitDebit² writeup covering a single-bit arbitrary flip, FSOP and JOP through glibc FILE structures, setcontext-based code execution, and a seccomp ABI bypass to recover the flag.

reverse

reconnaissance the binary lets us put attacker controlled data inside a huge malloc chunk before the bit flip. after that it installs seccomp and returns from main, so the main goal was getting code execution through libc cleanup. first i requested a big allocation like 1 << 25. it gets mmaped close to libc and the program prints the pointer, so libc base can be calculated with: libc_base = malloc_leak + SIZE + 0x3ff0 useful offsets in the provided libc were: _IO_list_all = libc + 0x2044c0 _IO_2_1_stderr_ = libc + 0x2044e0 _IO_wfile_jumps = libc + 0x202228 leak and bit flip the primitive only gives one arbitrary bit flip. i picked a bit k so that: fake_file = stderr_addr ^ (1 << k) points inside our mmap chunk. then i flipped that bit inside _IO_list_all, so instead of pointing to stderr the FILE list now points into attacker controlled memory. fsop inside the mmap region i placed fake FILE structures, fake _IO_wide_data, fake wide vtable and a context for setcontext. important FILE fields were: FILE + 0x88 = writable lock FILE + 0xa0 = fake wide data FILE + 0xc0 = 1 FILE + 0xd8 = libc + _IO_wfile_jumps when main returns, glibc cleanup walks the fake FILE and reaches: _IO_flush_all -> _IO_wfile_overflow -> _IO_wdoallocbuf -> fake __doallocate -> JOP -> setcontext setcontext gives control of rip, rsp and syscall arguments, so from here we basically have a rop/jop style execution primitive. seccomp bypass the seccomp filter only allows syscall numbers 9 and 295, but it doesnt validate seccomp_data.arch. 295 means different things depending on ABI: x86-64: preadv

i386: openat first i used syscall 9 to make a fixed executable mapping: mmap( 0x13370000, 0x4000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0 ); then copied shellcode there using bcopy at libc + 0xb1070 and jumped to it. the shellcode opened the flag using i386 int 0x80: mov eax, 295 mov ebx, -100 lea ecx, [path] xor edx, edx xor esi, esi int 0x80 after that i used x86-64 syscall 295 as preadv to read the flag into controlled memory. getting the flag write was blocked by seccomp, so there was no direct way to print the flag. instead i used a connection oracle. shellcode compares one byte with a threshold: if (flag[pos] >= threshold) for (;;) {} else __builtin_trap(); if the connection stays open, condition is true. if it closes, condition is false. binary searching every byte takes around 8 connections per character, and after recovering the whole string i got: CTF{d85bcbd101b11415ebd95ef88841f6c24893bb287c6179b739c94b8d3f00e403} gg, been fuckiing w it for the whole day

END OF NOTE / dencent444MORE WRITEUPS ↗