unity 打包 dll 可疑文件名揭示功能性模块,唯有libDexHelperso引人关注

unity 打包 dll 可疑文件名揭示功能性模块,唯有libDexHelperso引人关注

0. 背景

近日,一家老牌游戏厂商发布了一款MMO手游。 听说图形非常好,而且是基于unity3d的。 很想看看效果如何,可惜测试时间太短,而且需要激活码。 我必须等到安装它。 apk,输入后发现没有激活码,测试结束。 不过,登录界面看起来确实不错,所以我不得不对其进行逆向工程并看一下。

十年前,我们在微软的dotnet平台下研究了一些安全相关的东西。 十年后,由于mono项目和unity3d的流行,dotnet技术在移动平台上开始流行。 凭着断断续续的记忆和当年的一些资料,我就做了一个游戏逆转。

在移动平台上,dotnet dll没有签名,这在一定程度上使得逆向工程和修改变得更加容易。

本文简单介绍逆向过程,省略与游戏相关的内容。

1. apk解包、重新打包和签名

您可以使用“Android逆向助手”或ApkStudio。 我们使用ApkStudio进行解包,使用Android逆向助手进行重新打包和签名。

2.棒棒加解密

解压后的一级目录如下:

AndroidManifest.xml

apktool.yml

资产/

建造/

库/

原来的/

资源/

斯马利/

首先,使用Reflector或IlSpy打开assets/bin/Data/Managed目录下的标准unity3d游戏模块Assembly-CSharp.dll。

无法打开,文件已加密。 。

打开lib/armeabi-v7a目录可以看到依赖的so文件如下:

libAkSoundEngine.so

libBlueDoveMediaRender.so

libCrasheyeNDK.so

libDexHelper.so

libKGAudio.so

libmain.so

libmono.so

libmsc库

libslua.so

自由库

力布娃网

libweibosdkcore.so

看文件名,大部分都是功能模块。 只有 libDexHelper.so 是可疑的。 我上网一查,发现是棒棒的东西。 在网上找不到任何关于Bangbang的unity3d游戏的加密原理和解决方案的资料,只好自己看一下。

这时候就得用ida pro了。 我使用的是6.6版本。 首先,看一下 libmono.so。 这是mono的运行时库。 dotnet的加载和运行支持都在这里。 直接看一下mono_image_open_from_data_with_name函数。 经检查,没有发现明显的改装痕迹。 我也搜索了几个相关的加载相关函数,但没有发现修改的痕迹。 。

我怀疑 libmono.so 根本没有被修改。 unity3d的版本信息在它的资源文件中比较容易找到。 只需在assets/bin/Data下打开一个带有assets后缀的文件即可(记得使用十六进制编辑器)。 您可以在文件的开头看到 5.3.3p2。 它实际上使用的是补丁版本,而不是像f1那样的官方版本。 去unity3d网站下载对应版本的编辑器+android发布包。 安装后找到Unity5.3.3p2\Editor\ Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Libs\armeabi-v7a\libmono.so使用Beyond Compare等支持二进制比较的工具与libmono.so进行比较游戏。 一模一样。看来Bangbang不能静态修改。

libmono.so。 想一想。 像unity3d这样的版本更新频繁。 如果静态地改变每个版本2d游戏素材,那就相当被动了。 不过某厂的加密是静态修改的libmono.so(其实是我修改了源码重新编译的)。

我无法弄清楚 libDexHelper.so 是如何工作的。 我不是逆向工程专业人士。 我用ida pro打开看了一下。 没找到任何线索就放弃了(在ida pro中找不到Assembly-CSharp.dll这样的字符串,但是用十六进制编辑器在libDexHelper.so文件末尾看到了这个字符串。有兴趣的同学可以研究一下它的加密原理)。

我改变主意,获取解密后的dll,即以libmono.so开头。 我已经发现Bangbang并没有对这个文件进行静态处理,所以我们更容易随心所欲地处理它。 查看mono的源码,我们知道是在mono_image_open_from_data_with_name函数中。 dll 文件的内容将显示为完整的内存映像。

那么获取解密的dll至少有以下几种方法:

我在断点后转储了内存。 我的电脑上用的是虚拟机,破解不了。 。

修改mono源代码,添加dump代码并替换游戏的libmono.so。 这在理论上是可行的,但是对于这么小的事情来说就有点费力了。

直接修改libmono.so并手动打补丁。 查看mono源代码后,发现mono_image_open_from_data_with_name函数的开头有

一般情况下一段时间的空检查是没有用的。 我在ida pro中检查了这段代码占用的字节数,足以用于修补:)

我们还需要找到一个地方来放置我们的补丁代码,这需要一个很少使用的功能。 连孟黛猜测我选择的是mono_load_remote_field。 这个函数的空间足够写很多代码了。

我们要在函数开头添加的代码如下:

如果(数据长度>6000000){

文件* fp = fopen("/data/local/tmp/test.dll","wb");

如果(fp){

fwrite(数据,1,data_len,fp);

fclose(fp);

最初的字节数判断是只转储想要解密的dll。 因为这个dll很大,你可以通过它的大小来判断它,这样可以节省字节:)

我们需要手动打补丁,流程大致如下:

1)首先翻译成字节码。 这里我使用ADS 1.2来编译代码片段。 字节码和反汇编如下:

文件夹打包dll文件_打包dll_unity 打包 dll

这是相当不错。 它只是将字符串放在代码末尾,这特别适合在代码中打补丁。

2)、然后复制到目标函数中

这里我使用Hex Workshop,首先找到ida pro中函数对应的起始位置,然后nop掉之前提到的无用代码,添加一个

对于 mono_load_remote_field 的调用,只需将之前的字节码粘贴到函数 mono_load_remote_field 的开头即可。

3)。 然后手动重新定位系统功能。 。

因为我们使用了3个C语言库函数fopen/fwrite/fclose,为什么要用这3个函数呢? 因为一般程序都应该引入这个库,所以只需要修改偏移量即可(实际上ADS编译的指令中的这些调用也是为以后重定位保留的)。 在ida pro中找到这三个函数的导入代码(即三个进程)的地址,然后计算出三个调用位置。 目标偏移量(这里我不太明白,必须使用“目标地址-调用指令地址-8”。我在ARM手册中没有找到这个要求,如果有人知道更多,请告诉我)。 修改指令的最后3条。 字节可以用作偏移量。

修改 mono_image_open_from_data_with_name

文件夹打包dll文件_unity 打包 dll_打包dll

;以下是原代码

.text:00190AE8 LDR R3, [R11,#src]

..

修改mono_load_remote_field

unity 打包 dll_打包dll_文件夹打包dll文件

;以下是原代码

打包dll_unity 打包 dll_文件夹打包dll文件

现在您应该能够在重新打包和签名后获得解密的 dll。 我只是还无法进入游戏。 。

3. 消除对bangbangso的依赖

这个就按照网上说的来吧(我有点忘记了manifest的具体修改点了……)

1)。 修改解压包中的AndroidManifest.xml,去掉面向Bangbang的Activity。

2)修改解压后的smali文件,主要是smali\com\secneo\apkwrapper目录,注释掉加载DexHelper的代码。

现在将apk重新打包、签名并安装在虚拟机中并运行。 就可以进入游戏了(因为之前dll已经被解密了,这次需要把libmono.so替换成原来的unity3d的)。

4. 编写自己的调试模块

哈哈,终于可以从ARM的组装中回到人类世界了。

现在游戏dll已经解密,重新打包后就可以运行了。 所以我们可以尝试修补逻辑。 嗯,其实我想研究一下它有没有什么新花样。

我不会谈论编程。 大概是基于DebugConsole.cs(),然后添加一些我们需要的命令,比如动态加载Assembly,使用反射API调用函数等。 嗯,这在Android上确实是可行的。 ,对于dotnet来说,这基本上不是问题(所以详细的就不写了。需要注意的是,读取dll时,必须先使用文件API读取byte[],然后是Assembly.Load。另外就是dll所在的目录必须有权限读取,比如sdcard上的目录更好)

5. 将调试模块合并到目标游戏中

我们根据DebugConsole.cs的修改unity 打包 dll,编译了自己的dll。 下一步是将这个dll合并到目标Assembly-CSharp.dll中,并修改Assembly-CSharp.dll中的代码,该代码将在运行时到达以调用我们的代码。 。

微软开发了一个非常好的工具来合并dll(微软研究院经常做一些很奇怪的事情橙光游戏,比如Detours项目,还有这个IlMerge工具)。 除了合并dotnet dll文件之外,这个工具实际上是一个dotnet PE。 文件处理的源代码库(实际上找不到源代码,但使用IlSpy或Reflector基本没问题)。 另外,它还可以用来重新组织我们修改后的dotnet可执行文件。

6.修改目标游戏代码调用调试模块

这一步我使用了自制的工具。 如果你想手动处理,也可以使用CFF Explore,这里不再赘述。

我们简单说一下我们实际做的事情:

1)。 游戏的启动类Game在Update中调用TestInput。

私有无效更新()

尝试

...

this.TestInput();

catch(异常异常)

日志异常(异常);

私有无效测试输入()

TestInput 是一个空函数。 该函数不访问 Game 实例的任何变量。 它与静态函数具有相同的效果。 我们用我们提供的函数替换它的方法体(之前使用 IlMerge 将其合并到 Assembly-CSharp.dll 中)。 代码),这样我们的静态注入代码就有机会执行:

私有无效测试输入()

DebugConsoleHelper.Init(base.gameObject);

DebugConsoleHelper.Tick();

修改的原理就是将Game类的TestInput方法的元数据中的RVA改为我们合并到的类GamePatch的TestInput方法的RVA。

修改后,Game.TestInput和GamePatch.TestInput方法实际上共享相同的指令,这在dotnet PE文件中没有问题。

使用 CFF Explore 手动修改此步骤也更容易,因为它仅替换元数据。

因为我为了学习需要多次修改dll,所以我使用了自己的工具执行脚本来自动处理它(见下文)。

2)在游戏的PlayerController类的Update函数中,我们需要修改它来实现移动(主要用于在不连接服务器的情况下浏览场景)

私有无效更新()

if (activeController == this)

this.ProcessJoystick();

this.m_moveElapseTime += Time.deltaTime;

if (this.m_moveElapseTime > m_moveInterval)

DebugConsoleHelper.Move(this.m_player, this.m_moveElapseTime);

this.m_moveElapseTime = 0f;

this.ProcessSkillCD();

这里我们需要修改字节码来实现我们的代码。 它本质上与修改传统的可执行文件相同。 首先NOP一段代码,然后编写我们的代码。

.method 私有 hidebysig 实例 void Update() cil 管理

.maxstack 8

L_0000:调用类 PlayerController PlayerController::get_activeController()

L_0005:ldarg.0

L_0006: 调用 bool [UnityEngine]UnityEngine.Object::op_Equality(类 [UnityEngine]UnityEngine.Object, 类 [UnityEngine]UnityEngine.Object)

L_000b:brfalse L_0076

L_0010:ldarg.0

L_0011:调用实例 void PlayerController::ProcessJoystick()

L_0016:ldarg.0

L_0017:重复

L_0018: ldfld float32 PlayerController::m_moveElapseTime

L_001d:调用 float32 [UnityEngine]UnityEngine.Time::get_deltaTime()

L_0022:添加

L_0023: stfld float32 PlayerController::m_moveElapseTime

L_0028:ldarg.0

L_0029: ldfld float32 PlayerController::m_moveElapseTime

L_002e: ldsfld float32 PlayerController::m_moveInterval

L_0033:ble.un L_0076

;以下是我们修改后的代码

L_0038:ldarg.0

L_0039: ldfld 类玩家 PlayerController::m_player

L_003e: ldarg.0

L_003f: ldfld float32 PlayerController::m_moveElapseTime

L_0044:调用 void DebugConsoleHelper::Move(类 Player,float32)

L_0049:没有

L_004a:无

L_004b:无

L_004c:无

L_004d:无

L_004e:无

L_004f:无

L_0050:无

L_0051:没有

L_0052:没有

L_0053:没有

L_0054:没有

L_0055:没有

L_0056:没有

L_0057:没有

L_0058:没有

L_0059:没有

L_005a:无

L_005b:无

L_005c:无

L_005d:无

L_005e:无

L_005f:无

L_0060:没有

L_0061:没有

L_0062:没有

L_0063:没有

L_0064:没有

L_0065:没有

L_0066:没有

L_0067:没有

L_0068:没有

L_0069:没有

L_006a:无

;修改结束

L_006b:ldarg.0

L_006c:ldc.r4 0

L_0071: stfld float32 PlayerController::m_moveElapseTime

L_0076:ldarg.0

L_0077:调用实例 void PlayerController::ProcessSkillCD()

L_007c:ret

我还使用自制工具自动处理此步骤。 也可以使用CFF Explore和Hex Workshop手动修改,但如果需要多次修改就有点烦人了。

7.自制工具

我自己做了一个小工具,用于前面提到的方法体替换和方法代码修改(是从10年前的DeObfuscator修改而来的:)

对于方法体替换,由于多个类会共享方法体,这样的方法无法访问类的实例变量,这意味着它一般用于静态方法。

工具直接支持方法体替换。 添加目标文件后,输入替换的类和替换类,点击“方法实现替换”。

这个小工具主要是利用脚本来自动处理上面的修改。 这些脚本是基于DSL(我的另一个开源项目)实现的。

本文涉及的修改所使用的脚本如下:

过程(主要)

$files = getfilelist();

begin("开始脚本处理");

循环列表($文件){

$文件=$$;

beginfile($file,"开始修改"+$file+"...");

开始替换($文件);

替换($文件,“游戏”,“GamePatch”);

结束替换($文件);

开始修改($文件);

writeloadarg($file,"PlayerController","更新",0x38,0);

writeloadfield($file,"PlayerController","更新",0x39,"PlayerController","m_player");

writeloadarg($file,"PlayerController","更新",0x3e,0);

writeloadfield($file,"PlayerController","更新",0x3f,"PlayerController","m_moveElapseTime");

writecall($file,"PlayerController","更新",0x44,"DebugConsoleHelper","移动");

writenops($file,"PlayerController","更新",0x49,0x22);

结束修改($文件);

结束文件($文件);

};

end("结束脚本处理");

};

我把这个小工具开源了unity 打包 dll,欢迎使用:

观看雪地测试:

看学论坛:

-----微信ID:ikanxue-----

勘学研究院致力于安全研究16年!

文章来源:http://mp.weixin.qq.com/s?src=3×tamp=1694683042&ver=1&signature=cPkuNNeaaHn31QW4kdR4GrE5MOjDEeHAH4HCWWQs8casvuN2BB9NSwHc9gPKPby3bjMQrbolRQCC7VnbbMrG1R6NSzsMgRFYYw4Snd860fdlw94vT85Vq44jYOI31Geuor*jjjTjLy09THZ1vGpFhR68jkKd4QComdMoDSnMWfY=