短視頻、直播數據實時採集接口,請查看文檔: TiToDatajava
免責聲明:本文檔僅供學習與參考,請勿用於非法用途!不然一切後果自負。
python
以前咱們已經學習過了HOOK
普通函數、方法重載、構造函數,如今來更深刻的學習HOOK
在Android
逆向中,咱們也會常常遇到在Java
層的內部類。Java
內部類函數,使得咱們更難以分析代碼。咱們在這章節中對內部類進行一個基本瞭解和使用FRIDA
對內部類進行鉤子攔截處理。什麼是內部類?所謂內部類就是在一個類內部進行其餘類結構的嵌套操做,它的優勢是內部類與外部類能夠方便的訪問彼此的私有域(包括私有方法、私有屬性),因此Android
中有不少的地方都會使用到內部類,咱們來見一個例子也是最直觀的,以下圖4-17。
圖4-17 User類中的clz類
在圖4-17中看到User
類中嵌套了一個clz
,這樣的操做也是家常便飯了。在frida
中,咱們可使用$
符號對起進行處理。首先打開jadxgui
軟件對代碼進行反編譯,反編譯以後進入User
類,下方會有一個smali
的按鈕,點擊smali
則會進入smali
代碼,進入smali
代碼直接按ctrl+f
局部搜索字符串clz
,由於clz
是內部類的名稱,那麼就會搜到Lcom/roysue/roysueapplication/User\$clz;
,咱們將翻譯成java
代碼就是:com.roysue.roysueapplication.User\$clz
,去掉第一個字符串的L
和/
以及;
就構成了內部類的具體類名了,見下圖4-18。
圖4-18 smali代碼
通過上面的分析咱們已經得知最重要的部分類的路徑:com.roysue.roysueapplication.User\$clz
,如今來對內部類進行HOOK
,如今開始編寫js腳本。android
function hook_overload_3() { if(Java.available) { Java.perform(function () { console.log("start hook"); //注意此處類的路徑填寫更改所分析的路徑 var clz = Java.use('com.roysue.roysueapplication.User$clz'); if(clz != undefined) { //這邊也是像正常的函數來hook便可 clz.toString.implementation = function (){ console.log("成功hook clz類"); return this.toString(); } } else { console.log("clz: undefined"); } console.log("start end"); }); } }
執行腳本以後,咱們能夠看到控制也已經成功附加而且打印了成功hook clz
類,這樣咱們也可以對Java
層的內部類進行處理了。json
[Google Pixel::com.roysue.roysueapplication]-> 成功hook clz類 成功hook clz類
在前面咱們學會了如何在java
層的各類函數的HOOK
操做了,如今開始學習枚舉全部的類並定位類的騷套路了~,學習以前咱們要了解API
中的enumerateLoadedClasses
方法,它是屬於Java
對象中的一個方法。可以枚舉如今加載的全部類,enumerateLoadedClasses
存在2
個回調函數,分別是onMatch:function(ClassName):
爲每一個加載的具備className
的類調用,每一個ClassName
返回來的都是一個類名;和onComplete:function():
在枚舉全部類枚舉完以後回調一次。api
setTimeout(function (){ Java.perform(function (){ console.log("n[*] enumerating classes..."); //Java對象的API enumerateLoadedClasses Java.enumerateLoadedClasses({ //該回調函數中的_className參數就是類的名稱,每次回調時都會返回一個類的名稱 onMatch: function(_className){ //在這裏將其輸出 console.log("[*] found instance of '"+_className+"'"); //若是隻須要打印出com.roysue包下全部類把這段註釋便可,想打印其餘的替換掉indexOf中參數便可定位到~ //if(_className.toString().indexOf("com.roysue")!=-1) //{ // console.log("[*] found instance of '"+_className+"'"); //} }, onComplete: function(){ //會在枚舉類結束以後回調一次此函數 console.log("[*] class enuemration complete"); } }); }); });
當咱們執行該腳本時,注入目標進程以後會開始調用onMatch
函數,每次調用都會打印一次類的名稱,當onMatch
函數回調完成以後會調用一次onComplete
函數,最後會打印出class enuemration complete
,見下圖。
圖4-19 枚舉全部類數組
上文已經將類以及實例枚舉出來,接下來咱們來枚舉全部方法,打印指定類或者全部的類的內部方法名稱,主要核心功能是經過類的反射方法中的getDeclaredMethods()
,該api
屬於JAVAJDK
中自帶的API
,屬於java.lang.Class
包中定義的函數。該方法獲取到類或接口聲明的全部方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。固然也包括它所實現接口的方法。在Java
中它是這樣定義的:public Method[] getDeclaredMethods();
其返回值是一個Method
數組,Method
實際上就是一個方法名稱字符串,固然也是一個對象數組,而後咱們將它打印出來。session
function enumMethods(targetClass) { var hook = Java.use(targetClass); var ownMethods = hook.class.getDeclaredMethods(); hook.$dispose; return ownMethods; } function hook_overload_5() { if(Java.available) { Java.perform(function () { var a = enumMethods("com.roysue.roysueapplication.User$clz") a.forEach(function(s) { console.log(s); }); }); } }
咱們先定義了一個enumMethods
方法,其參數targetClass
是類的路徑名稱,用於Java.use
獲取類對象自己,獲取類對象以後再經過其.class.getDeclaredMethods()
方法獲取目標類的全部方法名稱數組,當調用完了getDeclaredMethods()
方法以後再調用$dispose
方法釋放目標類對象,返回目標類全部的方法名稱、返回類型以及函數的權限,這是實現獲取方法名稱的核心方法,下面一個方法主要用於注入到目標進程中去執行邏輯代碼,在hook_overload_5
方法中先是使用了Java.perform
方法,再在內部調用enumMethods
方法獲取目標類的全部方法名稱、返回類型以及函數的權限,返回的是一個Method
數組,經過forEach
迭代器循環輸出數組中的每個值,由於其自己實際就是一個字符串因此直接輸出就能夠獲得方法名稱,腳本執行效果以下圖4-20。
圖4-20 腳本執行後效果在圖4-17中clz
只有一個toString
方法,咱們填入參數爲com.roysue.roysueapplication.User$clz
,就可以定位到該類中全部的方法。app
咱們學會了枚舉全部的類以及類的有方法以後,那咱們還想知道如何獲取全部的方法重載函數,畢竟在Android
反編譯的源碼中方法重載不在少數,對此,一次性hook
全部的方法重載是很是有必要的學習。咱們已經知道在hook
重載方法時須要寫overload('x')
,也就是說咱們須要構造一個重載的數組,並把每個重載都打印出來。函數
function hook_overload_8() { if(Java.available) { Java.perform(function () { console.log("start hook"); var targetMethod = 'add'; var targetClass = 'com.roysue.roysueapplication.Ordinary_Class'; var targetClassMethod = targetClass + '.' + targetMethod; //目標類 var hook = Java.use(targetClass); //重載次數 var overloadCount = hook[targetMethod].overloads.length; //打印日誌:追蹤的方法有多少個重載 console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]"); //每一個重載都進入一次 for (var i = 0; i < overloadCount; i++) { //hook每個重載 hook[targetMethod].overloads[i].implementation = function() { console.warn("n*** entered " + targetClassMethod); //能夠打印每一個重載的調用棧,對調試有巨大的幫助,固然,信息也不少,儘可能不要打印,除非分析陷入僵局 Java.perform(function() { var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()); console.log("nBacktrace:n" + bt); }); // 打印參數 if (arguments.length) console.log(); for (var j = 0; j < arguments.length; j++) { console.log("arg[" + j + "]: " + arguments[j]); } //打印返回值 var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?) console.log("nretval: " + retval); console.warn("n*** exiting " + targetClassMethod); return retval; } } console.log("hook end"); }); } }
上面這段代碼能夠打印出com.roysue.roysueapplication.Ordinary_Class
類中add
方法重載的個數以及hook該類中全部的方法重載函數,如今來剖析上面的代碼爲何能夠對一個類中的全部的方法重載HOOK
掛上鉤子。首先咱們定義了三個變量分別是targetMethod、targetClass、targetClassMethod
,這三個變量主要於定義方法的名稱、類名、以及類名+方法名的賦值,首先使用了Java.use
獲取了目標類對象,再獲取重載的次數。這裏詳細說一下如何獲取的:var method_overload = cls[<func_name>].overloads[index];
這句代碼能夠看出經過cls
索引func_name
到類中的方法,然後面寫到overloads[index]
是指方法重載的第index
個函數,大體意思就是返回了一個method
對象的第index
位置的函數。而在代碼中寫道:var overloadCount = hook[targetMethod].overloads.length;
,採起的方法是先獲取類中某個函數全部的方法重載個數。繼續往下走,開始循環方法重載的函數,剛剛開始循環時hook[targetMethod].overloads[i].implementation
這句對每個重載的函數進行HOOK
。這裏也說一下Arguments:Arguments
是js
中的一個對象,js
內的每一個函數都會內置一個Arguments
對象實例arguments
,它引用着方法實參,調用其實例對象能夠經過arguments[]
下標的來引用實際元素,arguments.length
爲函數實參個數,arguments.callee
引用函數自身。這就是爲何在該段代碼中並看不到arguments
的定義卻可以直接調用的緣由,由於它是內置的一個對象。好了,講完了arguments
我們接着說,打印參數經過arguments.length
來循環以及arguments[j]
來獲取實際參數的元素。那如今來看apply
,apply
在js
中是怎麼樣的存在,apply
的含義是:應用某一對象的一個方法,用另外一個對象替換當前對象,this[targetMethod].apply(this, arguments);
這句代碼簡言之就是執行了當前的overload
方法。執行完當前的overload
方法而且打印以及返回給真實調用的函數,這樣不會使程序錯誤。那麼最終執行效果見下圖4-21:
圖4-21 終端顯示
能夠看到成功打印了add
函數的方法重載的數量以及hook
打印出來的參數值、返回值!工具
學會了如何HOOK
全部方法重載函數後,咱們能夠把以前學習的整合到一塊兒,來hook
指定類中的全部方法,也包括方法重載的函數。下面js
中核心代碼是利用重載函數的特色來HOOK
所有的方法,普通的方法也是一個特殊方法重載,只是它只是一個方法而已,直接把它看成方法重載來HOOK
就行了,打個比方正方形是特殊的長方形,而長方形是否是特殊的正方形。這個正方形是普通函數,而長方形是重載方法這樣你們應該很好理解了~在上一章節中已經知道了如何hook
方法重載,只是方法名稱和類名是寫死的,只須要把成員的targetClass、targetMethod
定義方法中的參數便可,在該例子中拿到指定類全部的全部方法名稱,更加靈活使用了,代碼以下。
function traceClass(targetClass) { //Java.use是新建一個對象哈,你們還記得麼? var hook = Java.use(targetClass); //利用反射的方式,拿到當前類的全部方法 var methods = hook.class.getDeclaredMethods(); //建完對象以後記得將對象釋放掉哈 hook.$dispose; //將方法名保存到數組中 var parsedMethods = []; methods.forEach(function(method) { //經過getName()方法獲取函數名稱 parsedMethods.push(method.getName()); }); //去掉一些重複的值 var targets = uniqBy(parsedMethods, JSON.stringify); //對數組中全部的方法進行hook targets.forEach(function(targetMethod) { traceMethod(targetClass + "." + targetMethod); }); } function hook_overload_9() { if(Java.available) { Java.perform(function () { console.log("start hook"); traceClass("com.roysue.roysueapplication.Ordinary_Class"); console.log("hook end"); }); } } s1etImmediate(hook_overload_9);
執行腳本效果能夠看到,hook
到了com.roysue.roysueapplication.Ordinary_Class
類中全部的函數,在執行其被hook
攔截的方法時候,也打印出了每一個方法相應的的參數以及返回值,見下圖4-22。
圖4-22 終端運行顯示效果
這裏的核心功能也用到了上一小章節中定義的traceClass
函數,該函數只須要傳入一個class
路徑便可對class
中的函數完成注入hook
。那麼在本小章節來hook
掉全部類的子類,使咱們的腳本更加的靈活方便。經過以前的學習咱們已經知道enumerateLoadedClasses
這個api
能夠枚舉全部的類,用它來獲取全部的類而後再調用traceClass
函數就能夠對全部類的子進行全面的hook
。可是通常不會hook
全部的函數,由於AndroidAPI
函數實在太多了,在這裏咱們須要匹配本身須要hook
的類便可,代碼以下。
//枚舉全部已經加載的類 Java.enumerateLoadedClasses({ onMatch: function(aClass) { //迭代和判斷 if (aClass.match(pattern)) { //作一些更多的判斷,適配更多的pattern var className = aClass.match(/[L]?(.*);?/)[1].replace(///g, "."); //進入到traceClass裏去 traceClass(className); } }, onComplete: function() {} });
在FRIDA
中,不但提供很完善的HOOK
機制,而且還提供rpc
接口。能夠導出某一個指定的函數,實如今python
層對其隨意的調用,並且是隨時隨地想調用就調用,極其方便,由於是在供給外部的python
,這使得rpc
提供的接口能夠與python
完成一些很奇妙的操做,這些導出的函數能夠是任意的java
內部的類的方法,調用咱們本身想要的對象和特定的方法。那咱們開始動手吧,如今咱們來經過RPC
的導出功能將圖4-9中的add
方法供給外部調用,開始編寫rpc_demo.py
文件,此次是python
文件了哦~不是js
文件了
import codecs import frida from time import sleep # 附加進程名稱爲:com.roysue.roysueapplication session = frida.get_remote_device().attach('com.roysue.roysueapplication') # 這是須要執行的js腳本,rpc須要在js中定義 source = """ //定義RPC rpc.exports = { //這裏定義了一個給外部調用的方法:sms sms: function () { var result = ""; //嵌入HOOK代碼 Java.perform(function () { //拿到class類 var Ordinary_Class = Java.use("com.roysue.roysueapplication.Ordinary_Class"); //最終rpc的sms方法會返回add(1,3)的結果! result = Ordinary_Class.add(1,3); }); return result; }, }; """ # 建立js腳本 script = session.create_script(source) script.load() # 這裏能夠直接調用java中的函數 rpc = script.exports # 在這裏也就是python下直接經過rpc調用sms()方法 print(rpc.sms()) sleep(1) session.detach()
當咱們執行python rpc_demo.py
時先會建立腳本而且注入到目標進程,在上面的source
實際上就是js邏輯代碼了。在js
代碼內咱們定義了rpc
能夠給python
調用的sms
函數,而sms
函數內部嵌套調用Java.perform
再對須要拿到的函數的類進行主動調用,把最終的結果返回做爲sms
的返回值,當咱們在python
層時候能夠任意調用sms
中的原型add
方法~
一個比較好的綜合案例 :dump
藍牙信息的「增強版」——BlueCrawl
。
VERSION="1.0.0" setTimeout(function(){ Java.perform(function(){ Java.enumerateLoadedClasses({ onMatch: function(instance){ if (instance.split(".")[1] == "bluetooth"){ console.log("[->]t"+lightBlueCursor()+instance+closeCursor()); } }, onComplete: function() {} }); Java.choose("android.bluetooth.BluetoothGattServer",{ onMatch: function (instance){ ... onComplete: function() { console.log("[*] -----");} }); Java.choose("android.bluetooth.BluetoothGattService",{ onMatch: function (instance){ ... onComplete: function() { console.log("[*] -----");} }); Java.choose("android.bluetooth.BluetoothSocket",{ onMatch: function (instance){ ... onComplete: function() { console.log("[*] -----");} }); Java.choose("android.bluetooth.BluetoothServerSocket",{ onMatch: function (instance){ ... onComplete: function() { console.log("[*] -----");} }); Java.choose("android.bluetooth.BluetoothDevice",{ onMatch: function (instance){ ... onComplete: function() { console.log("[*] -----");} }); }); },0);
該腳本首先枚舉了不少藍牙相關的類,而後choose
了不少類,包括藍牙接口信息以及藍牙服務接口對象等,還加載了內存中已經分配好的藍牙設備對象,也就是上文咱們已經演示的信息。咱們能夠用這個腳原本「查看」App
加載了哪些藍牙的接口,App
是否正在查找藍牙設備、或者是否竊取藍牙設備信息等。在電腦上運行命令:$ frida -U -l bluecrawl-1.0.0.js com.android.bluetooth
執行該腳本時會詳細打印全部藍牙接口信息以及服務接口對象~~
咱們來試下它的幾個主要的功能,首先是本地庫的導出函數。
setTimeout(function() { Java.perform(function() { trace("exports:*!open*"); //trace("exports:*!write*"); //trace("exports:*!malloc*"); //trace("exports:*!free*"); }); }, 0);
咱們hook
的是open()
函數,跑起來看下效果:
$ frida -U -f com.whatsapp -l raptor_frida_android_trace_fixed.js --no-pause
如圖所示*!open*
根據正則匹配到了openlog
、open64
等導出函數,並hook
了全部這些函數,打印出了其參數以及返回值。接下來想要看哪一個部分,只要扔到jadx裏,靜態「分析」一番,本身隨便翻翻,或者根據字符串搜一搜。好比說咱們想要看上圖中的com.whatsapp.app.protocol
包裏的內容,就能夠設置trace("com.whatsapp.app.protocol")
。能夠看到包內的函數、方法、包括重載、參數以及返回值全都打印了出來。這就是frida
腳本的魅力。固然,腳本終歸只是一個工具,你對Java
、安卓App
的理解,和你的創意纔是相當重要的。接下來能夠搭配Xposed module
看看別人都給whatsapp
作了哪些模塊,hook
的哪些函數,實現了哪些功能,學習本身寫一寫。
短視頻、直播數據實時採集接口,請查看文檔: TiToData
免責聲明:本文檔僅供學習與參考,請勿用於非法用途!不然一切後果自負。