抖音數(shù)據(jù)采集Frida教程,F(xiàn)rida Java Hook 詳解:代碼及示例(下)
短視頻、直播數(shù)據(jù)實(shí)時(shí)采集接口,請(qǐng)查看文檔: TiToData
免責(zé)聲明:本文檔僅供學(xué)習(xí)與參考,請(qǐng)勿用于非法用途!否則一切后果自負(fù)。
1.1 Java層攔截內(nèi)部類函數(shù)
之前我們已經(jīng)學(xué)習(xí)過(guò)了HOOK普通函數(shù)、方法重載、構(gòu)造函數(shù),現(xiàn)在來(lái)更深入的學(xué)習(xí)HOOK在Android逆向中,我們也會(huì)經(jīng)常遇到在Java層的內(nèi)部類。Java內(nèi)部類函數(shù),使得我們更難以分析代碼。我們?cè)谶@章節(jié)中對(duì)內(nèi)部類進(jìn)行一個(gè)基本了解和使用FRIDA對(duì)內(nèi)部類進(jìn)行鉤子攔截處理。什么是內(nèi)部類?所謂內(nèi)部類就是在一個(gè)類內(nèi)部進(jìn)行其他類結(jié)構(gòu)的嵌套操作,它的優(yōu)點(diǎn)是內(nèi)部類與外部類可以方便的訪問(wèn)彼此的私有域(包括私有方法、私有屬性),所以Android中有很多的地方都會(huì)使用到內(nèi)部類,我們來(lái)見一個(gè)例子也是最直觀的,如下圖4-17。
 圖4-17 User類中的clz類 在圖4-17中看到User類中嵌套了一個(gè)clz,這樣的操作也是屢見不鮮了。在frida中,我們可以使用$符號(hào)對(duì)起進(jìn)行處理。首先打開jadxgui軟件對(duì)代碼進(jìn)行反編譯,反編譯之后進(jìn)入User類,下方會(huì)有一個(gè)smali的按鈕,點(diǎn)擊smali則會(huì)進(jìn)入smali代碼,進(jìn)入smali代碼直接按ctrl+f局部搜索字符串clz,因?yàn)?code>clz是內(nèi)部類的名稱,那么就會(huì)搜到Lcom/roysue/roysueapplication/User\$clz;,我們將翻譯成java代碼就是:com.roysue.roysueapplication.User\$clz,去掉第一個(gè)字符串的L和/以及;就構(gòu)成了內(nèi)部類的具體類名了,見下圖4-18。
  圖4-18 smali代碼 經(jīng)過(guò)上面的分析我們已經(jīng)得知最重要的部分類的路徑:com.roysue.roysueapplication.User\$clz,現(xiàn)在來(lái)對(duì)內(nèi)部類進(jìn)行HOOK,現(xiàn)在開始編寫js腳本。
1.1.1 攔截內(nèi)部類函數(shù)代碼示例
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) {
//這邊也是像正常的函數(shù)來(lái)hook即可
clz.toString.implementation = function (){
console.log("成功hook clz類");
return this.toString();
}
} else {
console.log("clz: undefined");
}
console.log("start end");
});
}
}
執(zhí)行腳本之后,我們可以看到控制也已經(jīng)成功附加并且打印了成功hook clz類,這樣我們也能夠?qū)?code>Java層的內(nèi)部類進(jìn)行處理了。
[Google Pixel::com.roysue.roysueapplication]-> 成功hook clz類
成功hook clz類
1.2 Java層枚舉所有的類并定位類
在前面我們學(xué)會(huì)了如何在java層的各種函數(shù)的HOOK操作了,現(xiàn)在開始學(xué)習(xí)枚舉所有的類并定位類的騷套路了~,學(xué)習(xí)之前我們要了解API中的enumerateLoadedClasses方法,它是屬于Java對(duì)象中的一個(gè)方法。能夠枚舉現(xiàn)在加載的所有類,enumerateLoadedClasses存在2個(gè)回調(diào)函數(shù),分別是onMatch:function(ClassName):為每個(gè)加載的具有className的類調(diào)用,每個(gè)ClassName返回來(lái)的都是一個(gè)類名;和onComplete:function():在枚舉所有類枚舉完之后回調(diào)一次。
1.2.1 枚舉所有的類并定位類代碼示例
setTimeout(function (){
Java.perform(function (){
console.log("n[*] enumerating classes...");
//Java對(duì)象的API enumerateLoadedClasses
Java.enumerateLoadedClasses({
//該回調(diào)函數(shù)中的_className參數(shù)就是類的名稱,每次回調(diào)時(shí)都會(huì)返回一個(gè)類的名稱
onMatch: function(_className){
//在這里將其輸出
console.log("[*] found instance of '"+_className+"'");
//如果只需要打印出com.roysue包下所有類把這段注釋即可,想打印其他的替換掉indexOf中參數(shù)即可定位到~
//if(_className.toString().indexOf("com.roysue")!=-1)
//{
// console.log("[*] found instance of '"+_className+"'");
//}
},
onComplete: function(){
//會(huì)在枚舉類結(jié)束之后回調(diào)一次此函數(shù)
console.log("[*] class enuemration complete");
}
});
});
});
當(dāng)我們執(zhí)行該腳本時(shí),注入目標(biāo)進(jìn)程之后會(huì)開始調(diào)用onMatch函數(shù),每次調(diào)用都會(huì)打印一次類的名稱,當(dāng)onMatch函數(shù)回調(diào)完成之后會(huì)調(diào)用一次onComplete函數(shù),最后會(huì)打印出class enuemration complete,見下圖。
 圖4-19 枚舉所有類
1.3 Java層枚舉類的所有方法并定位方法
上文已經(jīng)將類以及實(shí)例枚舉出來(lái),接下來(lái)我們來(lái)枚舉所有方法,打印指定類或者所有的類的內(nèi)部方法名稱,主要核心功能是通過(guò)類的反射方法中的getDeclaredMethods(),該api屬于JAVAJDK中自帶的API,屬于java.lang.Class包中定義的函數(shù)。該方法獲取到類或接口聲明的所有方法,包括公共、保護(hù)、默認(rèn)(包)訪問(wèn)和私有方法,但不包括繼承的方法。當(dāng)然也包括它所實(shí)現(xiàn)接口的方法。在Java中它是這樣定義的:public Method[] getDeclaredMethods();其返回值是一個(gè)Method數(shù)組,Method實(shí)際上就是一個(gè)方法名稱字符串,當(dāng)然也是一個(gè)對(duì)象數(shù)組,然后我們將它打印出來(lái)。
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);
});
});
}
}
我們先定義了一個(gè)enumMethods方法,其參數(shù)targetClass是類的路徑名稱,用于Java.use獲取類對(duì)象本身,獲取類對(duì)象之后再通過(guò)其.class.getDeclaredMethods()方法獲取目標(biāo)類的所有方法名稱數(shù)組,當(dāng)調(diào)用完了getDeclaredMethods()方法之后再調(diào)用$dispose方法釋放目標(biāo)類對(duì)象,返回目標(biāo)類所有的方法名稱、返回類型以及函數(shù)的權(quán)限,這是實(shí)現(xiàn)獲取方法名稱的核心方法,下面一個(gè)方法主要用于注入到目標(biāo)進(jìn)程中去執(zhí)行邏輯代碼,在hook_overload_5方法中先是使用了Java.perform方法,再在內(nèi)部調(diào)用enumMethods方法獲取目標(biāo)類的所有方法名稱、返回類型以及函數(shù)的權(quán)限,返回的是一個(gè)Method數(shù)組,通過(guò)forEach迭代器循環(huán)輸出數(shù)組中的每一個(gè)值,因?yàn)槠浔旧韺?shí)際就是一個(gè)字符串所以直接輸出就可以得到方法名稱,腳本執(zhí)行效果如下圖4-20。
  圖4-20 腳本執(zhí)行后效果在圖4-17中clz只有一個(gè)toString方法,我們填入?yún)?shù)為com.roysue.roysueapplication.User$clz,就能夠定位到該類中所有的方法。
1.4 Java層攔截方法的所有方法重載
我們學(xué)會(huì)了枚舉所有的類以及類的有方法之后,那我們還想知道如何獲取所有的方法重載函數(shù),畢竟在Android反編譯的源碼中方法重載不在少數(shù),對(duì)此,一次性hook所有的方法重載是非常有必要的學(xué)習(xí)。我們已經(jīng)知道在hook重載方法時(shí)需要寫overload('x'),也就是說(shuō)我們需要構(gòu)造一個(gè)重載的數(shù)組,并把每一個(gè)重載都打印出來(lái)。
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;
//目標(biāo)類
var hook = Java.use(targetClass);
//重載次數(shù)
var overloadCount = hook[targetMethod].overloads.length;
//打印日志:追蹤的方法有多少個(gè)重載
console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");
//每個(gè)重載都進(jìn)入一次
for (var i = 0; i < overloadCount; i++) {
//hook每一個(gè)重載
hook[targetMethod].overloads[i].implementation = function() {
console.warn("n*** entered " + targetClassMethod);
//可以打印每個(gè)重載的調(diào)用棧,對(duì)調(diào)試有巨大的幫助,當(dāng)然,信息也很多,盡量不要打印,除非分析陷入僵局
Java.perform(function() {
var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
console.log("nBacktrace:n" + bt);
});
// 打印參數(shù)
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方法重載的個(gè)數(shù)以及hook該類中所有的方法重載函數(shù),現(xiàn)在來(lái)剖析上面的代碼為什么可以對(duì)一個(gè)類中的所有的方法重載HOOK掛上鉤子。首先我們定義了三個(gè)變量分別是targetMethod、targetClass、targetClassMethod,這三個(gè)變量主要于定義方法的名稱、類名、以及類名+方法名的賦值,首先使用了Java.use獲取了目標(biāo)類對(duì)象,再獲取重載的次數(shù)。這里詳細(xì)說(shuō)一下如何獲取的:var method_overload = cls[<func_name>].overloads[index];這句代碼可以看出通過(guò)cls索引func_name到類中的方法,而后面寫到overloads[index]是指方法重載的第index個(gè)函數(shù),大致意思就是返回了一個(gè)method對(duì)象的第index位置的函數(shù)。而在代碼中寫道:var overloadCount = hook[targetMethod].overloads.length;,采取的方法是先獲取類中某個(gè)函數(shù)所有的方法重載個(gè)數(shù)。繼續(xù)往下走,開始循環(huán)方法重載的函數(shù),剛剛開始循環(huán)時(shí)hook[targetMethod].overloads[i].implementation這句對(duì)每一個(gè)重載的函數(shù)進(jìn)行HOOK。這里也說(shuō)一下Arguments:Arguments是js中的一個(gè)對(duì)象,js內(nèi)的每個(gè)函數(shù)都會(huì)內(nèi)置一個(gè)Arguments對(duì)象實(shí)例arguments,它引用著方法實(shí)參,調(diào)用其實(shí)例對(duì)象可以通過(guò)arguments[]下標(biāo)的來(lái)引用實(shí)際元素,arguments.length為函數(shù)實(shí)參個(gè)數(shù),arguments.callee引用函數(shù)自身。這就是為什么在該段代碼中并看不到arguments的定義卻能夠直接調(diào)用的原因,因?yàn)樗莾?nèi)置的一個(gè)對(duì)象。好了,講完了arguments咱們接著說(shuō),打印參數(shù)通過(guò)arguments.length來(lái)循環(huán)以及arguments[j]來(lái)獲取實(shí)際參數(shù)的元素。那現(xiàn)在來(lái)看apply,apply在js中是怎么樣的存在,apply的含義是:應(yīng)用某一對(duì)象的一個(gè)方法,用另一個(gè)對(duì)象替換當(dāng)前對(duì)象,this[targetMethod].apply(this, arguments);這句代碼簡(jiǎn)言之就是執(zhí)行了當(dāng)前的overload方法。執(zhí)行完當(dāng)前的overload方法并且打印以及返回給真實(shí)調(diào)用的函數(shù),這樣不會(huì)使程序錯(cuò)誤。那么最終執(zhí)行效果見下圖4-21:
  圖4-21 終端顯示 可以看到成功打印了add函數(shù)的方法重載的數(shù)量以及hook打印出來(lái)的參數(shù)值、返回值!
1.5 Java層攔截類的所有方法
學(xué)會(huì)了如何HOOK所有方法重載函數(shù)后,我們可以把之前學(xué)習(xí)的整合到一起,來(lái)hook指定類中的所有方法,也包括方法重載的函數(shù)。下面js中核心代碼是利用重載函數(shù)的特點(diǎn)來(lái)HOOK全部的方法,普通的方法也是一個(gè)特殊方法重載,只是它只是一個(gè)方法而已,直接把它當(dāng)作方法重載來(lái)HOOK就好了,打個(gè)比方正方形是特殊的長(zhǎng)方形,而長(zhǎng)方形是不是特殊的正方形。這個(gè)正方形是普通函數(shù),而長(zhǎng)方形是重載方法這樣大家應(yīng)該很好理解了~在上一章節(jié)中已經(jīng)知道了如何hook方法重載,只是方法名稱和類名是寫死的,只需要把成員的targetClass、targetMethod定義方法中的參數(shù)即可,在該例子中拿到指定類所有的所有方法名稱,更加靈活使用了,代碼如下。
1.5.1 攔截類的所有方法代碼示例
function traceClass(targetClass)
{
//Java.use是新建一個(gè)對(duì)象哈,大家還記得么?
var hook = Java.use(targetClass);
//利用反射的方式,拿到當(dāng)前類的所有方法
var methods = hook.class.getDeclaredMethods();
//建完對(duì)象之后記得將對(duì)象釋放掉哈
hook.$dispose;
//將方法名保存到數(shù)組中
var parsedMethods = [];
methods.forEach(function(method) {
//通過(guò)getName()方法獲取函數(shù)名稱
parsedMethods.push(method.getName());
});
//去掉一些重復(fù)的值
var targets = uniqBy(parsedMethods, JSON.stringify);
//對(duì)數(shù)組中所有的方法進(jìn)行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);
執(zhí)行腳本效果可以看到,hook到了com.roysue.roysueapplication.Ordinary_Class類中所有的函數(shù),在執(zhí)行其被hook攔截的方法時(shí)候,也打印出了每個(gè)方法相應(yīng)的的參數(shù)以及返回值,見下圖4-22。
 圖4-22 終端運(yùn)行顯示效果
1.6 Java層攔截類的所有子類
這里的核心功能也用到了上一小章節(jié)中定義的traceClass函數(shù),該函數(shù)只需要傳入一個(gè)class路徑即可對(duì)class中的函數(shù)完成注入hook。那么在本小章節(jié)來(lái)hook掉所有類的子類,使我們的腳本更加的靈活方便。通過(guò)之前的學(xué)習(xí)我們已經(jīng)知道enumerateLoadedClasses這個(gè)api可以枚舉所有的類,用它來(lái)獲取所有的類然后再調(diào)用traceClass函數(shù)就可以對(duì)所有類的子進(jìn)行全面的hook。但是一般不會(huì)hook所有的函數(shù),因?yàn)?code>AndroidAPI函數(shù)實(shí)在太多了,在這里我們需要匹配自己需要hook的類即可,代碼如下。
//枚舉所有已經(jīng)加載的類
Java.enumerateLoadedClasses({
onMatch: function(aClass) {
//迭代和判斷
if (aClass.match(pattern)) {
//做一些更多的判斷,適配更多的pattern
var className = aClass.match(/[L]?(.*);?/)[1].replace(///g, ".");
//進(jìn)入到traceClass里去
traceClass(className);
}
},
onComplete: function() {}
});
1.7 RPC遠(yuǎn)程調(diào)用Java層函數(shù)
在FRIDA中,不但提供很完善的HOOK機(jī)制,并且還提供rpc接口??梢詫?dǎo)出某一個(gè)指定的函數(shù),實(shí)現(xiàn)在python層對(duì)其隨意的調(diào)用,而且是隨時(shí)隨地想調(diào)用就調(diào)用,極其方便,因?yàn)槭窃诠┙o外部的python,這使得rpc提供的接口可以與python完成一些很奇妙的操作,這些導(dǎo)出的函數(shù)可以是任意的java內(nèi)部的類的方法,調(diào)用我們自己想要的對(duì)象和特定的方法。那我們開始動(dòng)手吧,現(xiàn)在我們來(lái)通過(guò)RPC的導(dǎo)出功能將圖4-9中的add方法供給外部調(diào)用,開始編寫rpc_demo.py文件,這次是python文件了哦~不是js文件了
1.7.1 rpc導(dǎo)出Java層函數(shù)代碼示例
import codecs
import frida
from time import sleep
# 附加進(jìn)程名稱為:com.roysue.roysueapplication
session = frida.get_remote_device().attach('com.roysue.roysueapplication')
# 這是需要執(zhí)行的js腳本,rpc需要在js中定義
source = """
//定義RPC
rpc.exports = {
//這里定義了一個(gè)給外部調(diào)用的方法:sms
sms: function () {
var result = "";
//嵌入HOOK代碼
Java.perform(function () {
//拿到class類
var Ordinary_Class = Java.use("com.roysue.roysueapplication.Ordinary_Class");
//最終rpc的sms方法會(huì)返回add(1,3)的結(jié)果!
result = Ordinary_Class.add(1,3);
});
return result;
},
};
"""
# 創(chuàng)建js腳本
script = session.create_script(source)
script.load()
# 這里可以直接調(diào)用java中的函數(shù)
rpc = script.exports
# 在這里也就是python下直接通過(guò)rpc調(diào)用sms()方法
print(rpc.sms())
sleep(1)
session.detach()
當(dāng)我們執(zhí)行python rpc_demo.py時(shí)先會(huì)創(chuàng)建腳本并且注入到目標(biāo)進(jìn)程,在上面的source實(shí)際上就是js邏輯代碼了。在js代碼內(nèi)我們定義了rpc可以給python調(diào)用的sms函數(shù),而sms函數(shù)內(nèi)部嵌套調(diào)用Java.perform再對(duì)需要拿到的函數(shù)的類進(jìn)行主動(dòng)調(diào)用,把最終的結(jié)果返回作為sms的返回值,當(dāng)我們?cè)?code>python層時(shí)候可以任意調(diào)用sms中的原型add方法~
1.8 綜合案例一:在安卓8.1上dump藍(lán)牙接口和實(shí)例
一個(gè)比較好的綜合案例 :dump藍(lán)牙信息的“加強(qiáng)版”——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);
該腳本首先枚舉了很多藍(lán)牙相關(guān)的類,然后choose了很多類,包括藍(lán)牙接口信息以及藍(lán)牙服務(wù)接口對(duì)象等,還加載了內(nèi)存中已經(jīng)分配好的藍(lán)牙設(shè)備對(duì)象,也就是上文我們已經(jīng)演示的信息。我們可以用這個(gè)腳本來(lái)“查看”App加載了哪些藍(lán)牙的接口,App是否正在查找藍(lán)牙設(shè)備、或者是否竊取藍(lán)牙設(shè)備信息等。在電腦上運(yùn)行命令:$ frida -U -l bluecrawl-1.0.0.js com.android.bluetooth執(zhí)行該腳本時(shí)會(huì)詳細(xì)打印所有藍(lán)牙接口信息以及服務(wù)接口對(duì)象~~
1.9 綜合案例二:動(dòng)靜態(tài)結(jié)合逆向WhatsApp
我們來(lái)試下它的幾個(gè)主要的功能,首先是本地庫(kù)的導(dǎo)出函數(shù)。
setTimeout(function() {
Java.perform(function() {
trace("exports:*!open*");
//trace("exports:*!write*");
//trace("exports:*!malloc*");
//trace("exports:*!free*");
});
}, 0);
我們hook的是open()函數(shù),跑起來(lái)看下效果:
$ frida -U -f com.whatsapp -l raptor_frida_android_trace_fixed.js --no-pause
如圖所示*!open*根據(jù)正則匹配到了openlog、open64等導(dǎo)出函數(shù),并hook了所有這些函數(shù),打印出了其參數(shù)以及返回值。接下來(lái)想要看哪個(gè)部分,只要扔到j(luò)adx里,靜態(tài)“分析”一番,自己隨便翻翻,或者根據(jù)字符串搜一搜。比如說(shuō)我們想要看上圖中的com.whatsapp.app.protocol包里的內(nèi)容,就可以設(shè)置trace("com.whatsapp.app.protocol")。可以看到包內(nèi)的函數(shù)、方法、包括重載、參數(shù)以及返回值全都打印了出來(lái)。這就是frida腳本的魅力。當(dāng)然,腳本終歸只是一個(gè)工具,你對(duì)Java、安卓App的理解,和你的創(chuàng)意才是至關(guān)重要的。接下來(lái)可以搭配Xposed module看看別人都給whatsapp做了哪些模塊,hook的哪些函數(shù),實(shí)現(xiàn)了哪些功能,學(xué)習(xí)自己寫一寫。
短視頻、直播數(shù)據(jù)實(shí)時(shí)采集接口,請(qǐng)查看文檔: TiToData
免責(zé)聲明:本文檔僅供學(xué)習(xí)與參考,請(qǐng)勿用于非法用途!否則一切后果自負(fù)。
|