Frida之安装和使用教程
前言
在日常分析安卓應(yīng)用時(shí),通常會(huì)有對(duì)應(yīng)用進(jìn)行hook的需求,用的比較多的hook框架有Xposed,frida,xhook等,正好最近接觸Frida接觸的較多,所以對(duì)Frida的一些常用操作做個(gè)記錄,方便以后翻閱查詢,同時(shí)也可以對(duì)學(xué)習(xí)frida的小伙伴有個(gè)參考的資料;
一般Frida逆向三階段:
1.FRIDA安裝
1.1.安裝python3環(huán)境
https://www.python.org/ 下載最新的直接安裝
1.2.frida安裝
C:\Users\Administrator>pip install frida-tools Collecting frida-tools /...省略.../Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.Running setup.py install for frida-tools ... done Successfully installed colorama-0.4.0 frida-12.2.25 frida-tools-1.2.2 prompt-toolkit-1.0.15 pygments-2.2.0 six-1.11.0 wcwidth-0.1.71.3.frida-server下載
frida-server下載需要注意的有兩點(diǎn):
1.安裝的frida版本:需要下載與安裝的frida版本對(duì)應(yīng)的frida-server,否則會(huì)出錯(cuò)。
下載地址:https://github.com/frida/frida/releases
2.手機(jī)設(shè)備的架構(gòu):因?yàn)槲业氖謾C(jī)是arm64平臺(tái)的所以選擇的是android-arm64
frida-server-12.2.25-android-arm64.xz下載完畢之后,解壓后通過adb push到設(shè)備的臨時(shí)目錄下
adb push frida-server-12.2.25-android-arm64 /data/local/tmp/1.4.啟動(dòng)frida服務(wù)并連接
adb shell進(jìn)入設(shè)備shell環(huán)境,cd到臨時(shí)目錄下,給frida-server文件設(shè)置可執(zhí)行權(quán)限使其可以運(yùn)行
chmod 755 frida-server-12.2.25-android-arm64之后運(yùn)行frida服務(wù)文件,注意需要通過root權(quán)限去運(yùn)行
./frida-server-12.2.25-android-arm64然后 進(jìn)行端口轉(zhuǎn)發(fā)
adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:27043完畢后就可以在windows上運(yùn)行簡(jiǎn)單的frida命令測(cè)試我們是否安裝成功了
/* -U 連接USB設(shè)備 */ /* frida-ps 列出正在運(yùn)行的進(jìn)程*/ C:\Users\Administrator>frida-ps -U PID Name ---- -------------------------------- 310 1:3 714 360sguard 723 360sguard 373 ATFWD-daemon 575 adbd 259 adsprpcd [....] 3289 zygote1.5.搭建frida開發(fā)環(huán)境
$ git clone git://github.com/oleavr/frida-agent-example.git $ cd frida-agent-example/ $ npm install $ frida -U -f com.example.android --no-pause -l _agent.js2.FRIDA 基礎(chǔ)
首先來看一下frida的常用功能
2.1.frida查看當(dāng)前設(shè)備進(jìn)程
λ frida-ps -UPID Name ----- ------------------------------------------------------- 6241 adbd 897 adsprpcd 749 android.hardware.audio@2.0-service 947 android.hardware.biometrics.fingerprint@2.1-service.fpc 750 android.hardware.bluetooth@1.0-service-qti 600 android.hardware.boot@1.0-service 752 android.hardware.camera.provider@2.4-service 753 android.hardware.cas@1.0-service 612 android.hardware.configstore@1.1-service [...]2.2.frida對(duì)指定方法進(jìn)行trace
λ frida-ps -U | grep frida 31521 cn.gemini.k.fridatest 30073 frida-helper-32 λ frida-trace -i "open" -U "cn.gemini.k.fridatest" Instrumenting... open: Auto-generated handler at "E:\\Frida\\frida_work\\__handlers__\\libutils.so\\open.js" Started tracing 1 function. Press Ctrl+C to stop. /* TID 0x7b3a */ 16061 ms open()/* TID 0x7b3c */ 16114 ms open() 16114 ms open() 16114 ms open()用的比較多的一般就是上面的兩個(gè)功能,都是frida幫我們生成好了hook代碼,直接拿來使用就行,簡(jiǎn)單方便,但有時(shí)候我們想自己定制些功能怎么辦?
接下來看下如何通過編寫js代碼來實(shí)現(xiàn)對(duì)安卓APP中某些方法的hook。
在hook之前首先要熟悉我們需要hook的目標(biāo)方法,應(yīng)用包名,參數(shù)等基礎(chǔ)信息。這里簡(jiǎn)單寫了個(gè)demo,后面我們都通過這個(gè)demo來學(xué)習(xí)。
整理一下hook時(shí)需要的信息:
目標(biāo)應(yīng)用的包名:“cn.gemini.k.fridatest”(一般在AndroidManifest.xml文件中可以找到)
目標(biāo)方法所在類的類名:“cn.gemini.k.fridatest.FridaHook1”;
最后是目標(biāo)方法名和參數(shù):public int func1_add(int a,int b)
沒有源碼的情況下這些信息都可以通過反編譯工具jadx或jeb獲取。
接下來開始編寫hook代碼
2.3.frida hook代碼的兩種形式
2.3.1.代碼內(nèi)置在python文件中
load.py代碼如下:
# -*- coding: UTF-8 -*- import frida,sysjs_code = '''Java.perform(function(){console.log("Frida Test");var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");cls.func1_add.implementation = function(arg1,arg2){console.log("hook arg1:",arg1," hook arg2:",arg2);return this.func1_add(arg1,arg2);}}); '''# 目標(biāo)包名 appPacknName = "cn.gemini.k.fridatest" scriptFile = "hook_script.js"# 輸出日志的回調(diào)方法 def on_message(message, data):if message['type'] == 'send':print("[*] {0}".format(message['payload']))else:print(message)device = frida.get_usb_device() # spawn模式,找到目標(biāo)包名并重啟,在啟動(dòng)前注入腳本 pid = device.spawn([appPacknName]) session = device.attach(pid) # 注意這里需要將device.attach(pid)這句代碼寫在前面,這樣執(zhí)行才符合預(yù)期(啟動(dòng)時(shí)程序白屏,等待下面這行代碼來恢復(fù)執(zhí)行) # 其實(shí)在https://www.jianshu.com/p/b833fba1bffe這篇文章中有提到 device.resume(pid)# 方式一: 通過js文件創(chuàng)建hook代碼 #with open(scriptFile, encoding='UTF-8') as f : # script = session.create_script(f.read()) # 方式二: 直接將hook代碼寫在python文件中 script = session.create_script(js_code)script.on('message', on_message) script.load() #把js代碼注入到目標(biāo)應(yīng)用中 # 避免結(jié)束 sys.stdin.read()解釋下上面的python代碼,先通過frida.get_usb_device連接到usb設(shè)備,接著使用spawn將目標(biāo)應(yīng)用重啟并掛起,之后再通過attach附加到目標(biāo)應(yīng)用,完成附加后開始喚起應(yīng)用主線程,最后是構(gòu)造需要注入的js代碼,通過session.create_script生成js腳本,script.on用來注冊(cè)一個(gè)回調(diào)方法監(jiān)聽目標(biāo)進(jìn)程的所有消息,script.load會(huì)把前面生成的js代碼注入到目標(biāo)應(yīng)用中完成hook。這里hook代碼只是簡(jiǎn)單的將函數(shù)的參數(shù)給輸出了下。
運(yùn)行結(jié)果如下:
// frida打印日志 λ python load.py Frida Test hook arg1: 1 hook arg2: 2// APP程序執(zhí)行打印日志 19493-19493/cn.gemini.k.fridatest E/func1_add: arg1:1 arg2:2 19493-19493/cn.gemini.k.fridatest E/FridaHook1: 一般方法 ret:32.3.2.代碼封裝成js文件
hook_script.js代碼如下
function main(){Java.perform(function(){console.log("Frida Test");var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");cls.func1_add.implementation = function(arg1,arg2){console.log("hook arg1:",arg1," hook arg2:",arg2);return this.func1_add(arg1,arg2);}}); } setImmediate(main)load2.py代碼如下
# -*- coding: UTF-8 -*- import frida,sys# 目標(biāo)包名 appPacknName = "cn.gemini.k.fridatest" scriptFile = "hook_script.js"# 輸出日志的回調(diào)方法 def on_message(message, data):if message['type'] == 'send':print("[*] {0}".format(message['payload']))else:print(message)device = frida.get_usb_device() # spawn模式,找到目標(biāo)包名并重啟,在啟動(dòng)前注入腳本 pid = device.spawn([appPacknName]) session = device.attach(pid) # 注意這里需要將device.attach(pid)這句代碼寫在前面,這樣執(zhí)行才符合預(yù)期(啟動(dòng)時(shí)程序白屏,等待下面這行代碼來恢復(fù)執(zhí)行) # 其實(shí)在https://www.jianshu.com/p/b833fba1bffe這篇文章中有提到 device.resume(pid)# 方式一: 通過js文件創(chuàng)建hook代碼 with open(scriptFile, encoding='UTF-8') as f :script = session.create_script(f.read()) # 方式二: 直接將hook代碼寫在python文件中 # script = session.create_script(js_code)script.on("message", on_message) script.load() #把js代碼注入到目標(biāo)應(yīng)用中 # 避免結(jié)束 sys.stdin.read()運(yùn)行結(jié)果如下:
// frida打印日志 λ python load2.py Frida Test hook arg1: 1 hook arg2: 2// APP程序執(zhí)行打印日志 19493-19493/cn.gemini.k.fridatest E/func1_add: arg1:1 arg2:2 19493-19493/cn.gemini.k.fridatest E/FridaHook1: 一般方法 ret:3上面這種方式主要是將js代碼封裝成了一個(gè)單獨(dú)的js文件,不再是之前放在python文件中的形式,封裝成一個(gè)js文件可以方便我們對(duì)代碼進(jìn)行模塊化管理。同時(shí)這樣做還有一個(gè)好處就是可以在命令行直接使用frida注入工具來注入js代碼到指定進(jìn)程,省去了用python寫load的步驟。
λfrida -U -n "cn.gemini.k.fridatest" -l hook_script.js____/ _ | Frida 14.2.2 - A world-class dynamic instrumentation toolkit| (_| |> _ | Commands:/_/ |_| help -> Displays the help system. . . . object? -> Display information about 'object'. . . . exit/quit -> Exit. . . .. . . . More info at https://www.frida.re/docs/home/ Attaching... Frida Test Hook [AOSP on walleye::cn.gemini.k.fridatest]->2.4.frida的兩種注入模式
最后簡(jiǎn)單了解下frida的兩種注入模式:
第一種:直接通過attach方法對(duì)已啟動(dòng)的目標(biāo)應(yīng)用進(jìn)行注入,這種一般用在hook時(shí)機(jī)比較晚的場(chǎng)景。
第二種:先使用spawn方法以掛起模式重新啟動(dòng)目標(biāo)應(yīng)用后再通過attach方法進(jìn)行注入,一般用在hook時(shí)機(jī)比較早的場(chǎng)景。
暫時(shí)先大概有所了解,后面會(huì)將具體的使用場(chǎng)景。
3.FRIDA HOOK
3.1.hook普通方法、參數(shù)/返回值修改
還是上面的hook demo那個(gè)例子,我們這次對(duì)傳入的參數(shù)以及返回值嘗試修改。
修改js文件中的代碼
上面的js代碼就是我們hook要實(shí)現(xiàn)的功能,Java.use方法通過類名獲取類的類型并返回一個(gè)相同類型的對(duì)象cls,之后通過cls對(duì)象對(duì)普通方法func1_add進(jìn)行hook,成功后當(dāng)func1_add方法被別的方法調(diào)用時(shí)就會(huì)走到我們?cè)O(shè)置的hook方法。
我們的hook方法會(huì)將傳入的兩個(gè)參數(shù)都分別加1,被hook方法的參數(shù)獲取可以通過在hook方法中定義形參獲取也可通過在hook方法內(nèi)部利用arguments[*]數(shù)組獲取。修改完參數(shù)后再傳遞給原始方法進(jìn)行計(jì)算,原始方法將返回值返回給hook方法后我們?cè)賹⒎祷刂导?,最后返回給應(yīng)用的調(diào)用方法。
運(yùn)行結(jié)果如下:
從上面的結(jié)果可以看到,不管是傳入的參數(shù),還是最后的返回值都被我們修改掉了。
3.2.hook重載方法/所有重載方法
如果需要hook的方法存在重載方法,那么就需要使用overload關(guān)鍵字來指明參數(shù)類型,否則frida會(huì)報(bào)錯(cuò)。
js代碼:
執(zhí)行結(jié)果
λ python load2.py// APP程序執(zhí)行打印日志 6374-6374/cn.gemini.k.fridatest E/func2_add_overload: arg1:1 arg2:2 6374-6374/cn.gemini.k.fridatest E/FridaHook1: 重載方法 ret:6 6374-6374/cn.gemini.k.fridatest E/func2_add_overload: arg1:1 arg2:2 arg3:3 6374-6374/cn.gemini.k.fridatest E/FridaHook1: 重載方法 ret:9如果重載方法比較少上面的方法還行,但是如果重載方法較多的話一個(gè)個(gè)去寫重載就比較無趣了,frida還給我們提供了一種hook所有重載方法的方法。
js代碼
3.3.hook構(gòu)造方法
如果我們想hook一個(gè)類的構(gòu)造方法,那么使用固定的$init代替構(gòu)造方法名:
function Hook3(){Java.perform(function(){console.log("Frida Test Hook3");var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");cls.$init.implementation = function(){console.log("$init --> hook");//this.$init();}}); }執(zhí)行結(jié)果
λ python load2.py Frida Test Hook3 $init --> hook// APP程序執(zhí)行打印日志 因?yàn)閔ook方法只輸出了"$init --> hook"并沒有調(diào)用原始構(gòu)造方法,所以程序并沒有輸出。3.4.hook靜態(tài)方法
靜態(tài)方法的hook實(shí)際上和一般方法的hook類似。
function Hook4(){Java.perform(function(){console.log("Frida Test Hook4");var cls = Java.use("cn.gemini.k.fridatest.FridaHook1");cls.func3_verify_static.implementation = function(arg1){console.log("func3_verify_static --> hook arg1:",arg1);return this.func3_verify_static(arg1);}}); }執(zhí)行結(jié)果
λ python load2.py Frida Test Hook4 func3_verify_static --> hook arg1: 12345678// APP程序執(zhí)行打印日志 6374-6374/cn.gemini.k.fridatest E/func3_verify_static: 12345678 6374-6374/cn.gemini.k.fridatest E/func3_verify_static: 密碼錯(cuò)誤 6374-6374/cn.gemini.k.fridatest E/FridaHook1: 靜態(tài)方法 ret:03.5.hook內(nèi)部類方法/匿名類方法
對(duì)于內(nèi)部類或匿名類方法的hook需要通過$+名字的方式來使用。
function Hook5(){Java.perform(function(){console.log("Frida Test Hook5");// hook內(nèi)部類方法var innercls = Java.use("cn.gemini.k.fridatest.FridaHook1$inner_class");innercls.inner_class_func.implementation = function(arg1){console.log("inner_class_func arg1:",arg1);return this.inner_class_func(arg1);}// hook匿名類方法var innercls = Java.use("cn.gemini.k.fridatest.FridaHook1$1"); // 匿名類的類名一般使用反編譯軟件獲取innercls.output.implementation = function(){console.log("output 匿名類方法調(diào)用");return this.output();}}); }執(zhí)行結(jié)果
λ python load2.py Frida Test Hook5 inner_class_func arg1: 內(nèi)部類調(diào)用 output 匿名類方法調(diào)用// APP程序執(zhí)行打印日志 6374-6374/cn.gemini.k.fridatest E/inner_class_func: 內(nèi)部類調(diào)用 6374-6374/cn.gemini.k.fridatest E/匿名類方法 FridaHook1: ret:匿名類調(diào)用3.6.枚舉所有類與類的所有方法
Java.enumerateLoadedClasses(callbacks):無返回值,參數(shù)是一個(gè)回調(diào)方法,功能是列出當(dāng)前已經(jīng)加載的類,用回調(diào)方法處理。
回調(diào)方法:
onMath:function(name){}
找到加載的每個(gè)類的時(shí)候被調(diào)用,參數(shù)就是類的名字,可以將name傳入Java.use()來獲得一個(gè)js類,還可以通過name對(duì)枚舉的類進(jìn)行過濾
onComplete:function(){}
枚舉完所有類之后被調(diào)用,用來做一些完成后的收尾工作
Java.enumerateLoadedClassesSync():無參數(shù),方法返回所有已經(jīng)加載的類的數(shù)組。
function Hook6(){Java.perform(function(){console.log("Frida Test Hook6");// 枚舉所有類console.log("枚舉所有類");Java.enumerateLoadedClasses({onMatch: function(name){console.log(name);// 這里可以添加過濾邏輯用來過濾我們關(guān)注的類//if(name.indexOf("cn.gemini.k.fridatest") != -1){// console.log(name);//}},onComplete: function(){}});// 打印類中的所有方法console.log("打印類中的所有方法");var clszz = Java.use("cn.gemini.k.fridatest.FridaHook1");var methods = clszz.class.getDeclaredMethods(); // 獲取類中的所有方法可使用反射獲得console.log(methods);}); }運(yùn)行結(jié)果
λ python load2.py Frida Test Hook6 枚舉所有類 [.....] // 一大堆的系統(tǒng)類,可以添加過濾邏輯過濾一下 [Landroid.content.pm.ProviderInfo; androidx.core.app.CoreComponentFactory$CompatWrapped androidx.core.app.CoreComponentFactory 打印類中的所有方法 public static int cn.gemini.k.fridatest.FridaHook1.func3_verify_static(java.lang.String),public int cn.gemini.k.fridatest.FridaHook1.abc(),public int cn.gemini.k.fridatest.FridaHook1.func1_add(int,int),public int cn.gemini.k.fridatest.FridaHook1.func2_add_overload(int,int),public int cn.gemini.k.fridatest.FridaHook1.func2_add_overload(int,int,int)3.7.hook類中所有成員方法
如果類中有靜態(tài)方法,又有重載方法的話下面代碼會(huì)報(bào)錯(cuò),沒有靜態(tài)方法則可以hook類中的成員方法。
function Hook7(){Java.perform(function(){console.log("Frida Test Hook7");var clszz = Java.use("cn.gemini.k.fridatest.FridaHook1");// 先枚舉類的所有方法var methods = clszz.class.getDeclaredMethods();for(var i = 0; i < methods.length; i++){var methodName = methods[i].getName(); // 獲取到每個(gè)方法的名字console.log(methodName);console.log(clszz[methodName].overloads.length);// 重載方法的處理for(var j = 0; j < clszz[methodName].overloads.length; j++){clszz[methodName].overloads[j].implementation = function(){for(var k = 0;k < arguments.length; k++){console.log(this + " arg"+ k + ":" + arguments[k]);}return this[methodName].apply(this, arguments);}}}}); }3.8.實(shí)例化類對(duì)象/修改類字段
一個(gè)類的字段包括靜態(tài)字段和非靜態(tài)字段。
靜態(tài)字段的訪問可以直接通過 類.字段名.value = “XXX” 的方式進(jìn)行修改。
非靜態(tài)字段則需要先拿到對(duì)象實(shí)例才能修改,獲取對(duì)象實(shí)例可使用Java.choose()。同時(shí)還需要注意非靜態(tài)字段又分為有同名方法的字段和無同名方法的字段。
- 對(duì)于有同名方法的字段,在訪問時(shí)需要在字段名前面加一個(gè)下劃線"_"才能訪問。
- 對(duì)于無同名方法的字段,則直接可以訪問。
對(duì)象實(shí)例化則通過$new方法即可創(chuàng)建一個(gè)新的對(duì)象。
js代碼
執(zhí)行結(jié)果
λ python load2.py Frida Test Hook8 修改前靜態(tài)字段的值:88888888 修改后靜態(tài)字段的值:9 實(shí)例化一個(gè)類對(duì)象cn.gemini.k.fridatest.FridaHook1@a675c28 修改前的字段值: abc==10 cde==20 修改后的字段值: abc==200 cde==1003.9.frida方法主動(dòng)調(diào)用
frida的方法主動(dòng)調(diào)用,主要分以下幾種情況
1.frida主動(dòng)調(diào)用Java類中的靜態(tài)方法,也就是使用static關(guān)鍵字聲明的。
2.frida主動(dòng)調(diào)用對(duì)象的Java成員方法,通過對(duì)象才能調(diào)用的方法,非static方法。
- 方法一:創(chuàng)建一個(gè)新對(duì)象完成主動(dòng)調(diào)用
- 方法二:搜索內(nèi)存中已有對(duì)象完成主動(dòng)調(diào)用(推薦使用內(nèi)存中原有的對(duì)象,因?yàn)閮?nèi)存中的對(duì)象才是應(yīng)用真實(shí)的應(yīng)用使用的對(duì)象,自己創(chuàng)建對(duì)象的數(shù)據(jù)可能與應(yīng)用當(dāng)時(shí)實(shí)際使用的數(shù)據(jù)不一致,一般協(xié)議分析會(huì)存在上面的情況(那么問題來了,如果內(nèi)存中有多個(gè)對(duì)象該如何區(qū)分哪個(gè)是我們要的對(duì)象呢?),如果只是單純使用對(duì)象方法的功能那么一般問題不大)
3.frida主動(dòng)調(diào)用so中的方法。
對(duì)so方法的直接調(diào)用需要用到frida的NativeFunction方法,方法原型如下:
NativeFunction(address, returnType, argTypes[, abi])
1)address:要hook的方法地址
2)returnType:返回值類型
3)argTypes[, abi]: 參數(shù)類型 這里參數(shù)可以是多個(gè)
js代碼
文章轉(zhuǎn)載于: https://blog.csdn.net/weixin_46734340/article/details/117401345
總結(jié)
以上是生活随笔為你收集整理的Frida之安装和使用教程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: frida的用法--Hook Java层
- 下一篇: Frida 基础操作2