Safe

Enumeration

As always, we start with the enumeration phase, in which we try to scan the machine looking for open ports and finding out services and versions of those opened ports.

The following nmap command will scan the target machine looking for open ports in a fast way and saving the output into a file:

nmap -sS --min-rate 5000 -p- -T5 -Pn -n 10.10.10.147 -oN allPorts

  • -sS use the TCP SYN scan option. This scan option is relatively unobtrusive and stealthy, since it never completes TCP connections.

  • --min-rate 5000 nmap will try to keep the sending rate at or above 5000 packets per second.

  • -p- scanning the entire port range, from 1 to 65535.

  • -T5 insane mode, it is the fastest mode of the nmap time template.

  • -Pn assume the host is online.

  • -n scan without reverse DNS resolution.

  • -oN save the scan result into a file, in this case the allports file.

Now that we know which ports are open, let's try to obtain the services and versions running on these ports. The following command will scan these ports more in depth and save the result into a file:

nmap -sC -sV -p22,80,1337 10.10.10.147 -oN targeted

  • -sC performs the scan using the default set of scripts.

  • -sV enables version detection.

  • -oN save the scan result into file, in this case the targeted file.

The website shows the Apache2 Debian Default Page.

There is a comment in the source code saying that a binary called myapp, which is running on port 1337, can be downloaded from here.

Let's download it.

wget http://10.10.10.147/myapp; chmod +x myapp

If we run the binary, we'll see an output that looks like the one from the uptime command. Then, it will ask for input, and it will print back the input we just entered.

./myapp

If we try to enter a bunch of A characters, the program crashes. There could potentially be a buffer overflow vulnerability.

./myapp

Exploitation

For further inspection of the binary, let's open ghidra and create a new project. Select Non-Shared Project, select the project directory, and set the name of the project.

Then, import the myapp binary. We will see that it is a 64 bits ELF binary.

Drag and drop the binary into the CodeBrowser.

Then, press Ok, and Analyze.

Under Functions, select the main function, and we'll see that the local_78 variable is saving the input that the user enters. Note that the buffer has a size of 112 bytes. If we enter more than the buffer size then we start overwriting the stack and the program crashes.

Buffer Overflow

Let's start by finding the offset. Run the binary with gdb.

gdb ./myapp

Create a pattern of 200 bytes long.

gef➤ pattern create 200

Now, copy the pattern, run the program, and enter it. The program should crash.

gef➤ r

Now, we know that there are 120 bytes before starting to overwrite RSP, which is the register that controls the flow of the program.

gef➤ pattern offset $rsp

To prove that we have control of RSP, send a string with 120 A characters, 8 B characters, and 20 C characters.

What do you want me to echo back? AAA...AAABBB...BBCCC...CCC

As seen, the B characters appear in RSP. If we check the memory protections of the binary, we'll see that NX is enabled. Which means that it is not possible to execute code from the stack.

gef➤ checksec

Going back to the Ghidra CodeBrowser we'll see that there is another function called test. This function is copying the value of RSP and pasting it into RDI. Then, it is doing a JMP order to R13.

We can try to exploit ROP.

circle-info

Return Oriented Programming (or ROP) is the idea of chaining together small snippets of assembly with stack control to cause the program to do more complex things.

The idea is to be able to run the system() function with /bin/sh as an argument so the program spawns a shell. To use arguments in functions, we'll have to store those arguments in the following registries, ordered from first to last, before using them.

The strategy will be to put the /bin/sh string in RSP. As the test function is copying the value of RSP into RDI, the system() function will look for the argument in RDI, which will be /bin/bash\x00. The top of the stack will be copied to RDI, so we have to put the string right before RSP, in this case, the string is 8 bytes long, so the junk will be 112 bytes long followed by the /bin/bash\x00 string.

As the test function is doing a JMP order to R13, we have to search for a gadget pop r13. We find a gadget with ropper, that does pop r13; pop r14; pop r15. This way we can load the address of the system() function into R13, and fill R14 and R15 with null bytes.

ropper --file myapp --search "pop r13"

Now we need to know the address of system() to load it into R13.

objdump -D myapp | grep system

Finally, we need the address of the test function itself, so we can run it.

objdump -D myapp | grep test

Let's go through the whole process again. The final payload will consist of the junk, which is 112 bytes long, plus the /bin/sh\x00 string.

  • The junk, which is 112 bytes long, plus the /bin/sh\x00 string. This whole thing will be 120 bytes long, then we'll reach RSP. We do this because the test function is copying the top of the stack and pasting it into RDI, which will be the first argument for the system() function.

  • Then, we need to run the system() function. As test is doing a JMP order to R13, we found a gadget that makes a pop r13, making it possible to put the address of system() into R13. The gadget also does pop r14; pop r15, so we fill it with null bytes.

  • Finally, we call the test function so everything gets executed and a shell is spawned.

The final script will look like this.

Run the script, and get a shell as user. Then, we'll be able to grab the user flag.

python overflow.py

Privilege Escalation

To get a more stable shell, as port 22 is open, we can create a pair of SSH keys, copy the public key.

ssh-keygen

cat ~/.ssh/id_rsa.pub | tr -d '\n' | xclip -sel clip

And put it into the authorized_keys file of the victim machine.

echo "ssh-rsa AAA...o50= root@alfa8sa" > /home/user/.ssh/authorized_keys

Now login via SSH without a password as user.

ssh user@10.10.10.147

There are a few images and one interesting file called MyPasswords.kdbx in user home directory.

ls -la /home/user

Let's transfer all the images and the KeePass file to our local machine.

scp user@10.10.10.147:/home/user/IMG* .

scp user@10.10.10.147:/home/user/MyPasswords.kdbx .

The .kdbx files contain a password database but are only accessible with a passphrase. In addition to the passphrase, you can also protect the file with a file key. We could get the hash of the .kdbx file encrypted with every image, and try to break it with john.

for img in $(ls | grep .JPG); do keepass2john -k $img MyPasswords.kdbx >> hashes;done

Then, try to break one of the hashes.

john -w=/usr/share/wordlists/rockyou.txt hashes

Now that we have a password, we just have to go one by one image and see what is the right combination of the password and image. We'll see that the right image is IMG_0547.JPG.

keepassxc MyPasswords.kdbx

There is one entry with the password of the root user.

Become the root user, and all we have to do is reap the harvest and take the root flag.

su root

Last updated

Was this helpful?