Featured image of post 安卓逆向入门七:Frida入门

安卓逆向入门七:Frida入门

0x00 什么是Frida

frida官方文档

它是原生应用的Greasemonkey,或者用更专业的术语来说,它是一个动态代码插桩工具包。它允许你将 JavaScript 代码片段或你自己的库注入到 Windows、macOS、GNU/Linux、iOS、watchOS、tvOS、Android、FreeBSD 和 QNX 上的原生应用中。Frida 还提供了基于 Frida API 构建的一些简单工具。这些工具可以直接使用,也可以根据你的需求进行调整,或者作为 API 使用示例。

Frida原理及重要组件

frida注入的原理就是找到目标进程,使用ptrace跟踪目标进程获取mmapdlpoendlsym等函数库的偏移获取mmap在目标进程申请一段内存空间将在目标进程中找到存放frida-agent-32/64.so的空间启动执行各种操作由agent去实现

组件名称 功能描述
frida-gum 提供了inline-hook的核心实现,还包含了代码跟踪模块Stalker,用于内存访问监控的MemoryAccessMonitor,以及符号查找、栈回溯实现、内存扫描、动态代码生成和重定位等功能
frida-core fridahook的核心,具有进程注入、进程间通信、会话管理、脚本生命周期管理等功能,屏蔽部分底层的实现细节并给最终用户提供开箱即用的操作接口。包含了frida-server、frida-gadget、frida-agent、frida-helper、frida-inject等关键模块和组件,以及之间的互相通信底座
frida-gadget 本身是一个动态库,可以通过重打包修改动态库的依赖或者修改smali代码去实现向三方应用注入gadget,从而实现Frida的持久化或免root
frida-server 本质上是一个二进制文件,类似于前面学习到的android_server,需要在目标设备上运行并转发端口,在Frida hook中起到关键作用

Frida与Xposed的对比

工具 优点 缺点
Xposed 直接编写Java代码,Java层hook方便,可打包模块持久化hook 环境配置繁琐,兼容性较差,难以Hook底层代码。
Frida 配置简单,免重启hook。支持Java层和Native层的hook操作 持久化hook相对麻烦

Frida环境配置

  • 推荐用python的虚拟环境env

Python 虚拟环境的创建(venv)

  • Frida安装以及多版本处理
1
pip install frida-tools -i https://pypi.tuna.tsinghua.edu.cn/simple
  • push Frida-server
    • 注意要下载对应版本
    • 不知道手机基础框架(ABI)的,可以使用这个指令:adb shell getprop ro.product.cpu.abi
    • arm64-v8a → 64 位 ARM(主流新机)
    • armeabi-v7a → 32 位 ARM(老机型)
    • x86 / x86_64 → Intel 架构(模拟器或极少数真机)

FIrda基础知识

基础命令

  • 1.frida-ps -U 查看当前手机运行的进程

  • 2.frida-ps --help 查看help指令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
frida-ps --help
使用方式: frida-ps [选项]

选项:
  -h, --help            显示帮助信息并退出
  -D ID, --device ID    连接到具有给定ID的设备
  -U, --usb             连接到USB设备
  -R, --remote          连接到远程frida-server
  -H HOST, --host HOST  连接到HOST上的远程frida-server
  --certificate CERTIFICATE
                        与HOST进行TLS通信,期望的CERTIFICATE
  --origin ORIGIN       连接到设置了"Origin"头为ORIGIN的远程服务器
  --token TOKEN         使用TOKEN验证HOST
  --keepalive-interval INTERVAL
                        设置心跳包间隔(秒),或设置为0以禁用(默认为-1,根据传输方式自动选择)
  --p2p                 与目标建立点对点连接
  --stun-server ADDRESS
                        设置与--p2p一起使用的STUN服务器地址
  --relay address,username,password,turn-{udp,tcp,tls}
                        添加与--p2p一起使用的中继
  -O FILE, --options-file FILE
                        包含额外命令行选项的文本文件
  --version             显示程序版本号并退出
  -a, --applications    只列出应用程序
  -i, --installed       包括所有已安装的应用程序
  -j, --json            以JSON格式输出结果
  • 3.操作模式 CLI模式:通过命令行直接将JavaScript脚本注入进程中。 RPC模式:使用Python进行JavaScript脚本的注入,适合复杂数据处理。

  • 4.注入模式与启动命令

    • Spawn模式

    将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App

    当需要监控App从启动开始的所有行为时使用 frida -U -f 进程名 -l hook.js 进程名可以通过frida-ps -U来查看

    • Attach模式

    在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作

    frida -U 进程名 -l hook.js 在App已经启动,或者我们只关心特定时刻或特定功能的行为时使用

  • 5.frida_server自定义端口

1
2
3
4
5
frida server 默认端口:27042

taimen:/ $ su
taimen:/ # cd data/local/tmp/
taimen:/data/local/tmp # ./fs1280 -l 0.0.0.0:6666
  • 6.基础语法

    API名称 描述
    Java.use(className) 获取指定的Java类并使其在JavaScript代码中可用。
    Java.perform(callback) 确保回调函数在Java的主线程上执行。
    Java.choose(className, callbacks) 枚举指定类的所有实例。
    Java.cast(obj, cls) 将一个Java对象转换成另一个Java类的实例。
    Java.enumerateLoadedClasses(callbacks) 枚举进程中已经加载的所有Java类。
    Java.enumerateClassLoaders(callbacks) 枚举进程中存在的所有Java类加载器。
    Java.enumerateMethods(targetClassMethod) 枚举指定类的所有方法。
  • 7.日志输出

    日志方法 描述 区别
    console.log() 使用JavaScript直接进行日志打印 多用于在CLI模式中,console.log()直接输出到命令行界面,使用户可以实时查看。在RPC模式中,console.log()同样输出在命令行,但可能被Python脚本的输出内容掩盖。
    send() Frida的专有方法,用于发送数据或日志到外部Python脚本 多用于RPC模式中,它允许JavaScript脚本发送数据到Python脚本,Python脚本可以进一步处理或记录这些数据。
  • 8.日志捕获 D:表示级别 “zj2595"这个是标签

  • 9.Hook框架模板

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    // 定义主函数
    function main() {
        // Frida提供的Java环境操作API,确保在Java虚拟机上下文执行
        Java.perform(function() {
            // 调用自定义的Hook逻辑函数(需要自己实现)
            hookTest1();
        });
    }
    
    // 立即执行main函数(Frida脚本常用的启动方式)
    setImmediate(main);
    
    1. Java.perform(callback)Frida 的核心 API 之一,用于在目标进程的Java 虚拟机(JVM)上下文中执行代码。因为 Hook Java 方法需要访问 JVM 环境,Java.perform会确保回调函数中的代码在 JVM 就绪后执行,避免因环境未初始化导致的错误。

    2. **hookTest1()**这是一个自定义函数(需要你自己实现具体逻辑),用于编写实际的 Hook 操作,例如:

      • 拦截某个 Java 方法的调用
      • 修改方法的参数或返回值
      • 打印方法调用栈信息等
    3. **setImmediate(main)**用于启动整个 Hook 流程。setImmediate是 JavaScript 的异步 API,会将main函数放入事件循环中立即执行(类似setTimeout(fn, 0))。在 Frida 脚本中,这是启动 Hook 逻辑的标准方式,确保代码在进程初始化完成后运行。

Frida常用API

1. Hook普通方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//定义一个名为hookTest1的函数
function hookTest1(){
        //获取一个名为"类名"的Java类,并将其实例赋值给JavaScript变量utils
    var utils = Java.use("类名");
    //修改"类名"的"method"方法的实现。这个新的实现会接收两个参数(a和b)
    utils.method.implementation = function(a, b){
            //将参数a和b的值改为123和456。
        a = 123;
        b = 456;
        //调用修改过的"method"方法,并将返回值存储在`retval`变量中
        var retval = this.method(a, b);
        //在控制台上打印参数a,b的值以及"method"方法的返回值
        console.log(a, b, retval);
        //返回"method"方法的返回值
        return retval;
    }
}

此案例中,类名修改为com.zj.wuaipojie.Demo,将参数和方法名都修改。

最新的jadx中已经可以将方法复制成frida片段,如下:

1
2
3
4
5
6
7
var Demo = Java.use("com.zj.wuaipojie.Demo");
Demo["a"].implementation = function (str) {
    console.log(`Demo.a is called: str=${str}`);
    let result = this["a"](str);
    console.log(`Demo.a result=${result}`);
    return result;
};

使用frida-ps -U 查看当前手机运行的进程得到 1821 wuaipojie

然后使用frida -U 进程名 -l hook.js启动命令,下图是另一个终端窗口中logcat |grep "D.zj2595"进行的日志捕获

可以看到,终端输出了方法传入的参数和输出的返回值

let result = this["a"](str);这条语句中即可修改方法的返回值,但需注意原方法返回值的类型,若原方法返回int,应该返回数字22(而不是字符串"22"

2. Hook重载参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// .overload()
// .overload('自定义参数')
// .overload('int')
function hookTest2(){
    var utils = Java.use("com.zj.wuaipojie.Demo");
    //overload定义重载函数,根据函数的参数类型填
    utils.Inner.overload('com.zj.wuaipojie.Demo$Animal','java.lang.String').implementation = function(ab){
        b = "aaaaaaaaaa";
        this.Inner(a,b);
        console.log(b);
    }
}

再看一个自定义参数类型的方法案例

overload()中没有填入参数类型或者你不知道该参数的类型时候可以尝试运行在报错的提示中可以找到参数类型,也可以从Java文件定位到对应的smali语句,也可以看到相应的参数类型

有时候文件运行后,没有hook成功,可以多尝试几次

3. Hook构造函数

1
2
3
4
5
6
7
8
9
function hookTest3(){
    var utils = Java.use("com.zj.wuaipojie.Demo");
    //修改类的构造函数的实现,$init表示构造函数
    utils.$init.overload('java.lang.String').implementation = function(str){
        console.log(str);
        str = "52";
        this.$init(str);
    }
}

4. Hook字段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
function hookTest5(){
    Java.perform(function(){
        //静态字段的修改
        var utils = Java.use("com.zj.wuaipojie.Demo");
        //修改类的静态字段"flag"的值
        utils.staticField.value = "我是被修改的静态变量";
        console.log(utils.staticField.value);
        //非静态字段的修改
        //使用`Java.choose()`枚举类的所有实例
        Java.choose("com.zj.wuaipojie.Demo", {
            onMatch: function(obj){
                    //修改实例的非静态字段"_privateInt"的值为"123456",并修改非静态字段"privateInt"的值为9999。
                obj._privateInt.value = "123456"; //字段名与函数名相同 前面加个下划线
                obj.privateInt.value = 9999;
            },
            onComplete: function(){

            }
        });
    });

}

在第一次的使用过程中,发现obj.privateInt.value = 9999;并没有成功,可能的原因是

  1. private 修饰:Java 中 private 字段只能在当前类内部访问,外部(包括 Frida 直接访问)默认没有权限修改;
  2. final 修饰final 字段初始化后不允许重新赋值(即使是类内部也不行),Java 的语法规则会阻止修改。

翻论坛评论的时候发现了这个帖子

尝试了一下,确实可以,真的要和别人多交流学习啊(感慨)

附上代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function hookTest5(){
    Java.perform(function(){
        //非静态字段的修改
        //使用`Java.choose()`枚举类的所有实例
        Java.choose("com.zj.wuaipojie.Demo", {
            onMatch: function(obj){
                //修改实例的非静态字段"_privateInt"的值为"123456",并修改非静态字段"privateInt"的值为9999。
                // obj._privateInt.value = "123456"; //字段名与函数名相同时 前面加个下划线
                console.log("here!")
                obj.publicInt.value = 8888;
                obj.privateInt.value = 9999;
                //由于样本代码的原因,需要再主动调用一下Demo类日志输出函数test(),才能在adb的log界面看到输出改变
                obj.test();
                console.log(obj.privateInt.value )
            },
            onComplete: function(){

            }
        });
    });
}

5.Hook内部类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function hookTest5(){
    Java.perform(function(){
        //内部类
        var innerClass = Java.use("com.zj.wuaipojie.Demo$innerClass");
        console.log(innerClass);
        innerClass.$init.implementation = function(){
            console.log("eeeeeeee");
        }

    });
}

6. 枚举所有类与方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function hookTest6(){
    Java.perform(function(){
        //枚举所有的类与类的所有方法,异步枚举
        Java.enumerateLoadedClasses({
            onMatch: function(name,handle){
                    //过滤类名
                if(name.indexOf("com.zj.wuaipojie.Demo") !=-1){
                    console.log(name);
                    var clazz =Java.use(name);
                    console.log(clazz);
                    var methods = clazz.class.getDeclaredMethods();
                    console.log(methods);
                }
            },
            onComplete: function(){}
        })
    })
}

7.枚举所有方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
function hookTest7(){
    Java.perform(function(){
        var Demo = Java.use("com.zj.wuaipojie.Demo");
        //getDeclaredMethods枚举所有方法
        var methods =Demo.class.getDeclaredMethods();
        for(var j=0; j < methods.length; j++){
            var methodName = methods[j].getName();
            console.log(methodName);
            for(var k=0; k<Demo[methodName].overloads.length;k++){
                Demo[methodName].overloads[k].implementation = function(){
                    for(var i=0;i<arguments.length;i++){
                        console.log(arguments[i]);
                    }
                    return this[methodName].apply(this,arguments);
                }
            }
        }
    })
}

8.主动调用

  • 静态方法
1
2
var ClassName=Java.use("com.zj.wuaipojie.Demo"); 
ClassName.privateFunc("传参");

下图就能显示出修改后的加密结果

  • 非静态方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    var ret = null;
    Java.perform(function () {
        Java.choose("com.zj.wuaipojie.Demo",{    //要hook的类
            onMatch:function(instance){
                ret=instance.privateFunc("aaaaaaa"); //要hook的方法
            },
            onComplete:function(){
                    //console.log("result: " + ret);
            }
        });
    })
    //return ret;

0x01 objection

什么是objection

  • objection是基于frida的命令行hook集合工具, 可以让你不写代码, 敲几句命令就可以对java函数的高颗粒度hook, 还支持RPC调用。可以实现诸如内存搜索、类和模块搜索、方法hook打印参数返回值调用栈等常用功能,是一个非常方便的,逆向必备、内存漫游神器。

项目地址

  • 由于objection在2021就已经停止更新,所以需要使用较低版本的frida和frida_tools,建议重新设置一个虚拟环境,因为之前虚拟环境用的是最新版本的,不太支持objection,配置一个新的环境也方便管理 我这里配置的是

    1
    2
    3
    4
    5
    
    objection   	   1.11.0
    
    frida              16.2.1
    
    frida-tools        13.2.0
    
  • 简单测试一下,出现如下图所示就是成功了

objection 命令注释

  • 1.help命令注释
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
objection --help(help命令)
Checking for a newer version of objection...
Usage: objection [OPTIONS] COMMAND [ARGS]...

       _   _         _   _
   ___| |_|_|___ ___| |_|_|___ ___
  | . | . | | -_|  _|  _| | . |   |
  |___|___| |___|___|_| |_|___|_|_|
        |___|(object)inject(ion)

       Runtime Mobile Exploration
          by: @leonjza from @sensepost

  默认情况下,通信将通过USB进行,除非提供了`--network`选项。

选项:
  -N, --network            使用网络连接而不是USB连接。
  -h, --host TEXT          [默认: 127.0.0.1]
  -p, --port INTEGER       [默认: 27042]
  -ah, --api-host TEXT     [默认: 127.0.0.1]
  -ap, --api-port INTEGER  [默认: 8888]
  -g, --gadget TEXT        要连接的Frida Gadget/进程的名称。 [默认: Gadget]
  -S, --serial TEXT        要连接的设备序列号。
  -d, --debug              启用带有详细输出的调试模式。(在堆栈跟踪中包括代{过}{滤}理源图)
  --help                   显示此消息并退出。

命令:
  api          以无头模式启动objection API服务器。
  device-type  获取关于已连接设备的信息。
  explore      启动objection探索REPL。
  patchapk     使用frida-gadget.so补丁一个APK。
  patchipa     使用FridaGadget dylib补丁一个IPA。
  run          运行单个objection命令。
  signapk      使用objection密钥对APK进行Zipalign和签名。
  version      打印当前版本并退出。
  • 2.注入命令
1
2
3
4
5
6
objection -g 包名 explore

-   help:不知道当前命令的效果是什么,在当前命令前加help比如:help env,回车之后会出现当前命令的解释信息
-   按空格:不知道输入什么就按空格,会有提示出来
-   jobs:可以进行多项hook
-   日志:objection的日志文件生成在 C:\Users\Administrator\.objection

启动前就hook

1
objection -g 进程名 explore --startup-command "android hooking watch class 路径.类名"
  • 3.objection基础api

    • memory list modules -查看内存中加载的库
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    memory list modules
    Save the output by adding `--json modules.json` to this command
    Name                                                              Base          Size                 Path
    ----------------------------------------------------------------  ------------  -------------------  ------------------------------------------------------------------------------
    app_process64                                                     0x57867c9000  40960 (40.0 KiB)     /system/bin/app_process64
    linker64                                                          0x72e326a000  229376 (224.0 KiB)   /system/bin/linker64
    libandroid_runtime.so                                             0x72e164e000  2113536 (2.0 MiB)    /system/lib64/libandroid_runtime.so
    libbase.so                                                        0x72dfa67000  81920 (80.0 KiB)     /system/lib64/libbase.so
    libbinder.so                                                      0x72dec1c000  643072 (628.0 KiB)   /system/lib64/libbinder.so
    libcutils.so                                                      0x72de269000  86016 (84.0 KiB)     /system/lib64/libcutils.so
    libhidlbase.so                                                    0x72df4cc000  692224 (676.0 KiB)   /system/lib64/libhidlbase.so
    liblog.so                                                         0x72e0be1000  98304 (96.0 KiB)     /system/lib64/liblog
    
    • memory list exports so名称 - 查看库的导出函数
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    memory list exports liblog.so
    Save the output by adding `--json exports.json` to this command
    Type      Name                                  Address
    --------  ------------------------------------  ------------
    function  android_log_write_int32               0x72e0be77c8
    function  android_log_write_list_begin          0x72e0be76f0
    function  __android_log_bswrite                 0x72e0be9bd8
    function  __android_log_security                0x72e0bf2144
    function  __android_log_bwrite                  0x72e0be9a18
    function  android_log_reset                     0x72e0be75ec
    function  android_log_write_string8             0x72e0be7a38
    function  android_logger_list_free              0x72e0be8c04
    function  __android_log_print                   0x72e0be9728
    function  __android_logger_property_get_bool    0x72e0bf2248
    function  android_logger_get_id                 0x72e0be8270
    function  android_logger_set_prune_list         0x72e0be8948
    
    • android hooking list activities -查看内存中加载的activity /android hooking list services -查看内存中加载的services
    • android intent launch_activity 类名 -启动activityservice(可以用于一些没有验证的activity,在一些简单的ctf中有时候可以出奇效)
    • 关闭ssl校验 android sslpinning disable
    • 关闭root检测 android root disable
  • 4.objection内存漫游

    • 内存搜刮类实例
    1
    2
    3
    4
    5
    
    android heap search instances 类名(命令)
    Class instance enumeration complete for com.zj.wuaipojie.Demo  
     Hashcode  Class                  toString()
    ---------  ---------------------  -----------------------------
    215120583  com.zj.wuaipojie.Demo  com.zj.wuaipojie.Demo@cd27ac7
    
    • 调用实例的方法
    1
    2
    3
    4
    5
    
    android heap execute <handle> getPublicInt(实例的hashcode+方法名)
    如果是带参数的方法,则需要进入编辑器环境  
    android heap evaluate <handle>  
    console.log(clazz.a("吾爱破解"));
    按住esc+enter触发
    
    • android hooking list classes -列出内存中所有的类(结果比静态分析的更准确)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    android hooking list classes 
    
    tw.idv.palatis.xappdebug.MainApplication
    tw.idv.palatis.xappdebug.xposed.HookMain
    tw.idv.palatis.xappdebug.xposed.HookMain$a
    tw.idv.palatis.xappdebug.xposed.HookMain$b
    tw.idv.palatis.xappdebug.xposed.HookMain$c
    tw.idv.palatis.xappdebug.xposed.HookMain$d
    tw.idv.palatis.xappdebug.xposed.HookSelf
    u
    v
    void
    w
    xposed.dummy.XResourcesSuperClass
    xposed.dummy.XTypedArraySuperClass
    
    Found 10798 classes
    
    • android hooking search classes 关键类名 -在内存中所有已加载的类中搜索包含特定关键词的类
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    android hooking search classes wuaipojie
    Note that Java classes are only loaded when they are used, so if the expected class has not been found, it might not have been loaded yet.
    com.zj.wuaipojie.Demo
    com.zj.wuaipojie.Demo$Animal
    com.zj.wuaipojie.Demo$Companion
    com.zj.wuaipojie.Demo$InnerClass
    com.zj.wuaipojie.Demo$test$1
    com.zj.wuaipojie.MainApplication
    com.zj.wuaipojie.databinding.ActivityMainBinding
    ... 
    
    Found 38 classes
    
    • android hooking search methods 关键方法名 -在内存中所有已加载的类的方法中搜索包含特定关键词的方法(一般不建议使用,特别耗时,还可能崩溃)
    • android hooking list class_methods 类名 -内存漫游类中的所有方法
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    android hooking list class_methods com.zj.wuaipojie.ui.ChallengeSixth
    private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-0(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-1(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-2(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    private static final void com.zj.wuaipojie.ui.ChallengeSixth.onCreate$lambda-3(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    protected void com.zj.wuaipojie.ui.ChallengeSixth.onCreate(android.os.Bundle)
    public final java.lang.String com.zj.wuaipojie.ui.ChallengeSixth.hexToString(java.lang.String)
    public final java.lang.String com.zj.wuaipojie.ui.ChallengeSixth.unicodeToString(java.lang.String)
    public final void com.zj.wuaipojie.ui.ChallengeSixth.toastPrint(java.lang.String)
    public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$1lrkrgiCEFWXZDHzLRibYURG1h8(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$IUqwMqbTKaOGiTaeOmvy_GjNBso(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$Kc_cRYZjjhjsTl6GYNHbgD-i6sE(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    public static void com.zj.wuaipojie.ui.ChallengeSixth.$r8$lambda$PDKm2AfziZQo6Lv1HEFkJWkUsoE(com.zj.wuaipojie.ui.ChallengeSixth,android.view.View)
    
    Found 12 method(s)
    
  • 5.objectionHook

hook类的所有方法

1
android hooking watch class 类名

hook方法的参数、返回值和调用栈

1
android hooking watch class_method 类名.方法名 --dump-args --dump-return --dump-backtrace

hook 类的构造方法

1
android hooking watch class_method 类名.$init

hook 方法的所有重载

1
android hooking watch class_method 类名.方法名

参考:

实用FRIDA进阶:内存漫游、hook anywhere、抓包

使用objection对吾爱破解2023春节安卓初级题进行快速解题

trace实战java控制流混淆

用到的实战题目:吾爱破解2023春节红包题

用到的混淆项目:DEX控制流混淆 · BlackObfuscator

项目效果展示:

  • 混淆前

  • 混淆后

对抗方法:

  • ZenTracer

    注意:无法打印调用栈,无法hook构造函数,高版本Frida不一定兼容,需要pyqt5的库

  • r0tracer

AKA:精简版 objection + Wallbreaker

0x02 Process、Module、Memory基础

1.Process

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

API 含义
Process.id 返回附加目标进程的 PID
Process.isDebuggerAttached() 检测当前是否对目标程序已经附加
Process.enumerateModules() 枚举当前加载的模块,返回模块对象的数组
Process.enumerateThreads() 枚举当前所有的线程,返回包含 id, state, context 等属性的对象数组
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function processInfo() {
    Java.perform(function() {
        console.log("PID:", Process.id);
        console.log("架构:", Process.arch);
        console.log("平台:", Process.platform);
        console.log("调试器附加:", Process.isDebuggerAttached());
        
        // 枚举模块
        var modules = Process.enumerateModules();
        modules.forEach(function(module) {
            console.log("模块:", module.name, "基址:", module.base, "大小:", module.size);
        });
        
        // 枚举线程
        var threads = Process.enumerateThreads();
        threads.forEach(function(thread) {
            console.log("线程ID:", thread.id, "状态:", thread.state);
        });
    });
}

2.Module

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

API 含义
Module.load() 加载指定so文件,返回一个Module对象
enumerateImports() 枚举所有Import库函数,返回Module数组对象
enumerateExports() 枚举所有Export库函数,返回Module数组对象
enumerateSymbols() 枚举所有Symbol库函数,返回Module数组对象
Module.findExportByName(exportName)、Module.getExportByName(exportName) 寻找指定so中export库中的函数地址
Module.findBaseAddress(name)、Module.getBaseAddress(name) 返回so的基地址
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function moduleInfo() {
    Java.perform(function() {
        // 获取模块基址的多种方式
        var base1 = Module.findBaseAddress("libtarget.so");
        var base2 = Process.getModuleByName("libtarget.so").base;
        console.log("模块基址:", base1, base2);
        
        // 查找导出函数
        var exportAddr = Module.findExportByName("libtarget.so", "target_function");
        console.log("导出函数地址:", exportAddr);
        
        // 枚举导入表
        var imports = Module.enumerateImports("libtarget.so");
        imports.forEach(function(imp) {
            console.log("导入:", JSON.stringify(imp));
        });
        
        // 枚举导出表
        var exports = Module.enumerateExports("libtarget.so");
        exports.forEach(function(exp) {
            console.log("导出:", JSON.stringify(exp));
        });
    });
}

3.Memory

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

方法 功能
Memory.copy() 复制内存
Memory.scan() 搜索内存中特定模式的数据
Memory.scanSync() 同上,但返回多个匹配的数据
Memory.alloc() 在目标进程的堆上申请指定大小的内存,返回一个NativePointer
Memory.writeByteArray() 将字节数组写入一个指定内存
Memory.readByteArray 读取内存
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function memoryOperations() {
    Java.perform(function() {
        try {
            // 分配内存
            var allocated = Memory.alloc(1024);
            console.log("分配内存:", allocated);
            
            // 写入字符串
            var testStr = "Hello Frida";
            Memory.writeUtf8String(allocated, testStr);
            
            // 读取字符串
            var readStr = Memory.readCString(allocated);
            console.log("读取字符串:", readStr);
            
            // 内存扫描
            var pattern = "48 89 5C 24 ?? 48 89 6C 24";
            Memory.scan(Module.findBaseAddress("libtarget.so"), 
                       Process.getModuleByName("libtarget.so").size, 
                       pattern, {
                onMatch: function(address, size) {
                    console.log("找到模式在:", address, "大小:", size);
                },
                onError: function(reason) {
                    console.log("扫描错误:", reason);
                },
                onComplete: function() {
                    console.log("扫描完成");
                }
            });
            
        } catch(e) {
            console.log("内存操作错误:", e);
        }
    });
}

0x03 枚举导入导出表

  1. 导出表(Export Table):列出了库中可以被其他程序或库访问的所有公开函数和符号的名称。
  2. 导入表(Import Table):列出了库需要从其他库中调用的函数和符号的名称。

简而言之,导出表告诉其他程序:“这些是我提供的功能。”,而导入表则表示:“这些是我需要的功能。”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 function hookTest1(){
    Java.perform(function(){
        //打印导入表
        var imports = Module.enumerateImports("lib52pojie.so");
        for(var i =0; i < imports.length;i++){
            if(imports[i].name == "vip"){
                console.log(JSON.stringify(imports[i])); //通过JSON.stringify打印object数据
                console.log(imports[i].address);
            }
        }
        //打印导出表
        var exports = Module.enumerateExports("lib52pojie.so");
        for(var i =0; i < exports.length;i++){
            console.log(JSON.stringify(exports[i]));
        }

    })
}

0x04 Native函数的基础Hook打印

1.整数型、布尔值类型、char类型Hook

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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]);  //打印第一个参数的值
                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());
            }
        })
    }
})
}

2.字符串类型Hook

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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());
                }
            })
        }
    })
}

0x05 Native函数的基础Hook修改

1.整数型修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 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[0] = ptr(1000); //第一个参数修改为整数 1000,先转为指针再赋值
                console.log(args[0]);

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

2.字符串类型修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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);
            }
        })
    }
})
}

0x06 SO基址的获取方式

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

0x07 Hook未导出函数与函数地址计算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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 中的偏移

0x08 Hook_dlopen

dlopen/android_dlopen_ext 是 Android 加载 SO 的系统函数,Hook 后可捕获 SO 加载时机,实现延迟 Hook(避免 SO 未加载时 Hook 失败)。

dlopen源码 android_dlopen_ext源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function hook_dlopen() {
    var dlopen = Module.findExportByName(null, "dlopen");
    Interceptor.attach(dlopen, {
        onEnter: function (args) {
            var so_name = args[0].readCString();
            if (so_name.indexOf("lib52pojie.so") >= 0) this.call_hook = true;
        }, onLeave: function (retval) {
            if (this.call_hook) hookTest2();
        }
    });
    // 高版本Android系统使用android_dlopen_ext
    var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    Interceptor.attach(android_dlopen_ext, {
        onEnter: function (args) {
            var so_name = args[0].readCString();
            if (so_name.indexOf("lib52pojie.so") >= 0) this.call_hook = true;
        }, onLeave: function (retval) {
            if (this.call_hook) hookTest2();
        }
    });
}

参考文档:

IDA&Frida 学习

FRIDA-API使用篇:rpc、Process、Module、Memory使用方法及示例

IDA&Frida 学习

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

前途似海,来日方长。

<