Crakaker
“I did something awful with the flag. Would you be able to understand how to fix it?”
We are going to dive into the internals of this executable, to thoroughly understand the program and generate the input required for the flag.
At the end I’ll also attach source code(s) of the solution and my interpretation of the program.
Initial analysis of the executable
Before we push the executable into IDA to start reversing it, we should probably know what we are dealing with; we can of course open it up in either IDA or IDA64 and continue on from there but I would throw it into DIE to understand what I am dealing with.
From here, we can understand that we are dealing with a PE+(64bit) that was compiled using MinGW GCC, and so we can prepare ourselves to the compiler wrappers, boilerplate and etc.
IDA, and the real main
Pushing the executable into IDA64, first thing I would do is go through the exports to better understand where the executable actually starts executing code besides the entry-point, if such regions exist.
We do this to ensure we are fully aware of what the executable is going to do, because we want to trace these kind of operations because they can be used to affect the main program, this can be done using a variety of ways; for example:
* Abusing TLS functions
* Abusing CRT startup
* Abusing IAT
* etc.
In the Export Address Table, our IDA has picked up that TLS functions are available and that could be used to execute code before our start function via ntdll!LdrpCallInitRoutine
, this is always a good idea to analyze these functions but in this case going through the functions quickly they are just GCC critical state initializers/stub.
Moving on to start, we come across a small start
function, entering the first function call we can see the regular __security_init_cookie
so we can quickly rename that function to it’s proper name and move forward.
Side-note
From initial overview of the executable, we are able to notice that it’s not obfuscated or packed in anyway (DIE helps with this as well) so we can freely use the HexRays Decompiler to assist us in quick reviews to find the important stuff.
Inside the next call, we reach quite a big block of code so we should be careful to spot any tricks the author is attempting to use, from my knowledge of GCC and assuming this didn’t have any post-modifications done we can rule out majority of the code by using signatures to detect known GCC generated code.
I will try to use guessing and known code-snippets to attempt and detect most of the functionality of this function.
My best guest this is part of the heavy stackguard protection of GCC, however I might be wrong, this is only for ruling out which parts of the code I actually require.
This is some basic CRT startup, some SEH setting up and etc, we can safely ignore it however keep it mind there might be hidden stuff in the CRT we might need to check.
Command Line parsing, this would get the string after the argv[0]
for example if the commandline is Crakaker.exe nop
we will get a pointer to nop
in g_cmdline
Normally, the values that are being passed into the main function would be argc and argv and there would be a sort of program cleanup as the compilers add, from the exit we can see that result
is the exit code so we can assume that the function the exit code is coming from is our main!
Understanding the main
Approaching the main function, we can see a lot of magic values being used so I’ll rename the properly to attend to them later if they are important.
Looking over at input_size
, the maximum size is 16 and later we can see var_230
being turned into a number.. I would assume sub_407C4F
transforms our input to a number somehow.
Let’s approach this function step-by-step, the compiler tries to not interfere with the main
function, so this would be the author’s code we will start with sub_407E50
firstly.
So from first impression, this looks like an initializer of some sort, these are quite common to initialize stuff like global variables or memory/functions to be used by the program so it should be rather safe however… looking at sub_407DE0
points us to a sort of initializer that calls functions that are in the .text
section, that’s quite odd.
Let’s take one of the functions, and try to understand what this does.
Oh.. Yup, that’s some anti-debugging.
Okay, so we can rename all of the stuff in sub_407E50
appropriately, the bypass of this is rather simple, either we enable g_debug_check_passed
while running our debugger or modifying nt!_PEB->BeingDebugged
Moving on to sub_407B40
, we are able to see some sort of structure creation with copying from a global.
Looking at unk_40B060
we can see that it’s just a table that goes from 0x00
to 0xFF
, I would assume this is some sort of key system and that unk_40B060
is a key table of some sorts, arg_8
is being added to the table in a circular-motion I’ll assume it’s some sort of seed for the table while arg_10
being it’s size, let’s rename it properly and create a structure.
Some fields are unknown, also the key
in the end is being copied to key_dirty
my guess it needs the original key around.
So now our main got a bit clearer, and we can see that sub_407C4F
the function that transforms our v7
to a number with atoi
is being passed the key we just spawned, that means the input together with the key gets to be a number.
Alright, so we can see that indeed key_dirty
is being used and changed in the end, and var_C
is being used as some sort of offset for the key while var_5
is being used as the next swapping value and in the end a1
gets the result. I’ll assume this is some sort of decryption method from our key and that a1
is a target, I’ll rename the fields properly.
Alright, so we can update some of the stuff in our main
Looking at sub_406A43
, it’ll print “try again!” so for simplicity I’ll name sub_406A43
as print_fail
and sub_4016AD
as print_flag
, and so we will move on to print_flag
.
At first, it might seem overwhelming but we can see that these operations are mostly on the constant values at the very start of the main which are here to generate something into the var_90
field.
At the end, we can see that it’s indeed our flag printing and that our generated integer from the key is used as an allocation size, which is the flag size in the end, and the var_90
is some sort of a string that is being used against the integer and our original input.
Another sort of initialization, let’s look into it.
From a quick overview, it’s just an initialization of an ASCII table, we can see that it then uses this as an ascii map.
Treating a1
as a character_map
here, we rule out input_key_var
because it’s used as storage and it contains some rules for output_key_var
such as it must be output_key_var & 3 == 0
and the character_map
is the actual thing that builds the return value.
I’ll assume that function generates the bytes for the flag, preparing print_flag
by the information we got.
Continuing to sub_407AC4
Seems like a start for an unpacking, let’s continue on to sub_4079D8
Looking at the three first functions, I quickly rename them based on their appearance.
There’s nothing too special about them, and they are quick to understand.
Focusing onto sub_4077FA
, it looks like some sort of unpacking based on the value we get from the ascii map (which comes from our flag bytes that got generated from the character_map
).
Based on calculating the magic_index_value we can generate the following map of how the character gets unpacked:
[0] (x << 3) | (x >> 2)
[1] (x << 6) | (x << 1) | (x >> 4)
[2] (x << 4) | (x >> 1)
[3] (x << 7) | (x << 2) | (x >> 3)
[4] (x << 5) | (x << 0)
Research Summary
So we just went all of that, we understood most of the program thoroughly, what did we learn?
magic_value_0 = 117 * (argc / 2)
magic_value_1 = argc / 12.0;
magic_buffer_1 = aaaaabgfdgdgdsdf
magic_buffer_2 = aaaaaagfsdgsdgdsfgsdfg
character_map is generated by magic_value_* and magic_buffer_*
magic_formula_0 = (3 - 5 * a1 % 8)
magic_formula_1 = 5 * a1 / 8
character_to_ascii_map = accepted: A-Z 2-7
g_table_key goes from 0x00 to 0xFF
g_seed is [0x4B, 0x42, 0xB7, 0x14, 0xD8, 0xAF, 0xA2, 0x4E, 0x1D, 0x86, 0x46, 0xE0, 0xD3, 0xC3, 0x1A, 0xE0]
[important]
argc > 1
argv[1] must be at most 16 in length.
magic_buffer_* size is affected by argc.
output_key & 3 == 0
unpacking affects 5 flag characters at once and uses 8 of the flag_bytes at once.
Translate table:
[0] (x << 3) | (x >> 2)
[1] (x << 6) | (x << 1) | (x >> 4)
[2] (x << 4) | (x >> 1)
[3] (x << 7) | (x << 2) | (x >> 3)
[4] (x << 5) | (x << 0)
And after all of that, we also notice that our input only affects how many bytes we allocate for the string..
So looking at that, we only need to figure out how to properly generate the amount of bytes we want, that is generated by the key decryption and the proper amount of argc we need for the magic_buffer_* sizes.
Building the solving script.
So first of all, we are going to control the output of the key decryption, luckily if we look at the function we can see it’s just a XOR between our input to the key value, so we can just use the decryption as the encryption.
We just need to use the same functionality as the one we saw in the program and input what we want to except being used in the program.
Once we do that, we need to pick a size, remember that we need to have output_key & 3 == 0
satisfied to actually work so let’s pick 128 which fits that nicely and is big enough for a flag, in any case we can just make it bigger.
So our script would look like this:
import subprocess
import copy
__KEY_TABLE__ = [x for x in range(0x100)]
__KEY_SEED__ = [0x4B, 0x42, 0xB7, 0x14, 0xD8, 0xAF, 0xA2, 0x4E, 0x1D, 0x86, 0x46, 0xE0, 0xD3, 0xC3, 0x1A, 0xE0]
__MAX_INT8__ = 0xFF
__TARGET_PROGRAM__ = './Crakaker.exe'
__MALLOC_SIZE__ = b'128'
class key_t:
def __init__(self):
self._key = []
self._key_dirty = []
self._key_offset = 1
self._key_value = 0
def spawn(self, seed):
self._key = copy.copy(__KEY_TABLE__)
offset = 0
for i in range(len(self._key)):
offset += self._key[i] + seed[i % len(seed)]
offset &= __MAX_INT8__
swap_value = self._key[i]
self._key[i] = self._key[offset]
self._key[offset] = swap_value
self._key_dirty = copy.copy(self._key)
def decrypt(self, input):
if len(input) == 0 or len(input) > 0x10:
raise Exception('out of bounds')
offset = self._key_offset
value = self._key_value
output = [0] * len(input)
for i in range(self._key_offset, len(input) + offset):
value += self._key_dirty[i]
value &= __MAX_INT8__
swap_value = self._key_dirty[i]
self._key_dirty[i] = self._key_dirty[value]
self._key_dirty[value] = swap_value
output[i - offset] = self._key_dirty[(self._key_dirty[i] + self._key_dirty[value])] ^ input[i - offset]
self._key_offset = i
self._key_value = value
return output
def main():
key = key_t()
key.spawn(__KEY_SEED__)
value = bytes([c for c in key.decrypt(__MALLOC_SIZE__)])
proc = subprocess.Popen([__TARGET_PROGRAM__, value], stdout=subprocess.PIPE)
out, err = proc.communicate()
print(out.decode())
if __name__ == "__main__":
main()
So we might need to adjust argc
, but let’s just try to execute that as it is- and we get nice job: noxCTF{Move_Bitch_Get_Out_Da_Way}
Great, so the argc
does not need to be touched, and we are done!