frida的用法--Hook Java层类方法
frida是一款方便并且易用的跨平臺Hook工具,使用它不僅可以Hook Java寫的應用程序,而且還可以Hook原生的應用程序。
1. 準備
frida分客戶端環(huán)境和服務端環(huán)境。在客戶端我們可以編寫Python代碼,用于連接遠程設備,提交要注入的代碼到遠程,接受服務端的發(fā)來的消息等。在服務端,我們需要用Javascript代碼注入到目標進程,操作內存數(shù)據(jù),給客戶端發(fā)送消息等操作。我們也可以把客戶端理解成控制端,服務端理解成被控端。
假如我們要用PC來對Android設備上的某個進程進行操作,那么PC就是客戶端,而Android設備就是服務端。
1.1 準備frida服務端環(huán)境
本文,服務端在Android平臺測試。服務端環(huán)境準備步驟如下:
1.1.1 根據(jù)自己的平臺下載frida服務端并解壓
https://github.com/frida/frida/releases
1.1.2 執(zhí)行以下命令將服務端推到手機的/data/local/tmp目錄
adb push frida-server /data/local/tmp/frida-server1.1.3 執(zhí)行以下命令修改frida-server文件權限
adb shell chmod 777 /data/local/tmp/frida-server注:Windows系統(tǒng)執(zhí)行命令可以在CMD中進行;Linux和MacOS執(zhí)行命令可以在終端中進行。adb是Android一個調試工具,具體安裝方法不是本文的重點。
1.2 準備客戶端環(huán)境
在PC上安裝Python的運行環(huán)境,安裝完成后執(zhí)行下面的命令安裝frida
pip install frida-tools1.3 客戶端命令參數(shù)
下面是frida客戶端命令行的參數(shù)幫助
Usage: frida [options] targetOptions:--version show program's version number and exit-h, --help show this help message and exit-D ID, --device=ID connect to device with the given ID-U, --usb connect to USB device-R, --remote connect to remote frida-server-H HOST, --host=HOST connect to remote frida-server on HOST-f FILE, --file=FILE spawn FILE-n NAME, --attach-name=NAMEattach to NAME-p PID, --attach-pid=PIDattach to PID--debug enable the Node.js compatible script debugger--enable-jit enable JIT-l SCRIPT, --load=SCRIPTload SCRIPT-c CODESHARE_URI, --codeshare=CODESHARE_URIload CODESHARE_URI-e CODE, --eval=CODE evaluate CODE-q quiet mode (no prompt) and quit after -l and -e--no-pause automatically start main thread after startup-o LOGFILE, --output=LOGFILEoutput to log file1.3.1 將一個腳本注入到Android目標進程
frida -U -l myhook.js com.xxx.xxxx參數(shù)解釋:
- -U 指定對USB設備操作
- -l 指定加載一個Javascript腳本
- 最后指定一個進程名,如果想指定進程pid,用-p選項。正在運行的進程可以用frida-ps -U命令查看
1.3.2 重啟一個Android進程并注入腳本
frida -U -l myhook.js -f com.xxx.xxxx --no-pause參數(shù)解釋:
- -f 指定一個進程,重啟它并注入腳本
- –no-pause 自動運行程序
- 這種注入腳本的方法,常用于hook在App就啟動期就執(zhí)行的函數(shù)。
frida運行過程中,執(zhí)行%resume重新注入,執(zhí)行%reload來重新加載腳本;執(zhí)行exit結束腳本注入
2. Hook Java方法
2.1 載入類
Java.use方法用于加載一個Java類,相當于Java中的Class.forName()。比如要加載一個String類:
var StringClass = Java.use("java.lang.String");加載內部類:
var MyClass_InnerClass = Java.use("com.luoyesiqiu.MyClass$InnerClass");其中InnerClass是MyClass的內部類
2.2 修改函數(shù)的實現(xiàn)
修改一個函數(shù)的實現(xiàn)是逆向調試中相當有用的。修改一個函數(shù)的實現(xiàn)后,如果這個函數(shù)被調用,我們的Javascript代碼里的函數(shù)實現(xiàn)也會被調用。
2.2.1 函數(shù)參數(shù)類型表示
不同的參數(shù)類型都有自己的表示方法
1.對于基本類型,直接用它在Java中的表示方法就可以了,不用改變,例如:
- int
- short
- char
- byte
- boolean
- float
- double
- long
2.基本類型數(shù)組,用左中括號接上基本類型的縮寫
基本類型縮寫表示表:
| boolean | Z |
| byte | B |
| char | C |
| double | D |
| float | F |
| int | I |
| long | J |
| short | S |
例如:int[]類型,在重載時要寫成[I
任意類,直接寫完整類名即可
例如:java.lang.String
對象數(shù)組,用左中括號接上完整類名再接上分號
例如:[java.lang.String;
2.2.2 帶參數(shù)的構造函數(shù)
修改參數(shù)為byte[]類型的構造函數(shù)的實現(xiàn)
ClassName.$init.overload('[B').implementation=function(param){//do something }注:ClassName是使用Java.use定義的類;param是可以在函數(shù)體中訪問的參數(shù)
修改多參數(shù)的構造函數(shù)的實現(xiàn)
ClassName.$init.overload('[B','int','int').implementation=function(param1,param2,param3){//do something }2.2.3 無參數(shù)構造函數(shù)#
ClassName.$init.overload().implementation=function(){//do something }調用原構造函數(shù)
ClassName.$init.overload().implementation=function(){//do somethingthis.$init();//do something }注意:當構造函數(shù)(函數(shù))有多種重載形式,比如一個類中有兩個形式的func:void func()和void
func(int),要加上overload來對函數(shù)進行重載,否則可以省略overload
2.2.4 一般函數(shù)
修改函數(shù)名為func,參數(shù)為byte[]類型的函數(shù)的實現(xiàn)
ClassName.func.overload('[B').implementation=function(param){//do something//return ... }2.2.5 無參數(shù)的函數(shù)
ClassName.func.overload().implementation=function(){//do something }注: 在修改函數(shù)實現(xiàn)時,如果原函數(shù)有返回值,那么我們在實現(xiàn)時也要返回合適的值
ClassName.func.overload().implementation=function(){//do somethingreturn this.func(); }3. 調用函數(shù)
和Java一樣,創(chuàng)建類實例就是調用構造函數(shù),而在這里用$new表示一個構造函數(shù)。
var ClassName=Java.use("com.luoye.test.ClassName"); var instance = ClassName.$new();實例化以后調用其他函數(shù)
var ClassName=Java.use("com.luoye.test.ClassName"); var instance = ClassName.$new(); instance.func();4. 字段操作
字段賦值和讀取要在字段名后加.value,假設有這樣的一個類:
package com.luoyesiqiu.app; public class Person{private String name;private int age; }寫個腳本操作Person類的name字段和age字段:
var person_class = Java.use("com.luoyesiqiu.app.Person"); //實例化Person類 var person_class_instance = person_class.$new(); //給name字段賦值 person_class_instance.name.value = "luoyesiqiu"; //給age字段賦值 person_class_instance.age.value = 18; //輸出name字段和age字段的值 console.log("name = ",person_class_instance.name.value, "," ,"age = " ,person_class_instance.age.value);輸出:
name = luoyesiqiu , age = 185. 類型轉換
用Java.cast方法來對一個對象進行類型轉換,如將variable轉換成java.lang.String:
var StringClass=Java.use("java.lang.String"); var NewTypeClass=Java.cast(variable,StringClass);6. Java.available字段
這個字段標記Java虛擬機(例如: Dalvik 或者 ART)是否已加載, 操作Java任何東西之前,要確認這個值是否為true
7. Java.perform方法
Java.perform(fn)在Javascript代碼成功被附加到目標進程時調用,我們核心的代碼要在里面寫。格式:
Java.perform(function(){ //do something... });8. 實例講解
有了以上的基礎知識,我們就可以進行編寫代碼了
8.1 修改返回值
8.1.1 場景
假設有以下的程序,給isExcellent方法傳入兩個值,通過計算,返回一個布爾值,表示是否優(yōu)秀。默認情況下,它是只會顯示是否優(yōu)秀:false的,因為我們默認傳入的數(shù)很小:
我們編寫一個腳本來Hook isExcellent函數(shù),使它返回true,顯示為是否優(yōu)秀:true
對于這種簡單的場景,直接修改返回值就可以了,因為只有結果是重要的。
8.1.2 代碼
想直接返回結果很簡單,直接在匿名方法里return即可。
if(Java.available){Java.perform(function(){var MainActivity = Java.use("com.luoyesiqiu.crackme.MainActivity");MainActivity.isExcellent.implementation=function(){return true; }});}-
將上面的代碼保存為:exp1.js
-
執(zhí)行adb shell 'su -c /data/local/tmp/frida-server’啟動服務端
-
運行目標App
-
執(zhí)行frida -U -l exp1.js com.luoyesiqiu.crackme注入代碼
-
按返回鍵返回桌面,再重新打開App,發(fā)現(xiàn)達到預期
-
在命令行輸入exit,回車,停止注入代碼
注:這里為什么要打開兩次App?第一打開是為了讓frida能夠找到進程,第二次打開是為了驗證結果,即使Hook成功了,界面是有緩存的,并不能實時顯示Hook結果,所以需要重新打開App
8.2 修改參數(shù)
8.2.1 場景
假設有以下場景,isExcellent除了返回是否優(yōu)秀以外,方法的內部還把分數(shù)打印出來。
public class MainActivity extends AppCompatActivity {private String TAG="Crackme";private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView =findViewById(R.id.tv);textView.append("是否優(yōu)秀:"+isExcellent(46,54)+"\n");}private boolean isExcellent(int chinese, int math){textView.append("語文+數(shù)學總分:"+(chinese+math)+"\n");if( chinese + math >=180){return true;}else{return false;}} }這種情況下我們不可能只返回是否優(yōu)秀吧,顯示的總分很低,但是卻返回優(yōu)秀,是很尷尬的…所以我們要修改isExcellent方法的參數(shù),使其通過計算打印和返回合理的值。
8.2.2 代碼
if(Java.available){Java.perform(function(){var MainActivity = Java.use("com.luoyesiqiu.crackme.MainActivity");MainActivity.isExcellent.overload("int","int").implementation=function(chinese,math){return this.isExcellent(95,96); }});}上面的代碼,通過overload方法重載參數(shù),修改isExcellent方法實現(xiàn),并在實現(xiàn)函數(shù)里調用原來的方法,得到新的返回值
-
將上面的代碼保存為:exp2.js
-
執(zhí)行adb shell 'su -c /data/local/tmp/frida-server’啟動服務端(如果上面啟動的服務端還開著可省略這一步)
-
運行目標App
-
執(zhí)行frida -U -l exp2.js com.luoyesiqiu.crackme注入代碼
-
按返回鍵,再重新打開App,發(fā)現(xiàn)達到預期
-
在命令行輸入exit,回車,停止注入代碼
9. 配合Python腳本注入
在本文剛開始的時候說到,我們可以編寫Python代碼來配合Javascript代碼注入。下面我們來看看,怎么使用,先看一段代碼:
# -*- coding: UTF-8 -*-import frida, sysjscode = """ if(Java.available){Java.perform(function(){var MainActivity = Java.use("com.luoyesiqiu.crackme.MainActivity");MainActivity.isExcellent.overload("int","int").implementation=function(chinese,math){console.log("[javascript] isExcellent be called.");send("isExcellent be called.");return this.isExcellent(95,96); }});} """def on_message(message, data):if message['type'] == 'send':print("[*] {0}".format(message['payload']))else:print(message) pass# 查找USB設備并附加到目標進程 session = frida.get_usb_device().attach('com.luoyesiqiu.crackme') # 在目標進程里創(chuàng)建腳本 script = session.create_script(jscode) # 注冊消息回調 script.on('message', on_message) print('[*] Start attach') # 加載創(chuàng)建好的javascript腳本 script.load() # 讀取系統(tǒng)輸入 sys.stdin.read()-
將上面的代碼,保存為exp3.py
-
執(zhí)行adb shell 'su -c /data/local/tmp/frida-server'啟動服務端(如果上面啟動的服務端還開著可省略這一步)
-
運行目標App
-
執(zhí)行python exp3.py注入代碼
-
按返回鍵,再重新打開App,發(fā)現(xiàn)達到預期
-
按Ctrl+C停止腳本和停止注入代碼
上面是一段Python代碼,我們來分析它的步驟:
注:如果想在javascript輸出日志,可以調用console.log()方法。如果想給客戶端發(fā)送消息,可以在javascript代碼里調用send()方法,并在客戶端Python代碼里注冊一個消息回調來接收服務端發(fā)來的消息。
文章轉載于 : https://www.cnblogs.com/luoyesiqiu/p/10718997.html
總結
以上是生活随笔為你收集整理的frida的用法--Hook Java层类方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爬虫:Charles证书设置为系统信任证
- 下一篇: Frida之安装和使用教程