當前位置:首頁 > IT技術 > 其他 > 正文

Frida 實現(xiàn) Hook 功能的強大能力
2022-05-31 17:12:04

技術分享 | Frida 實現(xiàn) Hook 功能的強大能力

??更多技術文章??

Frida 通過 C 語言將 QuickJS 注入到目標進程中,獲取完整的內存操作權限,達到在程序運行時實時地插入額外代碼和數(shù)據(jù)的目的。官方將調用代碼封裝為 python 庫,當然你也可以直接通過其他的語言調用 Frida 中的 C 語言代碼進行操作。

Frida安裝和啟動

電腦端 Frida 安裝

pip install frida-tools
  • 如果在安裝中卡住,需要在 Frida 的 pypi 頁面下載對應系統(tǒng)的 egg 文件,對應頁面地址為:https://pypi.org/project/frida/#files ,并將該文件放置到個人文件夾路徑下,例如 C:Users當前用戶名,再重新使用命令安裝。
  • 安裝完畢后可以通過命令frida --version來查看安裝的版本,確認是否安裝成功。

手機端 Frida-server 安裝

  • 本次示例使用 Android App 作為目標程序,所以需要電腦端安裝 SDK 環(huán)境,以便能夠連接手機進行調試操作,還需在手機端準備一個 Frida-server,下載地址為:https://github.com/frida/frida/releases,下載匹配手機 CPU 架構和本地 Frida 版本的包。
  • 下載之后解壓文件,使用adb push命令將文件推送到手機端,建議放置在/data/local/tmp文件夾中,并修改該文件的權限為 755,以便之后進行啟動。

確認環(huán)境運行正常

  • 通過 Frida 提供的一些小工具,對 Frida 的安裝運行環(huán)境做簡單的確認。
  • 首先準備一個 Android 模擬器或者真機,將上一步中提到的 Frida-server 推送到手機端中,在本示例中將放置在手機的/data/local/tmp文件夾內,并將文件命名為frida-server。
  • 通過adb shell命令連接手機,運行/data/local/tmp/frida-server &,將 Frida-server 放在系統(tǒng)后臺自動運行。
  • 在本地電腦終端中運行frida-ps -U,結果如下展示手機中的進程信息,說明環(huán)境已經(jīng)準備完畢。
  PID  Name
----- --------------------------------------------------
1313 adbd
12621 android.process.acore
18037 android.process.media
14455 com.android.defcontainer
11656 com.android.deskclock

示例

目標應用介紹

  • 因為 Hook 需要通過分析源碼中的邏輯來實現(xiàn),所以先展示一下目標應用的源碼部分,方便分析其中的邏輯,找到 Hook 時要修改的方法和變量。
  • 代碼是簡單的猜黑白游戲,通過按下黑或白按鈕,與電腦結果進行對比,結果相同加 1 分,結果不同分數(shù)清零,當滿 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 次的情況,可以有兩種解決辦法:一種是通過修改用來記錄連贏次數(shù)的變量winCount,將連贏記錄改成 99,這樣只需要再贏一次就可以獲得勝利;還有一種是通過修改getCPUResult方法的返回值,讓其固定返回一種可能性,這樣只需要選擇對應的顏色就可以連續(xù)獲勝。接下來通過實現(xiàn)第一個方案,看看使用 Frida 如何達到想要的效果。

第一種實現(xiàn):修改結果變量中保存的值

  • 首先展示修改代碼,然后再進行逐步講解:
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 附加到目標的進程中時被執(zhí)行,運行其中定義的函數(shù)
# Java.choose方法:通過完整類名,獲取它的實例,從而對實例中的數(shù)據(jù)進行修改
# 通過 key:value 結構定義了兩個函數(shù):
# onMatch 對應的函數(shù)在命中一個實例的時候被調用,傳入函數(shù)中的參數(shù) instance 就是被命中的實例
# onComplete 函數(shù)會在所有實例遍歷完畢之后被調用,可以做一些后續(xù)處理操作
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代碼注入進程,并附加監(jiān)聽方法,用來獲取返回的日志信息
script = process.create_script(jscode)
script.on('message', on_message)
# 打印起始日志
print(f'[{date_str}] Start Frida on {target_app}')
# 加載注入的JS代碼邏輯
script.load()
# 使用系統(tǒng)輸入語句阻止函數(shù)運行完畢自動退出
sys.stdin.read()


if __name__ == '__main__':
run_all()
  • 代碼中的 python 語句已經(jīng)添加了注釋,Hook 的核心邏輯,JS 語句作為字符串保存在 jscode 變量中。
  • 梳理一下整個 JS 語句的流程:通過Java.choose函數(shù)獲取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 次連勝目標,如果沒能選中正確的顏色導致清零,可以再重復運行腳本修改一次數(shù)值,在這種情況下要達到預期的場景就很容易。

總結

第二個方案以及其他更多的可能性,就留給讀者自行探索,在這里送上 Frida 官方 JavaScript API 鏈接:https://frida.re/docs/javascript-api/ ,可以通過這個鏈接找到你所需要的 JS 函數(shù)。
通過示例可以看到 Frida 實現(xiàn) Hook 功能的強大能力,它可以定位到類的實例,并且對實例中的數(shù)據(jù)進行直接的修改,達到場景構建的目的。
???更多技術文章??

本文摘自 :https://blog.51cto.com/u

開通會員,享受整站包年服務立即開通 >