2

Frida 实现 Hook 功能的强大能力

 2 years ago
source link: https://blog.51cto.com/u_15600326/5329168
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

技术分享 | Frida 实现 Hook 功能的强大能力

 ​更多技术文章​

Frida 通过 C 语言将 QuickJS 注入到目标进程中,获取完整的内存操作权限,达到在程序运行时实时地插入额外代码和数据的目的。官方将调用代码封装为 python 库,当然你也可以直接通过其他的语言调用 Frida 中的 C 语言代码进行操作。

Frida安装和启动

电脑端 Frida 安装

pip install frida-tools
  • 如果在安装中卡住,需要在 Frida 的 pypi 页面下载对应系统的 egg 文件,对应页面地址为:https://pypi.org/project/frida/#files ,并将该文件放置到个人文件夹路径下,例如 C:\Users\当前用户名,再重新使用命令安装。
  • 安装完毕后可以通过命令frida --version来查看安装的版本,确认是否安装成功。

手机端 Frida-server 安装

  • 本次示例使用 Android App 作为目标程序,所以需要电脑端安装 SDK 环境,以便能够连接手机进行调试操作,还需在手机端准备一个 Frida-server,下载地址为:https://github.com/frida/frida/releases,下载匹配手机 CPU 架构和本地 Frida 版本的包。
  • 下载之后解压文件,使用adb push命令将文件推送到手机端,建议放置在/data/local/tmp文件夹中,并修改该文件的权限为 755,以便之后进行启动。

确认环境运行正常

  • 通过 Frida 提供的一些小工具,对 Frida 的安装运行环境做简单的确认。
  • 首先准备一个 Android 模拟器或者真机,将上一步中提到的 Frida-server 推送到手机端中,在本示例中将放置在手机的/data/local/tmp文件夹内,并将文件命名为frida-server。
  • 通过adb shell命令连接手机,运行/data/local/tmp/frida-server &,将 Frida-server 放在系统后台自动运行。
  • 在本地电脑终端中运行frida-ps -U,结果如下展示手机中的进程信息,说明环境已经准备完毕。
PID Name
----- --------------------------------------------------
1313 adbd
12621 android.process.acore
18037 android.process.media
14455 com.android.defcontainer
11656 com.android.deskclock

目标应用介绍

  • 因为 Hook 需要通过分析源码中的逻辑来实现,所以先展示一下目标应用的源码部分,方便分析其中的逻辑,找到 Hook 时要修改的方法和变量。
  • 代码是简单的猜黑白游戏,通过按下黑或白按钮,与电脑结果进行对比,结果相同加 1 分,结果不同分数清零,当满 100 分时打出胜利提示语。具体代码如下:
package com.example.target_frida;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
TextView winCountView;
TextView battleInfoTextView;
int winCount = 0;

@SuppressLint("SetTextI18n")
@Override
public void onClick(View view) {
if (winCount > 100) {
return;
}
if (view.getId() == R.id.tvButtonBlack) {
if (!getCPUResult()) {
winCount++;
winCountView.setText(winCount + "");
battleInfoTextView.setText("Right!");
} else {
winCount = 0;
winCountView.setText(winCount + "");
battleInfoTextView.setText("Wrong! Clean All!");
}
} else if (view.getId() == R.id.tvButtonWhite) {
if (getCPUResult()) {
winCount++;
winCountView.setText(winCount + "");
battleInfoTextView.setText("Right!");
} else {
winCount = 0;
winCountView.setText(winCount + "");
battleInfoTextView.setText("Wrong! Clean All!");
}
}
if (winCount >= 100) {
battleInfoTextView.setText("Win 100 times!!!");
}
}

@SuppressLint("SetTextI18n")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
winCountView = findViewById(R.id.winCount);
winCountView.setText(winCount + "");
battleInfoTextView = findViewById(R.id.battleInfo);
battleInfoTextView.setText("猜黑白!!!");
Button buttonBlack = (Button) findViewById(R.id.tvButtonBlack);
Button buttonWhite = (Button) findViewById(R.id.tvButtonWhite);
buttonBlack.setOnClickListener(this);
buttonWhite.setOnClickListener(this);
}

public boolean getCPUResult() {
//true为白,false为黑
return Math.random() > 0.5;
}

}

Hook 需求分析

  • 由于正常情况下,连赢 100 次的概率几乎为零,如果想要达到胜利条件,Hook 就是一个比较好的方式。
  • 首先分析一下源码,想要达到连赢 100 次的情况,可以有两种解决办法:一种是通过修改用来记录连赢次数的变量winCount,将连赢记录改成 99,这样只需要再赢一次就可以获得胜利;还有一种是通过修改getCPUResult方法的返回值,让其固定返回一种可能性,这样只需要选择对应的颜色就可以连续获胜。接下来通过实现第一个方案,看看使用 Frida 如何达到想要的效果。

第一种实现:修改结果变量中保存的值

  • 首先展示修改代码,然后再进行逐步讲解:
import time
import frida, sys

date_str = time.strftime('%m-%d %H:%M:%S')


def on_message(message, data):
if message['type'] == 'send':
print(f"[{date_str}] {message['payload']}")
else:
print(f"[{date_str}] {message}")


def run_all():
# Java.perform方法:当 js 附加到目标的进程中时被执行,运行其中定义的函数
# Java.choose方法:通过完整类名,获取它的实例,从而对实例中的数据进行修改
# 通过 key:value 结构定义了两个函数:
# onMatch 对应的函数在命中一个实例的时候被调用,传入函数中的参数 instance 就是被命中的实例
# onComplete 函数会在所有实例遍历完毕之后被调用,可以做一些后续处理操作
jscode = """
Java.perform(function () {
Java.choose("com.example.target_frida.MainActivity",{
onMatch:function(instance){
console.log("winCount value is "+instance.winCount.value);
instance.winCount.value=99;
console.log("winCount value is "+instance.winCount.value);
},
onComplete:function(){
console.log("Complete!!!")
}
});
});
"""
# attach目标App进程
target_app = 'com.example.target_frida'
process = frida.get_usb_device().attach(target_app)
# 将JS代码注入进程,并附加监听方法,用来获取返回的日志信息
script = process.create_script(jscode)
script.on('message', on_message)
# 打印起始日志
print(f'[{date_str}] Start Frida on {target_app}')
# 加载注入的JS代码逻辑
script.load()
# 使用系统输入语句阻止函数运行完毕自动退出
sys.stdin.read()


if __name__ == '__main__':
run_all()
  • 代码中的 python 语句已经添加了注释,Hook 的核心逻辑,JS 语句作为字符串保存在 jscode 变量中。
  • 梳理一下整个 JS 语句的流程:通过Java.choose函数获取com.example.target_frida.MainActivity类的实例。在获取到实例时,首先使用console.log语句将当前实例中的 winCount 变量值(使用 winCount.value)打印到日志中,之后直接通过赋值语句把变量值改为 99,再次输出日志确认修改无误,修改逻辑就完成了。
  • 在手机端启动 Frida-server 和被测 App,电脑端运行脚本,可以看到在命令行中输出如下内容:
[04-15 18:19:57] Start Frida on com.example.target_frida
winCount value is 0
winCount value is 99
Complete!!!

这时在 App 中选择一个颜色点击,只要选中正确的颜色,就可以成功达到预期的 100 次连胜目标,如果没能选中正确的颜色导致清零,可以再重复运行脚本修改一次数值,在这种情况下要达到预期的场景就很容易。

第二个方案以及其他更多的可能性,就留给读者自行探索,在这里送上 Frida 官方 JavaScript API 链接:https://frida.re/docs/javascript-api/ ,可以通过这个链接找到你所需要的 JS 函数。
通过示例可以看到 Frida 实现 Hook 功能的强大能力,它可以定位到类的实例,并且对实例中的数据进行直接的修改,达到场景构建的目的。
​​ ​更多技术文章​


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK