Write up Reverse Engineering IDSECCONF CTF 2022
Idsecconf CTF 2022 is CTF competition held by IDSECCONF Conference and is part of #IDSECCONF2022 as a security conference. I participated with a teams name kaela_simp and finished in 4th position when the competition ended. I managed to solve 4 reverse engineering challenges and 1 web challenge. All of the challenges can be downloaded on my github here. Here is a write-up of reverse engineering challenges that I solved in this CTF.
Table of contents
· InjinirPemula (100 Points / 56 solved)
· NontonSerial (200 Points / 5 solved)
· AnggapAjaPiem (250 Points / 5 solved)
· Pagarin (200 Points / 4 solved)
· Conclusion
InjinirPemula (100 Points / 56 solved)
We are provided with ELF Binary with a simple 64-bit binary.
Execute the binary would show output like this
Hmm, argument checking, let's view the code on the decompiler.
Check the string and we would see strings “Gut Lakk” and it references to main function. Main function had argv pass on the check function.
View the decompiled function of the check function would show output like this
There is some code that breaks the decompiler. Let's see function disassembly.
Disassembly shows there are analysis errors on the assembly and we see that function is not created on the checker code. Checker code also contains hex that supposed to be flag character. Lets created the function.
Now we see better-decompiled code. Extract the char and we got the flag
NontonSerial (200 Points / 5 solved)
On this challenge, we introduced with ELF binary with a strange machine type TI msp430.
A few popular decompilers sometimes had a few different behaviors when dealing with strange ELF like this. But in this case, I used Ghidra because Ghidra had many architectures and machine types to handle this scenario. Load the binary on the Ghidra and select language TI MSP430.
After completing the analysis, we try to inspect the function that handles the string or checker validation and found this function.
There are wrong statement strings stated on the function. On line 35, there are xor operations on the static address 0xfebe. That address is supposed to be encrypted data and should be decrypted after we got the right check validation. Extract the variable and do xor the value with 0xcc
Below is implementation code.
Run the code and the flag should acquire.
AnggapAjaPiem (250 Points / 5 solved)
On this challenge, we are provided with windows binary this time.
Run the .exe files would show a window form that would check your input.
Let's open the binary on the decompiler to further analysis.
On the binary, this is the dialog function that controls the window.
There are function checker sub_4012D0. Function checker gets param from GetDlgItemTextA that had return value user input string length. Let's check the function checker.
On the function checker, there our input is processed by some kind of calculation. After analyzing functions one by one, there are a few functions that do the arithmetic calculations, we renamed the function with name adder, multiplication, division, modulus, and subtract.
On the function checker also exist function 1 and function 2 that combined arithmetic functions. To get the flag, we should return the value 0 on this checker function.
We know that this function is processing our character flags one by one to calculate, and after replicating function 2 on the self c code, we know that the function2 return value would always have returned 0 value if the first parameter had 0 value. On this assumption, the flag is supposed to be shown up if the value of v12 always had 0 value on all character flags processed. If this scenario succeeds, we would get the flag if we brute force the flag character one by one.
However, We still did not know how long our flag length was and the value of v6. To get the value of v6, I try to debug the process and found out the value is 0x42 and never changed on every step.
Below is my code implementation of the brute force. In a simple way, I try to brute-force string length and brute-force flag per character, if return value of function2 is 0 and the flag character is printable, show the character to the output.
Compile the code and run, we should get the flag on flag length 54.
Pagarin (200 Points / 4 solved)
On this challenge, we provided with Linux binary. If we run the binary would close immediately.
Opening the binary on the decompiler would show interesting output.
Below is the code of the main function.
Main function would check our env PAGARNYA_GAN, and hash the string value, and compare to memory on &unk_30E0.
Extract this memory, and search on google, we should find this md5 hash of string “true” would return the same hash.
Let's set our env and run the binary again.
Now binary actually asks the input, if we check again the main function we should know why this binary had some strange behavior.
On the code on line 19, if we already pass the first env comparison, we should execute a function named BUG with decompiler.
If we look at the disassembly graph, we should find our instruction code is ud2.
If we look up the assembly documentation, the ud2 instruction is used to generate an invalid opcode for testing purposes. On this binary ud2 is used as anti-decompiler protection.
If we debug this execution flow, after the program executes instruction ud2, the program would jump to function sub_1A28. Below is the code of this function
If we analyze this function further, we know this function is declared by the init function below.
The Init function would set an exception and set function sub_1A28 as an exception handler. So if we execute ud2 as an invalid opcode, function sub_1A28 would be executed.
Let's view the function sub_1A28 again. On line 10, we view variable v4 assigned with some pointer from parameter 3. If we check this value on the debugger, we should see this value is the address of ud2 code before we entered the exception. So we can assume this value is used as some kind of saved instruction pointer.
If we check the handler function again, we know that this v4 is assigned with this pointer + 2, and this value is used as a comparison. There are 3 block codes that are executed.
The red code would be executed if our value is 3, the green code is executed if our value is 2 and the blue code would be executed if we got the value 1. After our block code is executed, we found out that our a3 + 168 is added with value 3. And after we debug this on the debugger, we found out that the application back to the instruction after we called ud2 and add with value + 3.
We know that there are few ud2 codes, so we created a simple script to get all ud2 codes, and get value passed to the handler function.
After running the script, we got a few addresses that contained ud2 instruction and the passed value. Let's view 0x5555555558d0 which contains value 2.
If we view this function, we know that this function contains ud2 code, there is a BUG function on the decompiled view.
On the previous analysis, we know that our code would return to ud2 code + 2 address after we entered the exception. Let's patch the code to view what code we executed after we returned to this function. We patch this three bytecode
After patching this bytecode, we now should view all the code. Don't forget to patch other function bytecode too, below is the main function code that also contains ud2. We know that main function code after ud2 is executed, would assign some global array parameter that we would used later.
After we done with main function, we should move to function sub_17ce.
Let's rename this function and the handler function to make the code easier to read.
On the function we renamed functionaneh, we found out that our code is split into two sections. Red block code would be executed first. After that, the exception is raised with ud2 instruction, and the code would go to green block on the exception handler. After green block was executed, the function returned and executed blue code.
After we know the execution flow, now we should find out what the code doing.
On the red code, the program would check our input to make sure our input only contains characters 1, 2, or 3. The application also make sure our input had length 310 character. After that, every two characters of our input is processed with green block code that also process array paramsatu and paramdua.
After all character pairs are processed, array paramsatu is compared with array name compariso1 that contains 4e,4e,0 that already initialized on the main function. If the comparison is true, we should get our input hashed with sha256 and the value xor with global variable aJ
Now how do we get the correct input? Bruteforce the character did not seems feasible (3 ** 310) and the key to xor encrypted value is the hash of our input. To analyze this thing easier, I try to duplicate code to C to analyze every parameter.
After trying the simulation, I know that every pair of our inputs would process paramsatu. In the example, if we try to input 12, paramsatu would change from 9c,0,0 to 49,53,0
If we input 21, we should view paramsatu changed from 49,53,0 back to 9c,0,0
Some value would come back to the value that we already visited. This problem can be mapped as a simple graph below.
After I know that this problem can be represented with a graph problem, I had the idea to solve this problem using BFS (breadth first search) to find the shortest path to make sure paramsatu had value 4e,4e,0. If we found the parameter input had a value that already exists, stops the traverse for the node. Our success input would be represented as traverse path from the beginning node that had value 9c,0,0 to final found node that had value 4e,4e,0. Below is the implementation of the code.
Run the script, and we should find the traverse output had a length 310, a length that exactly the program wants.
Let's input this code
And we got the flag that appeared on the output
Conclusion
I had so much fun doing reverse engineering challenges from this CTF, especially with challenge “Pagarin”, which contains anti-decompiler, anti-debugging techniques that trap me for an hour, and had some algorithm stuff like BFS, kindly rare on the local CTF used this approach as solving method. Congratulations to the winner and thanks to the problem setter who already provided us with good CTF.