无垠之码

深度剖析代码之道


frida零基础入门

It’s Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript or your own library into native apps on Windows, macOS, GNU/Linux, iOS, watchOS, tvOS, Android, FreeBSD, and QNX. Frida also provides you with some simple tools built on top of the Frida API. These can be used as-is, tweaked to your needs, or serve as examples of how to use the API.

Frida是一个动态代码插桩工具,或者说框架,它允许安全研究员,逆向工程师等在Windows、macOS、GNU/Linux、iOS、watchOS、tvOS、Android、FreeBSD和QNX上,向原生应用中注入JavaScript代码片段或自己的库。Frida还提供了一些基于其API构建的简单工具,可以直接使用、按需调整,或者作为使用API的参考示例。官网定义将其与Greasemonkey(油猴子)类比,Greasemonkey是一个Firefox插件,可以让用户编写用户脚本(UserScript)来修改网页内容或行为,在不改动网页源码的前提下,实现自定义功能。

Frida核心(Gum)使用c语言编写,通过将编写的js代码(开发者使用javascript语言编写的注入代码,gumjs包含微型的js执行引擎quickjs)注入目标进程中,达到访问目标进程的内存,hook特定操作,调用目标进程的函数的效果(目标进程与你嵌入的代码内置一个双向的通信通道)。这里使用javascript可以方便的捕获错误,而不是coredump。

0.Frida安装


避坑提醒,安装最新版本的frida工具,防止存在bug

objection工具是专为渗透测试场景而优化的Frida命令行客户端,将Frida的底层能力用简单命令进行了封装

sudo pip install -U frida-tools frida
sudo pip install -U objection
sudo npm install -g frida @types/frida-gum

1.相关概念


frida提供多种机制用于实现动态代码插桩的目的

  • 注入机制,适合完全控制目标设备,开发者想控制一个已有程序、附加到一个正在运行的程序,或者在程序刚启动时劫持它,然后运行自定义的插桩逻辑,frida-server场景
  • 嵌入机制,运用在某些仅拥有目标程序的源码或二进制文件,且无法直接控制设备的场景,frida-gadget场景

frida-core是frida的核心通信和注入层,负责将GumJS脚本注入目标进程
frida-server是一个运行在在目标设备上的守护进程,调用frida-core
frida-gadget是frida的轻量级注入载荷(payload),本质是一个可嵌入的共享库
stalker是frida内部的核心跟踪引擎,其提供线程跟踪,捕获函数和代码块甚至指令的执行

2.工具说明


基本工具的使用说明,官网已详尽的叙述,本节是对官方文档的实践

frida-ls-devices

用于列出当前可用的设备(包括物理设备、模拟器或远程连接的目标),方便开发者进行动态分析和调试

frida-ls-devices

Id              Type    Name             OS
--------------  ------  ---------------  ------------
local           local   Local System     Ubuntu 22.04
36081FDH30010K  usb     Pixel 7 Pro      Android 14
barebone        remote  GDB Remote Stub
socket          remote  Local Socket

frida-ps

展示目标设备正在运行的进程

frida-ps -U

...
10261  地图                                                                                               
20676  富途牛牛                                                                                             
12060  币安                                                                                               
13105  微信                                                                                               
 4305  微信输入法                                                                                            
 6365  支付宝                                                                                             
 4091  欧路词典                                                                                            
16677  知乎                                                                                               
12711  翻译                                                                                            
11561  通讯录          
...

frida-discover

frida-discover自动列出程序中所有可被frida-trace跟踪的内部函数

frida-discover -U 微信

boot.oat
        Calls           Function
        42              sub_691800
        26              sub_3059e0
        22              sub_3298c0
        ....
libutils.so
        Calls           Function
        26              sub_1cc90
        24              systemTime
        24              _ZN7android12uptimeMillisEv
        ...

frida-trace

frida-trace命令动态跟踪应用函数调用,自动生成跟踪的函数样例
比如下面的例子,跟踪所有模块的send开头的函数生成桩函数,除voipMain模块

frida-trace -U -i "send*" 微信 -X "libvoipMain.so"

sendEvent: Loaded handler at "/tmp/test/__handlers__/libtenpay_utils.so/sendEvent.js"
sendfile: Loaded handler at "/tmp/test/__handlers__/libc.so/sendfile.js"
sendmmsg: Loaded handler at "/tmp/test/__handlers__/libc.so/sendmmsg.js"
send: Loaded handler at "/tmp/test/__handlers__/libc.so/send.js"
sendmsg: Loaded handler at "/tmp/test/__handlers__/libc.so/sendmsg.js"
sendto: Loaded handler at "/tmp/test/__handlers__/libc.so/sendto.js"
sendSignalToProcessGroup: Loaded handler at "/tmp/test/__handlers__/libprocessgroup.so/sendSignalToProcessGroup.js"
Started tracing 7 functions. Web UI available at http://localhost:35103/

frida-kill

frida提供的命令行工具,kill指定设备的目标进程

frida-kill -U 16677

frida-cli

frdia提供一个REPL(交互式解释器)界面,旨在模拟IPython(或Cycript)的诸多优秀特性,让开发者能更贴近代码进行快速原型开发和便捷调试

3.Frida闯关


github:aprz512提供的frida_example.apk包含7个关卡,帮助初学者快速入门frida的Java层注入

分析apk时如果不知道当前界面使用的类实现,可以通过三种方法快速定位:

  • 通过adb logcat过滤ActvityManager过滤
  • 使用jadx针对frida_example.apk进行反编译,浏览AndroidManifest.xml的相关信息可以快速定位
  • adb shell dumpsys window可以过滤出当前窗口实现类

image

  1. 登录破解,java层函数重载

分析逆向源码发现,作者使用用户名作HmacSHA256算法的消息认证码,对其自身对用户名进行消息摘要

image

这里只需要hook关键验证函数a返回固定字符串talent即可完成破解

function main() {
    Java.perform(() => {
        const Activity = Java.use('com.example.androiddemo.Activity.LoginActivity');
        Activity.a.overload('[B').implementation = function (arg0) {
            return "talent";
        }
    });
}
  1. 修改函数参数及返回值

源码中作者将字符串"请输入密码:“gzip压缩后,使用base64编码与固定字符串判断

image

function main() {
  Java.perform(() => {
      const Activity = Java.use('com.example.androiddemo.Activity.FridaActivity1');
      Activity.a.implementation = function (arg0) {
          return "R4jSLLLLLLLLLLOrLE7/5B+Z6fsl65yj6BgC6YWz66gO6g2t65Pk6a+P65NK44NNROl0wNOLLLL=";
      }
  });
}
  1. 修改类的静态变量与实例的私有变量

成员函数oncheck中检查类的静态变量static_bool_var和实例的私有变量bool_var

image

function main() {
    Java.perform(() => {
        const Activity = Java.use('com.example.androiddemo.Activity.FridaActivity2');
        // 类的静态成员变量,在类初始化时就创建
        //   1.使用类提供的静态方法    
        //   2.直接设置类的静态变量
        Activity.setStatic_bool_var();
        Activity.static_bool_var.value = true;
        // 类的实例成员变量,在类实例化时创建
        //   1.使用choose方法,其在java的堆内存中查找类的实例,在匹配到指定的类实例后,调用指定的onMatch回调,查找完成后调用onComplete回调
        //   2.HOOK类的构造函数,在构造函数中设置相关动作,这要求在类的实例未创建,使用spawn模式可以完成相关工作
        Java.choose('com.example.androiddemo.Activity.FridaActivity2', {
            onMatch: function (inst) {
                inst.setBool_var();
            },
            onComplete: () => { }
        });
        Activity.$init.overload().implementation = function () {            
          this.$init();
          this.static_bool_var.value = true;
          this.bool_var.value = true;
        }
    });
}
  1. 同名成员变量与成员函数的特殊处理

相关问题在2017年已解决,https://github.com/frida/frida-java-bridge/issues/20

image

function main() {
    Java.perform(() => {
        Java.choose('com.example.androiddemo.Activity.FridaActivity3', {
            onMatch: function (inst) {
                inst.static_bool_var.value = true;
                inst.bool_var.value = true;
                inst._same_name_bool_var.value = true;
            },
            onComplete: () => { }
        });
    });
}
  1. 嵌套类的HOOK操作

关卡4主要涉及嵌套类,针对相同前缀的函数族HOOK操作

image

function main() {
    Java.perform(() => {
        const Inner = Java.use('com.example.androiddemo.Activity.FridaActivity4$InnerClasses');
        const JClass = Inner.class;
        const methods = JClass.getDeclaredMethods();
        methods.forEach(item => {
            const name = item.getName();
            if (name.startsWith("check")) {
                try {
                    const method = Inner[name];
                    method.overload().implementation = () => { return true; };
                } catch (err) {
                    console.warn(err.message);
                }
            }
        });
    });
}
  1. 动态类的HOOK操作

分析源码,关键函数getDynamicDexCheck调用loaddex动态创建com.example.androiddemo.Dynamic.DynamicCheck类的实例,实例的check方法返回false

image

破解的方法很多,比如实现一个CheckInterface类对象覆盖原有对象,又或者只是HOOK核心com.example.androiddemo.Dynamic.DynamicCheck类的实例的check函数
待解决,hook函数getDynamicDexCheck,创建新的FakeCheck实例并没有成功

方案一:

function main() {
  Java.perform(() => {
      const CheckInterface = Java.use('com.example.androiddemo.Dynamic.CheckInterface');
      const FakeCheck = Java.registerClass({
          name: 'com.example.androiddemo.Dynamic.FakeCheck',
          implements: [CheckInterface],
          methods: {
              check: function () { return true; }
          },
          fields: {
              description: 'java.lang.String',
              limit: 'int',
          }
      });

      Java.choose('com.example.androiddemo.Activity.FridaActivity5', {
          onMatch: function (inst) {
              inst.DynamicDexCheck.value = FakeCheck.$new();
          },
          onComplete: () => { }
      });
  });
}

方案二:

function main() {
    Java.perform(() => {
        Java.enumerateClassLoaders({
            onMatch: function (loader) {
                try {
                    if (loader.findClass('com.example.androiddemo.Dynamic.DynamicCheck')) {
                        Java.classFactory.loader = loader
                    }
                } catch (err) {
                    console.log(err.message);
                }
            },
            onComplete: function () {
                console.log("enumerateClassLoaders complete");
            }
        })

        Java.use('com.example.androiddemo.Dynamic.DynamicCheck').check.implementation = function () {
            return true
        }
    });
}
  1. import类的HOOK操作

image

// 方案一:
//    类是惰性加载的,在未执行onclick方法时,enumerateLoadedClasses并没有Frida6ClassX并没有加载
function main() {
    Java.perform(() => {
        Java.perform(() => {
            Java.enumerateLoadedClasses({
                onMatch: function (name, handle) {
                    try {
                        if (name.startsWith("com.example.androiddemo.Activity.Frida6.Frida6Class")) {
                            Java.use(name).check.implementation = () => { return true; };
                        }
                    } catch (err) {
                        console.log(err.message);
                    }
                },
                onComplete: function () {
                    console.log("enumerateLoadedClasses complete");
                }
            });
        });
    });
}

// 方案二:
Java.enumerateClassLoaders({
    onMatch: function (loader) {
        [
            "com.example.androiddemo.Activity.Frida6.Frida6Class0",
            "com.example.androiddemo.Activity.Frida6.Frida6Class1",
            "com.example.androiddemo.Activity.Frida6.Frida6Class2",
        ].forEach(item => {
            try {
                if (loader.findClass(item)) {
                    Java.classFactory.loader = loader;
                    Java.use(item).check.implementation = () => { return true; };
                }
            } catch (err) {
                console.log(err.message);
            }
        })
    },
    onComplete: function () {
        console.log("enumerateClassLoaders complete");
    }
});

至此frida在Java层的基本注入方式已经介绍完毕,无论是静态类的Java.use(),还是动态加载类时配合ClassFactory.get(classloader)的使用,Frida为Android应用分析和渗透测试提供了极其灵活的入口。但这仅仅是万里长征的第一步,真正复杂的逆向目标往往会涉及Native层函数劫持,绕过So加壳与延迟加载,动态分析JNI接口,内存结构观察与对象恢复,甚至网络流量的劫持等高级主题,这些后续有时间会陆续补充。

4.参考文献

  1. https://frida.re/docs/home/
  2. https://github.com/aprz512/Android-Crack/blob/master/practice/p14/frida_example_1.1.apk
comments powered by Disqus