#随笔-Try to reverse ACE 2023
😭5ea1
这里没有前言
哎三星大虫子三星劫三星瑟庄妮真有实力吧,上了个执事挂机,hook完回来一看收了400层,商店里连刷6只大虫子直接3星了,要啥运营啊
apk的本地库里有libil2cpp.so,放ida看一下,发现是加密过的,看不了
因为在实际调用过程中,肯定不可能直接使用这个被加密的lib库,所以可以考虑使用fridahook把要的库dump下来
这里可以采用getModuleByName的方法,直接传进去lib_name,获取地址,然后用lib.base和lib.size来读取内容
一开始我写了如下代码:
function dump_so(so_name){
Java.perform(function() {
var libso = Process.getModuleByName(so_name);
console.log("libso base address: " + libso.base.toString(16));
console.log("libso size: " + libso.size.toString(16));
console.log("libso path: " + libso.path);
console.log("libso name: " + libso.name);
Memory.protect(ptr(libso.base), libso.size, 'rwx');
var data = Memory.readByteArray(libso.base, libso.size);
var file = new File(/data/local/tmp/dump.so, "wb");
file.write(data);
file.flush();
file.close();
console.log("libso dumped to " + dump_path);
console.log("libso dump done!");
});
}
function main() {
var so_name = "libil2cpp.so";
dump_so(so_name);
}
setImmediate(main);
但是使用的时候发现两个错:
- 在读取的时候,会报错
Error: access violation accessing 0x71191e1000
这个报错的原因可能是因为在lib中有某种防止读取的内存防御措施,这个措施在0x71191e1000这个地址处,所以当我们修改内存权限的时候,要绕过它修改 - frida对于应用沙箱外的地址没有操作权限,所以产生的dump.so要在应用沙箱内完成创建
基于上面的两个错误,我们可以得到下面的代码:
function dump_so(so_name){
Java.perform(function() {
var context = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext();
var dir = context.getFilesDir().getAbsolutePath();
var libso = Process.getModuleByName(so_name);
console.log("libso base address: " + libso.base.toString(16));
console.log("libso size: " + libso.size.toString(16));
console.log("libso path: " + libso.path);
console.log("libso name: " + libso.name);
Memory.protect(ptr(libso.base), libso.size, 'rwx');
Memory.protect(ptr(libso.base).add(0x13B7000), libso.size - 0x13B7000, 'rwx');
var data = Memory.readByteArray(libso.base, libso.size);
var dump_path = dir + "/" + libso.name;
var file = new File(dump_path, "wb");
file.write(data);
file.flush();
file.close();
console.log("libso dumped to " + dump_path);
console.log("libso dump done!");
});
}
function main() {
var so_name = "libil2cpp.so";
dump_so(so_name);
}
setImmediate(main);
运行后得到:
libso base address: 7119420000
libso size: 13cc000
libso path: /data/app/~~c4CHbJNFMW6K-83Dl7_psQ==/com.com.sec2023.rocketmouse.mouse-Wo6nAo9UMfyLXh-IViOx-g==/lib/arm64/libil2cpp.so
libso name: libil2cpp.so
libso dumped to /data/user/0/com.com.sec2023.rocketmouse.mouse/files/libil2cpp.so
libso dump done!
由于这个位置需要su权限才能读取,不能直接pull,所以要先复制到共享文件夹,再pull拉取:
adb shell
su
cp /data/user/0/com.com.sec2023.rocketmouse.mouse/files/libil2cpp.so /sdcard/
exit
exit
adb pull /sdcard/libil2cpp.so
哦,这个位置还要记得保存一下基址,后面要用
拿完解密后的lib后,需要使用Il2CppDumper来获取符号表
这里要用到一个global-metadata.dat,这个dat直接能在apk中解压缩找到:
条件完备以后可以使用Il2CppDumper来操作
将这两个文件放入Il2CppDumper的input中后,双击Il2CppDumper.exe进行分析,地址直接填写前面获取的基址即可
运行后在output中获得结果
修复dump下来的so
由于dump下来的文件是在运行时的时候dump的,所以文件中的地址偏移也是在虚拟环境中的偏移,而ida这一类的静态分析工具,一般使用文件真实偏移来作为分析标准,所以这往往会导致ida的分析失败
所以我们需要把so文件中的真实偏移替换为虚拟偏移,让ida分析虚拟环境下的文件
此处替换需要考虑两个部分: segment(段)
和section(节)
,分别由program header table
和 secion header table
中的成员指出
使用010Editor,用本地库中的libil2cpp.so
和libil2cpp.sp_0x7122e17000_0x13cc000.so
对照修复
010Editor
中,可以使用Templates
中的模板辅助分析,然后在Templates Result
中,使用Ctrl Shift C
和Ctrl Shift V
可以直接对元素进行复制
修复segment
segment段
由program_header_table
中的成员定义,定义内容如下:
成员名称 | 含义 |
---|---|
p_offset_FROM_FILE_BEGIN |
在实际文件中的偏移 |
p_vaddr_VIRTUAL_ADDRESS |
在虚拟空间中的偏移 |
p_filesz_SEGMENT_FILE_LENGTH |
在实际文件中的大小 |
p_memsz_SEGMENT_RAM_LENGTH |
在虚拟空间中的大小 |
那么按照上面的定义,我们只需要将p_vaddr_VIRTUAL_ADDRESS 中的值转移到p_offset_FROM_FILE_BEGIN 中,并将p_memsz_SEGMENT_RAM_LENGTH 中的值转移到p_filesz_SEGMENT_FILE_LENGTH 中即可 |

修正节头表(secion header table)的偏移
节头表 (secion header table)
的位置在最后一个段 (segment)
之后,我们可以从 ELF 文件的 Execution View
直观看出
由这张图可见,节头表是整个elf中的最后一个element
,所以就对应了program_header_table
中的最后一个element
,即program_table_element[10]
其中可以看到这个段的起始地址是13BC000h
,段的长度为63352
,经过简单计算可得:
虚拟地址起点
=13BC000h+63352d=13BC000h+F778h=13CB778h
而在Template Results
中,节头表的起始地址由elf_header->e_shoff_SECTION_HEADER_OFFSET_IN_FILE
决定
所以只要直接把这个值修改为13CB778h
即可
修正section
内容
在 libil2cpp.so
点击 struct section_header_table
并按下 Ctrl + Shift + C
,,然后回到 libil2cpp.so_0x7122e17000_0x13cc000.so
中,选中 section_header_table
然后按下 Ctrl + Shift + V
, 按下 F5 重新运行模板 ELF.bt
恢复节 (section)
的名称
ELF 文件中的每个 section 都是有名字的,比如 .data 、 .text 、 .rodata ,每个名字都是一个字符串,既然是字符串就需要一个字符串池来保存,而这个字符串池也是一个 section ,或者说准备一个 section 用来维护一个字符串池,这个字符串池保存了其他 section 以及它自己的名字。这个特殊的 section 叫做 .shstrtab ,所有 section 的头部是连续存放在一起的,类似一个数组, e_shstrndx 变量是 .shstrtab 在这个数组中的下标
此时的每个section
的名字还是乱码,而elf文件
中会维护一个字符常量池来作为这些section
的名称;
通过观察发现,在 libil2cpp.so_0x7122e17000_0x13cc000.so
中,找到 elf_header->e_shtrndx_STRING_TABLE_INDEX
, 这个的值为 26 (0x1A)
, 说明了 section_header_table->section_table_element[26]
存储了所有 section
的名称

section_header_table->section_table_element[26]
中 s_offset
的值决定了 section
的名称将从 1199370h
去索引

跳转到对应地址发现,存放的字符串都是乱码;而libil2cpp.so
中的是正常字符串
所以只需要把正常的字符串常量池修正进去即可,即将 section 的符号名称从原来的 so 复制到 dump 下来的 so 里面,位置就是我们之前分析出来的 section_table_element[26] 中 s_offset 所指向的物理内存地址,即选中 libil2cpp.so 从 0x1199370h 到 0x1199470h 按下 Ctrl + Shift + C , 然后将光标移动到 libil2cpp.so_0x712a997000_0x13cc000.so 的 119A370h 处,按下 Ctrl + Shift + V
修正 节 (section) 的偏移
节 (section)
的位置和大小由节头表 (secion_header_table)
中这两个成员决定
| 成员名称 | 含义 |
| s_addr | 如果此 section 需要映射到进程空间,此成员指定映射的起始地址;如不需映射,此值为 0 |
| s_offset | 此 section 相对于文件开头的字节偏移量。如果 section 类型为 SHT_NOBITS , 表明该 section 在文件中不占空间,这时 sh_offset 没什么用 |
修正 节 (section)
的偏移有两条规则
- 如果
s_addr
为 0, 无需修改s_offset
- 如果
s_addr
不为 0, 则将s_addr
的值复制给s_offset