2016年12月24日 星期六

[Write-up] WhiteHat Grand Prix 2016 - pwn400 Cao lau / note_trial_1


This challenge wasn't solved by me but +winesap during the competition. 
I do this challenge off-line and the exploit has been confirmed by +winesap will work.





Basic Info
Attachments are two 32bit ELFs and one plaintext: note_trial_1, ptrace_32, blacklist.txt


The note_trial_1 binary is Full RELRO but no PIE enable. While it do have canary, the checksec of pwntools might have bugs.

The ptrace_32 binary is a sandbox, which use ptrace to forbid its child(i.e note_trial_1) from using syscalls listed in blacklist.txt.

We will see the forbidden syscalls later, let's focus on functions of note_trial_1 first.

Functions

(Only important functions are listed)
  • Initialize notes: 
    /*
    struct Note {
      char name[32];
      char date[32];
      char body[300];
    }; */
    for(i = 0; i <= 99; ++i ) {
      pool[i] = (Note *)malloc(364);
      memset(pool[i], 0, 364);
    }
    /* global variables */
    total = 0;
    cafe = 0xcafebabe;
    
  • Generate license:
    unsigned int seed;
    buf = (unsigned int *)malloc(4u);
    stream = fopen("/dev/urandom", "rb");
    fread(&ptr, 4u, 1u, stream);
    *buf = ptr;
    fclose(stream);
    srand(*buf);
    for(i = 0; i < 5; i++)
      license[i] = rand() % 6969 + 1000;
    

    Briefly, reads 4 bytes from /dev/urandom as srand seed and generates a five number license. Keep in mind that the seed is recorded on a heap buffer.
  • add:
    • note = pool[total++];
    • readline(note->name, 32)
    • readline(note->date, 32)
    • readline(note->body, 300)
  • read:
    • Input index
    • Show contents of pool[index].
  • teencode:
    • Input index
    • note = pool[index];
    • convert content of note->body to leetcode(e.g. A->4, C->[, M->|V|).
  • license:
    • return if cafe != 0xcafebabe
    • If input the correct five number license, will have format string vulnerability.
    • cafe = 0xc4f3b4b3
Vulnerability

One vulnerability has been described above: if input the correct license then we can use the fmt string attack.

However, we don't know what the correct license is - if we don't know the random seed.

Another vulnerability is the command teencode, which converts M to |V|, K to |<, and thus makes the string longer than buffer size, leads to heap overflow.

Exploitation

Exploitation contains two parts, first part is to leak the seed and second part is use fsa to leak information and control eip.

Leak seed

First part is very easy.
Remember that the seed has been recorded on heap, which located right after the 100th note. So just simply use the command teencode to make the 100th note's body "touch" the seed, then read the note will print the seed out.

                 +--------------------------+------------+------+
                 | ...AAAAAAAAAAMMKFFFF\x00 | 0x00000011 | seed |
                 +--------------------------+------------+------+
                                         |
                                         v
                 +--------------------------+------------+------+
                 | ...4444444444|V||V||<    |    FFFF    | seed |
                 +--------------------------+------------+------+

Now we have the seed, and we write a simple C code to get the corresponding license.

Second part is the fmt string attacking.
However, there's a global variable cafe that make us only have one chance to use fsa. But the solution is easy, use fsa to overwrite cafe back to 0xcafebabe every time then we have unlimited times of fsa.

Information leak

With fsa, information leak is very easy.
Here we leak the libc, heap and stack address, and use DynELF of pwntools to get the remote glibc version.

Control EIP

With fsa and stack address, we can overwrite the return address and use ROP to control code flow.
While call system("/bin/sh") directly will fail! Yes, the execve syscall will be caught by the sandbox ptrace_32.

Since there's input length limit of the format string, use it to create ROP chain is inconvenient. We choose to put shellcode on heap and use ROP to call mprotect(heap, 0x1000, 7), then we can execute arbitrary code with (almost) unlimited length.

Bypass sandbox

It's time to see what syscalls are forbidden.
The syscall numbers listed in blacklist.txt are
120 (clone)
2 (fork)
190 (vfork)
11 (execve)
37 (kill)
238 (tkill)
270 (tgkill)

We can use open + getdents + write to create a /bin/ls like shellcode. While this will find lots of directories and files exist on remote server, and none of them named "flag". So we choose to bypass the sandbox and get shell to make life easier.

Common two strategies to bypass the ptrace-base sandbox are:
  1. simply fork/vfork/clone
  2. kill parent process
Obviously we cannot use these two strategies.


The other common strategy is to change architecture. Though this binary is 32bit, it can switch to 64bit and use 64bit syscalls.
Change architecture to 64 bit is simple:
  push 0x33
  call change_to_64

  /* switch to 64 bits after retf */
  jmp s64 /* same asm between 32 and 64 */
change_to_64:
  retf
s64:
  /* 64bit shellcode */

Notice that even we switch to 64bit arch, we still cannot use those forbidden syscall numbers. Unfortunately, the syscall number 2 is open in 64bit.

But this is not a problem, now we can use the normal strategy to escape from our parent - use fork.

s64:
  /* 64bit shellcode */
  /* call fork() */
  push 0x39
  pop rax
  syscall
  test eax, eax
  jne exit
  /* shellcode without sandbox tracing! */
  ...
exit:
  /* call exit() */
  push 0x3c
  pop rax
  syscall

Then we can get shell and find flag in one of lots files ;).

Exploit script

Flag: WhiteHat{3500ab3a525e759b258585f8494891435b5b16f8}

沒有留言:

張貼留言