CTF游戏逆向入门
Unity游戏逆向
Unity3D最大的一个特点是一次制作,多平台部署,而这一核心功能是靠 Mono 实现的。可以说一直以来 Mono 是 Unity3D 核心中的核心,是 Unity3D 跨平台的根本。这种形式一直持续到 2014 年年中,Unity3D 官方博客上发了一篇 “The future of scripting in unity ” 的文章,引出了 IL2CPP 的概念,这种相比 Mono 来说安全性更强的方式。
1.Mono 打包逆向
Mono 打包方式逆向比较简单,其核心代码都在 game/data/Managed/AssemblyCSarp.dll 这个 dll 文件中,使用 dnSpy 进行分析,几乎就是可以明文随便篡改。
[BJDCTF2020]BJD hamburger competition
这个Mono还是比较好识别出来的, 游戏名_Data -> Managed -> AssemblyCSarp.dll
将AssemblyCSarp.dll放入dnSpy里面进行分析,找到ButtonSpawnFruit(),里面有md5和sha1,点进去进行交叉引用
可以推测,每一个菜都对应着一种操作,这与上菜的顺序有关,按照一定的顺序加菜,使得 str 的 Sha1与那一串相等,然后将 str 进行md5加密,但是这里要注意,要看看Sha1加密和md5是否都是标准的。
Sha1和 md5都是标准的,但是md5是取前20个字符,X表示的是大写16进制,x表示小写16进制
import hashlib
result = hashlib.md5("1001".encode()).hexdigest().upper()
flag=result[0:20]
print(flag)
##B8C37E33DEFDE51CF91E
[SCTF2019]Who is he
同样的方法,将AssemblyCSarp.dll放入dnSpy里面进行分析,里面密文和key都有
flag就是 EncryptData 通过 Decrypt 来解密得到的。
这个解密过程是先进行base64解密,然后再进行DES-CBC模式解密,其中找不到iv是因为key=iv="1234"。
需要注意的是:dnSpy里的字符串是用UTF-16编码存储的,也就是宽字节(双字节)
比如:字符串"1234"
它在内存里面其实是:
31 00 32 00 33 00 34 00
每个字符后面都跟一个00h,也就是说每个字符占两个字节。
写出脚本
import base64
from Crypto.Cipher import DES
cipher="1Tsy0ZGotyMinSpxqYzVBWnfMdUcqCMLu0MA+22Jnp+MNwLHvYuFToxRQr0c+ONZc6Q7L0EAmzbycqobZHh4H23U4WDTNmmXwusW4E+SZjygsntGkO2sGA=="
enc=base64.b64decode(cipher)
key=iv=b"1\x002\x003\x004\x00"
des=DES.new(key,DES.MODE_CBC,iv)
print(des.decrypt(enc).decode('utf-8'))
##He_P1ay_Basketball_Very_We11!Hahahahaha!
本以为美美结束了,但是这个是错的flag。接下来需要使用 Cheat Engine,它是由Dark Byte开发的免费开源游戏内存修改工具,主要运行于Windows平台。 其核心功能是通过扫描并修改进程内存数据调整游戏参数(如生命值、弹药数),集成调试器、反汇编器、变速器及Direct3D操作工具。官网:https://www.cheatengine.org/
个人不建议在官网下载哈,我看网友说好像会强制下载流氓软件……
CE的使用教程:https://www.cnblogs.com/LyShark/p/10799926.html
出题人应该隐藏了真实的密文和key,这些都会放在dll里面,Managed里面的dll是所有的代码。
所以在Managed文件夹里面按照时间排序,看最近的日期就能知道哪些是可疑的。
然后打开CE和那个游戏,在CE里选择这个游戏进程,会看到出现了Mono,点击它选择分析Mono。就会看到这些dll,找到刚才可疑的3个dll与AssemblyCSarp.dll对比。
两者对比会发现,真正的密文和key都在UnityEngine.UmbraModule里面。我们可以换成 Mono ->.Net Info进行分析,在这里面找到UnityEngine.UmbraModule程序集,里面有个.Main类,打开看看。
知道key=iv="test"。它是静态的,可以直接读取值,但是密文是动态的
找到存放密文的变量,选中后点击Lookup 按钮,它就会自动查找所有可能的内存指针然后读取里面的内容。
写出脚本
import base64
from Crypto.Cipher import DES
cipher="xZWDZaKEhWNMCbiGYPBIlY3+arozO9zonwrYLiVL4njSez2RYM2WwsGnsnjCDnHs7N43aFvNE54noSadP9F8eEpvTs5QPG+KL0TDE/40nbU="
enc=base64.b64decode(cipher)
key=iv=b"t\x00e\x00s\x00t\x00"
des=DES.new(key,DES.MODE_CBC,iv)
print(des.decrypt(enc).decode('utf-8'))
##She_P1ay_Black_Hole_Very_Wel1!LOL!XD!
第二种方法
直接搜索字符串"Emmmmm",这个是输入正确的flag后弹出的弹窗里的部分提示语
经过扫描会看到只有两个地址有关,都分别定位内存中的位置,可以看到这两个真假密文和key
第三种方法:使用uniref框架解出真的密文
uniref框架:https://github.com/in1nit1t/uniref
uniref 是一个辅助分析 Unity 应用的框架。它可以帮助我们取获取 Unity 应用中的类、方法、成员变量等的反射信息,也可以实时地查看和操作它们。
from uniref import WinUniRef
ref = WinUniRef("Who is he.exe") #传进程的id,或者exe文件名
main = ref.find_class_in_image("UnityEngine.UmbraModule","UnityEngine.UmbraModule.Main")
# print(main)
key = main.find_field("encryptKey")
print(key.is_static()) #返回True是静态的,可以直接访问value
print(key.value)
enc = main.find_field("EncryptData")
print(enc.is_static()) #返回false是动态的,不能直接访问value
addresses = main.guess_instance_address()
print(list(map(hex,addresses)))
for addr in addresses:
enc.set_instance(addr)
print(hex(addr),enc.value) #打印出所有的可能内存地址及其内容
2.IL2CPP 打包逆向
IL2CPP 将游戏 C# 代码转换为 C++ 代码,然后编译为各平台 Native 代码。现在市面上的很多游戏基本上都是用Il2cpp的方式打包的。
global-metadata.dat 文件里面记录了所有的 C# 中的类名、方法名、属性名、字符串等地址信息。目前对于il2cpp逆向都以Android中的.so文件居多。
IL2CPP 打包的应用在逆向前会多一步操作,即使用项目 Il2CppDumper 和应用程序目录中的 global-metadata.dat 和 GameAssembly.dll 来获得 dll 的符号信息。之后再通过 IDA 加载 GameAssembly.dll 及符号信息来进行逆向分析(常规 C 语言逆向)。
[MRCTF2021]EzGame
打开游戏先玩一玩,好玩爱玩……
按Esc出现任务单,要求:
- 吃到所有的星星
- 找到外星人
- 吃到饼干
- 回到家
- 不能死太多次
这些全部完成才能得到flag。
打开后在Data文件夹里看到 il2cpp_data ,这就是il2cpp打包的unity游戏。
需要的工具:Il2CppDumper 下载使用:https://github.com/Perfare/Il2CppDumper
命令
Il2CppDumper.exe GameAssembly.dll的路径(第一个参数) global-metadata.dat的路径(第二个参数) 输出的路径(第三个参数)
这样就是正确执行了。output文件夹里面会有一些文件
dump.cs:这个文件会把 C# 的 dll 代码的类、方法、字段列出来
IL2cpp.h:生成的 cpp 头文件,从头文件里可以看到相关的数据结构
script.json:以 json 格式显示类的方法信息
stringliteral.json:以 json 的格式显示所有字符串信息
DummyDll:进入该目录,可以看到很多dll,其中就有 Assembly-CSharp.dll 和我们刚刚的 dump.cs 内容是一致的
接下来使用IDA来加载 GameAssembly.dll ,但是这个题加了个Themida的壳,很难脱,这个壳是一款商业级的壳,特点是保护强度高,经常被用来保护游戏、外挂、商业软件。
其实到这里这个常规的方法就行不通了,下面是拿这道题来演示一下,常规的流程是什么。
选择 File -> Script file 去应用 ida_with_struct_py3.py文件。
然后选择该文件
再导入头文件
然后就来等待它分析就好了。那这种方法行不通,换CE来看看。
与上一题相同的方法,看到GetFlag很容易猜想到主要逻辑就在这。
其他条件都可以改成True,但是tokenGet如果直接改成105是不行的,看到下面的EatTokenUpdateKey方法,(这里也是看过别人的wp才明白的)就应该猜到了每调用一次这个方法,上面的token会加一,这个方法里面应该还有最后解密的key也会随着调用的次数而改变。这样如果直接改上面的值,key还是原来的错误key,flag也就没有被解密出来。我们可以直接右键选择调用,但是这样要一直点105下。于是我们用uniref框架来写。
使用时要和CE一样,确保游戏还开着。
from uniref import WinUniRef
ref = WinUniRef("GameHack.exe")
get_flag = ref.find_class_in_image("Assembly-CSharp.dll","Platformer.Flag.GetFlag")
# print(get_flag)
eat_cookie = get_flag.find_field("eatCookie")
find_alien = get_flag.find_field("findAlien")
go_home = get_flag.find_field("goHome")
# print(eat_cookie.value)
eat_cookie.value = True
find_alien.value = True
go_home.value = True
update = get_flag.find_method("EatTokenUpdateKey")
print(update.is_static())
for i in range(105): #调用105次
update()
成功得到flag!🤗
Android游戏逆向
无论是Android或者是Windows都能够使用unity引擎进行开发。安卓unity游戏的核心逻辑一般位于 assets\bin\Data\Managed\Assembly-CSharp.dll
BUU-[MRCTF2020]PixelShooter
使用jeb打开这个apk文件,直接在找到assets\bin\Data\Managed\Assembly-CSharp.dll这个位置打开。
然后可以按ctrl+F进行筛查,有点考察我的眼力了🥱
普通C#程序
使用 .NET 框架提供的编译器可以直接将源程序编译为 .exe 或 .dll 文件,但此时编译出来的程序代码并不是 CPU 能直接执行的机器代码,而是一种中间语言 IL(Intermediate Language)的代码。
这个时候再放入IDA里面进行分析,就不太行了。放入DIE里面查看,出现如下
这可以使用新的工具dnSpy,它是C#逆向的好帮手。
dnSpy是一个.NET调试器和反编译器,可以在无源码的情况下,进行代码调试和修改。遇到此类题目,我们可以把它放到dnSpy里面进行分析。
[ISC 2016]Classical CrackMe
运行一下看看
把它放到dnSpy里面进行分析,但是发现本该显示form1的字样的地方成了一串字符,这就加壳混淆了
这个时候需要使用de4dot来帮助,它是是一个很强的.Net程序脱壳,反混淆工具,支持对于以下工具混淆过的代码的清理:如Xenocode、.NET Reactor、MaxtoCode、Eazfuscator.NET、Agile.NET、Phoenix Protector、MancoObfuscator 、CodeWall、NetZ .NET Packer 、Rpx .NET Packer、Mpress .NET Packer、ExePack.NET Packer、Sixxpack .NET Packer、Rummage Obfuscator、Obfusasm Obfuscator、Confuser1.7、Agile.NET、Babel.NET、CodeFort、CodeVeil、CodeWall、CryptoObfuscator、DeepSea
Obfuscator、Dotfuscator、 Goliath.NET、ILProtector、MPRESS、Rummage、SmartAssembly、Skater.NET、Spices.Net 等。
使用方法:直接 de4dot exe所在的位置 就好,会生成一个xxx-cleaned.exe
然后把新的exe再放入dnSpy里面进行分析
这下就好了,逻辑清晰可见,那串密文是base64加密
PCTF{Ea5y_Do_Net_Cr4ck3r}
[FlareOn5]Ultimate Minesweeper
先查壳,无壳32位,是net写的。我们试着运行一下程序,发现是个扫雷游戏,而且这个只要扫到雷就会弹出失败的弹窗然后退出游戏。
把它放到dnSpy里面进行分析,能找到MainForm部分,这里就是重要部分
里面有GetKey方法,这应该就是flag的生成逻辑
private string GetKey(List<uint> revealedCells)
{
revealedCells.Sort();
Random random = new Random(Convert.ToInt32(revealedCells[0] << 20 | revealedCells[1] << 10 | revealedCells[2]));
byte[] array = new byte[32];
byte[] array2 = new byte[]
{
245,
75,
65,
142,
68,
71,
100,
185,
74,
127,
62,
130,
231,
129,
254,
243,
28,
58,
103,
179,
60,
91,
195,
215,
102,
145,
154,
27,
57,
231,
241,
86
};
random.NextBytes(array);
uint num = 0U;
while ((ulong)num < (ulong)((long)array2.Length))
{
byte[] array3 = array2;
uint num2 = num;
array3[(int)num2] = (array3[(int)num2] ^ array[(int)num]);
num += 1U;
}
return Encoding.ASCII.GetString(array2);
}
分析后发现,这从输入 revealedCells生成一个动态伪随机数种子,用于扰乱一个固定的字节数组 array2,最后返回扰乱后的结果字符串,想要逆向过去还是很难的。往上翻看,发现了扫雷的校验逻辑
上面红框是扫到雷后,弹出失败的弹窗并退出游戏,下面的是将所有非雷块都扫了而且没扫到雷就能通过这个游戏,会调用GetKey方法获取flag弹出胜利弹窗。
第一种方法:我们只需要把上面扫到雷的失败弹窗部分的代码(也就是红框框住的)都删掉,然后保存,就能一直扫雷不会弹出失败弹窗,也不会退出了。然后记住正确的位置,再在原来的程序上点击就能顺利通关了。
第二种方法:
TotalUnrevealedEmptySquares是代表着剩下还没被揭开的"非雷"方块数量
点进去,然后下翻找到初始化棋盘部分,如下所示
MinesPresent → 存储“是否有雷”。 MinesVisible → 存储“是否被翻开”。 MinesFlagged → 存储“是否被插旗”。
选中这部分后,右键选择编辑方法
true代表可视,false代表不可视。初始化全部都是false。这个是遍历所有方块都改成true。然后点击编译就🆗了。
最后全部保存。再点开程序就是这样的。