Il2CppDumper处理最低版本iOS 15的Unity IL2CPP时崩溃的一种可能解决方案

有人拿这玩意来问我,所以就在这里留档一下,以免又遇到了。

故障表现

System.InvalidOperationException: Sequence contains no matching element
   at System.Linq.ThrowHelper.ThrowNoMatchException()
   at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
   at Il2CppDumper.Macho64.Search() in Il2CppDumper\ExecutableFormats\Macho64.cs:line 165

原因解析

我们一步一步来,首先是这个错误的直接原因。
我们看到报错点是下面这个地方:

var __mod_init_func = sections.First(x => x.sectname == "__mod_init_func");

那么有什么问题呢?
打开IDA Pro检查了一下Segments,发现这个MachO它并没有__mod_init_func,所以First就抛出了错误。
显然Il2CppDumper在设计时并没有想到MachO64居然没有这个Section

为什么会没有呢?有两种可能:

  • 开发者有意隐藏了这一Section,阻碍逆向工程
  • 自iOS 15开始启用LC_DYLD_CHAINED_FIXUPS后不再使用__mod_init_func

我在标题里说了最低版本iOS 15,所以这个地方大家一看就知道了。
但是当时我不知道,所以我们继续排查。心急的直接跳最后一段。

因为Il2CppDumper此时的问题是找不到Registration,所以我就先从Registration下手了。

找Registration

这个事情已经是老生常谈了,但是我们还是慢慢来一次。
这款应用它并没有使用字符串拼接之类的东西,字符串是原样的,所以我们可以通过global-metadata.dat直接找到初始化函数。

翻了翻就看见了一个Assert:

CMP W9, W11
B.GE ...

...
__assert_rtn(
              "Initialize",
              "MetadataCache.cpp",
              269,
              "genericMethodIndex < s_Il2CppMetadataRegistration->methodSpecsCount");

很明显这个W9即是s_Il2CppMetadataRegistration->methodSpecsCount,而这个W11就是genericMethodIndex
我们要找s_Il2CppMetadataRegistration,所以看看这个W9是怎么来的?

ADRP            X13, #qword_10CF115B8@PAGE
LDR             X8, [X13,#qword_10CF115B8@PAGEOFF]
LDR             W9, [X8,#0x20]
CMP             W9, #1

这里从qword_10CF115B8偏移0x20处读入到W9,那么这个qword_10CF115B8就是我们要找的s_Il2CppMetadataRegistration
双击过去,发现不是常量,那只能看看Xref。
Xref中这个变量只有一个地方是写入操作,那么我们就过去看看:

sub_108BC332C
...
ADRP            X8, #qword_10CF115B0@PAGE
STR             X0, [X8,#qword_10CF115B0@PAGEOFF]
STR             X1, [X8,#qword_10CF115B8@PAGEOFF]
STR             X2, [X8,#qword_10CF115C0@PAGEOFF]

我们发现传入这个函数的参数被写入了qword_10CF115B8,所以继续Xref向上追查:

sub_1089B43F8
ADRL            X0, unk_10BCAF520
ADRL            X1, unk_10BECC9C0
ADRL            X2, unk_10B09F2E8
B               sub_108BC332C

很好,传入参数都是数据段,那么我们看看这些数据段都是什么:

unk_10BCAF520
DCB  0xD
DCB 0x8B
DCB    6
DCB    0
DCB    0
DCB    0
DCB    0
DCB    0
DCQ off_10C04C720

让我们对照一下Il2CppDumperIl2CppXXXXRegistration的元数据结构,发现和这里非常符合。
那么我们要找的就是这些东西。

把导致崩溃的代码注释掉,直接让程序跑进Manual模式,输入地址,成了?
还是不行!依然是类似的崩溃。

但是这次崩溃发生在

public override ulong MapVATR(ulong addr) {
  var section = sections.First(x => addr >= x.addr && addr <= x.addr + x.size);
  ...
}

那为什么呢?

确认问题根源

在出问题的First这句上打断点,检查sections和传入的addr变量。
这一看不得了了,这传入的addr怎么是9007199456380704啊。没有一个sections与之相符,为什么呢?

通过前面的找Registration过程,我们已经知道Registration的确切位置,尝试过手动输入位置,
但最后都会在MapVATR这里传入一个超大的地址,然后爆炸。

通过将传入地址转回HEX,和IDA Pro中读到的HEX对比,发现Il2CppDumper读出的地址并不是IDA Pro中的地址。
反复检查代码,认为没有问题。使用WinHex读取MachO目标偏移量位置,发现内容确实和Il2CppDumper读出的一致。

为什么,IDA Pro的就不一样?

这时候我发现IDA Pro的HEX视图中某些列是红色标记的。
红色标记表明什么?

LC_DYLD_CHAINED_FIXUPS!

可能的处理方案

iOS 15加入了新的MachO command LC_DYLD_CHAINED_FIXUPS用于减少单独表段,加速应用启动。
但这也导致了我们面对的问题:可执行文件的二进制字节并不是其真实的字节,不做处理就读,就会出错。

我们必须要在读入地址前对内容进行修复。

  1. 使用LIEFLIEF支持LC_DYLD_CHAINED_FIXUPS,可以通过MachO.BindingInfo读取其Address。利用LIEF编写一个命令行程序,或编写一个DLL,改造Il2CppDumper读入映射。
  2. 使用macOS命令dyld_info -fixup_chain_details先导出一份映射,然后在Il2CppDumper中读入。

我暂时只有这两个设想。
当然,迁移LIEF的逻辑过来也不是不行,但是我不是很有积极性。
等遇到了再说吧。

posted @ 2026-03-17 02:07  yyfll  阅读(1)  评论(0)    收藏  举报