type
status
date
slug
summary
tags
category
icon
password
 

0x00 引言

之前一直想仔细做一做初赛的题,但是奈何当时心太浮躁,也没有什么时间。趁着放假时间耐心的重新回头看这道题目,花费了很大的精力,但是迈出了很多“第一步”,做了很多新尝试,最终完整解出这道初赛赛题。

0x01 游戏逆向

1. 反调试检测

一开始发现压根进不去游戏,首先环境是Google Pixel 6的原系统,内核为自己编译的GKI内核,KernelSU提供ROOT支持,在其他软件压根检测不到的情况下游戏检测到了调试。
通过mv /data/local/tmp /data/local/tmp1 测试发现居然通过了,原来是底下放了gdbserverlldb-server以及android_server64,给这三个放到单独的文件夹就探测不到了。实际上通过eBPF探查能够发现其在线程内一直通过faccessat系统调用循环检查android_server64android_server ,甚至连frida-server等都不做检测
notion image
 

2.IL2CPP逆向

使用 IL2CPP 开始构建时,Unity 会自动执行以下步骤:
  1. 将 Unity Scripting API 代码编译为常规 .NET DLL(托管程序集)。
  1. Applies managed bytecode stripping. This step significantly reduces the size of a built game.
  1. 将所有托管程序集转换为标准 C++ 代码。
  1. 使用本机平台编译器编译生成的 C++ 代码和 IL2CPP 的运行时部分。
  1. 将代码链接到可执行文件或 DLL,具体取决于目标平台。
notion image

1) dump

可以使用Il2CppDumper 获取dump.cs ,也可以使用Zygisk-Il2CppDumper直接静态dump但是前一种方法能得到更多的产物,方便后续的分析,缺点是在某些游戏有混淆等保护的情况下无法正常工作,而后一种因为是在运行过程中动态dump函数名和函数偏移,可绕过保护、加密以及混淆。因为绕不过对libil2cpp.so的分析,所以还是选择使用Il2CppDumper静态dump,以获取更多的产物辅助分析。
而这道题目的libil2cpp.so进行了加固,直接静态分析无法拿到真实的代码逻辑,需要在运行时进行解密。因此利用Frida dump运行过程中的so,这里把内存页面的属性修改为可读,不然就会出现无法访问内存的异常,但是这样就会导致程序crash掉,不过能够成功dump即可。
对修复之前的so进行静态dump,能够成功解析。在解析之后利用
elf-dump-fix
maiyao1988Updated Jan 4, 2025
对so进行修复,完成后通过il2cppdumper提供的IDA脚本恢复符号。
notion image
 

2) 硬币数量修和生命修改

在Dump出的内容中搜索coin,能搜到一个MouseController ,显然是游戏控制相关。其中 private TssSdtInt Coins; 就是硬币数量的变量。同时还关注到CollectCoin 这个方法,控制硬币数量增加的代码应该就在这里,通过offset定位到具体代码。
确定通过TssSdtInt__op_Increment 增加硬币数量。
notion image
而在该函数内部通过GetValue获取值并增1后SetValue 实现硬币收集时的数量增加。只需要Hook到ADD指令之后,修改x1寄存器的值即可。同理找到触碰到射线时生命减少的逻辑一样Hook即可实现硬币和生命值修改。
notion image
已经能够成功拿到flag sec2023_89e761
notion image
 

3) Mod Menu分析

notion image
在Dumper出的内容中能找到一个SmallKeyboard 类,并且其方法名和部分成员是混淆的,很容易就可以和Mod Menu的输入键盘建立起联系。
利用IDA分析时发现其中有一个方法SmallKeyboard__iI1Ii 有三个分支,结合其中的逻辑可以大致判断出三个分支分别是
  • KeyType < 2 键入数字,直接与之前的输入拼接
  • KeyType == 2 按下OK提交最终输入的数字串
  • KeyType == 3 按下Del删除前一个输入
因此需要着重分析第二个OK的分支。
notion image
主要关注后面的代码,首先将string转为uint64类型,然后进行check,最后生成下一轮的Token并存储类成员变量里,该token是随机生成的。因此主要分析SmallKeyboard__iI1Ii_4610736 中的check逻辑。
notion image
在调用这个check函数的过程中,第一个参数是SmallKeyboard_o *this 存储在x0寄存器。第二个参数是用户输入值的uint64 ,存储在X1寄存器中
notion image
 

3.libil2cpp.so 加密分析

check内部会有一个间接跳转,跳转位置是g_sec2023_p_array + 0x48 位置存储的地址。
notion image
而这个符号是从libsec2023.so导入的,直接分析能够发现0x48 偏移位置刚好是sub_31164
notion image
内部会调用sub_3B8CC ,传入的参数就是转换后的uint64类型输入
notion image
通过Frida hook能够计算出g_sec2023_o_array 指向的其实是libil2cpp.so中的0x465AB4 位置,但是由于存在检测,只能hook一瞬间就会退出。
如果IDA无法反汇编得到伪码的情况下(提示START OF FUNCTION CHUNK FOR .init_proc ,当然如果使用IDA9.0就不会遇到这个问题),需要先undefine掉影响其反汇编的.init_proc 函数(边界识别错误导致的),然后在0x465AB4 创建函数。
 
 

1) sub_465AB4 分析

在调用该函数的时候会传入两个参数,其中a2 就是经过libsec2023.so 加密完后的结果,又传回libil2cpp.so 进行后续的加密与校验,这里会将其从uint64拆成高低两个uint32值,然后传入OO0OoOOo_Oo0___ctor 赋值到,OO0OoOOo_Oo0__oOOoO0o0 进行加密,然后重新取出加密后的结果。
notion image
对加密后的结果进行魔改XTEA加密,最终高位的uint32需要是0,低位的uint32将结果与tokenuint32值进行比较。
notion image
通过frida Hook尝试直接开启作弊,能够成功实现永生和加速。但是题目要求是注册机,需要继续分析加密算法。由上面的分析可知,在libil2cpp中有两次加密,libsec2023中有其他加密,先分析libil2cpp 中的加密。
 

2) VM分析

首先需要分析OO0OoOOo_Oo0__oOOoO0o0 的加密,主要逻辑在下面的大循环中,从private Dictionary<int, Action> oOOO0O00; 这个成员变量中一直取Item并执行。
notion image
通过Frida Hook可以确定在不同输入情况下的索引是不会改变的,就是单纯的固定虚拟机。
notion image
那么可以尝试通过Frida拿到这些op对应的handler
从输出的结果中筛出用到的所有Op以及其handler ,定位到具体的函数发现其实就是在这个类中的一些函数名经过混淆的方法。
notion image
 
0x46ae50 为例,利用IDA插件简化MBA表达式后,能够看明白其主要的逻辑就是从数组中取出东西进行运算后再放到数组arr[index] = arr[index] + arr[index + 1],但是并非所有Handler都是这样的逻辑,而且暂时未知这个数组和index的具体含义,除此之外还有一些其他的数组和类成员变量,因此需要分别分析,然后使用Frida进行VM的trace。
notion image
利用Frida Trace虚拟机,在进行分析后能够大致推测出array32 应该是一个类似于栈的存储空间,array16 则是存储op的缓存区,除此之外还有一个存储输入数据的uint32类型数组。另外在结构体中对每个缓存区都有一个index 用于从数组中索引数据。
notion image
能够从中逆向出VM加密算法并据此写出解密算法。
 

3) 魔改XTEA分析

notion image
编写加解密脚本,到此为止在libil2cpp.so中的加密部分已经分析完成,就是先通过vm加密,然后进行魔改xtea加密,最后进行校验。因此下一步的目标就是逆向libsec2023.so中的加密逻辑。
 

4. libsec2023.so加密分析

 

1) CRC校验反调试

只要使用Frida对libsec2023 进行hook分析就会在一两秒后杀死App,怀疑是针对Frida InlineHook 进行的CRC校验,通过stackplz 进行分析发现每隔几秒就会读取一次libsec2023.so 文件,因此可以更加确定这一猜想。同时利用stackplz 的堆栈追踪功能定位进行文件读取的操作的位置。
大部分关键代码都被间接跳转混淆,导致很难进行分析。通过分析汇编结合Frida的Hook能够发现将动态链接库文件每次读取0x1000 ,通过间接跳转到前面的位置进行CRC校验,在这个过程中每次都会将上一次的值从w22寄存器存储到w3作为参数传入,然后将更新后的结果从w0存储到w22,直到计算完成。
notion image
在往调用栈的前面定位能够发现是0x37108位置调用函数完成上述的文件打开和读取以及CRC计算等工作,值得注意的是传入了一个参数,即x19 的值,而返回值与0比较。在后续BR X8的时候会跳转到0x37134 ,这里从x19 + 0x20 的位置取出两个值(位置1)并在位置2进行比较,因此可以推测传入了一个指针用于存储可能表征比较结果的内容,在位置2进行最终的比较,尝试使用Frida在CMP的位置修改这两个值,发现这两个值是两个地址,因此可能不是真正的CRC校验代码
notion image
再往前追发现在0x36498 位置进行了跳转,结束后通过CMP比较返回值和0,那么可以猜测只要hook这里修改w0寄存器的值即可。
notion image
结果每次会出现五次比较,其中第一次返回值为-1,只要修改为0就不会退出。
notion image
 

2) 去混淆

首先想法是采用frida-stalker去trace指令执行流,对trace结果的br跳转情况进行分析,并且由于加密过程中存在混淆操作,所以br的跳转情况一般最多只会出现两种,大部分的跳转集中在循环过程中,跳转目标是一致的,另外可能会发生在循环边界位置,因此只需要看trace结果就能直接修复间接跳转,手动修复都会很快。
但是需要注意Patch的过程中不要把正常指令给Patch掉,秉持着下面的原则
  • 尽量少patch指令,一般情况下只patch最后两条即可。
  • 只patch与间接跳转寄存器相关的指令
  • 保证修复后的跳转在其他正常指令的后面。
💡
本题目中间接跳转不是很多,手动patch就行,但是对于其他大规模使用间接跳转的情况就需要考虑自动化了。这里放两位大佬的复现文章,他们用自动化脚本实现去混淆。
  1. https://bbs.kanxue.com/thread-278648.htm
  1. https://bbs.kanxue.com/thread-276893.htm
notion image
 

3) 加密1分析

恢复完成后分析sub_3B8CC 函数,内部先调用enc_1对输入变形后的的uint64进行第一次整体加密,然后分别对高低32bit进行enc_2加密,最后拼接。
notion image
首先看enc_1
notion image
直接拷贝加密算法,然后写出解密算法,验证后确认没问题。
 

4) 加密2分析

加密2的算法主要调用了sec2023.Encryptencrypt方法
notion image
脱壳后直接使用JEB 5打开就已经是去混淆的结果,直接写加解密算法即可
notion image

0x02 注册机

直接将各个算法加解密穿起来即可分别是libsec2023中的加密1 -> 加密2,然后是libil2cpp中的vm -> 魔改xtea ,输入按顺序经过这四个加密,然后解密倒过来即可。
相关文章
基于eBPF实现一个简单的隐蔽脱壳工具-eBPFDexDumper
Lazy loaded image
Frida Interceptor Hook实现原理图
Lazy loaded image
SystemUI As EvilPiP
Lazy loaded image
Android 悬浮窗覆盖攻击
Lazy loaded image
Magisk Eop本地提权漏洞
Lazy loaded image
CVE-2024-31317 Zygote命令注入提权system分析
Lazy loaded image
MRCTF2022 Stuuuuub WP基于eBPF实现一个简单的隐蔽脱壳工具-eBPFDexDumper
Loading...
LLeaves
LLeaves
Happy Hacking
最新发布
Android grantUriPermission与StartAnyWhere
2025-2-21
腾讯2023SecAndroid初赛题目
2025-2-19
基于eBPF实现一个简单的隐蔽脱壳工具-eBPFDexDumper
2025-1-9
LakeCTF At your Service 题解
2024-12-13
PendingIntent-security
2024-12-1
eBPF实践之修改bpf_probe_write_user以对抗某加固Frida检测
2024-11-10
公告