pwnfreemedium

Greetings

tjctf

Task: 64-bit PIE ELF with executable stack and no canary; user controls fgets size causing stack buffer overflow. Solution: inject shellcode in buffer, skip printf to preserve RAX (buffer pointer from fgets), partial-overwrite return address to jmp rax gadget with 1/16 PIE brute force.

$ ls tags/ techniques/
shellcode_injectionfgets_size_overflowpartial_return_address_overwriterax_register_preservationpie_brute_force

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Greetings — TJCTF 2026

Description

Greetings from TJ to you. Find the exploit, yes please do. Remote: nc tjc.tf 31373

We are given a 64-bit PIE ELF binary with source code. The program asks for a username size and a username, then greets the user if the name starts with @. The binary has an executable stack, no canary, and partial RELRO. A provided libc (Ubuntu GLIBC 2.42) and loader are included.

Analysis

Source Code

void greetUser() { int uname_size; char uname[64]; printf("Enter the size of your username: "); scanf("%d", &uname_size); getchar(); uname_size += 2; printf("Enter username (start with @): "); fgets(uname, uname_size, stdin); if (*(char *) uname == '@') { printf("Greetings to you: %s!", uname); } }

Vulnerability

The user controls uname_size via scanf("%d"). After +2 is added, this value is passed directly to fgets(uname, uname_size, stdin). Since uname is only 64 bytes on the stack, providing a large size causes a classic stack buffer overflow past saved registers and the return address. No stack canary protects the function.

Binary Protections

Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX unknown - GNU_STACK missing
PIE:        PIE enabled
Stack:      Executable
RWX:        Has RWX segments

The executable stack (RWX) is the critical enabler — shellcode injection is viable.

Stack Layout

From disassembly of greetUser:

greetUser:
  pushq %rbx           ; save RBX
  subq $0x50, %rsp     ; allocate 0x50 = 80 bytes

  rsp+0x0c: uname_size (4 bytes, int)
  rsp+0x10: uname[64]  (64-byte buffer)
  rsp+0x50: saved RBX  (8 bytes)
  rsp+0x58: return address (8 bytes) -> main+9 = PIE_base + 0x1089

Offsets from uname buffer start:

  • Saved RBX: offset 64 (0x40)
  • Return address: offset 72 (0x48)

Key Gadgets

...

$ grep --similar

Similar writeups