Unity 游戏逆向:从内存中获取未保护的 IL2Cpp 可执行文件

查看 142|回复 10
作者:mlgmxyysd   
书接上回,在逆向元气骑士的时候,遇到了 ERROR: This file may be protected. 报错(传送门)。
根据 Il2CppDumper 的文档提示,libil2cpp.so 被保护了,可以使用 GameGuardian 从游戏内存中来获取未保护的可执行文件。


image-44.png (55.17 KB, 下载次数: 0)
下载附件
2023-10-15 13:38 上传

GameGuardian 固然强大,但问题是 我 不 会 用 啊。作者还提到了他的另一个项目 Zygisk-IL2CppDumper,但这个项目局限性非常大,只能取出 dump.cs,无法从 global-metadata.dat 中生成 stringliteral.json(IL2Cpp 的字符串保存在 global-metadata.dat 中,如果不使用脚本应用字符串,IDA 是反编译不出来的),也无法生成 script.json 来辅助 IDA Pro 分析代码。
而且 Magisk 自 23.0 以后就已经实质性停更,最重要的 MagiskHide 功能也被去掉了,后面版本用 Zygisk 实现的隐藏模块都是假隐藏,所以我一直停留在 23.0 版本,自然也就无缘 Zygisk-IL2CppDumper。
既然是从内存中取出内容,那就必然少不了内存操作,让我们先来了解一下一些基础的 Linux 内核进程机制:
  • Linux 进程的信息保存在 /proc 虚拟文件系统中,/proc/ 文件夹内是进程  的信息
  • 进程的 UID、PID 等信息可以通过 ps -ef 命令获取
  • /proc//mem 是进程所占用的虚拟内存空间,不可直接读取或复制,需要使用 open、read 或 lseek 等系统调用来使用
  • /proc//maps 是一个列表,记录了进程或线程中的连续虚拟内存区域
  • 进程的内存空间在一般情况下只能由自己读写,读写其他进程的内存空间需要足够的权限(一般是 root)

    以上不是全部,但这些信息已经足够我们完成当前的工作了。
    我们还是以元气骑士为例,既然是从内存中获取,进程肯定要在后台运行。首先启动游戏,看到加载界面时,我们需要的东西就已经加载到内存了,可以退回到桌面了。(奔跑的小猫真可爱)


    image-45.png (368.46 KB, 下载次数: 1)
    下载附件
    2023-10-15 13:38 上传

    读写应用时,建议将应用挂在后台,这是因为,应用可能会对 /proc/self/mem 挂文件监测,识别到意外读写时杀死自身,这会对我们的分析造成不利影响。
    读写内存需要权限,这里我们执行了 su,后续将默认以 root 用户执行命令。先使用 ps -ef 命令获取进程 PID:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ $ su
    TB371FC:/ # ps -ef
    UID            PID  PPID C STIME TTY          TIME CMD
    root             1     0 1 16:36 ?        00:00:02 init second_stage
    root             2     0 0 16:36 ?        00:00:00 [kthreadd]
    root             3     2 0 16:36 ?        00:00:00 [rcu_gp]
    root             4     2 0 16:36 ?        00:00:00 [rcu_par_gp]
    root             5     2 0 16:36 ?        00:00:00 [kworker/0:0-events]
    root             6     2 0 16:36 ?        00:00:00 [kworker/0:0H-events_highpri]
    root             7     2 0 16:36 ?        00:00:00 [kworker/u16:0+NPU_CNTL]
    root             8     2 0 16:36 ?        00:00:00 [mm_percpu_wq]
    root             9     2 0 16:36 ?        00:00:00 [ksoftirqd/0]
    root            10     2 0 16:36 ?        00:00:00 [rcu_preempt]
    root            11     2 0 16:36 ?        00:00:00 [rcu_sched]
    root            12     2 0 16:36 ?        00:00:00 [rcu_bh]
    root            13     2 0 16:36 ?        00:00:00 [rcuop/0]
    root            14     2 0 16:36 ?        00:00:00 [rcuos/0]
    # ... 此处省略无数行 ...
    u0_a281       5957   755 9 17:12 ?        00:00:21 com.ChillyRoom.DungeonShooter
    # ... 此处省略无数行 ...
    root          9079  9052 8 21:06 /debug_+ 00:00:00 ps -ef
    root          9090   755 3 21:06 ?        00:00:00 zygote64
    不出意外的,后台运行着很多进程,想要找到我们需要的进程并不容易,我们知道应用的包名是 com.ChillyRoom.DungeonShooter,可以使用 grep 精准找到该进程:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # ps -ef | grep com.ChillyRoom.DungeonShooter
    u0_a281       5957   755 4 08:17:11 ?     00:00:22 com.ChillyRoom.DungeonShooter
    root         10483  9052 10 08:26:09 /debug_ramdisk/.magisk/pts/0 00:00:00 grep com.ChillyRoom.DungeonShooter
    Android 有多用户机制,如果有其他用户也在运行该进程(如应用多开等),输出结果会对我们造成干扰。
    输出的第一列代表进程用户,Android 的 Linux 用户是有规律的,u0 表示 user 0,a281 表示 app 281,我们可以使用 am get-current-user 命令获取前台用户:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # am get-current-user
    0
    使用 grep 命令套一层:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # ps -ef | grep com.ChillyRoom.DungeonShooter | grep u$(am get-current-user)
    u0_a281       5957   755 3 08:17:12 ?     00:00:24 com.ChillyRoom.DungeonShooter
    输出的第二列就是我们需要的 PID,也可以再套一层 awk 命令实现自动化获取:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # ps -ef | grep com.ChillyRoom.DungeonShooter | grep u$(am get-current-user) | awk '{print $2}'
    5957
    [color=]猫
    cat 一下 maps 看看:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # cat /proc/5957/maps
    12c00000-32c00000 rw-p 00000000 00:00 0                                  [anon:dalvik-main space (region space)]
    32c00000-32c01000 rw-p 00000000 00:00 0                                  [anon:libc_malloc]
    6ffb0000-70240000 rw-p 00000000 00:00 0                                  [anon:dalvik-/system/framework/boot.art]
    70240000-70282000 rw-p 00000000 00:00 0                                  [anon:dalvik-/system/framework/boot-core-libart.art]
    70282000-702ab000 rw-p 00000000 00:00 0                                  [anon:dalvik-/system/framework/boot-okhttp.art]
    702ab000-702ed000 rw-p 00000000 00:00 0                                  [anon:dalvik-/system/framework/boot-bouncycastle.art]
    702ed000-702ee000 rw-p 00000000 00:00 0                                  [anon:dalvik-/system/framework/boot-apache-xml.art]
    702ee000-70381000 r--p 00000000 fc:05 2867                               /system/framework/arm64/boot.oat
    70381000-70665000 r-xp 00093000 fc:05 2867                               /system/framework/arm64/boot.oat
    70665000-70666000 rw-p 00000000 00:00 0
    # ... 此处省略无数行 ...
    74113e6000-7411c78000 r--s 00000000 fc:05 2741                           /system/fonts/ZUKChinese.ttf
    7411c78000-7412940000 ---p 00000000 00:00 0
    7412940000-7412942000 rw-p 00000000 00:00 0
    7412942000-7413a8a000 ---p 00000000 00:00 0
    7413a8a000-7413a8c000 rw-p 00000000 00:00 0
    7413a8c000-7413ce6000 ---p 00000000 00:00 0
    7413ce6000-7413ce8000 rw-p 00000000 00:00 0
    7413ce8000-7414c8c000 ---p 00000000 00:00 0
    7414c8c000-7414c8e000 rw-p 00000000 00:00 0
    7414c8e000-7415c78000 ---p 00000000 00:00 0
    7415cc7000-7415cdd000 r--p 00000000 fc:0a 58226                          /data/user_de/0/com.google.android.gms/app_chimera/m/00000023/oat/arm64/DynamiteLoader.odex
    # ... 此处省略无数行 ...
    77d9aab000-77d9ab3000 rw-p 00000000 00:00 0
    77d9ab3000-77d9ab4000 ---p 00000000 00:00 0
    77d9ab4000-77d9ad4000 r--s 00000000 00:11 13673                          /dev/__properties__/properties_serial
    77d9ad4000-77d9ad8000 rw-p 00000000 00:00 0                              [anon:System property context nodes]
    77d9ad8000-77d9af0000 r--s 00000000 00:11 11302                          /dev/__properties__/property_info
    77d9af0000-77d9b54000 r--p 00000000 00:00 0                              [anon:linker_alloc]
    77d9b54000-77d9b56000 rw-p 00000000 00:00 0                              [anon:bionic_alloc_small_objects]
    77d9b56000-77d9b57000 r--p 00000000 00:00 0                              [anon:atexit handlers]
    77d9b57000-77d9e9e000 ---p 00000000 00:00 0
    # ... 此处省略无数行 ...
    接下来是找规律时间。不难理解,第一列是当前连续内存区域的起止位置(起-止);第二列是内存权限;第三列是类似偏移之类的地址;第四列是文件所在设备前四位的十六进制;第五列是文件的 Inode 值;最后一列则是内存区域名称或对应的文件名。
    其中第四、第五列的含义是猜的,和对应文件 stat 的结果可以对应上,如果不对烦请赐教。
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # stat /system/framework/arm64/boot.oat
      File: /system/framework/arm64/boot.oat
      Size: 3777592  Blocks: 7384    IO Blocks: 512  regular file
    Device: fc05h/64517d     Inode: 2867     Links: 1        Device type: 0,0 #
    既然有文件名的信息,我们找一下 il2cpp 的内存:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # cat /proc/5957/maps | grep il2cpp
    73c71c2000-73c8a39000 r--s 00000000 fc:0a 57632                          /data/media/0/Android/data/com.ChillyRoom.DungeonShooter/files/il2cpp/Metadata/global-metadata.dat
    7417c25000-7417c78000 r--s 00000000 fc:0a 51745                          /data/media/0/Android/data/com.ChillyRoom.DungeonShooter/files/il2cpp/Resources/mscorlib.dll-resources.dat
    奇怪,居然没有 libil2cpp.so,难道没加载进内存?但这很明显是不可能的,我们看一下应用安装时解压出来的 libraries:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # pm path com.ChillyRoom.DungeonShooter
    package:/data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/base.apk
    package:/data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_base_assets.apk
    package:/data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    TB371FC:/ # cd /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/
    TB371FC:/data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg== # ls lib/arm64/
    惊不惊喜,意不意外,空的~ 运行库压根就没解压~
    看到 split_xxx.apk 就知道,这是一个以 App Bundle 打包方式分发的应用,到这里就没头猪了,怎么办,查!
    在 Stack Overflow 上找到了一个高赞回答(传送门),大概意思就是 App Bundle 打包的应用如果不设置 android.bundle.enableUncompressedNativeLibs=false,在安装时是不会解压的,应用直接从 APK 中读取运行库,这样做的好处是减少安装后的存储占用,也优化了 I/O 性能。


    image-46.png (205.16 KB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    扯远了,既然应用直接从 APK 读取运行库,那么在内存中的文件名也应该是存储运行库的那个 APK,也就是 split_config.arm64_v8a.apk,找一下看看:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # cat /proc/5957/maps | grep split_config.arm64_v8a.apk
    729caba000-729cd62000 r-xp 00142000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    729cd62000-729cd6a000 r--p 003e9000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    729cd6a000-729cdb2000 rw-p 003f1000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    72c0c80000-72c9a18000 r--p 00a00000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    72c9a18000-72caf02000 r--p 09fe7000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    72e9d18000-72eaa62000 r-xp 0b4d1000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    72eaa71000-72eaac9000 rw-p 0c21a000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73e3a0c000-73ebd62000 r-xp 00a00000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ebd63000-73ec790000 rw-p 08d56000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec9d0000-73ec9d3000 r-xp 09783000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec9d4000-73ec9d6000 rw-p 09786000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec9d8000-73ec9ec000 rw-p 09784000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7404a1b000-7405e9b000 r-xp 09fe7000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7405e9c000-7405eea000 r--p 0b467000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7405eea000-7405f05000 rw-p 0b4b5000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7417bd6000-7417bd7000 r-xp 09fb3000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7417bd8000-7417bd9000 r--p 09fb4000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7417bd9000-7417bda000 rw-p 09fb5000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7419f06000-7419f33000 r-xp 09fb6000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7419f34000-7419f37000 r--p 09fe3000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    7419f37000-7419f38000 rw-p 09fe6000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    741f4c8000-741f69f000 r-xp 00439000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    741f69f000-741f6a2000 r--p 0060f000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    741f6a2000-741f6ce000 rw-p 00612000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    77cdcfc000-77cdcfd000 r--s 0f6ab000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    77d4c08000-77d4c09000 r--s 0f6ab000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    有了,还挺多,只看 maps 也看不出来哪个是 libil2cpp.so,怎么办?愣着啊,全 dump 出来干嘛?还准备慢慢看嘛?
    说干就干,和已知一样,mem 不能被 cat,会报 I/O error 错误:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # cat /proc/5957/mem
    cat: /proc/5957/mem: I/O error
    不过我们可以用 dd 命令,来读写指定范围的内存,命令格式如下:
    [Bash shell] 纯文本查看 复制代码dd if="输入文件" of="输出文件" bs=块大小 skip=起始偏移块 count=块数量
    # 从 if 中的第 skip 块后开始读取 count 个块,保存到 of 中,每个块大小为 bs 字节
    (我已经不认识“块”这个字了)
    取出速度取决于块大小,过小会非常慢,过大会丢失精度,同时也会降低速度。你想一下,dd 就像是在搬砖的你(不是我!是你!正在读这篇文章的你!),count 就像是每次搬的砖数量,一次搬一个,那得要搬多少次啊,一次搬太多反而会把自己累的搬不动,一不小心还超出需求,白搬了,所以选一个合适的快大小是很有必要的。
    在读取内存时一般与系统的内存页大小(Pagesize)相同,可以用系统命令读取:
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # getconf PAGESIZE
    4096
    起始地址除以块大小做 skip;用结束地址减去起始地址,再除以块大小,得到 count。(以第一行的为例)
    [Bash shell] 纯文本查看 复制代码TB371FC:/ # dd if="/proc/5957/mem" of="/sdcard/729caba000.bin" bs=$(getconf PAGESIZE) skip=120179386 count=680
    680+0 records in
    680+0 records out
    2785280 bytes (2.6 M) copied, 0.135125 s, 20 M/s


    image-47.png (1.72 MB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    不错,是 ELF 文件头,之前的思路是对的。但大小一看就不是 libil2cpp.so,这样一个一个提取太麻烦了,集合一下上面的思路,写成脚本自动化。
    [Bash shell] 纯文本查看 复制代码#!/system/bin/sh
    package=com.ChillyRoom.DungeonShooter
    pid=$(ps -ef | grep $package | grep u$(am get-current-user) | awk '{print $2}')
    mem_list=$(grep "split_config.arm64_v8a.apk" /proc/$pid/maps | awk -v OFS='|' '{for (i = 1; i
    值得注意的是,由于 shell 的数字范围是 Int 32,最大只有 2147483647,直接计算会数值溢出,所以要使用 bc 命令计算表达式。bc 可以接受十六进制,但是表达式的值必须是大写。


    Screenshot_20231015-103313.png (288.13 KB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    执行脚本后,出现了一堆文件,找出来两个最大的(APK 里的 libil2cpp.so 有 140MB),尝试用 IL2CppDumper 解析。


    image-48-1536x1259.png (642.56 KB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    两个都解析失败,看来还需要在其他文件上面下功夫。
    so 文件有固定的文件头 7F 45 4C 46,猜测需要将其他内容加到 so 后面,补一下脚本。
    [Bash shell] 纯文本查看 复制代码#!/system/bin/sh
    package=com.ChillyRoom.DungeonShooter
    pid=$(ps -ef | grep $package | grep u$(am get-current-user) | awk '{print $2}')
    mem_list=$(grep "split_config.arm64_v8a.apk" /proc/$pid/maps | awk -v OFS='|' '{for (i = 1; i >"$lastFile" # 将文件合并到上一个 ELF 中
                            rm -f "$fileOut"
                    else
                            lastFile=$fileOut
                    fi
            fi
            
            rm -f "/data/local/tmp/mem_temp"
    done
    老规矩,拉出来大小合适的出来跑 IL2CppDumper,发现还是报错,但之前识别不出来信息的那个 ELF 文件大小变大了不少,也可以识别了,算是有进步。
    接下来就从这个文件下手,起始地址为 73E3A0C000,再看一眼 maps。
    [Bash shell] 纯文本查看 复制代码73e3a0c000-73ebd62000 r-xp 00a00000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ebd62000-73ebd63000 ---p 00000000 00:00 0
    73ebd63000-73ec790000 rw-p 08d56000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec790000-73ec9cf000 rw-p 00000000 00:00 0                              [anon:.bss]
    73ec9cf000-73ec9d0000 ---p 00000000 00:00 0
    73ec9d0000-73ec9d3000 r-xp 09783000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec9d3000-73ec9d4000 ---p 00000000 00:00 0
    73ec9d4000-73ec9d6000 rw-p 09786000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec9d6000-73ec9d8000 ---p 00000000 00:00 0
    73ec9d8000-73ec9ec000 rw-p 09784000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    原来在两个 APK 之间,还有一到两个非文件内存,我们在这里将其称为间隙块(Gap blocks),补全脚本把这段也加上。
    [Bash shell] 纯文本查看 复制代码#!/system/bin/sh
    package=com.ChillyRoom.DungeonShooter
    pid=$(ps -ef | grep $package | grep u$(am get-current-user) | awk '{print $2}')
    mem_list=$(grep "split_config.arm64_v8a.apk" /proc/$pid/maps | awk -v OFS='|' '{for (i = 1; i >"$lastFile"
                            fi
                      cat "$fileOut">>"$lastFile"
                            rm -f "$fileOut"
                    else
                            lastFile=$fileOut
                    fi
            fi
            
            lastEnd=$end
            
            rm -f "/data/local/tmp/mem_temp"
    done
    正当我准备运行的时候,我发现了一个问题,在偏移稍微靠后的位置,有一些间隙块非常的大,可以达到几万甚至几百万块,这很明显不是同一个连续的内存区域,我们不能把他们强行合并到一起。


    image-49.png (60.09 KB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    [Bash shell] 纯文本查看 复制代码#!/system/bin/sh
    package=com.ChillyRoom.DungeonShooter
    pid=$(ps -ef | grep $package | grep u$(am get-current-user) | awk '{print $2}')
    mem_list=$(grep "split_config.arm64_v8a.apk" /proc/$pid/maps | awk -v OFS='|' '{for (i = 1; i >"$lastFile"
                                    fi
                            fi
                            if [[ $skipMerge == "false" ]]; then
                        cat "$fileOut">>"$lastFile"
                              rm -f "$fileOut"
                            fi
                    else
                            lastFile=$fileOut
                    fi
            fi
            
            lastEnd=$end
            
            rm -f "/data/local/tmp/mem_temp"
    done
    运行脚本,取出文件大小与原 libil2cpp.so 相似的文件,扔给 IL2CppDumper 解析,这次有一个可以正常解析了。


    image-50.png (24.3 KB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    换一款游戏再来测试一下,想起了之前逆向过的开罗游戏创意糕点部(传送门),就拿它来开这第二刀了。


    image-51.png (133.45 KB, 下载次数: 1)
    下载附件
    2023-10-15 13:38 上传

    这款游戏最新版也用了 IL2Cpp 打包,并且加了 so 保护,但没有使用 App Bundle 发布。这意味着我们的脚本需要做一些改动,是时候直接在内存中搜索 libil2cpp.so 了,顺手再一起搜索一下设备支持的其他架构。
    Android 设备支持的架构可以通过命令 getprop ro.product.cpu.abilist 获取,以半角逗号 , 分割。
    [Bash shell] 纯文本查看 复制代码#!/system/bin/sh
    package=$1 # 通过命令行传入目标包名
    pid=$(ps -ef | grep $package | grep u$(am get-current-user) | awk '{print $2}')
    targets="libil2cpp.so "$(getprop ro.product.cpu.abilist | awk -F',' '{for (i = 1; i >"$lastFile"
                                    fi
                            fi
                            if [[ $skipMerge == "false" ]]; then
                        cat "$fileOut">>"$lastFile"
                              rm -f "$fileOut"
                            fi
                    else
                            lastFile=$fileOut
                    fi
            fi
            
            lastEnd=$end
            
            rm -f "/data/local/tmp/mem_temp"
    done
    老规矩,运行代码,找到合适大小的文件,拉出来喂工具解析,成功解析。


    image-52.png (37.77 KB, 下载次数: 1)
    下载附件
    2023-10-15 13:38 上传

    扔进 IDA Pro 反编译(注:不能使用直接从 APK 提取出的so,因为解析出的偏移是根据 dump 出来的 so 计算的),弹了两个报错,可以忽略掉。跑脚本报了一堆错,凑合用。
    可能 dump 出来的 so 需要修复,不过超出这篇文章的范畴了。(其实主要问题是我也不会 x)


    image-53.png (331.86 KB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    脚本是没什么问题了(大概),但我们总不能每次都靠猜测来判断哪个是正确的 libil2cpp.so。那么能不能用脚本来代替我们来“猜测”呢,答案是肯定的。当然,不是根据大小来判断。
    通过把 App Bundle 合并安装测试、将几个 maps 进行比对,我们发现内存中有一个 global-metadata.dat 文件,而在这个文件之后的一个 ELF 文件,通常就是我们要找的 libil2cpp.so。
    [Bash shell] 纯文本查看 复制代码# 合并 App Bundle 后的元气骑士
    7977268000-7980000000 r--p 00000000 fc:0a 49763                          /data/app/~~6kC6XFUULhHulM01DwI6fA==/com.ChillyRoom.DungeonShooter-_E4Bf_EJNotbVBZxg5eWXQ==/lib/arm64/libil2cpp.so
    # ... 此处省略无数行 ...
    79cd789000-79cf000000 r--s 00000000 fc:0a 50772                          /data/media/0/Android/data/com.ChillyRoom.DungeonShooter/files/il2cpp/Metadata/global-metadata.dat
    # ... 此处省略无数行 ... 下面就是我们要找的
    7aa5415000-7aad76b000 r-xp 00000000 fc:0a 49763                          /data/app/~~6kC6XFUULhHulM01DwI6fA==/com.ChillyRoom.DungeonShooter-_E4Bf_EJNotbVBZxg5eWXQ==/lib/arm64/libil2cpp.so
    7aad76b000-7aad76c000 ---p 00000000 00:00 0
    7aad76c000-7aae199000 rw-p 08356000 fc:0a 49763                          /data/app/~~6kC6XFUULhHulM01DwI6fA==/com.ChillyRoom.DungeonShooter-_E4Bf_EJNotbVBZxg5eWXQ==/lib/arm64/libil2cpp.so
    7aae199000-7aae3d8000 rw-p 00000000 00:00 0                              [anon:.bss]
    7aae3d8000-7aae3d9000 ---p 00000000 00:00 0
    7aae3d9000-7aae3dc000 r-xp 08d83000 fc:0a 49763                          /data/app/~~6kC6XFUULhHulM01DwI6fA==/com.ChillyRoom.DungeonShooter-_E4Bf_EJNotbVBZxg5eWXQ==/lib/arm64/libil2cpp.so
    7aae3dc000-7aae3dd000 ---p 00000000 00:00 0
    7aae3dd000-7aae3df000 rw-p 08d86000 fc:0a 49763                          /data/app/~~6kC6XFUULhHulM01DwI6fA==/com.ChillyRoom.DungeonShooter-_E4Bf_EJNotbVBZxg5eWXQ==/lib/arm64/libil2cpp.so
    7aae3df000-7aae3e1000 ---p 00000000 00:00 0
    7aae3e1000-7aae3f5000 rw-p 08d84000 fc:0a 49763                          /data/app/~~6kC6XFUULhHulM01DwI6fA==/com.ChillyRoom.DungeonShooter-_E4Bf_EJNotbVBZxg5eWXQ==/lib/arm64/libil2cpp.so
    # ... 此处省略无数行 ...
    # 原版元气骑士
    # ... 此处省略无数行 ...
    729caba000-729cd62000 r-xp 00142000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    729cd62000-729cd6a000 r--p 003e9000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    729cd6a000-729cdb2000 rw-p 003f1000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    # ... 此处省略无数行 ...
    73c71c2000-73c8a39000 r--s 00000000 fc:0a 57632                          /data/media/0/Android/data/com.ChillyRoom.DungeonShooter/files/il2cpp/Metadata/global-metadata.dat
    # ... 此处省略无数行 ... 下面就是我们要找的
    73e3a0c000-73ebd62000 r-xp 00a00000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ebd62000-73ebd63000 ---p 00000000 00:00 0
    73ebd63000-73ec790000 rw-p 08d56000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec790000-73ec9cf000 rw-p 00000000 00:00 0                              [anon:.bss]
    73ec9cf000-73ec9d0000 ---p 00000000 00:00 0
    73ec9d0000-73ec9d3000 r-xp 09783000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec9d3000-73ec9d4000 ---p 00000000 00:00 0
    73ec9d4000-73ec9d6000 rw-p 09786000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    73ec9d6000-73ec9d8000 ---p 00000000 00:00 0
    73ec9d8000-73ec9ec000 rw-p 09784000 fc:0a 73347                          /data/app/~~wwI8fTI34usd9DBbBqpP4A==/com.ChillyRoom.DungeonShooter-olvDyD0tf9exDoyTM7Hwgg==/split_config.arm64_v8a.apk
    # ... 此处省略无数行 ...
    # 创意糕点部
    # ... 此处省略无数行 ...
    731780a000-7319fb3000 r--p 00000000 fc:0a 68394                          /data/app/~~EeYFpmSrnO1pEm-dVmp9rQ==/net.kairosoft.android.okashi_en-AWdoF0iBkO1WbE3SF5a1qA==/lib/arm64/libil2cpp.so
    # ... 此处省略无数行 ...
    734a86e000-734b000000 r--s 00000000 fc:0a 69978                          /data/media/0/Android/data/net.kairosoft.android.okashi_en/files/il2cpp/Metadata/global-metadata.dat
    # ... 此处省略无数行 ... 下面就是我们要找的
    7410624000-7412b11000 r-xp 00000000 fc:0a 68394                          /data/app/~~EeYFpmSrnO1pEm-dVmp9rQ==/net.kairosoft.android.okashi_en-AWdoF0iBkO1WbE3SF5a1qA==/lib/arm64/libil2cpp.so
    7412b11000-7412c5b000 r--p 024ec000 fc:0a 68394                          /data/app/~~EeYFpmSrnO1pEm-dVmp9rQ==/net.kairosoft.android.okashi_en-AWdoF0iBkO1WbE3SF5a1qA==/lib/arm64/libil2cpp.so
    7412c5b000-7412dce000 rw-p 02636000 fc:0a 68394                          /data/app/~~EeYFpmSrnO1pEm-dVmp9rQ==/net.kairosoft.android.okashi_en-AWdoF0iBkO1WbE3SF5a1qA==/lib/arm64/libil2cpp.so
    # ... 此处省略无数行 ...
    找到了这个规律,我们就可以将其写入脚本中了。顺便呢,再完善一下输入输出,方便调试。
    [Bash shell] 纯文本查看 复制代码#!/system/bin/sh
    echo "########################"
    echo "# IL2Cpp Memory Dumper #"
    echo "# by NekoYuzu neko.ink #"
    echo "########################"
    if [[ $1 == "" ]]; then
            echo "* Usage: $0  [out]"
            exit
    fi
    package=$1
    if [[ $2 == "" ]]; then
            out=/sdcard/dump
    else
            out=$2
    fi
    echo "- Target package: $package"
    echo "- Output directory: $out"
    mkdir -p "$out"
    user=$(am get-current-user)
    pid=$(ps -ef | grep $package | grep u$user | awk '{print $2}')
    if [[ $pid == "" ]]; then
            echo "! Target package of current user ($user) not found, is process running?"
            exit
    fi
    echo "- Found target process: $pid"
    targets="global-metadata.dat libil2cpp.so "$(getprop ro.product.cpu.abilist | awk -F',' '{for (i = 1; i /dev/null
            
            if [[ $(cat "${out}/tmp") == $(echo -ne "\x7F\x45\x4C\x46") ]]; then
                    fileExt="so"
            else
                    fileExt="dump"
            fi
            
            local fileOut="${out}/${offset}_${package}_${memName}.${fileExt}"
            
            if [[ $metadataOffset != "" ]] && [[ $(echo "ibase=16;(${metadataOffset}-${offset})/dev/null
            
            if [[ $fileExt == "so" ]]; then
                    lastFile=$fileOut
            else
                    if [[ $lastFile != "" ]]; then
                            echo "- Merging memory..."
                            skipMerge=false
                            if [[ $lastEnd != $offset ]]; then
                                    gap_block=$(echo "ibase=16;(${offset}-${lastEnd})/${HEX_PAGESIZE}" | bc)
                                    if [[ $gap_block -gt 32 ]]; then
                                            echo "- Gap block(s) $gap_block is too large, skipping merge..."
                                            skipMerge=true
                                            lastFile=$fileOut
                                    else
                                            echo "- Adding $gap_block gap block(s)..."
                                            dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${lastEnd}/$HEX_PAGESIZE" | bc) count=$gap_block of="$out/tmp" 2>/dev/null
                                            cat "$out/tmp">>"$lastFile"
                                    fi
                            fi
                            if [[ $skipMerge == "false" ]]; then
                                    cat "$fileOut">>"$lastFile"
                                    rm -f "$fileOut"
                            fi
                    else
                            echo "- No ELF header found, but nothing to merge. Treating as a raw dump..."
                            lastFile=$fileOut
                    fi
            fi
            
            lastEnd=$end
            rm -f "${out}/tmp"
    done
    echo "- Done!"
    再进行最后一次测试,no problem~


    image-54.png (859.31 KB, 下载次数: 0)
    下载附件
    2023-10-15 13:38 上传

    后话:
    脚本只能辅助,却不能代替我们去分析代码。
    此外,脚本还有些不足之处,例如:so 文件可能需要修复;内存结束偏移后面还可能有一些有用的信息未 dump;等等。
    希望有大佬可以帮着完善一下,有哪些说的不对的地方也欢迎指出~
    脚本已开源在 GitHub,后续完善将在 GitHub 更新:https://github.com/MlgmXyysd/IL2CppMemoryDumper

    代码, 文本

  • mlgmxyysd
    OP
      

    脚本已开源在 GitHub,后续完善将在 GitHub 更新:https://github.com/MlgmXyysd/IL2CppMemoryDumper
    修复 ELF 可以尝试使用:https://github.com/F8LEFT/SoFixer
    XhyEax   

    楼主可以试试https://github.com/BryanGIG/PADumper
    lyl610abc   

    意想不到的思路
    我通常都是使用 frida 执行脚本直接 dump 出 so 文件:https://github.com/lasting-yang/frida_dump (PS:会自动进行 so-fix 修正,超好用)
    然后对于 global-meta.dat 有 2 种办法:
    1.直接搜索特征码(针对在内存中解密后 global-meta.dat 头部特征不变的):https://github.com/350030173/global-metadata_dump/tree/master
    2.分析 MetadataLoader::LoadMetadataFile , hook 后获取加载完 global-meta.dat 的内存返回值,然后再 dump 出来 :https://raw.githubusercontent.co ... -metadata-finder.js
    参考链接:
    1.https://github.com/IroniaTheMast ... tadata-First-Method
    2.https://github.com/IroniaTheMast ... adata-Second-Method
    可以直接使用 frida dump 出 il2cpp.so 和 global-meta.dat
    PS: Zygisk-il2cppDumper 走的是另一个路子,通过 il2cpp 导出函数以及反射解析出 dump.cs
    BonnieRan   

    看完了 学到了新的dump姿势,感谢楼主这么详细的贴
    罗萨   

    牛,不过我选择使用更简单的frida
    rain1986   

    好帖子,学习啦
    mlgmxyysd
    OP
      


    XhyEax 发表于 2023-10-15 16:31
    楼主可以试试https://github.com/BryanGIG/PADumper

    收藏了,之前没搜到相关软件,就自己写了🤣
    xyz989   

    大佬 有没有提取素材(PNG)后转动态化(GIF)的教程
    是网页游戏(农场3.0采用Unity重制游戏)
    [i]
    人物素材链接(原文件)
    https://xyz989.lanzouo.com/ir00j0n5vmah
    提取工具AssetStudioGUI
    [i]
    导出的资源目录如下
    [i]
    mlgmxyysd
    OP
      


    xyz989 发表于 2023-10-15 23:03
    大佬 有没有提取素材(PNG)后转动态化(GIF)的教程
    是网页游戏(农场3.0采用Unity重制游戏)

    我记得之前有个东西可以支持导入Unity的导出模型来着,让我找找看
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部