Four buttons are hidden within its walls. The flag will appear when all of them are activated.
Solution
Through unzipping the archive we can see that the challenge made with unity. Logic of the game are stored in GameAssembly.dll and we can utilize il2cppdumper to recover the function name and struct from global-metadata.dat and apply it on pseudocode of GameAssembly.dll.
Open GameAssembly.dll in IDA and load script ida_with_struct_py3.py then
Choose script.json from .\Dump directory
Choose il2cpp.h from .\Dump directory
After script running completely we can see many functions renamed, for a reference we can decompile Assembly-CSharp.dll in directory .\Dump\DummyDll using dnSpy.
In ida we can see the pseudocode for each function, for example for function Awake in GameManager class.
Through running the game and from the title we know that the game is about maze, in this case player put in a maze and need to find something in a maze which later we know that we need to find a button and press it.
Lets take a look on level file by UABEA and click on View Scene.
Expanding the m_LocalPosition in Button Object we can see the coordinate of the Button
Back to IDA, take a look XREF on function GameManager_CalculateSHA256.
From code above we can see that there is a process of converting some value to string, append it previous hash, and hash it using SHA256. After that it will check count of pressed button and total button then if count of pressed button greater it will show flag. Now we need to know the value of data before it hashed, to do that we can debug the program.
To debug the program we need to set the executable to yet-another-maze.exe, change it on Debugger > process options > Application. Set breakpoint on 0x1801D8C0C, instruction on that address is executed if we press e in game.
Scrolling down on the same function (PlayerInteraction_Update)
Code above basically check if we collide with an object and that object is a button object it will continue the process (calling GameManager__OnButtonPressed). Previously we've been looking at function GameManager__OnButtonPressed that create hash and showing flag if all button has been pressed. So, we can utilize this process to find what string is hashed. Following is the idea to hit GameManager__OnButtonPressed
Need to make Component_object is valid button object
We can do this by getting address of actual button component object by setting up breakpoint at ButtonController$$Start+34
Debug it to make sure GameManager__OnButtonPressed
To make debugging easier, press e while hitting a collider like a wall so we don't need to bypass the collider check
Setting up breakpoint at PlayerInteraction$$Update+198
During debugging process we got following values (address of button object) at breakpoint ButtonController$$Start+34
0000026EC1304980
0000026EC1304940
0000026EC1304900
0000026EC13048C0
If the execution hit breakpoint at PlayerInteraction$$Update+198 , set rax value with one of value of button object we got, for example 0000026EC1304980. In address PlayerInteraction$$Update+1F3 , set IP to PlayerInteraction$$Update+1F5 then continue step in and we will hit instruction that call GameManager__OnButtonPressed.
Next, setup breakpoint at GameManager$$OnButtonPressed+B8 and continue the process. In second argument we can see a pointer that store value of plaintext which is 257.29461586.2037 and from level 0 we can see that the those value is produced from the position of Button_Object_3
By looking at the result of hash in rax after calling the function we can see that it match with our SHA256 implementation
Let's continue to next step which is trying to press another button. Do the same flow but with different address of button object which is 0000026EC1304940. Now we can see that the hash value is appended with a next coordinate value.
Continue the execution and we will see the new hash value which is same with our SHA256 implementation.
If we look at coordinate value we can see that the coordinate is the coordinate for Button_Object_1. With this information we can reconstruct the actual hash with correct order which is 1,2,3, and 4. Following is my script to solve the challenge.
This is ebee, the ultimate packet processor for ya
Solution
Given 2 files as following
ebee - ELF 64-bit LSB pie executable, x86-64
prog.o - ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), not stripped
Lets decompile the ebee file using IDA, from main function we know how to run the executable
bpf_obj_file should be prog.o but what is ifname? take a look on function sub_40102 below
ifname passed to if_nametoindex, so ifname is network interface name.
We can see list of interface name by using linux command such as ifconfig, ip a, etc
bpf program (prog.o) loaded and then the ebee program will find check_packets function
ebee will attach function check_packets to network interface that we passed before through argument
check_packets will process packet that has been sent to the interface and then ebee program will check if there is ebeep_map
if yes, it will create new ring buffer and execute the callback function which is sub_40076
So we need to know what check_packets do first, we can dump the instruction using llvm-objdump
We can see the original code as following
Ensure that the packet use UDP protocol
Ensure that the length of payload <= 20
And some packet validation as following
payload[6] ^ port == 8143
payload[7] + payload[8] == 130
payload[9] + payload[10] == 163
payload[10] == 96
We can easily found value for packet validation by hand, let's create script to send the packet.
Setup breakpoint at callback function and run the program (use root privilege)
After running client.py, program will hit breakpoint at callback function. So for the next step we can focus on callback function. Callback function is complicated, my first approach is to find instruction that make the return false or compare instruction. From several function called in beginning of function we notice that there is LLVM and JIT initialization, so there will be dynamic code compilation and execution. To make writeup easier to read i'll divide the validation on several part.
a1 = input
First Validation
First validation will do xor process for input[:4] and input[4:8] then it will hash the result using crc32. Then crc32 hash will be compared with 0xFEB9A9BE. Following is crc32 function
So there is lookup process for specific code in JIT by using key t1 and we know that the code will be in v11 since v11 is called in above function. Setting up breakpoint on 0x96DA and step in.
Looks like the code are obfuscated
Previously i've been facing control flow obfuscation about tail call and dead code, and binary ninja can automatically defeat it. So i decide to dump the whole instruction and opening it in binary ninja.
Call it using following command
Following is decompiled result from binary ninja
From the constant and operation we can see that it is crc32 function, we can confirm it by checking the output from v11 call and our crc32 function
From the first 8 bytes value we don't know the actual value but we only know the hash of xored first 4 bytes and second 4 bytes. We can bruteforce 4 bytes actually but it still the result of xored value so i decide to continue the execution to see next validation.
Second Validation
Scrolling down callback function we will see following of code
Second validation lies at function sub_8FEC and sub_8B52.
So the JIT code will be on v6, breakpoint at 0x9107 and dump the code.
From the argument we also can see that it process first 6 bytes value and looking at the instruction it looks like calling another function. So for this code i decide to dump specific region of memory, run vmmap command and we can see following address (near rbx value)
Dump it using the previous script (dump.py)
Open it using binary ninja
Function looks like obfuscated rc4, to make sure we can set hardware breakpoint on our input and continue the execution.
Setup new breakpoint at 0x8C3F and continue the process and we can see that the value compared is the xored value of previous instruction (xor ecx, eax).
By repeating the same process with different value of input we can see that the ecx value still same this indicating that the keystream is static. From this information we can get the correct plaintext by xoring plaintext, ciphertext, and compare values.
Now we know the first 6 bytes value is j1tT3D. In first validation it process first 8 bytes and until this step only 2 bytes are unknown so we can bruteforce those 2 bytes.
We got the valid 8 bytes which is j1tT3D_4 . Because we know the value of payload[6] which is 95 so we can get the correct target_port which is 8143^95 == 8080. Until this step we already found following value j1tT3D_4ND_ .
Third Validation
Third validation more complicated than first and second validation, following is the code
We already know first 11 bytes of input and only 9 bytes left. Looking at several function i notice that last validation use VM to process our input. Lets take a look on process_func_1 (sub_39EEA)
There are three lines of code that i highlighted, those code calling function from JIT code. From callback function we can see also that when an instruction constructed using uuid and some value it will call process_func_1 function which is calling JIT code. So i decide to dump the JIT code and dump the call sequence of it.
Set breakpoint at those 3 JIT code call in process_func_1
We will get the following output
By using three different input (last 9 bytes) we can ensure which one is static value and which one is variable. Because there are only few operation, i decided to convert it by hand and solve it using z3. Following is my final script