Tutorial Decryption of Assembly-CSharp for games that can't be dumped with the usual methods

Yaskashije

PMT Elite Modder
Original poster
Staff member
Modding-Team
Sep 9, 2018
4,553
834,671
1,213
Minkowski Space
Hello everyone,

I found this on a chinese forum, and I decided to translate it into english the best I could.
This tutorial is less than 2 years old, but it might not work for modern and widely used apks. However, it's educational value can't be ignored. And those who want to know the depths modding world reaches, can read it as a leisure activity.
As the title states, some games come with the Assembly-CSharp file encrypted, and the game can't be dumped with the usual tools (Game Guardian and the like).
The example comes with an old version of FGO CN, which used to satisfie that condition (This method got patched the next version after this tutorial was posted).

Disclaimer:
This is a process that should be used as last resort, we have several other ways to try before it, most of them explained in tutorials found here.
I had a hard time in writing this step by step tutorial and I won't answer any technical question since I consider I'm not qualified enough.
If you want to replicate this method on other apks, you must know it's for advanced modders and requires further knowledge than the usually requiered for modding, mostly on the dinamic debugging side.
Some steps are less explained because they are long and there are already english tutorials online that are easy to find (like IDA remote debugger).
Some steps weren't explained in source and poster just referenced other websites. I added those "external website" steps.
Original post followed a blog-like narrative, showing thought processes and including dead ends, lucky findings, etc op found. I tried to turn it into an straightforward tutorial by following the correct chain of steps.
This was translated from chinese (and english is not my native language). Some parts content was deduced by context and logic. I made sure there were no mistakes, but errare humanum est.




Credits:
ss22219, the one that explained how he approached the decryption problem and solved it.
linchaolong, for smalidea tutorial on how it works and Android Studio smali debug.
lhfzhh, for the auto-dumper.
Perfare, for his PE Header fixing post.


Used tools:
-Android Studio
-Android-OpenDebug
-IDA Pro
-Smalidea
-mumu emulator? (ss22219 is using it as device, so there's the slight chance some step can't be replicated on other platforms)


Without further ado:

>Download and install Android-OpenDebug in the device/emulator you will be using for the process and enable all applications debug. Restart device afterwards.
>Open Dalvisk Debug Monitor (DDM used was from an Android Studio version previous to the 24.3.4 update [August 2015] found in tools\ddms.bat), and connect your device using adb through cdm.
(DDM from Android Studio was used because IDA debug process made the game crash)
>Write the following commands in the cmd to start game in debug mode:
adb shell am start -D com.bilibili.fatego/jp.delightworks.Fgo.player.AndroidPlugin
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700
(If jdb can't be found, path YourJavaLocation\jdkYourJdkVersion\bin )
>Download Smalidea, open Android Studio, press Alt+Ctrl+S to open the setting interface, click Plugins > Install plugin from disk…> Select the downloaded smalidea > Apply. Restart AS so the changes get applied.
>Download backsmali-x.jar and smali-x.jar from the linked bitbucket in the github repo.
>Use the following command to get the smali code of the apk on a new cmd and another folder:
java -jar baksmali-VersionYouDownloaded apkname.apk -o ./projects/apkname/src
1596060988493.png
(most recent versions)
1596061072901.png
(how the folder looks like [ignore the .bat])
>Once done, we open Android Studio, File>New>Import Project... We select the dir that got created with backsmali and gradle to it.
1596061318456.png
1596061682244.png

>We then mark src dir as sources directory in Android Studio.
1596061725539.png

>After opening the Dalvik Debug Monitor project and creating it successfully (what we just did), we can see that the Android Device Monitor button cannot be clicked because this is not a complete android project, but we can go to the Android SDK/tools directory and click monitor.bat to open the DDMS.
>We create aremote debug server in Run > Edit Configurations and then setting the port to 8700.
1596062167661.png

>We open the .smali generated in Notepad++ and we look for LoadLibrary. There's only one call: Lcom/unity3d/player/UnityPlayer; ->loadLibraryStatic
>We set a breakpoint at loadLibraryStatic in Android Studio
1596064050437.png

>We then load in libmain.so in IDA, we analyze the JNI_Onload in libmain.so to provide com.unity3d.player.NativeLoader with the native implementation of .so loading. We find that com.unity3d.player.NativeLoader.load has the loading of libmono and libunity in the code's implementation.
>We find sub_78B08130 which calls sub_78B08350 for libmono.so. Intuition says sub_78B08350 will be the subroutine that loads libmono.so, so we go to it, and find dlopen. Bingo. We set a breakpoint on dlopen and in sub_78B08350 branching instruction. (View in IDA pseudocode)
1596063607169.png
1596063543584.png

>On the cmd where we used adb to start debug, we write the following command:
adb shell am start -D com.bilibili.fatego/jp.delightworks.Fgo.player.AndroidPlugin
>Once we reach Android Studio's break point, we attach IDA's remote debugger to the game process. (Process it's explained, but there are tutorials of this in english language on other pages and are not hard to find).
>We keep the game running from Android Studio, and it will then hit IDA's breakpoint.
1596064570723.png

EAX contains the return of dlopen
> Press shift+f7 in ida and find init_array Section, click to show several existing function addresses.
> Trace the third address and find that it is a method to decrypt so.
( There is no decryption operation in JNI_load after dlopen, so we can get the correct import and export table for sure with this process).
>Then, we find the mono_image_open_from_data_with_name method to get the dll decryption algorithm.
>Now that we obtained the function address, and libmono has further anti.debug protections, we restart the game, we wait for the game running loading screen to come out.
>We open libunity.so with IDA to attach to the game's process.
>Right click to analyze libmono, IDA will find the corresponding function address according to the import of libunity. We then look for the call of _mono_image_open_from_data_with_name
1596065449456.png

>We trace to _mono_image_open_from_data_with_name and we see that there is only one jmp instruction, check it's the reference.
1596065502012.png

>This is what happened next (I'll be quoting ss22219 here):
"I found that this address was incorrect, but the address of mono_image_close was accidentally parsed. It was easy to find and read the mono source code, and I found that mono_image_close is located under mono_image_open_from_data_with_name. I guess that the compilation will also compile keeping same order."
Seems that he got the wrong address, but it a good reference one, since he managed to find mono_image_open_from_data_with_name.
>We'll find the folling string: Bad call to mono_mutex_unlock result:
>We compare with libmono's source code and we found this string definition in the LeaveCriticalSection macro. We see that the register_image method uses this macro twice, and thus, we find the register_image method location.
>We find that there are two references to register_image. The one we're looking for should be the closest one. By comparing with the source code, we conclude it really is mono_image_open_from_data_with_name.
1596066184918.png

(With enough practice and x86 assembly knowledge, we can reach the conclusion that they're the same)
>We find there's a lot of database code in mono_image_open_from_data_with_name function, which means it has been processed and it's very difficult to restore.
>To obtain the correct process, we debug again.
>Through debugging we terminate the game after dlopen libunity, thus init_array section of libunity can be analyzed. There will dozens of function addresses
>This is what happened next (I'll be quoting ss22219 here):
"However, when I saw that a thread was created in the output window, I felt suspicious. I open the debug option and set the thread start/end to find that a thread was started in libNetHTProtect. When was libNetHTProtect loaded? (I asked myself)".
He realized there was a process that started libNetHTProtect.so, a library strongly linked with libmono's anti-debug protection (He further explains libmono will be called after libNetHTProtect).
>By pausing all threads created by libNetHTProtect.s, libunity.so will be successfully loaded.
>We execute the following instructions in the adb cmd (ps will show us all the processes, there we look for the game's PID):
adb shell
ps
1596070196685.png

>We then write:
cp /proc/XXXXX/map_files/* ./
In this case, FGO has PID 2420, so XXXXX = 2420: (it'll change every time) cp /proc/2420/map_files/* ./
>We execute the following instruction:
ls
1596071751716.png

>We copy-paste code from AutoDumpACS.txt and execute (we requiere visual studio, and we execute it on vs++). It will automatically dump the dll into the corresponding folder. (There may appear more than one).
>In this case, more than one appear, but only one could be Assembly-CSharp.dll (because of file size).
>That .dll can't still be recognized, so we will proceed with the PE Header fixing algorithm (found in Perfare's blog) .
(Perfare asks to link if quoting his blogspot, and google translate is decent enough to understand how it works).
>Congratulations, Assembly-CSharp is now moddable as the usual ones.
1596073442437.png
 

Attachments

Last edited:

Duy112233

Platinian
Dec 24, 2019
6
0
1
23
Vietnam
That's so hard bro, thank you for your translation so much, I will try it as soon as possible cuz now I'm just a beginner ;-;