frida脱壳脚本原理分析一(frida_dump)

查看 42|回复 3
作者:快乐的小跳蛙   
前言
目前通过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。
核心函数分析
    function dump_dex() {
    var libart = Process.findModuleByName("libart.so");
    var addr_DefineClass = null;
    var symbols = libart.enumerateSymbols();
    for (var index = 0; index = 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("[DefineClass:]", addr_DefineClass);
    if (addr_DefineClass) {
        Interceptor.attach(addr_DefineClass, {
            onEnter: function(args) {
                var dex_file = args[5];
                //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[base] == undefined) {
                    dex_maps[base] = 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("[find dex]:", 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("[dump dex]:", 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对象的引用。
    //DefineClass函数声明,是一个native函数
mirror::Class* ClassLinker::DefineClass(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        Handle 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 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地址】
总结
通过这次对frida_dump的脚本进行分析,对脱壳脚本执行更加清晰,增加了对frida脚本编写的理解

函数, 脚本

weishuirenjia   

这个可以脱最新的数字壳不?能不能还原被抽的代码?
快乐的小跳蛙
OP
  


weishuirenjia 发表于 2024-9-3 14:06
这个可以脱最新的数字壳不?能不能还原被抽的代码?

没试过数字壳,不能还原代码抽取
whitegold   

学习了frida脱壳脚本原理分析
您需要登录后才可以回帖 登录 | 立即注册

返回顶部