前面的文章中介绍了Frida基本API的使用,在这篇文章中介绍一些更加强大的API。同时简单介绍下HOOK 系统函数的利器frida-trace。

内存,内存还是内存。

Java对象

Java对象

Java是极其重要的API。无论想对so层亦或java层进行拦截,通常都须编Java.perform。

  • Java.available: 该函数一般用来判断当前进程是否加载了JavaVM,Dalvik或ART虚拟机
  • Java.androidVersion: 显示Android系统版本号
  • Java.enumerateLoadedClasses(callbacks): 枚举当前加载的所有类信息,它有一个回调函数分别是onMatch、onComplete函数。这是一个异步的方法,有一个同步的API是Java.enumerateLoadedClassesSync。
  • Java.enumerateClassLoaders(callbacks): 枚举Java VM中存在的类加载器,其有一个回调函数,分别是onMatch: function (loader)与onComplete: function ()。这是一个异步的方法,有一个同步的API是Java.enumerateClassLoadersSync()。
  • Java.array(type, elements): 根据指定的元素类型创建一个Java数组。

还有一些API可参考官方文档,Java.isMainThread()、Java.registerClass(spec)、Java.deoptimizeEverything()、Java.enumerateMethods(query)等,具体用法可参考官方文档。在12.8.20版本上没有Java.enumerateMethods(query)这个API。

示例代码
function frida_Java() {
  Java.perform(function () {
      //作为判断用
      if(Java.available)
      {
          //注入的逻辑代码
          console.log("",Java.androidVersion);
      }else{
          //未能正常加载JAVA VM
          console.log("error");
      }
      //枚举当前加载的所有类
      Java.enumerateLoadedClasses({
          //每一次回调此函数时其参数className就是类的信息
          onMatch: function (className)
          {
              //输出类字符串
              console.log("",className);
          },
          //枚举完毕所有类之后的回调函数
          onComplete: function ()
          {
              //输出类字符串
              console.log("输出完毕");
          }
      });
      //枚举当前加载的Java VM类加载器
      Java.enumerateClassLoaders({
        //回调函数,参数loader是类加载的信息
        onMatch: function (loader)
        {
            console.log("",loader);
        },
        //枚举完毕所有类加载器之后的回调函数
        onComplete: function ()
        {
            console.log("end");
        }
    });
    var values = Java.array('int', [ 1003, 1005, 1007 ]);
 	var JString = Java.use('java.lang.String');
    var str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));

  });
}       
setImmediate(frida_Java,0);

部分运行结果如下:

在这里插入图片描述

Java.vm

Java.vm对象十分常用,比如想要拿到JNI层的JNIEnv对象,可以使用getEnv()。

function frida_Java() {
  Java.perform(function () {
        Interceptor.attach(Module.getExportByName(null, 'read'), {
          onEnter: function (args) {
            this.fileDescriptor = args[0].toInt32();
          },
          onLeave: function (retval) {
            console.log("Env对象"+JSON.stringify(Java.vm.getEnv()));
            retval.replace(1337);
            console.log("retval"+retval);
          }
        });
  });
}       
setImmediate(frida_Java,0);

部分运行结果:
在这里插入图片描述

Intercepter对象

在这里插入图片描述该对象功能十分强大,函数原型是Interceptor.attach(target, callbacks):参数target是需要拦截的位置的函数地址,也就是填某个so层函数的地址即可对其拦截,target是一个NativePointer参数,用来指定你想要拦截的函数的地址,NativePointer是一个指针。需要注意的是对于Thumb函数需要对函数地址+1,callbacks则是它的回调函数,分别是以下两个回调函数:

  • onEnter: function (args): callback function given one argument args that can be used to read or write arguments as an array of NativePointer objects.
  • onLeave: function (retval): callback function given one argument retval that is a NativePointer-derived object containing the raw return value. You may call retval.replace(1337) to replace the return value with the integer 1337, or retval.replace(ptr(“0x1234”)) to replace with a pointer. Note that this object is recycled across onLeave calls, so do not store and use it outside your callback. Make a deep copy if you need to store the contained value, e.g.: ptr(retval.toString()).

很多时候,我们需要hook系统函数read,查看文件描述符,缓存等信息。

function frida_Java() {
  Java.perform(function () {
        Interceptor.attach(Module.getExportByName(null, 'read'), {
          onEnter: function (args) {
            console.log('Context information:');
            console.log('Context  : ' + JSON.stringify(this.context));
            console.log('Return   : ' + this.returnAddress);
            console.log('ThreadId : ' + this.threadId);
            console.log('Depth    : ' + this.depth);
            console.log('Errornr  : ' + this.err);
        
            // Save arguments for processing in onLeave.
            this.fd = args[0].toInt32();
            this.buf = args[1];
            this.count = args[2].toInt32();
            console.log("fd:"+this.fd+" buf:"+this.buf+" count:"+this.count);
          },
          onLeave: function (retval) {
            console.log("Env对象"+JSON.stringify(Java.vm.getEnv()));
            retval.replace(1337);
            console.log("retval"+retval);
          }
        });
  });
}       
setImmediate(frida_Java,0);

运行结果如下:

在这里插入图片描述关于this比较重要的属性在表格中列了出来:

属性 含义
returnAddress 返回地址,类型是NativePointer
context 上下文:具有键pc和sp的对象,它们是分别为ia32/x64/arm指定EIP/RIP/PC和ESP/RSP/SP的NativePointer对象。其他处理器特定的键也可用,例如eax、rax、r0、x0等。也可以通过分配给这些键来更新寄存器值
errno 当前errno值
lastError 当前操作系统错误值
threadId 操作系统线程ID
depth 相对于其他调用的调用深度
Interceptor.replace

相当于替换掉原本的函数,用替换时的实现替换目标处的函数。如果想要完全或部分替换现有函数的实现,则通常使用此函数。
示例代码如下,替换open函数,打印出打开文件的文件描述符和文件路径。

var openPtr = Module.getExportByName('libc.so', 'open');
var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
Interceptor.replace(openPtr, new NativeCallback(function (pathPtr, flags) {
  var path = pathPtr.readUtf8String();
  log('Opening "' + path + '"');
  var fd = open(pathPtr, flags);
  log('Got fd: ' + fd);
  return fd;
}, 'int', ['pointer', 'int']));

运行结果如下:
在这里插入图片描述

Frida-trace

在这里插入图片描述官网首页就给出了frida-trace的用法,可见其功能强大。这里以windows记事本为例,将记事本打开的文件路径打印出来。
Frida-trace是一个动态跟踪函数调用的工具,其强大之处在于能够hook系统函数。

在Windwos下打开函数使用的是CreateFileW函数,
在这里插入图片描述可以看出在Kernel32.dll中导出的。
在这里插入图片描述
使用frida-trace -i CreateFileW notepad.exe开启跟踪,修改相应的文件如下:

在这里插入图片描述
使用readUtf16String是因为Windwos使用Unicode字符编码。通过记事本打开Warcraft目录下的ij115.dll。结果如下,打开路径被成功打印出来。

在这里插入图片描述

写在最后

Frida hook系统API是如此的简单,不得不说,Frida is so great!

公众号

更多Frida内容,欢迎关注我的微信公众号。

在这里插入图片描述

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐