快乐的小跳蛙 发表于 2024-9-3 13:51

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

这个可以脱最新的数字壳不?能不能还原被抽的代码?

快乐的小跳蛙 发表于 2024-9-3 15:07

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

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

whitegold 发表于 2024-9-3 15:24

学习了frida脱壳脚本原理分析

clabobo 发表于 2024-9-3 17:02

学到了 大佬

asdasxzca 发表于 2024-9-3 17:15

牛逼,学习学习

cmsttasd 发表于 2024-9-3 17:54

大佬,会山姆app脱壳吗?

wsadwhh 发表于 2024-9-3 18:46

这真的是学到东西了

debug_cat 发表于 2024-9-3 19:19

感谢分享,现在测试过那个厂子的可呢

男神冷熙啊 发表于 2024-9-3 23:13

学到了 大佬
页: [1] 2
查看完整版本: frida脱壳脚本原理分析一(frida_dump)