Write Up IntechCTF — Game
Continuing from the previous article (https://medium.com/@maulvialf/write-up-intechctf-android-challenge-1ec1b1822be0), I would share write-up about challenge IntechCTF named game.
This challenge consist of few problems and had different flags on each problem. Challenge can be accessed on my challenge repository here. So here the write up.
Table of Content
· Problem 1
· Problem 2
· Problem 3
· Problem 4
· Conclusion
Problem 1
Problem setter only provided us with APK files with no library source files.
Launch the application, there should flag input form.
Next, open the application on the JADX-Gui to static analysis APK files. On the main activity, there are two native methods named check and Init.
Function init is called on this method. Function init runs at first before the second native lib function.
And function check is called on this method.
There are two libraries that exist on the application. On the application, we still did not find that libgame.so is imported on this level so we can ignore this for now.
After doing initial static analysis, next we move to static analysis on the library. Extract the APK files (can be done using unzip) and load libintechfest.so on the decompiler.
This is the init function of library libintechfest.so.
On these functions, there are static variables g_key that process some kind of encryption/decryption. Below is the value of g_key.
Extract value and do the decryption process would get the first flag.
Problem 2
After we get the first flag, let's analyze the init function again. On line 43–44, memory gets xored by value of g_key after decryption. Memory that xored is addressed from .text segment.
Look up the .text segments, the byte code of the instruction is encrypted.
According to this information, we know that the application should decrypt the .text segment on the library on the init function on the memory. From this information, we know the application should have a library that decrypted after the init function. So let's try to dump the library using Frida.
Frida is able to dump memory on the runtime. There are a few approaches to this. On this challenge, I use these tools to dump decrypted library. Clone the files and run dump_so.py with the library as a parameter.
After library file is dumped, open the library on the decompiler and we should see that .text segments are already decrypted and able to be analyzed.
Now we should look at checkFlag function.
On the checkFlag, there are functions level1 and level2. We can check this is there any other level function from function list.
There are functions level1 and level2. The first and second functions only show the pointer and execute the function pointer.
However, some string decompilation showed on the third function on the list.
Decompilation shows a broken function, let's check the disassembly view.
On the disassembly view, there are code on the .text segment that did not assigned as function. Lets declare the function and see what its look like. To create bytecode as function, right click on the bytecode position and choose create function.
After we create function from the bytecode, we should see the decompiled code like this
From decompiled function, we know that application do another decrypting function on the segment .level2 when this function called. This function is created below the function 1, that we can assume this code is part of function level1. Lets analyze this function.
Function level1, had two parameter, int64 a1 and int a2. First parameter used on function sub_104d0. However after we tried hooking this function with Frida application failed show anything useful. I still did not know why sometimes frida failed to hook native function, my hypothesis is there are error parse (decompiler also found error positive sp value has been detected). So we find another function that can be hook and had same parameter.
If you noticed, this function looks compromising to hook. It had first parameter same with sub_10610 and second parameter is return value of something decrypted, looks so legit.
The function also did not had some kind of error that maybe broken like function before.
Lets hook this function with frida. Below is function hook code.
Run the hooks, and input AAAA in input form.
Great, we know hat second parameter is another flag. Our input is compared with decrypted flag. Lets input itf{h1dd3n_bY_w3ak_3ncrypt10n} and we successfully continued to level2 or in this case problem 3.
Problem 3
After we submit the flag, we should redirected to level 2. There are information is that app’s name is weird. So in this case problem related to filename checking. Lets noted this for later.
Click Go To Level 3 did not show anything. Different from second problem or level1 that had input form, on this level we did not provided with form.
Java code did not show anything different from level 1 or level 2. There are no code that significantly change.
Lets check again native function checkFlag on the library that we already decrypted.
On the function checkFlag, we know that parameter a1 is game level. If value a3 equal to 1, function level1 would executed. So level2 supposed to be our challenge now.
Look on the function level2 had weird function. Function level2 called another declared object.
However this function is did not like instruction bytecode. We can check this by viewing the disassembly.
Welp, it seems level2 had instruction encrypted on the .text segment. But why this happened?. We know that this library decrypted on the first phase before we clearing level1. So it seems, after clearing level1, another library segment would (in this case level2) decrypted.
This can be confirmed by disassembly code of function level1.
If we dissect the function more deeper, there are method decrypt_section executed and had parameter .level2.
So how to get new library that had .level2 segment decrypted this time. Same with first problem. We should dump library again!. Lets run another dump_so after we clearing level1.
After new library acquired, lets open new library on the decompiler. Function level2 now is created and already decrypted.
Now lets analyze function level2. Below is code of level2.
Function level2 had complexity more than function level1 that we solved before. But lets break it down the function.
On the line 35–49 there are generated some string that decrypted and saved on parameter named pointer. Pointer used by function pthread_setspecific.
On the line 55–75 there are JNI function call executed. However method and java classname is decrypted on the runtime. But we should able to hook the function call parameter and return value. Below is hook code to retrieve return from function SSM:Decrypt.
Run the frida script would return this output below. There are few times application executed.
Lets rewrite decompiled code with the decrypted string. Below is code that we already modified with decrypted string.
In general this code would called method loadLabel which part of method in getApplicationInfo and return the value in CharSequence. After we research this, we found out loadLabel that used exist on the class https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/pm/PackageItemInfo.java.
Lets hook this function with frida to know the return value.
Run the script would return this output.
Load label method had value nHVZeGukN75PpvXrhtOe that similar to package name. So application supposed to be compared this string to pointer that decrypted here. However function spawned with pthread_setspicific.
Lets check another approach. If application compared string on the library, application maybe had a chance used strcmp on the comparison. We able to hook strcmp and filtered to only show parameter when one of the parameter had value nHVZeGukN75PpvXrhtOe. From this, we should able to retrieve another parameter that compare. This is frida code that implement that logic.
Running the script, would return another parameter named Circle Of Trust. Great, Our guess luckily success.
Lets hook loadLabel again to replace nHVZeGukN75PpvXrhtOe to Circle Of Trust.
Run this frida script. And click the go to Level 3 and great, we able to continued to next level.
Problem 4
After we completed the Level 2, We able to access next level. On this level there are some kind of game.
If we click play game, there are pop up window like this.
Click go, we should redirected to the game. The game need us to click 10 times to circles that generated on the screen. All 9 circle can be clicked easily.
However real struggle exist on the final circle. Final circle would would spawn circle randomly that nearly impossible to click.
After few trying that failed. Lets look at the code instead. After successfully pass application name check. We should go to code that decrypt .level3. This approach same with decryption from level2.
Below is instruction code that encrypted. There are function initDex that named on the encrypted code.
Same with dump library from before, Lets dump binary again .
After new library generated, lets open the library on the decompiler to analysis. After opening the binary on the decompiler, we should get new function named initDex. On the application string decrypted with same SSM:Decrypt.
Lets running SSM:Decrypt hook again chained with loadlabel hook to view what string that decrypted this time.
Run this script would show output below.
From the string that shown, application called java code that getAsset and get game.dex files. That confirmed assets also exist on the application resources. There also format string that supposed to /data/data/com.intechfest.game/cache. There also some kind of random key n1n0_k4w4ii. From these information we get know that application would read file game.dex on the assets, processed somehow with key and write on the application cache.
This confirmed that file game.dex in the assets is not valid as dex that indicated this file encrypted.
If we look application folder cache, there are few cache dex that are generated.
Dump this file and we know that this file is decrypted file of game.dex. Great !.
Open the dex files to Jadx-Gui would get class named GameView.
We got logic from the game !. But if we hook this on frida, frida would shown output like this.
This can be happened because dex file run on the runtime. Default frida hook only able hook that already defined DexPathList. But this can be modified.
We found this issue on frida repo https://github.com/frida/frida-java-bridge/issues/227. From this issue we should able to load runtime dex if we overload dalvik_system_BaseDexClassLoader and reload the loader. If we reload the loader, Frida would generated the dex that load and we should be able to hook runtime dex.
Now which function that we wanna hook?. Lets check method onTouchEvent. On this method, our click would compared. If we success, parameter m_score would increased by one and submitScore() executed one time. Lets doubled it on so our score did not touch 9 and directly get to 10. Lets also hook get_flag method to know the return value.
Below is code that implemented load label override, decrypt string and hook runtime dex.
Run the Frida script, and every touch that we click would get 2 score and we able to get the flag. However the flag is cropped and did not show full on the screen.
Check on the Frida output, and we get full flag itf{0bFusC4t10n_V3rryy_AnN0y1nG_R1gHt} and completed all the level on this challenge.
Conclusion
I enjoyed a lot doing this challenge. I learn a lot about hooking native function, dumping library, library encryption on the android, override internal android class and hook runtime dex class. This challenge also can be solvable from different way (in example we able to decrypt the string by static analysis). Kudos for the problem setter (https://github.com/aimardcr and teams) that created this challenge.