frida初探

文章最后更新时间为:2021年08月03日 15:12:11

Frida是一个很常用的Hook工具,动态代码插桩工具,可以将JavaScript代码注入android、ios等等程序中。它使用了“客户端-服务端”模型,并利用Frida内核和谷歌v8引擎hook进程,具体做法是将QuickJS注入到目标进程中,插入的js可以完全访问内存,函数等。

和xposed不同的是,frida使用方便,既不需要额外的编程,也不需要重启设备。

下面将通过一个小实验认识frida的用法。

1 安装frida

Frida分为2个部分,第一个是位于PC的客户端,第二个是位于手机端的服务端。在安装frida之前需要做一下准备

  • 一台 root 后的安卓手机
  • PC上安装 python3 环境 + adb 调试工具

1.1 PC上安装客户端

pip3 install frida frida-tools -i https://pypi.tuna.tsinghua.edu.cn/simple

$ frida --version
15.0.12

1.2 手机上安装server端

1. 从https://github.com/frida/frida/releases下载对应版本的server端,安卓手机就是frida-server-15.0.13-android-arm64.xz,或者adb shell getprop ro.product.cpu.abi查看手机架构

2. 将server解压上传到手机上
adb push frida-server-15.0.13-android-arm64 /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server-15.0.13-android-arm64"

1.3 测试安装

1. 在手机上启动frida-server(需要su权限)
adb shell
su 
cd /data/local/tmp/
./frida-server-15.0.13-android-arm64

2. pc上测试
adb forward tcp:27042 tcp:27042 把手机端口转发到pc上
frida-ps -U

2021-08-02T07:29:53.png

2. hook简单实战

2.1 实现一个计算器app

这里我使用android studio实现了一个简单的加法计算器,输入两个数字,点击等于号,会计算出两个数字之和。其中代码为

  • MainActivity.java
package com.example.add;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;


import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    EditText et_num1;
    EditText et_num2;
    TextView tv_result;
    Button btn_add;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 绑定控件
        setContentView(R.layout.activity_main);
        et_num1 = findViewById(R.id.et_num1);
        et_num2 = findViewById(R.id.et_num2);
        tv_result = findViewById(R.id.tv_result);
        btn_add = findViewById(R.id.btn_add);
        //给等于号普通按钮安装一个监听器,便于点击等于号按钮,和就能求出
        btn_add.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        int num1, num2;
        try {
            num1 = Integer.valueOf(et_num1.getText().toString());
        } catch (Exception e) {
            num1 = 0;
        }

        try {
            num2 = Integer.valueOf(et_num2.getText().toString());
        } catch (Exception e) {
            num2 = 0;
        }

        int result = Add(num1, num2);
        showSucceed(num1,num2,result);
        tv_result.setText(String.valueOf(result));

    }
    private int Add(int num1, int num2) {
        return num1 + num2;
    }
    private void showSucceed(int num1, int num2,int result){
        Toast.makeText(this,String.format("%d+%d=%d",num1,num2,result), Toast.LENGTH_SHORT).show();
    }

}
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/et_num1"
        android:layout_width="60dp"
        android:layout_height="45dp"
        android:layout_marginStart="76dp"
        android:layout_marginLeft="76dp"
        android:layout_marginTop="140dp"
        android:ems="10"
        android:hint="数字1"
        android:inputType="textShortMessage|numberSigned"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="80dp"
        android:layout_height="60dp"
        android:layout_marginTop="132dp"
        android:gravity="center"
        android:hint="和"
        android:textSize="24sp"
        app:layout_constraintStart_toEndOf="@+id/btn_add"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="140dp"
        android:text="+"
        android:textSize="24sp"
        app:layout_constraintStart_toEndOf="@+id/et_num1"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/et_num2"
        android:layout_width="60dp"
        android:layout_height="45dp"
        android:layout_marginTop="140dp"
        android:ems="10"
        android:hint="数字2"
        android:inputType="numberSigned|textPersonName"
        app:layout_constraintStart_toEndOf="@+id/textView"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_add"
        android:layout_width="50dp"
        android:layout_height="60dp"
        android:layout_marginTop="132dp"
        android:text="="
        android:textSize="24sp"
        app:layout_constraintStart_toEndOf="@+id/et_num2"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

你也可以使用我编译好的apk: https://wwr.lanzoui.com/ibjyus569cf 密码:5jb9

app 界面如下:

2021-08-02T07:48:04.png

2.2 hook计算器app

使用frida 进行动态hook的步骤为:

  1. 使用附加的api将frida客户端绑定应用进程
  2. 找出包含你要分析或者修改的方法所在的类
  3. 找出你要hook的api或者方法
  4. 创建js脚本,调用create_script将脚本推送到进程
  5. 使用script.load方法将js代码推送到进程
  6. 触发代码,并查看结果

下面我们尝试hook apk的Add函数,打印出参数和结果,然后使函数返回1234。

先打开app,然后再pc端运行下面这段python代码:

import frida
import sys
 
jscode = """
Java.perform(function () {
    var mainClass = Java.use("com.example.add.MainActivity")

    mainClass.Add.implementation = function (a1, a2) {
        console.log("a1:" + a1)
        console.log("a2:" + a2)

        var result = this.Add(a1, a2)

        console.log("result:" + result)

        return 1234
    }
})
"""
# 1. 连接进程
process = frida.get_remote_device().attach('add')
# 2.将脚本推送到进程
script = process.create_script(jscode)
# 3.将js代码推送到进程
script.load()
sys.stdin.read()
  • Java.use用来寻找要 hook 的类
  • mainClass.Add.implementation是重写该hook类的 Add()方法,如果要 hook 的方法带参数,需要传对应类型。

然后在app上操作,发现无论输入什么,结果都是1234

2021-08-02T08:08:15.png

2021-08-02T08:10:04.png

可以看到,已经使用frida动态插桩成功改变了应用的行为,而无需重启设备。

3. 常见用法

3.1 进程间消息交互

  • 将消息发送回python进程
import frida
import sys

session = frida.get_remote_device().attach('add')
script = session.create_script("send(1337);")

# 自定义回调函数
def on_message(message, data):
    print(message)
script.on('message', on_message)
script.load()
sys.stdin.read()

运行结果

$ python send.py
{u'type': u'send', u'payload': 1337}
  • 从目标进程接受消息
import frida
import sys

session = frida.get_remote_device().attach('add')
script = session.create_script("""
    recv('poke', function onMessage(pokeMessage) { send('pokeBack'); });
""")
def on_message(message, data):
    print(message)
script.on('message', on_message)
script.load()
script.post({"type": "poke"})
sys.stdin.read()

3.2 python接口

  • 获取设备信息
import frida
import sys

#获取设备信息
rdev = frida.get_remote_device()
print(rdev)

usb = frida.get_usb_device()  # test ok
print(usb)

ldev = frida.get_local_device()
print(ldev)
  • 获取进程信息
import frida
import sys
 
# 获取设备信息
rdev = frida.get_usb_device()

# 获取在前台运行的APP
front_app = rdev.get_frontmost_application()
print (front_app)

# 获取进程信息
process = rdev.enumerate_processes() 
print(process)

3.3 js接口

js接口比较多,也是hook中的主要代码,更多的内容见:https://frida.re/docs/javascript-api/

  • 查看被加载到内存的modules (Process.enumerateModules)

    import frida
    import sys
     
    
    import frida
    import sys
    
    session = frida.get_remote_device().attach('add')
    script = session.create_script("send(JSON.stringify(Process.enumerateModules()));")
    
    def on_message(message, data):
        print(message)
    script.on('message', on_message)
    script.load()
    sys.stdin.read()

4. 总结

这是对frida的入门教程,如有错误,欢迎指正。frida的接口文档https://frida.re/docs/home/比较简略,有些内容还是需要读代码才可以解决,后面应该会写一些app hook实战的文章,敬请期待。

1 + 8 =
快来做第一个评论的人吧~