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 chall [*] '/ironforge/chall' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No
Let's check the raw decompilation of main():
undefined4 main(void) { undefined4 uVar1; int in_GS_OFFSET; int local_3c; char local_38 [37] = { 0 }; // Ghidra does it the long way
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:
param_1 is never used, so we'll remove it. This changes the signature to void main(void).After these changes, we get the following code:
int main(void) { int iVar1; int i; char buffer [37] = { 0 }; int local_14;
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 <= 0x23) { if (enckey[i] != buffer[i]) { puts("You lose!"); return 0
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:
enc_key?buffer?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.
This is the definition of enc_key:
enckey XREF[4]: Entry Point(*), encode:00011233(R), encode:00011255(R), main:0001134d(R)
enckey holds an address, making it a pointer. We can click on DAT_00012008 to see what's there.
DAT_00012008 XREF[4]: encode:00011240(*), encode:00011255(*), main:00011358(*), 00014024(*)
We notice that enc_key is 36 bytes big and starts at 0x12008. 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:
enckey = [ 0x31, 0xc0, 0x50, 0x68, 0x62, 0x6f, 0x6f, 0x74, 0x68, 0x6e, 0x2f,
Now that we have our data, we can look at encode():
undefined * encode(int param_1) { undefined4 local_c; for (local_c = 0; *(char *)(param_1 + local_c) !=
From this, we notice three things:
encode() takes an int argument.encode() must cast the input to an int, and then cast it again inside the function.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 enckey is a char*, so we'll change the return type too.
char * encode(char *s) { int i; for (i = 0; s[i] != '\0'; i =
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 undo-able, meaning:
in = (out - 8) ^ 0x55
This is our key to solving this problem! If we take the output buffer, and perform this operation on each byte, we'll get the correct input.
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.
def decode(in_list): out_list = [] for i in in_list: out_list.append((i - 8) ^ 0x55) return
We'll take our buffer and decode it:
enckey = [ 0x31, 0xc0, 0x50, 0x68, 0x62, 0x6f, 0x6f, 0x74, 0x68, 0x6e, 0x2f,
Now, we need to convert this list to a string. We can do this with the bytes() function:
payload = bytes(payload)
Finally, we can send this payload to the server:
proc = process() proc.sendline(payload) proc.interactive()
This gives us the flag:
$ python3 asd.py [*] '/ironforge/chall' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled Stripped: No [+] Starting local process '/ironforge/chall'What is the password?