內(nèi)存讀寫斷點?
var code = null
var text_access = null
var data_access = null
var base = null
var size = null
function set_bp(basee , sizee , offset){
base = basee
size = sizee
data_access = Process.getRangeByAddress(base.add(offset))
//console.log('data_access : ' + data_access)
var ret = Memory.protect(base.add(offset) , size , 'r--')
if(!ret){
console.log('Target addr changne false ')
}else{
console.log('Init target addr protect succeed , addr : ' + base.add(offset))
}
// 處理異?;卣{(diào)函數(shù)
Process.setExceptionHandler(function(details){
console.log('---------------Exception func---------------')
if(details.type == 'access-violation'){
console.log('details.type : ' + details.type)
console.log('Exception from addr : ' + details.address)
if(details.memory.address >= base.add(offset) && details.memory.address < base.add(offset + size)){
console.log('Target addr accessed : ' + details.address)
console.log(hexdump(details.address.add(-0x10)))
return false;
}else if(details.memory.address > base.add(offset - 0x1000) && details.memory.address < base.add(offset + 0x1000) ){
console.log('Target addr memory page accessed , addr : ' + details.memory.address)
if(data_access.protection == null){
console.log('data_access.protection don\'t init')
return false
}
var ret = Memory.protect(base.add(offset) , size , data_access.protection)
console.log('Change base.add(offset) protection : ' + data_access.protection)
if(!ret){
console.log('Memory page revert fail')
}
text_access = Process.getRangeByAddress(details.address.add(4))
Memory.protect(details.address.add(4) , 4 , 'rwx')
console.log('Exception data : ' + details.address.readU32())
code = details.address.add(4).readU32()
console.log('Save code : ' + code.toString(16))
details.address.add(4).writeS32(0xcccccccc)
console.log('Write cc at : ' + details.address.add(4))
return true;
}else{
console.log('Other Exception , Exception addr : ' + details.memory.address)
return false;
}
}else if( details.type == 'illegal-instruction'){
console.log(hexdump(details.address.add(-0x10)))
console.log('details.type : ' + details.type)
console.log('Exception from addr : ' + details.address)
console.log('Exception code : ' + details.address.readU64())
if(details.address.readU32() != 0xcccccccc){
console.log('Process oneself illegal-instruction Exception')
return false;
}
console.log('Write code : ' + code.toString(16))
console.log('text_access.protection : ' + text_access.protection)
details.address.writeU32(code)
Memory.protect(details.address , 4 , text_access.protection)
Memory.protect(base.add(offset) , size , 'r--')
return true;
}else{
console.log('details.type : ' + details.type)
return false;
}
});
};
function main() {
var symbols = Module.enumerateSymbols("linker64");
for(var i = 0; i < symbols.length; i++){
if(symbols[i].name.indexOf("find_libraries") >= 0){
Interceptor.attach(symbols[i].address, {
onEnter: function(args){
if(args[3] != 1){
console.log("More than one ELF loaded simultaneously,Path : ")
for(var i = 0; i < args[3]; i++){
console.log( ptr(args[2]).add(i * Process.pointerSize ).readPointer().readCString())
}
console.log("\n")
}else{
var path = ptr(args[2]).readPointer().readCString()
if(path.indexOf('your_so_name.so') > 0){
this.hook = 1
console.log(' ')
console.log("ELF_LoadPath : " + ptr(args[2]).readPointer().readCString())
}
}
}, onLeave(retval){
if(!this.hook) return;
var lib = Process.findModuleByName('your_so_name.so')
if(lib != null){
console.log('your_so_name base : ' + lib.base)
set_bp(lib.base , 8 , 0x123456)
}else{
console.log('get your_so_name base final')
}
}
})
break;
}
}
}
setImmediate(main)
Xposed hook多個dex
//hook 多dex
XposedHelpers.findAndHookMethod(Application.class, "attach",
Context.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
ClassLoader cl = ((Context) param.args[0]).getClassLoader();
Class<?> hookclass = null;
try {
hookclass = cl.loadClass("com.kuaishou.android.security.kfree.a");
} catch (Exception e) {
Log.e("DEBUG", "load class error", e);
return;
}
Log.i("DEBUG", "load success");
XposedHelpers.findAndHookMethod(hookclass, "invoke", Object.class, Method.class, Object[].class,
new XC_MethodHook() {
//TODO: 相關(guān)hook操作
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.d("cxa", " has Hooked!");
}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Object[] ars = (Object[]) param.args[2];
Log.d("cxa", ars[0].toString());
}
});
}
});
firda hook libart.so文件導(dǎo)出函數(shù)(專治各種靜態(tài)找不到)
Java.perform(function(){
console.log(" ");
console.log("-------------------------------BagaBoy-------------------------------------");
// libart.so 所有導(dǎo)出函數(shù)表
var symbols = Module.enumerateSymbolsSync("libart.so");
var addr_register = null;
for(var i = 0; i < symbols.length; i++){
var symbol = symbols[i];
var method_name = symbol.name;
if(method_name.indexOf("art") >= 0){
if(method_name.indexOf("_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi") >= 0){
addr_register = symbol.address;
}
}
}
// 開始hook
if(addr_register){
Interceptor.attach(addr_register, {
onEnter: function(args){
var methods = ptr(args[2]);
var method_count = args[3];
console.log("[RegisterNatives] method_count:", method_count);
for(var i = 0; i < method_count; i++){
var fn_ptr = methods.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
var find_module = Process.findModuleByAddress(fn_ptr);
if(i == 0){
console.log("module name :", find_module.name);
console.log("module base :", find_module.base);
}
console.log("\t method_name :", methods.add(i * Process.pointerSize * 3).readPointer().readCString());
console.log("\t method_sign :", methods.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString());
console.log("\t method_fnPtr :", fn_ptr);
console.log("\t method offset:", fn_ptr.sub(find_module.base));
}
}, onLeave(retval){
}
})
}
//hook 系統(tǒng) fork 函數(shù)
var pthread_create_addr = null;
var symbols = Process.findModuleByName("libc.so").enumerateSymbols();
for (var i = 0; i < symbols.length; i++){
if (symbols[i].name === "fork"){
console.log("Find fork is OK");
pthread_create_addr = symbols[i].address;
};
};
Interceptor.attach(pthread_create_addr,{
onEnter: function(args){
//console.log("args is ->" + args[0], args[1], args[2],args[3]);
},
onLeave: function(retval){
console.log("原始retval :" , retval);
if(!retval)
{
retval = 0x666;
console.log("fork retval change :" , retval);
}
}
});
console.log("-------------------------------BagaBoy-------------------------------------");
});
hook liblinker.so
function hook() {
//call_function("DT_INIT", init_func_, get_realpath());
var linkermodule = Process.getModuleByName("linker");
var linker_function_addr = null;
var symbols = linkermodule.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) {
linker_function_addr = symbol.address;
}
}
Interceptor.attach(linker_function_addr, {
onEnter: function (args) {
var type = ptr(args[0]).readUtf8String();
var address = args[1];
var sopath = ptr(args[2]).readUtf8String();
console.log("path:" + sopath + "--addr:" + address + "--type:" + type);
if (sopath.indexOf("YourSoName") != -1) {
var soAddr = Process.getModuleByName("YourSoName");
//后續(xù)修改或hook操作
}
}
})
}
Java.perform(function() {
hook();
}
hook init_array中的函數(shù),
需要指定so名稱,且會hook之后的所有so的init_array中的函數(shù),可以通過hook_init函數(shù)中for循環(huán),使用函數(shù)偏移過濾。
function hook_init(){
var symbols = Module.enumerateSymbols("linker64");
var init_hook = 0
var init_addr = 0
for(var i = 0; i < symbols.length; i++){
if(symbols[i].name.indexOf("call_constructors") >= 0){
init_addr = symbols[i].address
}
}
if(init_addr){
Interceptor.attach(init_addr, {
onEnter: function(args){
var func_addr = ptr(args[0]).add(0x98).readPointer()
var func_num = ptr(args[0]).add(0xA0).readPointer()
if(func_num > 0 ){
console.log("init_arry func num: " + func_num )
console.log("ELF addr: " + Module.findBaseAddress("libjrcejx.so") )
for(var i = 0; i < func_num ; i++){
var a = func_addr.add(i * Process.pointerSize).readPointer()
console.log("func addr : " + a)
//可以在此處過濾,init_array中的函數(shù)地址a - libjrcejx.so的基址,是目標偏移在執(zhí)行下面的hook
Interceptor.attach(a, {
onEnter: function(args){
console.log("hook func " + i)
}
})
}
}
}
})
}
}
Java.perform(function() {
console.log(" " )
var symbols = Module.enumerateSymbols("linker64");
for(var i = 0; i < symbols.length; i++){
if(symbols[i].name.indexOf("find_libraries") >= 0){
Interceptor.attach(symbols[i].address, {
onEnter: function(args){
var ELF_Path = ptr(args[2]).readPointer().readCString();
if(ELF_Path.indexOf("libjrcejx.so") >= 0){
hook_init()
}
}
})
}
}
})
通過libc.so的底層dlopen函數(shù)實現(xiàn)dump so
/*
dump so
// init 和 initarray 已經(jīng)執(zhí)行完成 JNI_Onload 沒被執(zhí)行
soSavePath: /data/data/cn.ishansong/ 保存so文件的路徑(可能遇到權(quán)限問題)
soName: libDexHelper.so 保存so文件的名稱
*/
function android_dlopen_ext_dunpso(soSavePath, soName){
var android_dlopen_extPtr = Module.findExportByName('libc.so','android_dlopen_ext')
Interceptor.attach(android_dlopen_extPtr,{
onEnter: function(args){
console.log(`android_dlopen_ext(path="${args[0].readUtf8String()}")`);
if (args[0].readUtf8String().indexOf(soName)){
this.isdump = true;
}
},onLeave: function(retval){
if(this.isdump){
var libSo = Process.findModuleByName(soName)
if (libSo != null){
console.log('---------- start dump : '+soName)
var file = new File(soSavePath+soName,'w')
Memory.protect(ptr(libSo.base),libSo.size,'rwx')
var buffer = ptr(libSo.base).readByteArray(libSo.size)
file.write(buffer)
file.close()
}
}
}
})
}
Xposed模塊
新建Android項目,識別為Project,在main文件夾下添加lib文件夾,導(dǎo)入jar包,右鍵jar包,點擊線面的add****(將該文件添加為項目的依賴)。
在main目錄下新建assets文件夾,新建init文件,填寫插件的入口類的路徑
打印堆棧
XposedBridge.log("Dump Stack: " + "---------------start----------------");
Throwable ex = new Throwable();
StackTraceElement[] stackElements = ex.getStackTrace();
if (stackElements != null) {
for (int i = 0; i < stackElements.length; i++) {
XposedBridge.log("Dump Stack" + i + ": ");
XposedBridge.log(stackElements[i].getClassName()
+ "----" + stackElements[i].getFileName()
+ "----" + stackElements[i].getLineNumber()
+ "----" + stackElements[i].getMethodName());
}
}
Inline-Hook和SandHook?
? ? ? ? 都是基于inline-hook基礎(chǔ),通過修改函數(shù)的跳轉(zhuǎn)地址實現(xiàn)hook,br? mycode_addr;據(jù)說inline主要用于32位hook,sandhook主要用于64位hook。
PLT/GOT hook
? ? ? ? 全局偏移表(GOT)和動態(tài)鏈接表(PLT),主要通過解析so文件,將導(dǎo)出表函數(shù)地址替換為自己的native函數(shù)地址實現(xiàn)hook,導(dǎo)入表hook本質(zhì)上和導(dǎo)出表hook原理相同,只不過hook的是系統(tǒng)so或者其他so的導(dǎo)出表,優(yōu)點是相比于Inline-hook穩(wěn)定性更好,缺點是只能hook導(dǎo)入導(dǎo)出表
Unicorn hook(沒太看懂,貌似很牛逼)
? ? ? ? 跨平臺的模擬框架,通過模擬不同平臺的cpu實現(xiàn),內(nèi)部并沒有函數(shù)的概念,只是一個單純執(zhí)行指令的cpu,可以實現(xiàn)指令級hook
?Xposed hook
? ? ? ? 原理:Java函數(shù)執(zhí)行時會調(diào)用到dvmCallMethodV判斷是Java層還是native層函數(shù),xposed通過修改dvmCallMethod的accessFlags值來欺騙虛擬機為調(diào)用native層函數(shù),再將nativeFunc指針指向自己實現(xiàn)的native方法();
? ? ? ? ? ? ? ? Android版本高于5.0時,使用xposed需要刷入框架,替換系統(tǒng)的
? ? ? ? 1.重寫系統(tǒng)的Zygote,加入一段代碼,所有從zygote fork出的進程都注入XposedBridge.jar ,使用XposedHelpers.findAndHookMethod 方法hook系統(tǒng)函數(shù)。
? ? ? ? 2.通過反射機制找到Method,判斷安卓版本,初始化參數(shù)與返回值類型。
? ? ? ? 3.修改函數(shù)指針。
萬物皆可 Hook,探究 Xposed 框架 Hook 原理 - 知乎 (zhihu.com)?(hook代碼及xposed檢測)
Frida hook
? ? ? ? 原理:和Xopsed類似,也是欺騙虛擬機執(zhí)行自己的native函數(shù),修改accessflags,函數(shù)指針,函數(shù)執(zhí)行模式。
? ? ? ? 1.函數(shù)是否 為AOT 編譯的熱點函數(shù),是則直接執(zhí)行。
? ? ? ? 2.Java層函數(shù)調(diào)用,未經(jīng)過AOT編譯,ARTMethod 的值為artQuickToInterpreterBridge(快速編譯代碼的入口點),從二進制執(zhí)行模式(AOT)切換到邊解釋邊執(zhí)行(JIT)模式
? ? ? ? 3.Native層函數(shù)調(diào)用,未經(jīng)過AOT編譯,ARTMethod的值為artQuickGenericJniTrampoline?
? ? ? ? 所以Frida會更改ARTmethod結(jié)構(gòu)中的:
????????access_flags_(執(zhí)行的是Java層還是Native層)
entry_point_from_jni_
art_quick_generic_jni_trampoline
artInterpreterToCompiledCodeBridge
? ? ? ? 檢測:frida開啟的端口、被注入進程中是否存在frida-agent-32.so模塊、檢測進程是否有frida-server、探測端口通信D-bus協(xié)議。
Xposed 與 frida 的區(qū)別:
? ? ? ? Xposed是通過修改字節(jié)碼實現(xiàn)hook,因此只能搞Java層函數(shù),且安裝需要root權(quán)限,穩(wěn)定性較好。
? ? ? ? frida是通過向程序注入JavaScript腳本,動態(tài)二進制插樁實現(xiàn),所以native層函數(shù)也可以搞,不用root,上去就是干,但是穩(wěn)定性較差,沒事就崩給你看。
APK簡單對抗
1.Java層混淆:jadx -> 工具 -> 反混淆
2.資源加密:特點 直接用Android killer打開,反編譯失敗,資源文件報錯;在手機MT管理器中更改代碼。
3.簽名校驗:
?? ? ? ? java層:關(guān)鍵API?getPackageManager(獲取包管理器)??getPackageInfo(獲取包信息)? packageInfo.signatures(包簽名信息)? ?
? ? ? ? so層:先找到執(zhí)行簽名校驗的so文件,拖入IDA,查看導(dǎo)出表,check_sign jni_onload(動態(tài)注冊) java_(靜態(tài)注冊)等關(guān)鍵函數(shù),分析校驗邏輯更改so或者java層文件。?
通過Java反射機制調(diào)用簽名校驗Java反射機制詳解_楊 戩的博客-CSDN博客。
PackageInfo packageInfo = getPackageManager().getPackageInfo(
getPackageName(),
PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
4.模擬器檢測:檢測藍牙,電話信息權(quán)限,已安裝的APK,CPU框架等。
5.類抽?。?/strong>將dex文件中的函數(shù)指令轉(zhuǎn)為NOP,程序跑起來時在so層填充該函數(shù)指令。
正向還原:第一種是通過讀取/proc/<pid>/maps
文件獲取加載的DEX文件地址,然后對DEX文件進行解析,找到NOP指令進行還原;第二種方法就是通過Hook系統(tǒng)函數(shù)(最簡單的就是dexFindClass
函數(shù))然后進行指令還原。
逆向還原:最終解密出的Java函數(shù)還是要在內(nèi)存中的,使用IDA動態(tài)調(diào)試拿到原本的函數(shù)字節(jié)碼,填充回dex,或者同樣hook系統(tǒng)函數(shù),等程序填充后在dump dex。
dex方法還原:
????????使用 010Editor 找到該方法的類(在class區(qū)段中找)
? ? ? ? ?選中并打開類數(shù)據(jù)標簽(2),打開方法列表(3,下圖中有兩個列表),在列表中找到參數(shù)類型與返回值類型與咱們的目標函數(shù)相同的方法,此次實驗?zāi)繕藶閜ublic void org.a.a.m.c.a(char[], int, int)方法。
? ? ? ? ?按照下圖打開選項卡,找到方法對應(yīng)的16進制數(shù)據(jù),進行填充或者刪除,最后還需要更改dex文件頭中的SHA1(或者其他的?)校驗信息。
?
6.第四代VMP殼:程序啟動時,將加密后的dex文件解密,加載到內(nèi)存;在內(nèi)存中創(chuàng)建一個虛擬機,將dex轉(zhuǎn)換為vmp虛擬指令集;再將指令集轉(zhuǎn)換為Dalvik字節(jié)碼執(zhí)行(用到那個dex中的方法就解密哪個,不是一次性將dex轉(zhuǎn)為Dalvik字節(jié)碼)
7.函數(shù)級加密:
????????在應(yīng)用程序運行時,解密函數(shù)會被調(diào)用來解密每個被加密的函數(shù),然后將其加載到內(nèi)存中,并在執(zhí)行時動態(tài)解密。
????????靜態(tài)函數(shù)級加密:每個函數(shù)加密方式相同,拿到解密函數(shù)還原。
? ? ? ? ?動態(tài)函數(shù)級加密:每個函數(shù)加密方式不同;動態(tài)函數(shù)級加密中,每個函數(shù)使用不同的密鑰進行加密,獲取每個函數(shù)對應(yīng)的密鑰;找出解密函數(shù),解密函數(shù)使用密鑰對每個函數(shù)進行解密。
8.不落地加載殼:
? ? ? ? 先執(zhí)行殼程序,在內(nèi)存中解密程序本身的dex,在so層調(diào)用系統(tǒng) libdvm.so 中的 openDexFile 函數(shù)加載內(nèi)存中的dex,該函數(shù)的返回類型為int類型的cookie。
9.so加殼:
? ? ? ? 通過加殼器對原始so文件進行混淆與加密,運行時在解密,詳見?Android雜項?中的自定義linker
10.ollvm混淆:
? ? ? ? 控制流平坦化:聽起來好像很難懂,其實就是把原本直接的函數(shù)調(diào)用,前面加上各種 if 、for、switch、while等分支判斷的邏輯,加大逆向分析的時間開銷,函數(shù)邏輯變得混亂。
- (1)函數(shù)的開始地址為序言的地址;
- (2)序言的后繼塊為主分發(fā)器;
- (3)后繼為主分發(fā)器的塊為預(yù)處理器;
- (4)后繼為預(yù)處理器的塊為真實塊;
- (5)無后繼的塊為retn塊;
- (6)剩下的為無用塊。
下圖為經(jīng)過ollvm控制流平坦化處理的函數(shù)
? ? ? ? ?去混淆的思路就是去除無用塊及確定真實塊執(zhí)行的先后順序。
? ? ? ? 指令替換:將程序原本簡單的二元運算(加 減 乘 除 異或 與 等等),替換為更加復(fù)雜的運算,但結(jié)果和原本一樣(PS:純純有病,技術(shù)不行,cpu來湊?)
? ? ? ? 控制流偽造:和平坦化差不多,就是 if 、for、switch、while 循環(huán)的替換,加大分析難度,讓cpu更容易冒煙。
Android反調(diào)試
1.關(guān)鍵文件檢測:改文件名。
2.調(diào)試端口檢測:更改ida,frida等默認連接端口;
3.進程名檢測:android_server gdbserver gdb等進程名檢測;父進程名檢測,桌面啟動的父進程名應(yīng)為zygote,hook 或修改smali?
4.Java層反調(diào)試:android.os.Debug.isDebuggerConnected(),killer中搜索函數(shù)更改;xml中applicationInfo屬性中添加android:debuggable=“true”。hook該函數(shù)讓其返回1;
5.ptrace檢測(相當于Windows中的attch):手動跟蹤到ptrace函數(shù)修改返回值;編寫新的ptrace函數(shù)so文件放到app的lib下并設(shè)置環(huán)境變量;hook大法,
6.TracerPid 檢測:基本同上,檢測/proc/self/status的TracerPid 值。不為0就是被調(diào)試了,通過修改內(nèi)核文件/dev/block/mmcblk0/boot中TracerPid函數(shù)硬編碼實現(xiàn);程序建立子線程附加自己,如果TracerPid值為0則退出;。
7.斷點檢測:rtld_db_dlactivity函數(shù):這個函數(shù)默認情況下為空函數(shù),這里的值應(yīng)該為0,而當有調(diào)試器時,這里會被改為斷點指令;獲取該函數(shù)地址,將其nop。
8.時間檢測,單步調(diào)試陷阱:app主動設(shè)置斷點,并在代碼中注冊斷點信號處理函數(shù),未被調(diào)試(執(zhí)行斷點發(fā)出信號—進入處理信號函數(shù)—NOP替換斷點—退回PC),被調(diào)試,執(zhí)行調(diào)試程序的處理函數(shù),他會恢復(fù)目標處指令失敗,然后回退PC,進入死循環(huán)。
9.利用ida先截獲信號的特性:將關(guān)鍵函數(shù)放在信號處理函數(shù)中,未被執(zhí)行則被調(diào)試。
10.雙進程守護:主進程a,fork子進程b,b進程ptrace a的所有線程;修改安卓源碼;
11.通過修改so文件程序頭的特征,so二進制文件開頭一般是.ELF,frida執(zhí)行前會檢測該二進制數(shù)據(jù)是否為.ELF,但是該數(shù)據(jù)在so執(zhí)行中并不會使用,所以可以通過將so文件程序頭中的.ELF特征更改,這樣在frida使用attch方式附加時會直接崩潰;通過frida -U -f app_packeageName --no-pause啟動app,原因是frida代碼文件校驗?zāi)J絾栴}。
12.通過frida中findExportByName函數(shù)校驗漏洞:frida在使用該函數(shù)時會獲取linker中的__dl__Z17solist_get_somainv的返回值,并且未經(jīng)過為空校驗直接使用,所以將該函數(shù)返回值改為0,frida腳本中只要使用到了findExportByName就會崩潰(frida在hookJava函數(shù)時一定會調(diào)用),該函數(shù)在正常程序執(zhí)行中大概率不會用到。
Java層的雙進程通過xposed hook應(yīng)該可以解決(xposed是通過重寫zygote進程實現(xiàn)hook,理論上可以攔截所有Java層的操作);so層的可以在該so剛加載時就附加,然后修改fork操作(未實踐)?;蛘呔帉懸粋€so庫,里面重寫ptrace和dlopen函數(shù),重寫的函數(shù)里面最后在調(diào)用真正的系統(tǒng)函數(shù),加載該庫。
分類:
frida檢測:
1.TracerPid 檢測? ? ? ? ? 2.進程名檢測? ? ? ? 3.端口檢測? ? ? ? 4.已加載庫文件檢測: /proc/self/maps 目錄是否包含frida關(guān)鍵字? ? ? ?
5.通過獲取 libart.so 的函數(shù)遍歷函數(shù)表(該函數(shù)包含每個so函數(shù)的地址),判斷每個加載的so函數(shù)開頭是否為0xd61f020058000050(firda實現(xiàn)hook是通過inline hook插入的代碼特征)? ? ? ?
6.java層檢測:frida hook java層API是通過將其轉(zhuǎn)換為native函數(shù)在進行hook,可以判斷java函數(shù)的屬性是否為native函數(shù)來檢測
Android風(fēng)控
????????風(fēng)險控制,為了解決和預(yù)防將要發(fā)生,或者可能發(fā)生的一些危險情況,從而減輕損失。
? ? ? ? 注:并不完全是新內(nèi)容,選讀,因為近期面試遇到過不少,所以自己學(xué)學(xué)之后打算寫個筆記加深一下印象。
蜜罐數(shù)據(jù):當發(fā)現(xiàn)作弊以后返回的數(shù)據(jù)是非正常的數(shù)據(jù),可能存在埋點等信息,比如在視頻的隨機幀中添加標識,水印等,返回錯誤,重復(fù)的數(shù)據(jù)。
IP限制:當一個IP請求過量或過于頻繁時,返回錯誤數(shù)據(jù)或者蜜罐數(shù)據(jù)。
設(shè)備指紋:設(shè)備指紋的核心是使用設(shè)備的唯一識別碼,就像每個人的身份證號碼一樣。
java函數(shù)獲?。篠ettings. Secure / Global .getString(context.getContentResolver(),Settings.Secure.ANDROID_ID)
黑灰產(chǎn)通過協(xié)議逆向,實現(xiàn)批量注冊等“薅羊毛”行為,可通過網(wǎng)絡(luò)數(shù)據(jù)包中的設(shè)備指紋判斷,是否為同一設(shè)備,短時間內(nèi)大量注冊賬號,從而判定是否遭到黑產(chǎn)攻擊。
App環(huán)境信息:可以通過該設(shè)備運行環(huán)境的信息,將用戶劃分為 可疑設(shè)備 與 黑產(chǎn)設(shè)備。
1.root檢測:su權(quán)限,文件檢測;掃描 magisk 關(guān)鍵字;maps檢測等。
2.Hook檢測:主流hook框架Frida與xposed檢測上報。
3.沙箱檢測:判斷APK的私有路徑是否為 /data/data/包名;
4.自定義rom檢測:需要有手機的驅(qū)動源碼才能自定義rom,通過對比md5值等檢測是否存在自定義rom。
5.查殺分離:當當前設(shè)備被服務(wù)器判定為黑產(chǎn)時,不會立即封殺,而是延時幾小時或者幾天在封殺,防止黑產(chǎn)一次一步步摸清風(fēng)控判斷依據(jù)。
6.心跳包&用戶行為檢測:在app剛跑來就與服務(wù)器建立socket連接,每隔一段時間發(fā)送一個心跳包,如果通過單純的逆向網(wǎng)絡(luò)協(xié)議實現(xiàn)算法接口,再通過PC發(fā)送數(shù)據(jù)的話,服務(wù)端在長時間沒有收到用戶的心跳包后,就可以將此設(shè)備判定為黑產(chǎn)設(shè)備。用戶通過逆向字節(jié)寫的點擊框架,可能沒有一點多余的操作,如正常用戶的滑動屏幕,點擊時間間隔,屏幕點擊坐標等,也可以以此作為風(fēng)控判定標準。
7.異常&行為埋點:正常用戶點擊登入接口時,在之前肯定會打開app的登入頁面,可以在登入頁面設(shè)置埋點,發(fā)送特定的數(shù)據(jù)包給服務(wù)器,如果用戶只發(fā)送了登入的數(shù)據(jù)包,或者發(fā)送的埋點數(shù)據(jù)不足時,可以此判定該設(shè)備為黑產(chǎn)設(shè)備。
Android 進程注入
動態(tài)注入? ? ??
????????由于Linux的進程機制,每個進程在安裝后都會分配一個獨享的UID,所以要想達到進程注入,需要拿到系統(tǒng)的root權(quán)限,之后總體流程和PC端大同小異;
獲取進程pid
附加進程? ? ????????????????:Attch(pid)
保存目標進程寄存器? :GetRegs(pid,&SaveRegister)
目標進程內(nèi)申請空間? :通過mmap映射申請,與binder差不太多
寫入shell? ? ? ? ? ? ? ? ?? ?:
修改PC寄存器,
執(zhí)行shell。文章來源:http://www.zghlxwxcb.cn/news/detail-456887.html
靜態(tài)注入
? ? ? ? 通過修改ELF文件實現(xiàn),文章來源地址http://www.zghlxwxcb.cn/news/detail-456887.html
到了這里,關(guān)于Android hook、檢測及對抗相關(guān)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!