书接上回,在逆向元气骑士的时候,遇到了 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:
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
精准找到该进程:
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
命令获取前台用户:
TB371FC:/ # am get-current-user
0
使用 grep
命令套一层:
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
命令实现自动化获取:
TB371FC:/ # ps -ef | grep com.ChillyRoom.DungeonShooter | grep u$(am get-current-user) | awk '{print $2}'
5957
猫 cat
一下 maps
看看:
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
的结果可以对应上,如果不对烦请赐教。
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
的内存:
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:
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
,找一下看看:
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 错误:
TB371FC:/ # cat /proc/5957/mem
cat: /proc/5957/mem: I/O error
不过我们可以用 dd 命令,来读写指定范围的内存,命令格式如下:
dd if="输入文件" of="输出文件" bs=块大小 skip=起始偏移块 count=块数量
# 从 if 中的第 skip 块后开始读取 count 个块,保存到 of 中,每个块大小为 bs 字节
(我已经不认识“块”这个字了)
取出速度取决于块大小,过小会非常慢,过大会丢失精度,同时也会降低速度。你想一下,dd 就像是在搬砖的你(不是我!是你!正在读这篇文章的你!),count 就像是每次搬的砖数量,一次搬一个,那得要搬多少次啊,一次搬太多反而会把自己累的搬不动,一不小心还超出需求,白搬了,所以选一个合适的快大小是很有必要的。
在读取内存时一般与系统的内存页大小(Pagesize)相同,可以用系统命令读取:
TB371FC:/ # getconf PAGESIZE
4096
起始地址除以块大小做 skip;用结束地址减去起始地址,再除以块大小,得到 count。(以第一行的为例)
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,这样一个一个提取太麻烦了,集合一下上面的思路,写成脚本自动化。
#!/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 后面,补一下脚本。
#!/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。
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),补全脚本把这段也加上。
#!/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
正当我准备运行的时候,我发现了一个问题,在偏移稍微靠后的位置,有一些间隙块非常的大,可以达到几万甚至几百万块,这很明显不是同一个连续的内存区域,我们不能把他们强行合并到一起。
#!/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
获取,以半角逗号 ,
分割。
#!/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
。
# 合并 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
# ... 此处省略无数行 ...
找到了这个规律,我们就可以将其写入脚本中了。顺便呢,再完善一下输入输出,方便调试。
#!/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