Java对象

  • 无论是想对so层亦或java层进行拦截,都必须编写Java.perform().

Java.available()

  • 用来判断当前进程是否加载了JavaVM,Dalvik或ART虚拟机.
function frida_Java(){
    Java.perform(function () {
        if(Java.available)
        {
            console.log("hello java vm");
        }
        else {
            console.log("error");
        }
    });
}

setImmediate(frida_Java,0);
  • 运行代码,效果如图:

Java.androidVersion

  • 显示Android系统版本号
function frida_Java(){
    Java.perform(function () {
        if(Java.available)
        {
            console.log("", Java.androidVersion);
        }
        else {
            console.log("error");
        }
    });
}

setImmediate(frida_Java,0);
  • 运行效果如图:

Java.enumerateLoadedClasses()

  • 该API枚举当前加载的所有类信息,它有一个回调函数分别是onMatch、onComplete函数.
function frida_Java(){
    Java.perform(function () {
        if(Java.available)
        {
            Java.enumerateLoadedClasses({
               onMatch:function (className) {
                   console.log("",className)
               },
               onComplete:function () {
                   console.log("output complete!!!")
               }
            });
        }
        else {
            console.log("error");
        }
    });
}

setImmediate(frida_Java,0);
  • 运行效果如图:

Java.enumerateClassLoaders()

  • 该api枚举Java VM中存在的类加载器,其有一个回调函数,分别是onMatch: function (loader)与onComplete: function ().
function frida_Java(){
    Java.perform(function () {
        if(Java.available)
        {
            Java.enumerateClassLoaders({
               onMatch:function (loader) {
                   console.log("",loader);
               },
               onComplete:function () {
                   console.log("output complete!!!");
               }
            });
        }
        else {
            console.log("error");
        }
    });
}

setImmediate(frida_Java,0);
  • 运行效果显示:

Java.perform()

  • Java.perform(fn)主要用于当前线程附加到Java VM并且调用fn方法。
function frida_Java(){
    Java.perform(function () {
        if(Java.available)
        {
            console.log("hello");
        }
        else {
            console.log("error");
        }
    });
}

setImmediate(frida_Java,0);
  • 执行效果如图:

Java.use()

  • Java.use(className),动态获取className的类定义,通过对其调用$ new()来调用构造函数,可以从中实例化对象。当想要回收类时可以调$Dispose()方法显式释放,当然也可以等待JavaScript的垃圾回收机制,当实例化一个对象之后,可以通过其实例对象调用类中的静态或非静态的方法.
function frida_Java(){
    Java.perform(function () {
        var Activity = Java.use('android.app.Activity');
        var Exception = Java.use('java.lang.Exception');
         //调用onResume方法的时候,会在此处被拦截并且调用以下代码抛出异常!
        Activity.onResume.implementation = function () {
          throw Exception.$new('error'); 
        };
    });
}

setImmediate(frida_Java,0);

Java.choose()

  • 在堆上查找已经实例化的对象。
function frida_Java(){
    Java.perform(function () {
        Java.choose("android.view.View",{
            onMatch:function (instance) {
                console.log(instance);
            },
            onComplete:function () {
                console.log("end");
            }
        });
    });
}

setImmediate(frida_Java,0);
  • 运行结果如图:

Java.cast()

  • Java.cast(handle, klass),就是将指定变量或者数据强制转换成你所有需要的类型;

Java.array

  • frida提供了在js代码中定义java数组的api,该数组可以用于传递给java API.
function frida_Java(){
    Java.perform(function () {
       var intarr = Java.array('int', [1003, 1005, 1007]);
       var bytearr = Java.array('byte', [0x48, 0x65, 0x69]);
       for(var i=0;i<bytearr.length;i++)
       {
           console.log(bytearr[i]);
       }
    });
}

setImmediate(frida_Java,0);
  • 运行效果如图:

Java.registerClass(spec)

  • Java.registerClass:用于注册一个类到内存,这个类可以是我们自己定义的,也就是说我们可以通过这个方式来自定义类加入到内存中,也可以是已经存在的类,其中规范是一个包含:
  1. name:指定类名称的字符串。
  2. superClass:(可选)父类。要从 java.lang.Object 继承的省略。
  3. implements:(可选)由此类实现的接口数组。
  4. fields:(可选)对象,指定要公开的每个字段的名称和类型。
  5. methods:(可选)对象,指定要实现的方法。
  • 注册一个类,返回类的实例。
function frida_Java(){
    Java.perform(function () {
      var hellojni = Java.registerClass({
            name: 'com.example.demo01.hellojni'
          });
      var instance = hellojni.$new();
          console.log(instance.addInt(1,2));
    });
}

setImmediate(frida_Java,0);
  • 运行结果如下图:

Java.vm

  • 比如想要拿到JNI层的JNIEnv对象,可以使用Java.vm.getEnv();

Interceptor对象

  • Interceptor.attach(target, callbacks):参数target是需要拦截的位置的函数地址,也就是填某个so层函数的地址即可对其拦截.
  • target是一个NativePointer参数,用来指定你想要拦截的函数的地址。

Interceptor.attach

- onEnter:function(args) :  在原方法被调用前触发.args 是一个可以用来读写原方法参数的 NativePointer 数组.
- onLeave:function(retral): 在原方法返回前触发. retval 是一个含有原返回值的 NativePointer 驱动的对象.可以调用 retval.replace(1337) 以整数 1337 替换返回值,或者调用 retval.replace(ptr("0x1234"))以替换为指针。
- 示例代码如下:
function frida_Java(){
    Java.perform(function () {
    //使用Module对象getExportByNameAPI直接获取libc.so中的导出函数read的地址,对read函数进行附加拦截
      Interceptor.attach(Module.findExportByName('libc.so','read'),{
      //每次read函数调用的时候会执行onEnter回调函数
            onEnter: function(args) {
                this.fd = args[0].toInt32();
            },
	//read函数执行完成之后会执行onLeave回调函数
            onLeave:function(retval){
                if(retval.toInt32() > 0)
                {
                    console.log("do something")
                }
            }
      });
    });
}

setImmediate(frida_Java,0);

另外, 这个对象包含一些实用的属性:

  • returnAddress: NativePointer 类型的返回地址
  • context: 带有键 pc 和 sp 的 NativePointer 对象, 分别为 ia32 / x64 / arm 指定 EIP / RIP / PC 和 ESP / RSP / SP. 也可以使用其他处理器特定的键, 例如 eax, rax, r0, x0等.
  • errno: (UNIX) 当前的 errno 值 (您可以替换它)
  • lastError: (Windows) 当前操作系统的 error 值 (您可以替换它)
  • threadId: 操作系统线程 ID
  • depth: 相对其他调用的调用深度.
  • 代码示例如下:
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();
          },
          onLeave: function (result) {
            console.log('----------');
            // Show argument 1 (buf), saved during onEnter.
            var numBytes = result.toInt32();
            if (numBytes > 0) {
              console.log(hexdump(this.buf, { length: numBytes, ansi: true }));
            }
            console.log('Result   : ' + numBytes);
          }
        });
    });
}

setImmediate(frida_Java,0);

Interceptor.detachAll

  • 让之前所有的Interceptor.attach附加拦截的回调函数失效。

Interceptor.replace

  • 相当于替换掉原本的函数,用替换时的实现替换目标处的函数。
  • 示例如下:
function frida_Java(){
    Java.perform(function () {
        //这个getSum方法有两个int参数、返回结果为两个参数相加
        let add_method = new NativeFunction(Module.findExportByName('libnative-lib.so','Java_com_example_ndkdemo_MainActivity_getSum'),'int',['int','int']);
        console.log("result:",add_method(1,2));
        Interceptor.replace(add_method, new NativeCallback(function (a,b) {
            //这里对原函数的功能进行替换实现
            return 123;
        },'int',['int','int']));
        console.log("result:",add_method(1,2));
        console.log("------------------------------")
    });
}

setImmediate(frida_Java,0);

NativePointer对象

  • 同等与C语言中的指针

new NativePointer(s)

  • 声明定义NativePointer类型
function frida_Java(){
    Java.perform(function () {
        let ptr1 = new NativePointer("100");
        console.log("ptr1 -> ", ptr1);
        let ptr2 = new NativePointer("0x64");
        console.log("ptr2 -> ", ptr2);
        let ptr3 = new NativePointer(100);
        console.log("ptr3 -> ",ptr3);
        console.log("---------------------")
    });
}

setImmediate(frida_Java,0);
  • 上述三个指针都会自动转为十六进制的0x64
ptr1 ->  0x64
ptr2 ->  0x64
ptr3 ->  0x64
---------------------

运算符以及指针读写API

frida java api_frida java api


frida java api_android_02

readByteArray()

function frida_Java(){
    Java.perform(function () {
       let base_pointer = Process.findModuleByName('libc.so').base;
        console.log(base_pointer.readByteArray(0x10));
    });
}

setImmediate(frida_Java,0);
  • 运行效果如图:

readPointer()

function frida_Java(){
    Java.perform(function () {
       let base_pointer = Process.findModuleByName('libc.so').base;
        console.log(base_pointer.readPointer());
    });
}

setImmediate(frida_Java,0);
  • readPointer的将前四个字节的内容转成地址产生一个新的NativePointer。

writePointer(ptr)

function frida_Java(){
    Java.perform(function () {
       let base_pointer = Process.findModuleByName('libc.so').base;
        console.log(base_pointer);
        let r = Memory.alloc(4);
        r.writePointer(base_pointer);
        let readByteArray = Memory.readByteArray(r,4);
        console.log(readByteArray);
        console.log("----------------------");
    });
}

setImmediate(frida_Java,0);

frida java api_frida java api_03

readS32()、readU32()

  • 从该内存位置读取有符号或无符号8/16/32/etc或浮点数/双精度值,并将其作为数字返回。
function frida_Java(){
    Java.perform(function () {
       let base_pointer = Process.findModuleByName('libc.so').base;
        console.log(base_pointer);
        console.log(base_pointer.readS32());
        console.log(base_pointer.readU32());
        console.log("------------------------");
    });
}

setImmediate(frida_Java,0);

writeS32()、writeU32()

  • 将有符号或无符号8/16/32/等或浮点数/双精度值写入此内存位置。
function frida_Java(){
    Java.perform(function () {
       let base_pointer = Process.findModuleByName('libc.so').base;
       let r = Memory.alloc(4);
       r.writeS32(0x12345678);
        console.log(r.readByteArray(0x10));
    });
}

setImmediate(frida_Java,0);

frida java api_Java_04

readByteArray(length))、writeByteArray(bytes)

  • readByteArray(length))连续读取内存length个字节,、writeByteArray连续写入内存bytes。
function frida_Java(){
    Java.perform(function () {
      var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
      let r = Memory.alloc(arr.length);
      Memory.writeByteArray(r,arr);
      let buffer = Memory.readByteArray(r, arr.length);
        console.log(hexdump(buffer, {
            offset: 0,
            length: arr.length,
            header: true,
            ansi: false
        }));
        console.log("----------------------")
    });
}

setImmediate(frida_Java,0);

frida java api_java_05

readCString([size = -1])、writeUtf8String(str)

  • readCString功能是读取指针地址位置的字节字符串,对应的writeUtf8String是写入指针地址位置的字符串处。
function frida_Java(){
    Java.perform(function () {
      var arr = [ 0x72, 0x6F, 0x79, 0x73, 0x75, 0x65];
      let r = Memory.alloc(arr.length);
      Memory.writeByteArray(r,arr);
      let buffer = Memory.readByteArray(r, arr.length);
        console.log(hexdump(buffer, {
            offset: 0,
            length: arr.length,
            header: true,
            ansi: false
        }));
        console.log("----------------------")
        console.log("readCString():" + r.readCString());
        let newPtrstr = r.writeUtf8String("hhhhhhh");
        console.log("readCString():" + newPtrstr.readCString());
    });
}

setImmediate(frida_Java,0);

NativeFunction对象

  • 创建新的NativeFunction以调用address处的函数(用NativePointer指定),其中rereturn Type指定返回类型,argTypes数组指定参数类型
//创建friendlyFunctionPtr地址的函数
var friendlyFunctionName = new NativeFunction(friendlyFunctionPtr,
    'void', ['pointer', 'pointer']);
//申请内存空间    
var returnValue = Memory.alloc(sizeOfLargeObject);
//调用friendlyFunctionName函数
friendlyFunctionName(returnValue, thisPtr);
  • 函数定义格式为new NativeFunction(address, returnType, argTypes[, options]),参照这个格式能够创建函数并且调用!returnType和argTypes[,]分别可以填void、pointer、int、uint、long、ulong、char、uchar、float、double、int8、uint8、int16、uint16、int32、uint32、int64、uint64这些类型,根据函数的所需要的type来定义即可
  • 在定义的时候必须要将参数类型个数和参数类型以及返回值完全匹配,假设有三个参数都是int,则new NativeFunction(address, returnType, [‘int’, ‘int’, ‘int’]),而返回值是int则new NativeFunction(address, ‘int’, argTypes[, options]),必须要全部匹配,并且第一个参数一定要是函数地址指针。

NativeCallback对象

  • new NativeCallback(func,rereturn Type,argTypes[,ABI]):创建一个由JavaScript函数func实现的新NativeCallback,其中rereturn Type指定返回类型,argTypes数组指定参数类型。
  • 返回的对象也是一个NativePointer,因此可以传递给Interceptor#replace。当将产生的回调与Interceptor.replace()一起使用时,将调用func,并将其绑定到具有一些有用属性的对象,就像Interceptor.Attach()中的那样。
  • Example:
Java.perform(function () {
       var add_method = new NativeFunction(Module.findExportByName('libhello.so', 'c_getSum'), 
       'int',['int','int']);
       console.log("result:",add_method(1,2));
       //在这里new一个新的函数,但是参数的个数和返回值必须对应
       Interceptor.replace(add_method, new NativeCallback(function (a, b) {
            return 123;
       }, 'int', ['int', 'int']));
       console.log("result:",add_method(1,2));
    });