frida-native

frida hook native 模板

感谢正己大佬开源:安卓逆向这档事

常用api(获取地址)

Process

Process 对象代表当前被Hook的进程,能获取进程的信息,枚举模块,枚举范围等

API 含义
Process.id 返回附加目标进程的 PID
Process.isDebuggerAttached() 检测当前是否对目标程序已经附加
Process.enumerateModules() 枚举当前加载的模块,返回模块对象的数组
Process.enumerateThreads() 枚举当前所有的线程,返回包含 id, state, context 等属性的对象数组

Module

Module 对象代表一个加载到进程的模块(例如,在 Windows 上的 DLL,或在 Linux/Android 上的 .so 文件),能查询模块的信息,如模块的基址、名称、导入/导出的函数等

API 含义
Module.load() 加载指定so文件,返回一个Module对象
enumerateImports() 枚举所有Import库函数,返回Module数组对象
enumerateExports() 枚举所有Export库函数,返回Module数组对象
enumerateSymbols() 枚举所有Symbol库函数,返回Module数组对象
Module.findExportByName(soName,exportName)、Module.getExportByName(soName,exportName) 寻找指定so中export库中的函数地址,没有会抛出null,异常.
Module.findBaseAddress(name)、Module.getBaseAddress(name) 返回so的基地址

Memory

Memory是一个工具对象,提供直接读取和修改进程内存的功能,能够读取特定地址的值、写入数据、分配内存等

方法 功能
Memory.copy() 复制内存
Memory.scan() 搜索内存中特定模式的数据
Memory.scanSync() 同上,但返回多个匹配的数据
Memory.alloc() 在目标进程的堆上申请指定大小的内存,返回一个NativePointer
Memory.writeByteArray() 将字节数组写入一个指定内存
Memory.readByteArray 读取内存

hook打印基本类型

基本方式

//在Frida脚本中实现native层Hook的API函数是Interceptor.attach(addr, callbacks)
Interceptor.attach(addr, {
    onEnter(args) {
        //在函数调用前产生的回调,在这个函数中可以处理函数参数的相关内容.
        //被Hook的函数参数内容是以数组的方式存储在args中.
    },
    onLeave(retval) {
        //在被Hook的目标函数执行完成后执行的函数.
        //被Hook的函数返回值用retval变量来表示.
    }
});

整数型、布尔值类型、char类型、寄存器内容、hexdump

function hookTest2(){
Java.perform(function(){
    //根据导出函数名打印地址
    var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
    console.log(helloAddr); 
    if(helloAddr != null){
            //Interceptor.attach是Frida里的一个拦截器
        Interceptor.attach(helloAddr,{
                //onEnter里可以打印和修改参数
            onEnter: function(args){  //args传入参数
                console.log(args[0]);  //打印第一个参数的值,指ida的第一个参数,我们要用[2]获得参数.
                console.log(this.context.x1);  // 打印寄存器内容
                console.log(args[1].toInt32()); //toInt32()转十进制
                                    console.log(args[2].readCString()); //读取字符串 char类型
                                    console.log(hexdump(args[2])); //内存dump

            },
            //onLeave里可以打印和修改返回值
            onLeave: function(retval){  //retval返回值
                console.log(retval);
                console.log("retval",retval.toInt32());
            }
        })
    }
})
}

字符串类型

因为这个比较特殊,没法直接打印(不知道为什么).有两种方式,通过类型转化获得,通过获取JNIenv环境获得

function hookTest2(){
    Java.perform(function(){
        //根据导出函数名打印地址
        var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
        if(helloAddr != null){
            Interceptor.attach(helloAddr,{
                //onEnter里可以打印和修改参数
                onEnter: function(args){  //args传入参数
                    // 方法一
                    var jString = Java.cast(args[2], Java.use('java.lang.String'));
                    console.log("参数:", jString.toString());
                    // 方法二
                    var JNIEnv = Java.vm.getEnv();
                    var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();        
                    console.log("参数:", originalStrPtr);                                
                },
                //onLeave里可以打印和修改返回值
                onLeave: function(retval){  //retval返回值
                    var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
                    console.log("返回值:", returnedJString.toString());
                }
            })
        }
    })
}

hook修改参数

整数型

function hookTest3(){
Java.perform(function(){
    //根据导出函数名打印地址
    var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
    console.log(helloAddr);
    if(helloAddr != null){
        Interceptor.attach(helloAddr,{
            onEnter: function(args){  //args参数
                args[2] = ptr(1000); //第一个参数修改为整数 1000,先转为指针再赋值.
                console.log(args[0]);

            },
            onLeave: function(retval){  //retval返回值
                retval.replace(20000);  //返回值修改
                console.log("retval",retval.toInt32());
            }
        })
    }
})
}

字符串型

通过获得JNIenv环境获得字符串,变为cstring读取修改并返回.

function hookTest2(){
Java.perform(function(){
    //根据导出函数名打印地址
    var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
    if(helloAddr != null){
        Interceptor.attach(helloAddr,{
            //onEnter里可以打印和修改参数
            onEnter: function(args){  //args传入参数
                var JNIEnv = Java.vm.getEnv();
                var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();        
                console.log("参数:", originalStrPtr);
                var modifiedContent = "至尊";
                var newJString = JNIEnv.newStringUtf(modifiedContent);
                args[2] = newJString;                                
            },
            //onLeave里可以打印和修改返回值
            onLeave: function(retval){  //retval返回值
                var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
                console.log("返回值:", returnedJString.toString());
                var JNIEnv = Java.vm.getEnv();
                var modifiedContent = "无敌";
                var newJString = JNIEnv.newStringUtf(modifiedContent);
                retval.replace(newJString);
            }
        })
    }
})
}

获取so库内存

var moduleAddr1 = Process.findModuleByName("lib52pojie.so").base;  
var moduleAddr2 = Process.getModuleByName("lib52pojie.so").base;  
var moduleAddr3 = Module.findBaseAddress("lib52pojie.so");

获取函数地址(适用于内联hook,动态加载函数和未导出函数)

function hookTest6(){
    Java.perform(function(){
        //根据导出函数名打印基址
        var soAddr = Module.findBaseAddress("lib52pojie.so");
        console.log(soAddr);
        var funcaddr = soAddr.add(0x1071C);  
        console.log(funcaddr);
        if(funcaddr != null){
            Interceptor.attach(funcaddr,{
                onEnter: function(args){  //args参数

                },
                onLeave: function(retval){  //retval返回值
                    console.log(retval.toInt32());
                }
            })
        }
    })
}

函数地址计算

  1. 安卓里一般32 位的 so 中都是thumb指令,64 位的 so 中都是arm指令
  2. 通过IDA里的opcode bytes来判断,arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph) 输入4)
  3. thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 + 1
    arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移

inlinehook和读写asm

常见inlinehook框架:
Android-Inline-Hook
whale
Dobby
substrate

打印和修改寄存器

function inline_hook() {
    var soAddr = Module.findBaseAddress("lib52pojie.so");
    if (soAddr) {
        var func_addr = soAddr.add(0x10428);//要修改的相应位置
        Java.perform(function () {
            Interceptor.attach(func_addr, {
                onEnter: function (args) {
                    console.log(this.context.x22); //注意此时就没有args概念了
                    this.context.x22 = ptr(1); //赋值方法参考上一节课
                },
                onLeave: function (retval) {
                }
            }
            )
        })
    }
}

解析地址指令为汇编

var soAddr = Module.findBaseAddress("lib52pojie.so");
var codeAddr = Instruction.parse(soAddr.add(0x10428));
console.log(codeAddr.toString());

patch

var soAddr = Module.findBaseAddress("lib52pojie.so");
var codeAddr = soAddr.add(0x10428);
Memory.patchCode(codeAddr, 4, function(code) {
const writer = new Arm64Writer(code, { pc: codeAddr });
writer.putBytes(hexToBytes("20008052"));
writer.flush();
});
function hexToBytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
    return null;
}
len /= 2;
var hexA = new Array();
for (var i = 0; i < len; i++) {
    var s = str.substr(pos, 2);
    var v = parseInt(s, 16);
    hexA.push(v);
    pos += 2;
}
return hexA;
}

arm_asm转hex:https://armconverter.com/

主动调用

喜欢我反复注入调用本地函数吗(bushi)

数据类型

nativefunction

数据类型 描述
void 无返回值
pointer 指针
int 整数
long 长整数
char 字符
float 浮点数
double 双精度浮点数
bool 布尔值

普通调用

var funcAddr = Module.findBaseAddress("lib52pojie.so").add(0x1054C);
//声明函数指针
//NativeFunction的第一个参数是地址,第二个参数是返回值类型,第三个[]里的是传入的参数类型(有几个就填几个)
var aesAddr = new NativeFunction(funcAddr , 'pointer', ['pointer', 'pointer']);
var encry_text = Memory.allocUtf8String("OOmGYpk6s0qPSXEPp4X31g==");    //开辟一个指针存放字符串       
var key = Memory.allocUtf8String('wuaipojie0123456'); 
console.log(aesAddr(encry_text ,key).readCString());

jni的主动调用

ai给的:

Java.perform(() => {
  const module = Process.getModuleByName("libtarget.so");
  const funcAddress = module.base.add(0x1234);

  const nativeFunc = new NativeFunction(
    funcAddress,
    'void',
    ['pointer', 'pointer']
  );

  const env = Java.vm.getEnv();
  if (!env) {
    Java.vm.attachCurrentThread().then(attachedEnv => {
      callFunction(attachedEnv);
    });
  } else {
    callFunction(env);
  }

  function callFunction(env) {
    const context = Java.android.app.ActivityThread.currentApplication();
    nativeFunc(env, context.$handle);
  }
});

动态注册

放到本地文件夹fr中去了,可以直接用.

posted @ 2025-04-16 16:51  T0fV404  阅读(94)  评论(0)    收藏  举报