rpisec_nuke binary exploitation writeup

This semester at RPI I’m taking the class “Modern Binary Exploitation”. This post will detail how I reversed and cracked the rpisec_nuke binary.

Understanding rpisec_nuke

Running the binary shows us 3 options, KEY 1, KEY 2, and KEY 3

KEY 1 is seemingly the most simple, it asks us for a launch key, and then tries to authenticate with that.

KEY 2 looks more complicated, it askes us for an ‘AES-128 CRYPTO KEY’, a data length, and the data to encrypt.

KEY 3 is presumably to be the most complicated. it starts by asking us to confirm the launch session, which is found at the top of the banner, then it gives us a challenge and asks for a response.

Unlocking KEY 1

After some time in IDA I found a fantastically simple bug that lets us get through all the authentication steps. If I input null bytes then it stops reading with length 0, with this the strncomp passes since all of the ( zero ) characters were matches.

Unlocking KEY 2 and determining PRNG

After spending a bunch of time in IDA looking through two, I couldn’t find the vuln. I gave up and went to key three. The first thing I noticed is that if you fail the launch session on the first time you open three it frees that struct on the WOPR. In GDB I could see that going back into two after this occurance causes it to allocate the struct right on top of where three’s free’d one was. If I enter in garbage on two and then go back to three the challenge that I see will contain the password for two xor’d with random integers.

The first step here was to figure out the prng seed. It was easy to find using IDA that it was seeded with the current time, and since in key three they give us the time of access we can use this as a reference point. If I just enter new lines during key two then when I get the key three, the first line of the challenge after uxoring should be

0a00000000000000000000000000000000

I wrote a loop to keep trying times lesser than the time output in key three until it was able to unxor to this value. Once I had this I knew the prng seed and could take advantage of any random numbers in the program. I also used this seed to unxor key two which gave me the password in plain text :) Entering this key in unlocks the level.

key2 = 4e96e75bd2912e31f3234f6828a4a897

Unlocking KEY 3

At this point I can guess random numbers. I spent some time looking at what the challenge actually does: Each time you open key three it xors the challenge with random numbers, then xors the diagonal 4 bytes at a time with 4 bytes of the password. when it checks your challenge it does the same thing agian. since I already can undo the random number xor, figuring out the password was as simple as unxoring the random numbers.

key3 =
90357303000000000000000000000000
00000000412ef8220000000000000000
0000000000000000cd10e79800000000
000000000000000000000000760491a6

To unlock the level I simple xor the challenge with the next 16 random numbers and also with the block above.

Programming Nuke86

The first thing I see when pressing the almighty 4 is a prompt asking me for the targetting code. After I give my input nuke86 tells me the checksum and then fails. The first thing to do is to figure out how to pass this step. In IDA I see that the checksum is compared against the xor of all three key flags which represent whether or not you have unlocked that key.

checksum = 0xCAC380CD ^ 0xBADC0DED ^ 0xACC3D489
checksum = 0xDCDC59A9

Also in the compute_checksum function I see that the checksum is just the composite of my input xor’d 4 bytes at a time. I played around with my input until I was able to get it to match 0xDCDC59A9 This showed me “PROGRAMMING COMPLETE”. When I then type launch it shows me mission failed shutting down, etc. Now it’s time to really learn how to program this thing.

In IDA launch_nuke reveals the “programming language” used by nuke86. Here’s a mapping of the instructions:

R = Reprogram Nuke
D = Compare to DOOM and DISARM, if DOOM then detonate, if DISARM, then disarm.
E = Compare to END, end the program.
I = Increment the "program pointer"
O = Output the "Targetting status code" which is relative to where the "program pointer" is
S = Set the character at the "program pointer" to the next character

With this it’s very easy to write a program to detonate on general doom, just use S and I to write it in, then type DOOM to blow it up

The exploit was pretty trivial to find, if I just keep typing I, then the “program pointer” goes way overboard and gives me write access to the function pointers in the struct namely the detonate_nuke pointer. So I can just I until I’m there, then put in my rop chain and DOOM to detonate. I tested this using strace to see if I can set the real program pointer to 0x41414141 and I was successful.

Since I already have a pointer to the WOPR I can just use that to point to where /bin/sh is input within the nuke86 program, and all I have to do is put in my rop chain after I do all my offsetting with I. I did not actually do the rop chain but I have a high suspicion that it will be the same execve setup that we’ve done many times.

My Auth Program can be found here: auth.py

My Key2 && Key3 inital finders can be found here: keys.py

tw33tchainz binary exploitation writeup

This semester at RPI I’m taking the class “Modern Binary Exploitation”. This post will detail how I reversed and cracked the tw33tchainz binary.

Understanding tw33tchainz

When we run tw33tchainz the first thing we are presented with is some art and a prompt for our username, and salt. upon entering these values we are given a generated password.

afterwards we are given a menu with 4 options, labelled 1, 2, 4, and 5. the option labelled ‘3’ is missing. Naturally I entered into the prompt 3, then I was asked to enter a password, my guess was incorrect.

Option 1 lets us enter a tweet which gets stored somewhere, and is appended to the penguin’s beak the next time the menu is output to our term.

Option 2 lets us view all of our previous tweets.

Option 4 simply prints out the ascii art that’s on top of the menu. ( looks like a mallet smashing a sombrero, or maybe a bird? nah… )

Option 5 quits the program.

Digging deeper

I decided a good first step would be to map out all the functions defined in the binary, I did this through the use of gdb

[email protected]:/levels/project1$ echo "disas main" | gdbpeda tw33tchainz | grep "call" | grep -v "@plt"
   0x08048e7f <+155>:	call   0x8048fac <print_banner>
   0x08048e84 <+160>:	call   0x804886c <gen_pass>
   0x08048e89 <+165>:	call   0x8048923 <gen_user>
   0x08048eb4 <+208>:	call   0x8048fac <print_banner>
   0x08048eb9 <+213>:	call   0x8048a34 <print_menu>
   0x08048f27 <+323>:	call   0x8048c0e <do_tweet>
   0x08048f2e <+330>:	call   0x8048bcd <view_chainz>
   0x08048f35 <+337>:	call   0x8048d40 <maybe_admin>
   0x08048f48 <+356>:	call   0x8048fac <print_banner>
   0x08048f4f <+363>:	call   0x8048fc1 <print_exit>


I then ran the same command for each function in the output.

a quick checksec shows us that RELRO isn’t full, therefore we can overwrite to the GOT.

CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial


From here I wanted to see if there were any vulnerable printf statements that would let me make arbitrary memory writes.

I was looking for printfs that touched user input, in the entire program there are only two locations where these reside: print_menu, and view_chainz. I started by going through each printf statement in print_menu and surely enough there is printf which takes the previous tweet and uses it as the first argument, a cardinal sin.

Unfortunately the block of code containing the vulnerable printf looks like this:

0x08048a84 <+80>:	movzx  eax,BYTE PTR ds:0x804c0a8
0x08048a8b <+87>:	test   al,al
0x08048a8d <+89>:	je     0x8048ab4 <print_menu+128>
0x08048a8f <+91>:	mov    DWORD PTR [esp],0x8049143
0x08048a96 <+98>:	call   0x80485d0 <[email protected]>
0x08048a9b <+103>:	lea    eax,[ebp-0x19]
0x08048a9e <+106>:	mov    DWORD PTR [esp],eax
0x08048aa1 <+109>:	call   0x80485d0 <[email protected]>


the first three lines there check to see if you are admin. If 0x804c0a8 is 0 then you are not admin, if either of the bottom 2 bytes are non-zero then you are considered an admin.

the vulnerable printf cannot be reached unless we’re set to admin. So the next step is to figure out what the correct password for option 3 is.

taking a look at the gen_pass function I see that it simply reads 16 bytes from /dev/urandom, these 16 random bytes are the password required for option 3.

gen_user does the following

  1. memset the salt with 16 bytes of 0xba
  2. memset the user with 16 bytes of 0xcc
  3. read 16 bytes into user
  4. read 16 bytes into salt
  5. call the hash function

hash then creates the generated password like so

  1. byte wise addition between the salt and the secret password
  2. xors that new value with the user

then later down in gen_user it calls print_pass which outputs the generated password as hex.

in print_pass

0x08048851 <+52>:	mov    DWORD PTR [esp],0x8049070
0x08048858 <+59>:	call   0x80485d0 <[email protected]>


value at 0x08049070

0x08049070 : 25 30 38 78 25 30 38 78 25 30 38 78 25 30 38 78   %08x%08x%08x%08x


given this, with a carefully chosen user and salt, we can easily recover the secret pass from the generated pass that we are given.

I chose to use 15 of 0x01 for the user and 15 of 0xff for the salt, I’m using 15 instead of 16 because in gdb I saw that fgets adds a null terminator as the 16th byte in each.

with these values I know that when we’re adding the secret password to 0xff the value will overflow and be 1 less than the original value.

afterwards when we xor this with the value 0x01 that will turn even numbers odd, one greater than the original value, and odd numbers even, one less than the original value.

for example if the secret byte was 0x55, when that’s added to 0xff we end up with the value 0x54, which then turns back into 0x55 after we xor it with 0x01.

further if the secret byte was 0x56, when that’s added to 0xff we end up with the value 0x55, which turns into 0x54 after we xor it with 0x01.

this means that in our generated password, we simply add 2 to the even bytes, and leave the odd bytes unchanged. except for the last byte, which was null. The last byte remains unchanged from the secret password.

I wrote a python script which takes the generated password as a command line argument and returns the secret password.

decode_pass.py


Success!, now that we’re admin we have a menu option labelled 6, and more importantly, access to the vulnerable printf statement in print_menu

since tweets are limited to 16 characters, the format string for the exploit must be this length.

in gdb I noticed that the stack is actually misalighned by 1 byte so the first character of the format string will be used to realign it.

tweet="A"

This means we have 15 bytes remaining for an arbitrary write.

First thing’s first, we need the address to write to, this will consume 4 more bytes which leaves us 11 bytes for %x and %n to actually write the value.

With 11 bytes there is simply not enough room for enough %x tokens to get to the address on the stack, which means I’ll have to use direct parameter access. with all the required characters our tweet format looks like this

tweet="A....%???x%#$hhn"

…. will be the address

The only thing left is to determine what values to substitute for ???, and which parameter # will be.

looking at the stack I can see that # has to be 8.

when doing some test writes in gdb I can see that whatever value I put in for ??? will be written to memory as 5 higher than the original value which means I just simply subtract 5 from the value I want to write and then the correct value will be written. This works unless the value I want to write is less than 5 since I can’t write a negative number of bytes. To mediate this I simply add 256 to every value which lets it overflow cleanly into the correct value.

The final anatomy of a malicious tweet which writes the value 0x10 to the location 0x43434343 looks like this.

tweet="ACCCC%267x%8$hhn"

Now it’s just a matter of finding a location to write my shellcode, then writing it, overwriting exit to point to that location ( Thanks RELRO! ) and cat’ing the password :)

I used the execve(“/bin/sh”) shellcode found here

and formatted it to be more easily readable by my final script: binsh.hex

I wrote a python script to create the format string given the address to write to, the tweet number, and the value to write: get_tweet.py

and my final driver to input all this to the program, and cat the password is written in bash: driver.sh

Results

$ ./driver.sh
...
...
Enter Choice: m0_tw33ts_m0_ch4inz_n0_m0n3y

urxvt unknown terminal type with ssh

If you use urxvt and ssh chances are you’ve ran into the same issue as I have:

$ clear
'rxvt-unicode-256color': unknown terminal type.

As it turns out, it’s quite easy to fix. All you have to do is copy over your local terminfo to the server which is giving you the problem.

create the terminfo folders on [email protected]:

mkdir -p ~/.terminfo/r/

copy over the terminal profile ( urxvt in my case ):

scp /usr/share/terminfo/r/rxvt-unicode-256color [email protected]:.terminfo/r/

All new ssh sessions will support the terminal profile you added :)