抖音資料採集Frida教程,Frida Java Hook 詳解:程式碼及示例(下)
抖音資料採集Frida教程,Frida Java Hook 詳解:程式碼及示例(下)
短視訊、直播資料實時採集介面,請檢視文件: TiToData
免責宣告:本文件僅供學習與參考,請勿用於非法用途!否則一切後果自負。
1.1 Java層攔截內部類函式
之前我們已經學習過了HOOK
普通函式、方法過載、建構函式,現在來更深入的學習HOOK
在Android
逆向中,我們也會經常遇到在Java
層的內部類。Java
內部類函式,使得我們更難以分析程式碼。我們在這章節中對內部類進行一個基本瞭解和使用FRIDA
對內部類進行鉤子攔截處理。什麼是內部類?所謂內部類就是在一個類內部進行其他類結構的巢狀操作,它的優點是內部類與外部類可以方便的訪問彼此的私有域(包括私有方法、私有屬性),所以Android
圖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指令碼。
1.1.1 攔截內部類函式程式碼示例
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
層的內部類進行處理了。
[Google Pixel::com.roysue.roysueapplication]-> 成功hook clz類
成功hook clz類
1.2 Java層列舉所有的類並定位類
在前面我們學會了如何在java
層的各種函式的HOOK
操作了,現在開始學習列舉所有的類並定位類的騷套路了~,學習之前我們要了解API
中的enumerateLoadedClasses
方法,它是屬於Java
物件中的一個方法。能夠列舉現在載入的所有類,enumerateLoadedClasses
存在2
個回撥函式,分別是onMatch:function(ClassName):
為每個載入的具有className
的類呼叫,每個ClassName
返回來的都是一個類名;和onComplete:function():
在列舉所有類列舉完之後回撥一次。
1.2.1 列舉所有的類並定位類程式碼示例
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 列舉所有類
1.3 Java層列舉類的所有方法並定位方法
上文已經將類以及例項枚舉出來,接下來我們來列舉所有方法,列印指定類或者所有的類的內部方法名稱,主要核心功能是通過類的反射方法中的getDeclaredMethods()
,該api
屬於JAVAJDK
中自帶的API
,屬於java.lang.Class
包中定義的函式。該方法獲取到類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。當然也包括它所實現介面的方法。在Java
中它是這樣定義的:public Method[] getDeclaredMethods();
其返回值是一個Method
陣列,Method
實際上就是一個方法名稱字串,當然也是一個物件陣列,然後我們將它打印出來。
1.3.1 列舉類的所有方法並定位方法程式碼示例
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
,就能夠定位到該類中所有的方法。
1.4 Java層攔截方法的所有方法過載
我們學會了列舉所有的類以及類的有方法之後,那我們還想知道如何獲取所有的方法過載函式,畢竟在Android
反編譯的原始碼中方法過載不在少數,對此,一次性hook
所有的方法過載是非常有必要的學習。我們已經知道在hook
過載方法時需要寫overload('x')
,也就是說我們需要構造一個過載的陣列,並把每一個過載都打印出來。
1.4.1 攔截方法的所有方法過載程式碼示例
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");
});
}
}
1.4.2 攔截方法的所有方法過載程式碼示例詳解
上面這段程式碼可以打印出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
打印出來的引數值、返回值!
1.5 Java層攔截類的所有方法
學會了如何HOOK
所有方法過載函式後,我們可以把之前學習的整合到一起,來hook
指定類中的所有方法,也包括方法過載的函式。下面js
中核心程式碼是利用過載函式的特點來HOOK
全部的方法,普通的方法也是一個特殊方法過載,只是它只是一個方法而已,直接把它當作方法過載來HOOK
就好了,打個比方正方形是特殊的長方形,而長方形是不是特殊的正方形。這個正方形是普通函式,而長方形是過載方法這樣大家應該很好理解了~在上一章節中已經知道了如何hook
方法過載,只是方法名稱和類名是寫死的,只需要把成員的targetClass、targetMethod
定義方法中的引數即可,在該例子中拿到指定類所有的所有方法名稱,更加靈活使用了,程式碼如下。
1.5.1 攔截類的所有方法程式碼示例
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 終端執行顯示效果
1.6 Java層攔截類的所有子類
這裡的核心功能也用到了上一小章節中定義的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() {}
});
1.7 RPC遠端呼叫Java層函式
在FRIDA
中,不但提供很完善的HOOK
機制,並且還提供rpc
介面。可以匯出某一個指定的函式,實現在python
層對其隨意的呼叫,而且是隨時隨地想呼叫就呼叫,極其方便,因為是在供給外部的python
,這使得rpc
提供的介面可以與python
完成一些很奇妙的操作,這些匯出的函式可以是任意的java
內部的類的方法,呼叫我們自己想要的物件和特定的方法。那我們開始動手吧,現在我們來通過RPC
的匯出功能將圖4-9中的add
方法供給外部呼叫,開始編寫rpc_demo.py
檔案,這次是python
檔案了哦~不是js
檔案了
1.7.1 rpc匯出Java層函式程式碼示例
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
方法~
1.8 綜合案例一:在安卓8.1上dump藍芽介面和例項
一個比較好的綜合案例 :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
執行該指令碼時會詳細列印所有藍芽介面資訊以及服務介面物件~~
1.9 綜合案例二:動靜態結合逆向WhatsApp
我們來試下它的幾個主要的功能,首先是本地庫的匯出函式。
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
免責宣告:本文件僅供學習與參考,請勿用於非法用途!否則一切後果自負。