吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 14097|回复: 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 从游戏内存中来获取未保护的可执行文件。



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)

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

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



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



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






不错,是 ELF 文件头,之前的思路是对的。但大小一看就不是 libil2cpp.so,这样一个一个提取太麻烦了,集合一下上面的思路,写成脚本自动化。

[Bash shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
#!/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 可以接受十六进制,但是表达式的值必须是大写。



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



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



so 文件有固定的文件头 7F 45 4C 46,猜测需要将其他内容加到 so 后面,补一下脚本。

[Bash shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/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

正当我准备运行的时候,我发现了一个问题,在偏移稍微靠后的位置,有一些间隙块非常的大,可以达到几万甚至几百万块,这很明显不是同一个连续的内存区域,我们不能把他们强行合并到一起。



[Bash shell] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/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 解析,这次有一个可以正常解析了。





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



这款游戏最新版也用了 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

老规矩,运行代码,找到合适大小的文件,拉出来喂工具解析,成功解析。



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

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





脚本是没什么问题了(大概),但我们总不能每次都靠猜测来判断哪个是正确的 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] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/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~





后话:
脚本只能辅助,却不能代替我们去分析代码。
此外,脚本还有些不足之处,例如: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!

查看全部评分

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

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

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

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




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


提取工具AssetStudioGUI




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

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

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

本版积分规则

返回列表

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

GMT+8, 2025-4-16 14:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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