This binary encodes our input data and is compared against a buffer. We can use Ghidra to determine how the flag is encrypted and reverse the encryption to get the flag.
This will be a good introduction to reversing obfuscated functions in Ghidra.
Running checksec on the binary, we notice all security features are enabled:
$ checksec undo
[*] '/home/joybuzzer/Documents/vunrotc/public/reverse-engineering/11-ghidra/undo/src/undo'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
This code is a mess. There is a label in the middle of the function, a do-while loop, and many wonky variables. Let's discuss how we can clean this up:
First, we'll rename variables to something more meaningful. This immediately makes the code easier to read.
Second, we'll remove the label by moving the code after the label to the jump.
Third, we can remove the canary check. We know it's there, and it doesn't affect control flow.
We notice that param_1 is never used, so we'll remove it. This changes the signature to void main(void).
Once this is done, we'll remove variables we no longer need and variables that aren't used.
After these changes, we get the following code:
voidmain(void){int i;char buf[41];puts("Enter your password");fflush(stdout);fgets(buf,0x29, stdin);printf("You entered: %s\n",buf);fflush(stdout);encode((int)buf); i =0;do {if (0x28< i) {win();return; }if (enc_key[i] != buffer[i]) {puts("You lose!");return; } i = i +1; } while( true );}
This is a lot easier to read. We can simplify this further by switching the do-while loop for a while loop where the condition is checked.
while (i <=0x28) {if (enc_key[i] != buffer[i]) {puts("You lose!");return; } i = i +1;}win();
Make sure you understand how we made this conversion. This is a tough thing to analyze and change. Although it's not required to rearrange the loop, it makes the code easier to read.
This means that we're checking the first 0x28 = 40 bytes of enc_key against buffer. If they match, we call win(). If they don't match, we call puts("You lose!") and return.
We have a few things to check out:
What is enc_key?
What is buffer?
What does the encode() function do?
If we look at enc_key and buffer, we notice these variables are colored aqua. This means they are global variables. Local variables are colored yellow. Double-clicking on enc_key and buffer takes us to the location where they are defined.
We notice that enc_key is 40 bytes big and starts at 0x14080. The data in this array is undefined.
Why?
We'll notice that the array is defined during encode(). The program has not started running, but since the variable is global, the space is allocated at compile-time.
On the left side, we see this data is initialized. The second column of data is the value at each byte. We can get Ghidra to copy this as a Python List using Right Click -> Copy Special -> Python List. With no extra effort, here is the value of buffer:
encode() must cast the input to an int, and then cast it again inside the function.
Ghidra doesn't know the return type.
From this, we can gather that Ghidra got the parameter type wrong. We can help Ghidra by changing the type to what we think it is. It appears that this function is casting to a char* and a byte*. A byte* is simply a signed char*. Let's change the type in Ghidra to char* and see what happens. At the same time, we know that enc_key is a char*, so we'll change the return type too.
char*encode(char*param_1){int i;for (i =0; param_1[i] !='\0'; i = i +1) { enc_key[i] = (param_1[i] ^0x55U) +8; }return enc_key;}
This is a lot better. This function takes a string, performs byte-wise operations on each byte, and stores it in enc_key. It performs the following operation:
out = (in ^0x55) +8
Both these operations are undoable, meaning:
in = (out -8) ^0x55
This is our key to solving this problem! If we take the ouput buffer, and perform this operation on each byte, we'll get the correct input.
Writing a Solve Script
First, we must write a decode() function that reverses the input. This will take in the list and return a list with the decoded bytes.
defdecode(in_list): out_list = []for i in in_list: out_list.append((i -8) ^0x55)return out_list
$ python3 exploit.py
[+] Opening connection to vunrotc.cole-ellis.com on port 11200: Done
[*] Switching to interactive mode
Enter your password
You entered: \xev j\x9e\xa6}-@\x8eN\x04y\x86\xf6\xb0\xde\x14\xfd5}\xc3\xd0zA \x15&2yh\x8a\x97\xc2\xd3\x1d\x96
flag{ghidra_is_awesome}You win! Here you go:
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to vunrotc.cole-ellis.com port 11200