快乐的小跳蛙 发表于 2024-9-4 10:30

frida脱壳脚本原理分析二(frida_unpack)

### 前言
上篇帖子分析了frida_dump脚本如何实现dump dex的原理,本篇分析frida_unpack如何脱壳的。
(https://github.com/dstmath/frida-unpack)
frida_unpack作者提供了rpc调用和js脚本调用两种方式,本次分析js脚本调用,其实js脚本调用没问题的话,可以直接copy到rpc调用里面,都是一样的。
### 原版核心函数分析
```Javascrpit

                'use strict';
/**
* 此脚本在以下环境测试通过
* android os: 7.1.2 32bit(64位可能要改OpenMemory的签名)
* legu: libshella-2.8.so
* 360:libjiagu.so
*/
Interceptor.attach(Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_"), {
    onEnter: function (args) {
      
      //dex起始位置
      var begin = args
      //打印magic
      console.log("magic : " + Memory.readUtf8String(begin))
      //dex fileSize 地址
      var address = parseInt(begin,16) + 0x20
      //dex 大小
      var dex_size = Memory.readInt(ptr(address))

      console.log("dex_size :" + dex_size)
      //dump dex 到/data/data/pkg/目录下
      var file = new File("/data/data/xxx.xxx.xxx/" + dex_size + ".dex", "wb")
      file.write(Memory.readByteArray(begin, dex_size))
      file.flush()
      file.close()
    },
    onLeave: function (retval) {
      if (retval.toInt32() > 0) {
            /* do something */
      }
    }
});
```
作者提供的这个脚本是在32位android 7环境下的。可以看出frida_unpack的脱壳点是hook了OpenMemory函数,通过参数拿到dex文件头,从dex文件头+0x20偏移处得到dex的文件长度, 然后dump到手机上的。但是这个脚本不能直接运行,没有给出真实的dump路径。
作者在github上也说了,android 10上的脱壳点变成了OpenCommon了。
> android 10为/apex/com.android.runtime/lib/libdexfile.so方法为OpenCommon
我在[安卓在线源码网站](http://xrefandroid.com/)上查询到从android 8及以后OpenMemory都变成了OpenCommon,所以你想用原版的脚本运行在android 7以上的环境时,都需要修改脚本,否则不能成功脱壳。
### 修改后的unpack.js

``` javascrpit
function dump_dex(){
    var libdexfileAddr = Module.getExportByName("libdexfile.so","_ZN3art13DexFileLoader10OpenCommonEPKhjS2_jRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE")
    if (libdexfileAddr == undefined || libdexfileAddr == null){
      console.error("libdexfileAddr = ",libdexfileAddr)
      return;
    }
    Interceptor.attach(libdexfileAddr,{
      onEnter: function(args){
            //dex在内存中的起始位置
            var base = args;
            //console.log("base = " , base)
            var size = args;
            //console.log("size = " ,size)
            //打印magic 并验证
         // var magic1 = Memory.readUtf8String(base);
            //console.log("magic1",magic1)
            var magic = ptr(base).readCString();
            if(magic.indexOf("dex") == 0){
                //找到了dex文件

                //Dexd filesize地址
                //var address = parseInt(base,16) + 0x20
                //var dex_size = Memory.readInt(ptr(address))
                //console.log("dex_size = ", size);
                var dex_size = parseInt(size,16)
                console.log(" base = "+ base +" size = " + dex_size )
                //dump dex到/sdcard/pkg目录下,需要确保手机sd卡下有这个文件夹,没有则手动创建
                var file = new File("/sdcard/Download/package/"+ dex_size + ".dex","wb");

                file.write(Memory.readByteArray(base,dex_size))
                file.flush();
                file.close();


            }


      },
      onLeave:function(retval){ }
    } )
}

setImmediate(dump_dex)
```

我在原版的基础上修改了几点:
1. findExportByName改为getExportByName,因为findExportByName的报错提示搞不清楚哪里报错了。
2. 增加一个判断,如果没找到libdexfile.so的地址就退出
3. 取dex文件长度从参数中取,而不是通过偏移来取
4. dump dex文件到sd卡目录下
OpenCommon的函数声明
```
        std::unique_ptr<DexFile> DexFileLoader::OpenCommon(const uint8_t* base,
                                                   size_t size,
                                                   const uint8_t* data_base,
                                                   size_t data_size,
                                                   const std::string& location,
                                                   uint32_t location_checksum,
                                                   const OatDexFile* oat_dex_file,
                                                   bool verify,
                                                   bool verify_checksum,
                                                   std::string* error_msg,
                                                   std::unique_ptr<DexFileContainer> container,
                                                   VerifyResult* verify_result)
```
### 遇到的问题及总结
我尝试通过导出我android 10 真机的libdexfile.so来获取OpenCommon的导出符号,得到的是:
> _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE

在getExportByName时失败了,提示是找不到这个符号,很奇怪,用了作者提供的OpenCommon的导出符号就成功找到了符号,这个有点奇怪
> _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE

通过学习frida_dump和frida_unpack这个两个脚本大致原理类似,都是通过找到dex文件的出现的系统函数hook然后dump下来。找到更多的脱壳点就能实现新的脱壳脚本。

快乐的小跳蛙 发表于 2024-9-4 18:14

Liebesfreud 发表于 2024-9-4 15:36
大佬讲解仔细,奈何本人基础薄弱,看不太懂

找一篇部署frida的帖子,会部署以后,找个模拟器或者真机连接上,然后把我贴出来的代码自己手动写一个js脚本,把每一行都搞懂,不明白的就百度或者问kimi。完了之后你会收获很多。

eiyou 发表于 2024-11-13 11:10

都是通过找到dex文件的出现的系统函数hook然后dump下来中,找到dex文件的出现的系统函数是啥意思,是指这个吗

xzbljxh 发表于 2024-9-4 11:11

感谢楼主的讲解,学习了

vc囚封 发表于 2024-9-4 11:34


感谢楼主的讲解,学习了

yan999 发表于 2024-9-4 13:39

谢谢,学习到了!!!

光影由心 发表于 2024-9-4 15:10


感谢大佬分享

Liebesfreud 发表于 2024-9-4 15:36

大佬讲解仔细,奈何本人基础薄弱,看不太懂{:301_972:}

jyxz0721 发表于 2024-9-4 16:05

感谢楼主的讲解,学习了

zhaohainuo 发表于 2024-9-4 18:49

感谢楼主的讲解,学习了

DarthMask 发表于 2024-9-4 20:31

有学习到,感谢大佬分享
页: [1] 2
查看完整版本: frida脱壳脚本原理分析二(frida_unpack)