frida脱壳脚本原理分析一(frida_dump)
### 前言目前通过frida脚本对android进行脱壳获取dex文件是目前的主流手段。本次对frida_dump脱壳脚本原理分析是为了加深脱壳脚本如何工作的。知己知彼,百战不殆!
### 脚本分析
#### 整体分析
frida_dump一共有5个函数,自上至下分别是
1. get_self_process_name
2. mkdir
3. chmod
4. dump_dex
5. hook_dlopen
其中关于脱壳的核心函数是dump_dex。
#### 核心函数分析
``` javascript
function dump_dex() {
var libart = Process.findModuleByName("libart.so");
var addr_DefineClass = null;
var symbols = libart.enumerateSymbols();
for (var index = 0; index < symbols.length; index++) {
var symbol = symbols;
var symbol_name = symbol.name;
//这个DefineClass的函数签名是Android9的
//_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE
if (symbol_name.indexOf("ClassLinker") >= 0 &&
symbol_name.indexOf("DefineClass") >= 0 &&
symbol_name.indexOf("Thread") >= 0 &&
symbol_name.indexOf("DexFile") >= 0) {
console.log(symbol_name, symbol.address);
addr_DefineClass = symbol.address;
}
}
var dex_maps = {};
var dex_count = 1;
console.log("", addr_DefineClass);
if (addr_DefineClass) {
Interceptor.attach(addr_DefineClass, {
onEnter: function(args) {
var dex_file = args;
//ptr(dex_file).add(Process.pointerSize) is "const uint8_t* const begin_;"
//ptr(dex_file).add(Process.pointerSize + Process.pointerSize) is "const size_t size_;"
var base = ptr(dex_file).add(Process.pointerSize).readPointer();
var size = ptr(dex_file).add(Process.pointerSize + Process.pointerSize).readUInt();
if (dex_maps == undefined) {
dex_maps = size;
var magic = ptr(base).readCString();
if (magic.indexOf("dex") == 0) {
var process_name = get_self_process_name();
if (process_name != "-1") {
var dex_dir_path = "/data/data/" + process_name + "/files/dump_dex_" + process_name;
mkdir(dex_dir_path);
var dex_path = dex_dir_path + "/class" + (dex_count == 1 ? "" : dex_count) + ".dex";
console.log(":", dex_path);
var fd = new File(dex_path, "wb");
if (fd && fd != null) {
dex_count++;
var dex_buffer = ptr(base).readByteArray(size);
fd.write(dex_buffer);
fd.flush();
fd.close();
console.log(":", dex_path);
}
}
}
}
},
onLeave: function(retval) {}
});
}
}
```
从函数第一行开始分析可知,首先获取libart.so的模块基址,然后通过frida提供的api(enumerateSymbols)来遍历模块的导出函数。
从里面得到**DefineClass**的函数地址,从导出名称看出libart.so在编译导出函数时没有按照C风格也是extern “C”对函数名称进行修饰。
导致导出函数名称不规则,通过对DefineClass函数名称中的特征字符来来过滤DefineClass的函数地址。
获取到**DefineClass**函数地址后,用frida提供的Interceptor来附加在函数地址上,(通过frida提供的注释看,Interceptor附加采用的是inline hook的方式)
为什么要hook DefineClass呢?因为DefinClass的第6个参数(第一个参数是this指针)是dexFile对象的引用。
``` Java
//DefineClass函数声明,是一个native函数
mirror::Class* ClassLinker::DefineClass(Thread* self,
const char* descriptor,
size_t hash,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def);
DexFile::DexFile(const uint8_t* base, //dex文件基址
size_t size, //dex文件长度
const uint8_t* data_begin,
size_t data_size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file,
std::unique_ptr<DexFileContainer> container,
bool is_compact_dex)
```
dexFile是dex文件加载到内存后的对象。该对象是包含了dex在内存中的基址和长度。有了这些信息就可以把dex文件dump出来了
脚本会将DefineClass加载的每一个dex文件dump到当前包名的files目录下。并按照顺序对dex文件命名"class1 class2"。
#### myfridadump.js
myfridadump.js是我对照dump_dex.js自己实现的dump dex的脚本,其核心一样是在DefineClass hook拿到dexFile后dump dex文件到手机上。
区别是删除了原脚本的无用函数hook_dlopen,增加了更多的注释,帮助理解,本来想通过chrome的devtools对frida的js脚本进行单步调试,
这是会对脚本执行过程的理解更清晰,无奈折腾半天附加不上js脚本后放弃,采用日志输出的方式来看执行流程。
[【myfridadump.js github地址】](https://github.com/Samael-Z/Android/tree/0c9b162853ad074c723897949f709fa21f7d0e8a/frida-js/mydumpdexjs)
### 总结
通过这次对frida_dump的脚本进行分析,对脱壳脚本执行更加清晰,增加了对frida脚本编写的理解 这个可以脱最新的数字壳不?能不能还原被抽的代码? weishuirenjia 发表于 2024-9-3 14:06
这个可以脱最新的数字壳不?能不能还原被抽的代码?
没试过数字壳,不能还原代码抽取 学习了frida脱壳脚本原理分析 学到了 大佬 牛逼,学习学习 大佬,会山姆app脱壳吗? 这真的是学到东西了 感谢分享,现在测试过那个厂子的可呢 学到了 大佬
页:
[1]
2