吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 12557|回复: 94
收起左侧

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

  [复制链接]
mlgmxyysd 发表于 2023-10-15 13:46
本帖最后由 mlgmxyysd 于 2023-10-15 23:15 编辑

书接上回,在逆向元气骑士的时候,遇到了 ERROR: This file may be protected. 报错(传送门)。

根据 Il2CppDumper 的文档提示,libil2cpp.so 被保护了,可以使用 GameGuardian 从游戏内存中来获取未保护的可执行文件。

image-44.png

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/<pid> 文件夹内是进程 <pid> 的信息
  • 进程的 UID、PID 等信息可以通过 ps -ef 命令获取
  • /proc/<pid>/mem 是进程所占用的虚拟内存空间,不可直接读取或复制,需要使用 open、read 或 lseek 等系统调用来使用
  • /proc/<pid>/maps 是一个列表,记录了进程或线程中的连续虚拟内存区域
  • 进程的内存空间在一般情况下只能由自己读写,读写其他进程的内存空间需要足够的权限(一般是 root)

以上不是全部,但这些信息已经足够我们完成当前的工作了。

我们还是以元气骑士为例,既然是从内存中获取,进程肯定要在后台运行。首先启动游戏,看到加载界面时,我们需要的东西就已经加载到内存了,可以退回到桌面了。(奔跑的小猫真可爱)

image-45.png

读写应用时,建议将应用挂在后台,这是因为,应用可能会对 /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

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 # <- 702ee000-70381000 r--p 00000000 fc:05 2867
Access: (0644/-rw-r--r--)       Uid: (    0/    root)   Gid: (    0/    root)
Access: 2009-01-01 08:00:00.000000000 +0800
Modify: 2009-01-01 08:00:00.000000000 +0800
Change: 2009-01-01 08:00:00.000000000 +0800
TB371FC:/ # stat /data/user_de/0/com.google.android.gms/app_chimera/m/00000023/oat/arm64/DynamiteLoader.odex
  File: /data/user_de/0/com.google.android.gms/app_chimera/m/00000023/oat/arm64/DynamiteLoader.odex
  Size: 480880   Blocks: 944     IO Blocks: 512  regular file
Device: fc0ah/64522d     Inode: 58226    Links: 1        Device type: 0,0 # <- 7415cc7000-7415cdd000 r--p 00000000 fc:0a 58226
Access: (0444/-r--r--r--)       Uid: (10149/ u0_a149)   Gid: (10149/ u0_a149)
Access: 2023-10-09 19:29:42.674133462 +0800
Modify: 2023-10-09 19:29:42.878133462 +0800
Change: 2023-10-12 02:27:00.179999995 +0800
TB371FC:/ # stat /dev/__properties__/properties_serial
  File: /dev/__properties__/properties_serial
  Size: 131072   Blocks: 8       IO Blocks: 512  regular file
Device: 11h/17d  Inode: 13673    Links: 1        Device type: 0,0 # <- 77d9ab4000-77d9ad4000 r--s 00000000 00:11 13673
Access: (0444/-r--r--r--)       Uid: (    0/    root)   Gid: (    0/    root)
Access: 2023-10-15 08:16:40.956000000 +0800
Modify: 1970-02-01 23:04:31.907999999 +0800
Change: 1970-02-01 23:04:31.907999999 +0800

既然有文件名的信息,我们找一下 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

扯远了,既然应用直接从 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

不错,是 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 <= NF; i+=6) {print $i,$(i+1),$(i+2),$(i+3),$(i+4),$(i+5)}}') # 把分隔符替换成"|",方便循环提取
SYS_PAGESIZE=$(getconf PAGESIZE)
HEX_PAGESIZE=$(printf "%x" $SYS_PAGESIZE) # 取一下十六进制,计算用
for memory in $mem_list; do
        local range=$(echo $memory | awk -F'|' '{print $1}')
        local offset=$(echo $range | awk -F'-' '{print toupper($1)}') # 转换成大写
        
        dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${offset}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;($(echo $range | awk -F'-' '{print toupper($2)}')-${offset})/$HEX_PAGESIZE" | bc) of="/sdcard/${offset}.bin" # 使用 bc 命令计算表达式
done

值得注意的是,由于 shell 的数字范围是 Int 32,最大只有 2147483647,直接计算会数值溢出,所以要使用 bc 命令计算表达式。bc 可以接受十六进制,但是表达式的值必须是大写。

Screenshot_20231015-103313.png

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

image-48-1536x1259.png

两个都解析失败,看来还需要在其他文件上面下功夫。



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 <= NF; i+=6) {print $i,$(i+1),$(i+2),$(i+3),$(i+4),$(i+5)}}')

SYS_PAGESIZE=$(getconf PAGESIZE)
HEX_PAGESIZE=$(printf "%x" $SYS_PAGESIZE)

lastFile= # 保存一下最后的 ELF 文件名

for memory in $mem_list; do
        local range=$(echo $memory | awk -F'|' '{print $1}')
        local offset=$(echo $range | awk -F'-' '{print toupper($1)}')
        
        dd if="/proc/$pid/mem" bs=1 skip=$(echo "ibase=16;$offset" | bc) count=4 of="/data/local/tmp/mem_temp" # 取前四位
        
        if [[ $(cat "/data/local/tmp/mem_temp") == $(echo -ne "\x7F\x45\x4C\x46") ]]; then
                fileExt="so" # 检测到 ELF 文件头
        else
                fileExt="bin"
        fi
        
        local fileOut="/sdcard/${offset}.${fileExt}"
        
        dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${offset}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;($(echo $range | awk -F'-' '{print toupper($2)}')-${offset})/$HEX_PAGESIZE" | bc) of="$fileOut"
        
        if [[ $fileExt == "so" ]]; then
                lastFile=$fileOut
        else
                if [[ $lastFile != "" ]]; then
                  cat "$fileOut">>"$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 <= NF; i+=6) {print $i,$(i+1),$(i+2),$(i+3),$(i+4),$(i+5)}}')

SYS_PAGESIZE=$(getconf PAGESIZE)
HEX_PAGESIZE=$(printf "%x" $SYS_PAGESIZE)

lastFile=
lastEnd= # 保存上个内存区域的结束偏移

for memory in $mem_list; do
        local range=$(echo $memory | awk -F'|' '{print $1}')
        local offset=$(echo $range | awk -F'-' '{print toupper($1)}')
        local end=$(echo $range | awk -F'-' '{print toupper($2)}')
        
        dd if="/proc/$pid/mem" bs=1 skip=$(echo "ibase=16;$offset" | bc) count=4 of="/data/local/tmp/mem_temp"
        
        if [[ $(cat "/data/local/tmp/mem_temp") == $(echo -ne "\x7F\x45\x4C\x46") ]]; then
                fileExt="so"
        else
                fileExt="dump" # 我改个后缀不过分吧(
        fi
        
        local fileOut="/sdcard/${offset}.${fileExt}"
        
        dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${offset}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;(${end}-${offset})/$HEX_PAGESIZE" | bc) of="$fileOut"
        
        if [[ $fileExt == "so" ]]; then
                lastFile=$fileOut
        else
                if [[ $lastFile != "" ]]; then
                        if [[ $lastEnd != $offset ]]; then
                          dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${lastEnd}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;(${offset}-${lastEnd})/${HEX_PAGESIZE}" | bc) of="/data/local/tmp/mem_temp" # 发现不一样啦,来把间隙块补全吧
                                cat "/data/local/tmp/mem_temp">>"$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

[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 <= NF; i+=6) {print $i,$(i+1),$(i+2),$(i+3),$(i+4),$(i+5)}}')

SYS_PAGESIZE=$(getconf PAGESIZE)
HEX_PAGESIZE=$(printf "%x" $SYS_PAGESIZE)

lastFile=
lastEnd=

for memory in $mem_list; do
        local range=$(echo $memory | awk -F'|' '{print $1}')
        local offset=$(echo $range | awk -F'-' '{print toupper($1)}')
        local end=$(echo $range | awk -F'-' '{print toupper($2)}')
        
        dd if="/proc/$pid/mem" bs=1 skip=$(echo "ibase=16;$offset" | bc) count=4 of="/data/local/tmp/mem_temp"
        
        if [[ $(cat "/data/local/tmp/mem_temp") == $(echo -ne "\x7F\x45\x4C\x46") ]]; then
                fileExt="so"
        else
                fileExt="dump"
        fi
        
        local fileOut="/sdcard/${offset}.${fileExt}"
        
        dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${offset}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;(${end}-${offset})/$HEX_PAGESIZE" | bc) of="$fileOut"
        
        if [[ $fileExt == "so" ]]; then
                lastFile=$fileOut
        else
                if [[ $lastFile != "" ]]; then
                        skipMerge=false
                        if [[ $lastEnd != $offset ]]; then
                                gap_block=$(echo "ibase=16;(${offset}-${lastEnd})/${HEX_PAGESIZE}" | bc)
                                if [[ $gap_block -gt 32 ]]; then # 我也不知道应该把上限设置为多少合适,先随手写一个 32 吧,应该不会有间隙块超过 32*4096 字节的...吧...
                                        skipMerge=true # 间隙块过大,不合并到上个文件
                                        lastFile=$fileOut # 让后面的文件合并到当前文件
                                else
                            dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${lastEnd}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;(${offset}-${lastEnd})/${HEX_PAGESIZE}" | bc) of="/data/local/tmp/mem_temp"
                                  cat "/data/local/tmp/mem_temp">>"$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



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

image-51.png

这款游戏最新版也用了 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 <= NF; i++) {gsub(/-/, "_"); print "split_config."$i".apk"}}') # 把可用架构的 "-" 替换成 "_" 以适配文件名

for target in $targets; do
        local maps=$(grep $target /proc/$pid/maps | awk -v OFS='|' '{for (i = 1; i <= NF; i+=6) {print $i,$(i+1),$(i+2),$(i+3),$(i+4),$(i+5)}}')
        if [[ $maps != "" ]]; then
                if [[ $mem_list != "" ]]; then
                        mem_list="${mem_list} ${maps}"
                else
                        mem_list=$maps
                fi
        fi
done

SYS_PAGESIZE=$(getconf PAGESIZE)
HEX_PAGESIZE=$(printf "%x" $SYS_PAGESIZE)

lastFile=
lastEnd=

for memory in $mem_list; do
        local range=$(echo $memory | awk -F'|' '{print $1}')
        local offset=$(echo $range | awk -F'-' '{print toupper($1)}')
        local end=$(echo $range | awk -F'-' '{print toupper($2)}')
        local memName=$(echo $memory | awk -F'|' '{print $6}' | awk -F'/' '{print $NF}') # 记录文件名以供区分
        
        dd if="/proc/$pid/mem" bs=1 skip=$(echo "ibase=16;$offset" | bc) count=4 of="/data/local/tmp/mem_temp"
        
        if [[ $(cat "/data/local/tmp/mem_temp") == $(echo -ne "\x7F\x45\x4C\x46") ]]; then
                fileExt="so"
        else
                fileExt="dump"
        fi
        
        local fileOut="/sdcard/${offset}_${package}_${memName}.${fileExt}" # 输出文件名记录包名
        
        dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${offset}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;(${end}-${offset})/$HEX_PAGESIZE" | bc) of="$fileOut"
        
        if [[ $fileExt == "so" ]]; then
                lastFile=$fileOut
        else
                if [[ $lastFile != "" ]]; then
                        skipMerge=false
                        if [[ $lastEnd != $offset ]]; then
                                gap_block=$(echo "ibase=16;(${offset}-${lastEnd})/${HEX_PAGESIZE}" | bc)
                                if [[ $gap_block -gt 32 ]]; then
                                        skipMerge=true
                                        lastFile=$fileOut
                                else
                            dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${lastEnd}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;(${offset}-${lastEnd})/${HEX_PAGESIZE}" | bc) of="/data/local/tmp/mem_temp"
                                  cat "/data/local/tmp/mem_temp">>"$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

扔进 IDA Pro 反编译(注:不能使用直接从 APK 提取出的so,因为解析出的偏移是根据 dump 出来的 so 计算的),弹了两个报错,可以忽略掉。跑脚本报了一堆错,凑合用。

可能 dump 出来的 so 需要修复,不过超出这篇文章的范畴了。(其实主要问题是我也不会 x)

image-53.png



脚本是没什么问题了(大概),但我们总不能每次都靠猜测来判断哪个是正确的 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 <package> [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 <= NF; i++) {gsub(/-/, "_"); print "split_config."$i".apk"}}') # 加入 global-metadata.dat 查找

for target in $targets; do
        local maps=$(grep $target /proc/$pid/maps | awk -v OFS='|' '{for (i = 1; i <= NF; i+=6) {print $i,$(i+1),$(i+2),$(i+3),$(i+4),$(i+5)}}')
        if [[ $maps != "" ]]; then
                if [[ $mem_list != "" ]]; then
                        mem_list="${mem_list} ${maps}"
                else
                        mem_list=$maps
                fi
        fi
done

cp /proc/$pid/maps "$out/${package}_maps.txt"

SYS_PAGESIZE=$(getconf PAGESIZE)
HEX_PAGESIZE=$(printf "%x" $SYS_PAGESIZE)

echo "- Starting dump process..."

lastFile=
lastEnd=
metadataOffset=

for memory in $mem_list; do
        local range=$(echo $memory | awk -F'|' '{print $1}')
        local offset=$(echo $range | awk -F'-' '{print toupper($1)}')
        local end=$(echo $range | awk -F'-' '{print toupper($2)}')
        local memName=$(echo $memory | awk -F'|' '{print $6}' | awk -F'/' '{print $NF}')
        
        if [[ $memName == "global-metadata.dat" ]]; then
                metadataOffset=$offset # 记录 global-metadata.dat 的偏移
                continue
        fi
        
        dd if="/proc/$pid/mem" bs=1 skip=$(echo "ibase=16;$offset" | bc) count=4 of="${out}/tmp" 2>/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})<0" | bc) -ne 0 ]]; then
                echo "- Dumping [$memName] $range... <- This might be the correct libil2cpp.so" # metadata 偏移的后一个匹配通常是我们要找的 so
                metadataOffset=
        else
                echo "- Dumping [$memName] $range..."
        fi
        
        dd if="/proc/$pid/mem" bs=$SYS_PAGESIZE skip=$(echo "ibase=16;${offset}/$HEX_PAGESIZE" | bc) count=$(echo "ibase=16;(${end}-${offset})/$HEX_PAGESIZE" | bc) of="$fileOut" 2>/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



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

免费评分

参与人数 29威望 +2 吾爱币 +139 热心值 +29 收起 理由
magua + 1 + 1 谢谢@Thanks!
fffxiaofu + 1 + 1 热心回复!
junjia215 + 1 + 1 用心讨论,共获提升!
繁星落月 + 1 谢谢@Thanks!
13668292490 + 1 + 1 我很赞同!
zlnxlzht + 1 + 1 热心回复!
damon7609 + 1 + 1 谢谢@Thanks!
王之蔑视 + 1 + 1 谢谢@Thanks!
time2s + 1 + 1 我很赞同!
Nittbone + 1 + 1 我很赞同!
hanlaoshi + 1 + 1 谢谢@Thanks!
HillBoom + 1 + 1 用心讨论,共获提升!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
胡家二少 + 2 + 1 我很赞同!
allspark + 1 + 1 用心讨论,共获提升!
shiqiangge + 1 我很赞同!
正己 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
LonelyCrow + 1 + 1 用心讨论,共获提升!
涛之雨 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
debug_cat + 2 + 1 谢谢@Thanks!
Minesa + 1 + 1 用心讨论,共获提升!
Atnil + 1 + 1 谢谢@Thanks!
lyl610abc + 3 + 1 我很赞同!
火蜥蜴 + 1 + 1 谢谢@Thanks!
chinawolf2000 + 1 + 1 热心回复!
CrazyNut + 3 + 1 用心讨论,共获提升!
lingyun011 + 1 + 1 热心回复!
zy1234 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
BonnieRan + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| mlgmxyysd 发表于 2023-10-15 23:20
脚本已开源在 GitHub,后续完善将在 GitHub 更新:https://github.com/MlgmXyysd/IL2CppMemoryDumper

修复 ELF 可以尝试使用:https://github.com/F8LEFT/SoFixer
XhyEax 发表于 2023-10-15 16:31

免费评分

参与人数 2吾爱币 +4 热心值 +2 收起 理由
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
mlgmxyysd + 2 + 1 我很赞同!

查看全部评分

lyl610abc 发表于 2023-10-16 14:07
本帖最后由 lyl610abc 于 2023-10-16 14:16 编辑

意想不到的思路
我通常都是使用 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 发表于 2023-10-15 17:14
看完了 学到了新的dump姿势,感谢楼主这么详细的贴
罗萨 发表于 2023-10-15 17:29
牛,不过我选择使用更简单的frida
rain1986 发表于 2023-10-15 22:05
好帖子,学习啦
 楼主| mlgmxyysd 发表于 2023-10-15 22:45
XhyEax 发表于 2023-10-15 16:31
楼主可以试试https://github.com/BryanGIG/PADumper

收藏了,之前没搜到相关软件,就自己写了&#129315;
xyz989 发表于 2023-10-15 23:03
本帖最后由 xyz989 于 2023-10-15 23:11 编辑

大佬 有没有提取素材(PNG)后转动态化(GIF)的教程
是网页游戏(农场3.0采用Unity重制游戏)




人物素材链接(原文件)
https://xyz989.lanzouo.com/ir00j0n5vmah


提取工具AssetStudioGUI




导出的资源目录如下
 楼主| mlgmxyysd 发表于 2023-10-15 23:17
xyz989 发表于 2023-10-15 23:03
大佬 有没有提取素材(PNG)后转动态化(GIF)的教程
是网页游戏(农场3.0采用Unity重制游戏)

我记得之前有个东西可以支持导入Unity的导出模型来着,让我找找看
xyz989 发表于 2023-10-15 23:21
mlgmxyysd 发表于 2023-10-15 23:17
我记得之前有个东西可以支持导入Unity的导出模型来着,让我找找看

收到 有劳了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-4 16:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表