Frida Hook 常用函数、java 层 hook、so 层 hook、RPC、群控

本文详细介绍了 Frida Hook 的使用,包括使用 IDE 如 VSCode、PyCharm 的智能提示,JS 单步调试,以及基础的 Frida 代码完成。重点讲解了 Java 层和 SO 层的 Hook,如枚举类加载器、JNI 函数、内部类和动态加载的 Dex 文件。此外,还涵盖了 RPC 的实现、群控软件的运用,以及 Frida 的多种 Hook 方法,如拦截函数调用、修改参数和返回值。文章还提供了丰富的实例代码和参考链接,是 Frida Hook 实践的全面指南。
摘要由CSDN通过智能技术生成

From:Frida hook 常用函数分享:https://www.52pojie.cn/thread-1196917-1-1.html
From:Frida Hook Android 常用方法:https://blog.csdn.net/zhy025907/article/details/89512096

Frida 使用:https://zhuanlan.zhihu.com/p/339504595

Frida 官方手册 - 函数 Hook:https://blog.csdn.net/freakishfox/article/details/78289293
Frida Java Hook 详解:代码及示例 (上):https://www.secpulse.com/archives/132082.html

写 APP爬虫会需要用到哪些工具呢?https://zhuanlan.zhihu.com/p/63899519

风控要略——互联网业务反欺诈之路主要分为洞察黑产、体系构建、实战教程和新的战场4个部分

使用 IDE 编写 frida js 时智能提示

工欲善其事,必先利其器。编写 frida js 时让 IDE 智能提示

方法 1:

  • git clone https://github.com/oleavr/frida-agent-example.git
  • cd frida-agent-example/,执行命令 npm install
  • 然后使用 VSCode、pycharm、idea 等 IDE 打开此工程,在 agent 目录下编写 JavaScript 代码时就会有智能提示。

JS单步调试

https://bbs.pediy.com/thread-265160.htm

能愉快的单步调试 frida 的 js 脚本,可以方便不少。首先运行 frida 脚本

frida -l </Users/name/path/test.js> --debug --runtime=v8 <port/name>

或者

session = dev.attach(app.pid)
script = session.create_script(jscode, runtime="v8")
session.enable_debugger()

启动后会回显 Inspector 正在监听 9229 默认端口

Chrome Inspector server listening on port 9229

chrome

打开 chrome://inspect, 点击 Open dedicated DevTools for Node。

此时 debug 已经连接,切换至 Sources,按 Command + P 加载要调试的脚本,即可下断调试了。

pycharm

首先安装 Node.js 插件,重启。添加调试器 Attaching to Node.js/Chrome,端口默认即可。Attach to 应选择 Node.js < 8 started with --debug, 下面的自动重连选项可选可不选。

触发断点需要在 debug 窗口切换到 script 选项卡,右键要调试的脚本,选择 Open Actual Source,在新打开的 Actual Source 窗口设置好断点后,需要再取消/启用一次所有断点作为激活,发现断点上打上对勾才真正可用了。

接下来就可以愉快的调试了

优缺点

  1. 用 Chrome 调试支持的更为顺滑,调试脚本自动重加载,断点也能正确响应。
  2. 用 PyCharm 调试断点有时需要手动激活有点麻烦,但可以使用PyCharm 的Debug 窗口和快捷键。
  3. PyCharm 使用 ts 环境调试时,可以直接在ts文件上下断,也不需要手动激活断点。

方法 2:

https://bbs.pediy.com/thread-258513.htm

基础 frida 代码完成

frida 代码提示插件。如果你安装了 Frida, 不管你熟不熟悉 nodejs 的生态, 肯定已经安装好了

npm install @types/frida-gum

你需要在你编写注入 js 文件的目录下运行 ( 可以不事先创建 package.json,只是会出现一条警告 )

然后使用可以TypeScript代码完成功能的编辑器 ( 比如 vscode、pycharm、idea ) 打开js文件即刻。

"基础 frida 代码完成" 就可以补全 frida js 代码,如果想要 类成员函数及成员变量的类型等功能,可以安装插件 frida-tsplugin 

下载并安装插件 frida-tsplugin 

在任意目录下:git clone https://github.com/tacesrever/frida-tsplugin ,然后在 frida-tsplugin 目录下运行:

npm install
npm run compile

frida-tsplugin 特性

  1. 可以识别 Java.use 和 Java.cast
  2. 可以追踪变量赋值传递
  3. 可以识别并追踪类成员函数及成员变量的类型
  4. 可以根据重载函数的参数类型识别对应的重载函数
  5. 可以识别 someJavaFunction[.overload(...)].implementation = function(...) {...} 函数块中的参数类型和this类型

ps. 对于未能追踪到的类型, 可以使用 Java.cast 来为其做一个声明

了解更多:http://www.yxfzedu.com/rs_show/115

Frida 简单汇总

内部类的调用

因为是内部类,所以需要在 "类名后面使用$加内部类的名字" 去引用

//获取内部类的类包
var clazzInner = Java.use("com.example.demo.Activity.MainActivity4$InnerClasses");
//getDeclaredMethods(); 获取声明当前类的方法,一般用于内部类的枚举
var all_method = clazzInner.class.getDeclaredMethods();

枚举 类加载器

一般用于判断 当前类中有多少函数和类 配合console.log() 在onMatch中 将函数和类全部打印出来

//只有一个参数就是回调函数 回调函数中包括 onMatch(instance),onComplete();
Java.enumerateClassLoaders({callback})

//在函数后加上.$className 就可以获取类名
onMatch(instance) // "实例对象" 或者 "找到的函数名" 或者 "获取类名"
console.log(instance.getDynamicDexCheck().$className)
this.$init(key); // 也可以直接使用this调用自己 这样就可以打印调用自身后key的值 一般用于查找flags

列举 加载的类

enumerateLoadedClasses({}) // 一般用于枚举类的加载

打开一个 dex 文件

Java.openClassFile("path");
Java.load(); // 加载打开的dex文件一般都是组合使用

获取 name.so 的基地址

var Base = Module.findBaseAddress("libmyjni.so");
// 查找按名称导出 俩个参数 参数1: 导出文件,参数2: 导出文件中的函数名
var n2 = Module.findExportByName("xxx.so","n2")

拦截对目标函数的调用 Interceptor

Interceptor.attach(n2,{
    onEnter : function(argc){   
        // argc 表示 n2函数的参数 argc是一个数组 可以由[x]去表示出来导出函数的参数
        console.log(" address: ",argc[0],argc[1],argc[3]);
        },
    onLeave : function(retval){ 
        // retval 返回值
    }
})

JNI 函数、libart.so

在 android 8.0 中所有的 JNI 函数都在 libart 的 so 里面,所以直接 hook libart.so 遍历寻找要找的JNI 函数名。

// 一般用于查找native层的JNI函数,返回一个模块,其地址或名称相匹配所指定的一个。
// 如果找不到此类模块, 则函数将返回null
var module = Process.findModuleByName("libart.so"); 

// enumerateSymbols 是 枚举模块的符号 
// 枚举模块的符号,   一般这两个组合使用,来寻找 JNI 函数并且拿到JNI函数的符号(NameString)
var symbols = module.enumerateSymbols();  

// 一般用于判断 当前的符号中 是否有"art"这个字符  
if(name.indexOf("art") >= 0)

以可写的方式打开 path File

var file = new File("/sdcard/reg.dat","w");

setImmediate(main)

为什么要使用 setImmediate 去调用 main 函数 ?

为了防止这个发生,要么在函数 setImmediate 中给你的脚本添加一层包装,要么export them as rpc。Frida中的RPC默认不会超时,一旦你修改了脚本,setImmediate 就会自动返回它,因此这相当方便。

FridaContainer 脚本集分享

From:https://bbs.pediy.com/thread-265160.htm

FridaContainer 整合了网上流行的和平时常用的 frida 脚本,为逆向工作提效之用。

地址:https://github.com/deathmemory/FridaContainer

反反调试 anti-anti-debug

整理了一些普通反调试的绕过或定位的方法。因为很多反调试的手段是通过读取各个状态文件,查找特征字符串来判断是否被调试,而读取文件内容又通常都用到了 fgets 函数,如此我们就直接 hook 此函数加入过滤规则就能过滤掉许多反调试检测。

例如:/proc/<pid>/status 检测 TracerPid: 0 、 State: S (sleeping) 、 SigBlk: 0000000000001204 ,/proc/<pid>/stat 检测 t (tracing stop)/proc/<pid>/wchan 检测 SyS_epoll_wait 等

Hook fgets 后,触发时首先获取一下 LR 寄存器的值,保存一下现场,之后返回信息时可以把 LR 带出来,方便定位。然后调用原函数,对赋值的 buffer 进行过滤就可以了。

FridaContainer 调用:FCAnd.anti.anti_debug();

小结:当上面的方法无法绕过反调试时,可以再 Hook 一些常用的退出函数来定位反调点,比如 hook exitkill ,再总结出一些其他过反调的方法,思路类似。

anti-ssl-pinning

我们也在 FridaContainer 里面集成了 Frida 版的 JustTrustMe 来过 SSL Pinning 检测。

此部分代码主要借鉴了:https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/

支持 20 种类库的SSL 验证绕过:

  • TrustManager (Android < 7)
  • TrustManagerImpl (Android > 7)
  • OkHTTPv3 (quadruple bypass)
  • Trustkit (triple bypass)
  • Appcelerator Titanium
  • OpenSSLSocketImpl Conscrypt
  • OpenSSLEngineSocketImpl Conscrypt
  • OpenSSLSocketImpl Apache Harmony
  • PhoneGap sslCertificateChecker
  • IBM MobileFirst pinTrustedCertificatePublicKey (double bypass)
  • IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass)
  • Conscrypt CertPinManager
  • CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager
  • Worklight Androidgap WLCertificatePinningPlugin
  • Netty FingerprintTrustManagerFactory
  • Squareup CertificatePinner [OkHTTP<v3] (double bypass)
  • Squareup OkHostnameVerifier [OkHTTP v3] (double bypass)
  • Android WebViewClient (double bypass)
  • Apache Cordova WebViewClient
  • Boye AbstractVerifier

FridaContainer 调用:FCAnd.anti.anti_ssl_unpinning();

小结:主要的出发点就是想摆脱 Xposed 框架,调试时减少被检测的风险,要绕过检测只绕 Frida 一个就好了。

dump dex

这里的 dump dex 是 dump 二代壳,通过 hook art so 的 art::DexFile::OpenCommon (_ZN3art7DexFile10OpenCommonEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_PNS0_12VerifyResultE) dump 动态加载的 dex 文件。通过 frida 动态加载自定义的 dex,在 dex 中实现类的枚举和主动加载,来尝试加载所有的类,使其触发 dex 动态加载,再通过之前的 hook ,将加载的 dex dump 下来。

FridaContainer 调用:FCAnd.dump_dex_common();

也有通过搜索内存的方式将 dex dump 出来  https://github.com/hluwa/FRIDA-DEXDump

multi-dex

有时候遇到动态加载的 dex 又不是用的 application 的 loader ,Java.use 可能会提示找不到类,遇到这种情况可以用修改 java loader 的方式来应对。
比如遇到利用 InMemoryDexClassLoader 来加载内存中的 dex 时,可以 Hook 它,当其触发时先走原流程,让其动态加载 dex ,然后利用此时的 loader object 修改 Java.classFactory.loader,再使用 Java.use 来获取类就可以了,使用后记得恢复现场,否则会崩溃。

动态加载实例:

完整代码: 

function anti_InMemoryDexClassLoader(callbackfunc) {
    //  dalvik.system.InMemoryDexClassLoader
    const InMemoryDexClassLoader = Java.use('dalvik.system.InMemoryDexClassLoader');
    InMemoryDexClassLoader.$init.overload('java.nio.ByteBuffer', 'java.lang.ClassLoader')
        .implementation = function (buff, loader) {
        this.$init(buff, loader);
        var oldcl = Java.classFactory.loader;
        Java.classFactory.loader = this;
        callbackfunc();
        Java.classFactory.loader = oldcl; // 恢复现场
    }
}

使用:

FCAnd.anti.anti_InMemoryDexClassLoader(function(){
    const cls = Java.use('find/same/multi/dex/class');
    ...
});

frida 的 java 反射调用

有时 dex 使用了一些非常规的动态加载方式,找 loader 定位起来也比较麻烦。假设我们只想调用某 jni 函数,看输入输出值的话,还可以用 frida java 反射的方式调用。

整体调用流程:

// 获取 so 基址
var base = Module.findBaseAddress('libxxxx.so');
// 根据偏移获取 jni 函数地址
var jnifunc_ptr = libsgmainso.add(0xE729);
// 声明 jni 函数
var jnifunc = new NativeFunction(jnifunc_ptr, 'pointer', ['pointer', 'pointer', 'int', 'pointer']);
// ********* 拼装 obj *********
// JNIEnv
var env = Java.vm.getEnv();
// 调用
var retval = jnifunc(env.handle,  ptr(0),  10401,  obj);

拼装 Obj:

// staticVaMethod 实现 Integer.valueOf(7)
const Integer_jcls = env.findClass('java/lang/Integer');
const Integer_valueOf = env.getStaticMethodId(Integer_jcls, 'valueOf', '(I)Ljava/lang/Integer;');
const invorkeStaticOjbectMethod = env.staticVaMethod('pointer', ['int']);
var pIn2 = invorkeStaticOjbectMethod(env.handle, Integer_jcls, Integer_valueOf, 7);
// 利用 constructor | vaMethod 组装 HashMap obj
// new HashMap().put('INPUT', 'xxxxxxxxxx')
const HashMap_jcls = env.findClass('java/util/HashMap');
const invokeHashmap_constructor = env.constructor([]);
const HashMap_init = env.getMethodId(HashMap_jcls, '<init>', '()V');
var HashMap_obj = invokeHashmap_constructor(env.handle, HashMap_jcls, HashMap_init);
const HashMap_put = env.getMethodId(HashMap_jcls, 'put', '(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;');
const invokeOjbectMethod = env.vaMethod('pointer', ['pointer', 'pointer']);
invokeOjbectMethod(env.handle, HashMap_obj, HashMap_put, env.newStringUtf('INPUT'), env.newStringUtf('xxxxxxxxxx'));

上面都是在反射调用的角度简单介绍了 staticVaMethodvaMethodconstructor 的使用方法。

trace java methods

用 Frida 也可以实现 java 层的 trace 。
因为基于 Frida 框架,如果直接 trace 所有的类效率太慢,也容易崩溃。所以这里是以白名单的方式实现的。核心方法就是枚举所有类,按过滤名单,匹配需要 trace 的类,Hook 目标类的所有方法(可指定),在方法被调用时,将其入参和返回值记录下来。

核心逻辑:遍历所有类,Hook 白名单中的类及方法。

调用方式:

/**
 * java 方法追踪
 * @param clazzes 要追踪类数组 ['M:Base64', 'E:java.lang.String'] ,类前面的 M 代表 match 模糊匹配,E 代表 equal 精确匹配
 * @param whitelist 指定某类方法 Hook 细则,可按白名单或黑名单过滤方法。
 *                  { '类名': {white: true, methods: ['toString', 'getBytes']} }
 * @stackFilter 按匹配字串打印堆栈。如果要匹配 bytes 数组需要十进制无空格字串,例如:"104,113,-105"
 */
FCAnd.traceArtMethods(
    ['M:MainActivity', 'E:java.lang.String'],
    {'java.lang.String': {white: true, methods:['substring', 'getChars']}},
    "match_str_show_stacks"
);
  • 支持精确/模糊匹配类名
  • 支持某类按白名单方式 trace 方法
  • 支持匹配到指定值时收集栈信息

具体实现:

Java.enumerateLoadedClassesSync().forEach((curClsName, index, array) => {
    dest_cls.forEach((destCls) => {
        // 按规则匹配是否需要 trace
        if (match(destCls, curClsName)) {
            // trace 核心方法
            traceArtMethodsCore(curClsName);
            return false; // end forEach
        }
    });
});
// Hook 核心逻辑
function traceArtMethodsCore(clsname: string) {
    let cls = Java.use(clsname);
    // 枚举方法
    let methods = cls.class.getDeclaredMethods();
    methods.forEach(function (method: any) {
        ...
        // 枚举重载
        let methodOverloads = cls[methodName].overloads;
        methodOverloads.forEach(function (overload: any) {
            ...
            // Hook
            overload.implementation = function () {
                // ... send entry msg
                // 利用 js 参数特性 arguments ,调用原函数以适配所有 Hook 方法的传参
                const retval = this[methodName].apply(this, arguments);
                // ... send exit msg
                return retval;
            }
        }
    }
}

通过 python/android/traceLogCleaner.py 脚本收集 trace 日志,将回传的日志按线程格式化输出日志,并且对字节数组,尝试进行 string 和 hex 转换以方便搜索。

格式化 trace 效果:

单条日志:

小结:

  • 优点:用 Frida 做 java 方法级的 trace ,优点就是方便、灵活、轻量级。
  • 缺点:限于 Frida 框架,该方式效率较低,trace 方法过多容易崩溃,所以无法做全量 trace。

推荐用于轻量级的 Java 方法 trace 可以有效的定位核心算法。

jni hook & trace

利用 Frida 的 Java.vm.getEnv() 获取 JNIEnv 指针,再根据 jni 结构体函数偏移,可以获取到各个 jni 函数地址,之后就可以根据需要进行 Hook 了。
例如

  • 可以将其封装成更便捷的获取各 jni 函数地址的功能,方便 Hook

核心逻辑:

// 列出 JNI 函数数组
const jni_struct_array = [
    "reserved0",
    "reserved1",
    "reserved2",
    "reserved3",
    "GetVersion",
    "DefineClass",
    "FindClass",
    "FromReflectedMethod",
    ...
];
// 获取 JNIEnv 地址
var env = Java.vm.getEnv();
var env_ptr = env.handle.readPointer();
// 根据函数名计算索引偏移
var offset = jni_struct_array.indexOf(func_name) * Process.pointerSize;
// 读取函数地址
jnienv_addr.add(offset).readPointer();
// Hook
Interceptor.attach(addr, callbacksOrProbe);

应用:

FCAnd.jni.hookJNI('NewStringUTF', {
    onEnter: function (args) {
      ...
    }
});
  • 可以 Hook RegistNatives 来获取动态注册的 jni 函数地址
export function hook_registNatives() {
    const tag = 'fridaRegstNtv';
    Jni.hookJNI("RegisterNatives", {
        onEnter: function (args) {
            var env = Java.vm.getEnv();
            var p_size = Process.pointerSize;
            var methods = args[2];
            var methodcount = args[3].toInt32();
            // 获取类名
            var name = env.getClassName(args[1]);
            DMLog.i(tag, "==== class: " + name + " ====");
            DMLog.i(tag, "==== methods: " + methods + " nMethods: " + methodcount + " ====");
            /** 根据函数结构原型遍历动态注册信息
             typedef struct {
                const char* name;
                const char* signature;
                void* fnPtr;
             } JNINativeMethod;
             jint RegisterNatives(JNIEnv* env, jclass clazz, const JNINativeMethod* methods, jint nMethods)
             */
            for (var i = 0; i < methodcount; i++) {
                var idx = i * p_size * 3;
                var fnPtr = methods.add(idx + p_size * 2).readPointer();
                const module = Process.getModuleByAddress(fnPtr);
                if (module) {
                    const modulename = module.name;
                    const modulebase = module.base;
                    var logstr = "name: " + methods.add(idx).readPointer().readCString()
                        + ", signature: " + methods.add(idx + p_size).readPointer().readCString()
                        + ", fnPtr: " + fnPtr
                        + ", modulename: " + modulename + " -> base: " + modulebase;
                    if (null != modulebase) {
                        logstr += ", offset: " + fnPtr.sub(modulebase);
                    }
                    DMLog.i(tag, logstr);
                }
                else {
                    DMLog.e(tag, 'module is null');
                }
            }
        }
    });
}

返回效果

==== class: com.xxxx.class.name ====
==== methods: 0xcd52d428 nMethods: 41 ====
[INFO][fridaRegstNtv]: name: initialize, signature: ()V, fnPtr: 0xcd50b6bd, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x66bd
[INFO][fridaRegstNtv]: name: onExit, signature: ()V, fnPtr: 0xcd50b6c7, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x66c7
[INFO][fridaRegstNtv]: name: getMMKVWithID, signature: (Ljava/lang/String;ILjava/lang/String;)J, fnPtr: 0xcd50b6d1, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x66d1                  
[INFO][fridaRegstNtv]: name: encodeBool, signature: (JLjava/lang/String;Z)Z, fnPtr: 0xcd50b76d, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x676d
[INFO][fridaRegstNtv]: name: decodeBool, signature: (JLjava/lang/String;Z)Z, fnPtr: 0xcd50b7bf, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x67bf
[INFO][fridaRegstNtv]: name: encodeInt, signature: (JLjava/lang/String;I)Z, fnPtr: 0xcd50b80f, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x680f
[INFO][fridaRegstNtv]: name: decodeInt, signature: (JLjava/lang/String;I)I, fnPtr: 0xcd50b85b, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x685b
[INFO][fridaRegstNtv]: name: encodeLong, signature: (JLjava/lang/String;J)Z, fnPtr: 0xcd50b8a5, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x68a5
[INFO][fridaRegstNtv]: name: decodeLong, signature: (JLjava/lang/String;J)J, fnPtr: 0xcd50b8f7, modulename: libxxxx.so -> base: 0xcd505000, offset: 0x68f7
[INFO][fridaRegstNtv]: name: encodeFloat,
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值