查看原文
其他

XYCTF两道Unity IL2CPP题的出题思路与题解

TubituX 看雪学苑 2024-07-16





在这次的“新生赛”上出了两道unity il2cpp逆向的题,没有设计游戏内容,只有一个check,单纯的考察il2cpp逆向的知识。因为考虑了是新生赛,所以check部分没有设计的很复杂,并且比赛时长接近一个月,所以目的就是让新生们通过搜索资料学习来解出这两题,并且对游戏引擎逆向有一些了解和经验。事实证明有很多新人师傅们做到了,这里也夸夸师傅们太强了哈哈。




baby unity


出题思路


因为看到ez unity让大家坐牢太久了,所以连夜又赶了一个简单版本的出来。

这题没有对il2cpp部分做任何魔改,单纯为了压缩题目文件,所以加了个upx。

解题思路


本题属于游戏引擎逆向,引擎为unity,并使用il2cpp打包。

如果熟悉il2cpp的话,可以知道他把C#中的类名、方法名、属性名、字符串等地址信息记录在
global-metadata.dat文件中,程序启动时会按需从中读取。

这里我们可以使用
Il2CppDumper(https://github.com/Perfare/Il2CppDumper)来静态dump。

1.首先要脱掉GameAssembly.dll的upx壳。


2.然后使用Il2CppDumper,依次选择GameAssembly.dll,global-metadata.dat,然后就可以dump了。


3.用dnspy分析DummyDll\Assembly-CSharp.dll,可以看到有个Check类,所以我们重点关注OnClick,CheckkkkkkkkkkFlag,EEEEEEEEEEEEEncrypt这三个方法即可,但是Il2CppDumper是无法将代码还原的,不明白的话可以去理解一下IL2CPP(IL to CPP)是什么意思,所以我们要去IDA里去看这个三个方法到底在做什么。


4.用IDA加载GameAssembly.dll,然后使用il2CppDumper目录下的ida_with_struct_py3.py脚本,依次选择script.jsonil2cpp.h,稍等之后就可以恢复符号表。

这里先放个核心源码:



实际上编译的时候将这三个方法全都内联到一起了,不过不影响解题,为了方便直接看CheckkkkkkkkkkFlag这个方法了。



简单分析一下流程就是输入 -> base64编码 -> 异或0xF,最后和密文进行比较,密文实际上也是一个StringLiteral,在脚本恢复符号的时候也一起写在注释里了。


5.最后厨子一把梭就好了。


总结


学会检索资料和使用工具,更重要的是在用之前多阅读一下工具的文档,就不会有一些奇奇怪怪的问题了。

比如有问我为什么dump后的dll里没有源码,首先在README里有说明,其次再去理解一下什么是IL to CPP。

还有问我为什么找不到libil2cpp.so的,这是Windows呀,肯定不会有so嘛。




ez unity


出题思路


出这题的时候思考了很久,单纯的加密global-metadata.dat文件直接内存中dump即可拿到解密后的文件,感觉有点过于简单,所以考虑直接修改Il2CPP的东西了。但是我尝试了直接修改libil2cpp的源码,结果就是要么编译失败,要么游戏运行直接崩了,不确定具体是什么问题。

最后的方案是去分析了一下build的部分,修改了
sanity,然后在Il2cppGlobalMetadataHeader中间插入了一个magic(第一张图里忘记写了,应该还有一个list.Add()的),但貌似这样会导致最后一个成员被挤掉了,但是不影响游戏正常运行我也就没管了,有大佬会这方面的还请提提意见Orz。



解题思路


首先这题对global-metadata.dat的结构做了一些特殊修改,所以工具是无法直接使用的。

这里有两种做法,一个是逆向修复结构(力气活),然后 Il2cppDumper->ida脚本恢复符号信息->找到关键方法->AES+BASE64解密。

理论上这样做会很耗时耗力,但是这题我没有对结构做很复杂的魔改,所以这样解也在预期解内。

这里简单讲讲另一种做法,Runtime下调用 il2cpp api,原理可以自行了解一下,这里直接使用
frida-il2cpp-bridge(https://github.com/vfsfitvnm/frida-il2cpp-bridge)来解决了。

1.按照官方文档安装,然后注入下面的脚本,得到 dump.cs。

import "frida-il2cpp-bridge";

Il2Cpp.perform(() => {
console.log(Il2Cpp.unityVersion);

Il2Cpp.dump("dump.cs", "./")
});

2.分析dump.cs,搜索 Assembly-CSharp,发现了Check这个类,下面有个CheckFlag,接着用ida分析一下,rva 是 0x0027d460。

// Assembly-CSharp
class Check : UnityEngine.MonoBehaviour
{
System.Void Start(); // 0x0027d670
System.Void Update(); // 0x0027d670
System.Void OnClick(); // 0x0027d4e0
System.Boolean CheckFlag(System.String input); // 0x0027d460
static System.String AESEncrypt(System.String text, System.String key, System.String iv); // 0x0027d200
static System.String AESDecrypt(System.String text, System.Byte[] key, System.Byte[] iv); // 0x0027d000
System.Void .ctor(); // 0x0027d680
}

3.GameAssembly.dll 加了upx壳并修改了一些特征信息,改回去然后upx -d脱壳。


4.ida分析CheckFlag(Imagebase + RVA),根据dump.cs里的RVA恢复一下符号,可以看到加密只有一个简单的AES,那么只要拿到key和iv就行了,因为字符串信息都是保存在global-metadata.dat里的,所以在GameAssembly.dll里是无法得到的,可以直接调试分析得到,也可以使用下面的方法。


5.接着我们使用frida-il2cpp-bridgeTracer来追踪一下String类。

import "frida-il2cpp-bridge";

Il2Cpp.perform(() => {
console.log(Il2Cpp.unityVersion);

const String = Il2Cpp.corlib.class("System.String");
Il2Cpp.trace(true).classes(String).and().attach();
});

注入,随便输点击一下按钮,就得到密文了pNufkEIU9dHjKXYXWiFyrthHYFEfqJAWcPM/t8/zX1w=

❯ python .\main.py 'ez unity.exe'
2022.3.17f1c1
il2cpp:
0x0077b6f0 ┌─System.String::FastAllocateString(length = 24)
0x0077b6f0 └─System.String::FastAllocateString = ""

il2cpp:
0x0077afd0 ┌─System.String::op_Equality(a = "arpEeXJzJ5Fh4j+2tx6fIw==", b = "pNufkEIU9dHjKXYXWiFyrthHYFEfqJAWcPM/t8/zX1w=")
0x0077afd0 └─System.String::op_Equality = false

il2cpp:
0x0077afd0 ┌─System.String::op_Equality(a = "Wrong!", b = "Wrong!")
0x0077afd0 └─System.String::op_Equality = true

6.再去追踪一下Check类。

import "frida-il2cpp-bridge";

Il2Cpp.perform(() => {
console.log(Il2Cpp.unityVersion);

const AssemblyCSharp = Il2Cpp.domain.assembly("Assembly-CSharp").image

const Check = AssemblyCSharp.class("Check");
Il2Cpp.trace(true).classes(Check).and().attach();
});

很轻松就拿到了key和ivkey = "a216d5d34c2723f5", iv = "9f68268f755b1363"。

❯ python .\main.py 'ez unity.exe'
2022.3.17f1c1
il2cpp:
0x0027d4e0 ┌─Check::OnClick(this = Button (Check))
0x0027d200 │ ┌─Check::AESEncrypt(text = "", key = "a216d5d34c2723f5", iv = "9f68268f755b1363")
0x0027d200 │ └─Check::AESEncrypt = "arpEeXJzJ5Fh4j+2tx6fIw=="
0x0027d4e0 └─Check::OnClick

再次祭出我们万能的厨子。


总结


这题的解题方法应该是很开放性的,这里贴一下看到的选手的一些解题方法,基本上都是预期到的解法,不过我是真没想到有人bindiff...还得是ctfer哈哈。

◆使用frida-il2cpp-bridge
◆自己写dumper
◆使用CE的mono工具
◆对比baby unity的文件修复global-metadata.dat
◆和baby unity恢复符号的GA.dll进行bindiff





笔者第一次出题,最后看到题目解出数量也在预期内,也算是没有失误,如果有更好的建议还请各位师傅们尽管提!




看雪ID:TubituX

https://bbs.kanxue.com/user-home-958990.htm

*本文为看雪论坛精华文章,由 TubituX 原创,转载请注明来自看雪社区



# 往期推荐

1、怎么让 IDA 的 F5 支持一种新指令集?

2、2024腾讯游戏安全大赛-安卓赛道决赛VM分析与还原

3、Windows主机入侵检测与防御内核技术深入解析

4、系统总结ARM基础

5、AliyunCTF 2024 - BadApple



球分享

球点赞

球在看



点击阅读原文查看更多

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存