前言
觀眾器者為良匠,觀眾病者為良醫(yī)。這篇文章分析了知名抓包軟件HttpCanary高級(jí)功能的使用限制,使用了許多實(shí)用的工具,過(guò)程寫(xiě)的盡可能的詳細(xì),希望對(duì)大家有所幫助。
筆者實(shí)踐環(huán)境:
- pixel 6
- Android 12
- frida 15.1.27
- HttpCanary v3.3.5
脫殼
在jadx中查看原包,檢查軟件是否加固
由于包下的類比較少且看見(jiàn)了stub,因此可以確定是360加固了,直接用工具脫殼,我用的是hluwa巨佬的frida-dexdump,具體用法可以看官網(wǎng),這里直接進(jìn)行嘗試脫殼
在眾多dex文件中通過(guò)入口類進(jìn)行篩選
脫殼完畢~~
核心代碼定位與分析
兩種思路:
- 從激活功能下手,修改成功邏輯
- 從vip功能下手,定位身份判斷關(guān)鍵點(diǎn)
對(duì)比兩種思路,第二種思路感覺(jué)更加的簡(jiǎn)單和直接,當(dāng)然第一種也是可以做的,只不過(guò)會(huì)比想想中的更加復(fù)雜,我這里直接給出第一種思路的frida版本,但它是不完善的,重啟軟件會(huì)失效,有興趣的可以研究一下
function pojie() {
// remove app detect expression
Java.choose("com.guoshi.httpcanary.ui.premium.PremiumActivateActivity", {
onMatch: function (instance) {
console.log("f7834 before value is " + instance.\uFC82.value)
instance.\uFC82.value = 1
},
onComplete: function () {
}
})
// break vip step1
let C2217 = Java.use("com.guoshi.\uFC70.\uFC71.\uFC72");
C2217["\uFC70"].implementation = function () {
let ret = this.\uFC70.apply(this, arguments);
var CodeActivateResBody = Java.use("com.guoshi.httpcanary.model.CodeActivateResBody")
var obj = CodeActivateResBody.$new()
obj.status.value = 1;
obj.encryptToken.value = "mdcg";
obj.token.value = "mdcg";
console.log("Create obj --> " + obj)
console.log("status --> " + obj.status.value)
console.log("encryptToken --> " + obj.encryptToken.value)
console.log("token --> " + obj.token.value)
return obj;
};
// break vip step2
let PremiumActivateActivity = Java.use("com.guoshi.httpcanary.ui.premium.PremiumActivateActivity");
PremiumActivateActivity["\uFC71"].overload('java.lang.String', 'java.lang.String').implementation = function (arg1, arg2) {
let ret = this.\uFC71(arg1, arg2);
console.log('\uFC71 ret before value is ' + ret);
ret = true
console.log('\uFC71 ret after value is ' + ret);
return ret;
};
}
function main() {
Java.perform(function () {
pojie()
})
}
setImmediate(main)
下面主要對(duì)第二種思路進(jìn)行分析,極速模式屬于高級(jí)功能,由一個(gè)按鈕進(jìn)行控制,最迅速的定位方式為hook點(diǎn)擊事件,并反推出核心判斷。我使用r0ysue巨佬的hookEvent.js快速定位點(diǎn)擊事件,代碼不復(fù)雜,建議研究一下
var jclazz = null;
var jobj = null;
function getObjClassName(obj) {
if (!jclazz) {
var jclazz = Java.use("java.lang.Class");
}
if (!jobj) {
var jobj = Java.use("java.lang.Object");
}
return jclazz.getName.call(jobj.getClass.call(obj));
}
function watch(obj, mtdName) {
var listener_name = getObjClassName(obj);
var target = Java.use(listener_name);
if (!target || !mtdName in target) {
return;
}
target[mtdName].overloads.forEach(function (overload) {
overload.implementation = function () {
console.log("[WatchEvent] " + mtdName + ": " + getObjClassName(this))
return this[mtdName].apply(this, arguments);
};
})
}
function OnClickListener() {
Java.perform(function () {
Java.use("android.view.View").setOnClickListener.implementation = function (listener) {
if (listener != null) {
watch(listener, 'onClick');
}
return this.setOnClickListener(listener);
};
Java.choose("android.view.View$ListenerInfo", {
onMatch: function (instance) {
instance = instance.mOnClickListener.value;
if (instance) {
console.log("mOnClickListener name is :" + getObjClassName(instance));
watch(instance, 'onClick');
}
},
onComplete: function () {
}
})
})
}
setImmediate(OnClickListener);
注冊(cè)點(diǎn)擊事件的類是com.guoshi.httpcanary.ui.-$ L a m b d a Lambda LambdaHomeActivity$L0WEKAV1lAcNYjl4nGyTdnm61r8,在jadx中查看
后面的注釋是我加上去的,下面從第一處判斷開(kāi)始分析
m6185函數(shù)傳入了一串加密字符串,我們需要知道它代表了什么意思,可以看一下這個(gè)函數(shù)
可以確定是一個(gè)解密函數(shù),但我們不需要逆向出解密的邏輯,因?yàn)榭梢灾苯觝ook獲取返回值
function hook_1(){
let C2146 = Java.use("com.guoshi.httpcanary.\uFC72");
C2146["\uFC70"].implementation = function (str) {
console.log('input str: ' + str);
let ret = this.\uFC70(str);
console.log('ret value is ' + ret);
return ret;
};
}
function main(){
Java.perform(function(){
hook_1()
})
}
setImmediate(main)
解密的字符串是settings_turbo_mode,翻譯成漢語(yǔ)就是設(shè)置極速模式,那m6317函數(shù)是什么呢?點(diǎn)進(jìn)去看看
懂了,m6317是從sharedPreferences中取出相應(yīng)key的value,而settings_turbo_mode保存的是當(dāng)前極速模式是打開(kāi)還是關(guān)閉,如果返回值為true,那么表明當(dāng)前屬于開(kāi)啟的狀態(tài),這樣就要進(jìn)入判斷成功的邏輯,把開(kāi)啟狀態(tài)變成關(guān)閉狀態(tài)。
下面分析第二處判斷,該處判斷取決于a函數(shù)和z函數(shù)的返回值,點(diǎn)開(kāi)看一下
有native關(guān)鍵字就意味著我們要分析so層了,那么第一步就是要確定這些函數(shù)在哪個(gè)so文件中,首先我們假設(shè)這些函數(shù)都是動(dòng)態(tài)注冊(cè)的,那么就可以使用yang神的hook_RegistNatives.js腳本迅速判斷,注意要以spawn模式啟動(dòng),腳本如下:
function find_RegisterNatives(params) {
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
hook_RegisterNatives(addrRegisterNatives)
}
}
}
function hook_RegisterNatives(addrRegisterNatives) {
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", ptr(fnPtr_ptr).sub(find_module.base), " callee:", DebugSymbol.fromAddress(this.returnAddress));
}
}
});
}
}
setImmediate(find_RegisterNatives);
確定了我們要分析的是libHttpCanary.so文件,并且函數(shù)的偏移地址也拿到了,把文件拖進(jìn)ida中看一下,重新命名的過(guò)程就不展示了
a方法調(diào)用了z方法和l方法,點(diǎn)進(jìn)z方法看一下
由上面的偽代碼可以推出,z方法就是判斷當(dāng)前是否是高級(jí)模式,如果是高級(jí)模式則返回true,下面看看l方法是什么
由偽代碼可以看出l方法返回用戶試用的剩余天數(shù),如果為正數(shù)則表明用戶的試用還沒(méi)有到期,如果為負(fù)數(shù)則表明用戶的試用到期了,現(xiàn)在重新看a方法應(yīng)該清晰了很多
這個(gè)邏輯表達(dá)式表示的是如果用戶不是vip且試用還沒(méi)有到期,那么結(jié)果為true。
那么兩個(gè)核心函數(shù)就找到了,一個(gè)是a方法,一個(gè)是z方法,a方法判斷是否是試用模式,z方法判斷是否是vip模式,那么這兩個(gè)方法只要有一個(gè)為真就能使用軟件的高級(jí)功能了,下面使用frida簡(jiǎn)單測(cè)試一下
function hook_2(){
let Cont = Java.use("com.guoshi.httpcanary.jni.Cont");
Cont["a"].implementation = function (context) {
let ret = this.a(context);
ret = true
console.log('a ret value is ' + ret);
return ret;
};
Cont["z"].implementation = function (context) {
let ret = this.z(context);
ret = false
console.log('z ret value is ' + ret);
return ret;
};
}
function main(){
Java.perform(function(){
// hook_1()
hook_2()
})
}
setImmediate(main)
使用腳本之前
使用腳本之后
結(jié)尾
這個(gè)軟件整體的防護(hù)還是很到位的,java層的混淆也是很給力,但如果加一些frida的反調(diào)試并且在so層多加一點(diǎn)防護(hù)那么就更安全了。上面只是使用了frida腳本進(jìn)行了功能的分析,我也把它寫(xiě)成了xposed模塊,方便大家的使用,原包與模塊都放在了下面的鏈接中,當(dāng)然有能力的還是要支持下正版。因?yàn)樾氯藙?chuàng)作不易,希望大家點(diǎn)個(gè)贊鼓勵(lì)一下((′▽`〃))
HttpCanary_v3.3.5.apk文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-455196.html
httpCanaryBreaker.apk文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-455196.html
到了這里,關(guān)于實(shí)戰(zhàn) 逆向最新黃鳥(niǎo)抓包軟件的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!