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:
Workerhas a virtualwork();Adminoverrides 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 == 8is 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
0stores0intochoice_history. Later,view_history()checkschoice <= choice_map.size()and then indexes choice_map[choice-1]. Forchoice==0that 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}