Sunday, September 21, 2014

Writeup - CSAW 2014 : Reversing 400 - saturn


The challenge saturn was misleadingly labeled as worth 400 points, suggesting that it would be much more difficult than prior challenges such as ish and s3. The prompt indicated that a shared object file 'libchallengeauth.so" or something along those lines  was forgotten, therefore we cannot run the binary on our end, yay for static analysis!

After plopping the binary into IDA (of course), we searched for a subroutine that would contain some kind of recognizable information, i.e. the string "CSAW ChallengeResponseAuthenticationProtocol" that is sent after the connection is made. After some poking around, we stumbled upon the string in sub_8048A7B which we promptly labeled main.



Within main the function calls fillChallengeResponse with pointers to two seperate offsets. Not knowing their purpose immediatley, we moved on to look at how the subroutine handled our input (read in as one byte in sub_8048825)


Here it is visible that the program takes the given input that is returned into eax, grabs the lower 8 bits of it, sign extends it, and compares the upper 4 bits of the number to either 128, 160, or 224. If one of these conditions are met, the program jumps to subroutines 8048A01, 80488E8, and 804885C. After the completion of the subroutine, the program continues the loop.



Subroutine 8048A01 (further referred to as the readflag subroutine) does exactly as its given title states, it reads the file flag.txt and flushes it to the user, provided that the subroutine it calls passes as well, which is governed by the return of

...
mov     eax, [ebp+var_4]
mov     eax, ds:dword_804A0A0[eax*4] ; -- Important
mov     edx, [ebp+var_8]
imul    eax, edx
mov     [ebp+var_8], eax
add     [ebp+var_4], 1
...

What the above does is recursively check to make sure that the values within the dword at 804A0A0 are not zero! If anyone of them is zero, the imul nullifies the return value. So, now we ask, how do we make those values not equal zero? After some poking around...

FOUND IT!


The subroutine triggered by a value sent between 224-239 (base 10) sets the values in that address space on the lower right hand branch of this call. So, you ask, how do you get there? This function, after being called, takes four bytes as input. If the four bytes you give it are equal to the memory at that offset plus your (224-239) && 0xF (a number between 0 and 15), then it sets the byte at 804A0A0 at that same offset to one. So, another question, how do we read that data?

BOOM! GOT IT!


This subroutine here (triggered by values 160-175) pulls information out of... wait... a different offset than the one we need?!?!?!?!?!? If you look in the .bss section of the binary, you see that the 20 byte section of data being read from is not the same as the 20 byte section of data we need to compare to... damn..

NO MATTER!, the sole purpose of this challenge is to recognize that when you increment the pointer to a character type (this offset) it increments by four, and when you increment the value of a pointer to an int, it increments by one. That means that within the context of this function we have access to (0-15)*4 = 0-64 bytes above us, WHICH MEANS THE VALUES OF THE OTHER BUFFER TOO!


Summary:
By calling 168 and writing that to 224, and repeating this upwards eight times, we can then call 128 to read the flag.

#science

Solution: 
 
 import socket  
 import struct  
 s = socket.socket()  
 s.connect(("54.85.89.65", 8888))  
 s.recv(1024)    
 x = {}  
 s.send('\xA8')  
 a0 = s.recv(128)  
 s.send('\xE0')  
 s.send(a0)  
 #  
 s.send('\xA9')  
 a1 = s.recv(128)  
 s.send('\xE1')  
 s.send(a1)  
 #  
 s.send('\xAA')  
 a2 = s.recv(128)  
 s.send('\xE2')  
 s.send(a2)  
 #  
 s.send('\xAB')  
 a3 = s.recv(128)  
 s.send('\xE3')  
 s.send(a3)  
 #  
 s.send('\xAC')  
 a4 = s.recv(128)  
 s.send('\xE4')  
 s.send(a4)  
 #  
 s.send('\xAD')  
 a4 = s.recv(128)  
 s.send('\xE5')  
 s.send(a4)  
 #  
 s.send('\xAE')  
 a4 = s.recv(128)  
 s.send('\xE6')  
 s.send(a4)  
 #  
 s.send('\xAF')  
 a4 = s.recv(128)  
 s.send('\xE7')  
 s.send(a4)  
 #  
 s.send('\x8F')  
 print s.recv(1024)  
 flag{greetings_from_pure_digital}  

flag{greetings_from_pure_digital}