bang加固簡單分析
一、dex
dex加固,可以使用frida-dexdump可以直接dump下來。

可以看到加載了SecShell進行脫殼調用,這個libSecShell.so是32位的。
二、libSecShell.so
export列表中看到了JNI_Onload,但是是加密的,分析不出來,修改代碼的話一定會調用mprotect,在mprotect處交叉引用,找不到調用,于是猜測可能是svc調用,用腳本跑了一下,發現了mprotect,腳本是之前論壇上看到的。
system call : 7d 70addr : c0783Func Name : __NR_mprotect c0 70 a0 e3 00 00 00 ef

在這里交叉引用發現都在sub_C0C30里調用。
用frida去hook這個函數。
var mprotect_cnt = 0//frida -U --no-pause -f com.testlinker.ty -l hook.jsfunction sleep(delay) { var start = (new Date()).getTime(); while ((new Date()).getTime() - start < delay) { continue; }} function hook_svc_mprotect() { let base_svc_mprotect = Module.findBaseAddress("libSecShell.so"); if (base_svc_mprotect != null) { console.log("base_svc_mprotect : " + base_svc_mprotect) }else{ return ; } let svc_mprotect = base_svc_mprotect.add(0xC0778);//32位 Interceptor.attach(svc_mprotect, { onEnter: function(args) { console.log("==========================================") console.log("svc_mprotect: start = " + args[0] + " , len = " + args[1] + " , ATTRIBUTES = " + args[2]) mprotect_cnt += 1 console.log(hexdump(base_svc_mprotect.add(0x281B4))) }, onLeave: function(){ console.log("svc_mprotect leave") console.log("==========================================") } })}function dis(address, number) { for (var i = 0; i < number; i++) { var ins = Instruction.parse(address); console.log("address:" + address + "--dis:" + ins.toString()); address = ins.next; }}//libc->strstr() 從linker里面找到call_function的地址function hook() {//call_function("DT_INIT", init_func_, get_realpath()); var linkermodule if (Process.pointerSize == 4) { linkermodule = Process.findModuleByName("linker"); }else if (Process.pointerSize == 8) { linkermodule = Process.findModuleByName("linker64"); } // var linkermodule = Process.getModuleByName("linker"); var call_function_addr = null; var symbols = linkermodule.enumerateSymbols(); for (var i = 0; i < symbols.length; i++) { var symbol = symbols[i]; //LogPrint(linkername + "->" + symbol.name + "---" + symbol.address); if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) { call_function_addr = symbol.address; //LogPrint("linker->" + symbol.name + "---" + symbol.address) } } Interceptor.attach(call_function_addr, { onEnter: function (args) { var type = ptr(args[0]).readUtf8String(); var address = args[1]; var sopath = ptr(args[2]).readUtf8String(); console.log("loadso:" + sopath + "--addr:" + address + "--type:" + type); if (sopath.indexOf("libSecShell.so") != -1) { var libnativemodule = Process.getModuleByName("libSecShell.so");//call_function正在加載目標so,這時就攔截下來 var base = libnativemodule.base; hook_svc_mprotect() } } })}function main() { hook();}setImmediate(main)
可以發現經過mprotect一次后,對應地址的值發生了變化。
[Pixel 3::com.example.cryptotest ]-> base_svc_mprotect : 0xcfaea000==========================================svc_mprotect: start = 0xcfaea000 , len = 0xa1000 , ATTRIBUTES = 0x7 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEFcfb121b4 9b 66 a6 75 82 ab ba fb 1a 80 e6 75 d7 0e 7f 1b .f.u.......u....svc_mprotect leave====================================================================================svc_mprotect: start = 0xcfb8b000 , len = 0x1f000 , ATTRIBUTES = 0x3 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEFcfb121b4 2d e9 f0 4f ad f6 ac 4d df f8 44 4e df f8 44 3e -..O...M..DN..D>svc_mprotect leave====================================================================================svc_mprotect: start = 0xcfaea000 , len = 0xa1000 , ATTRIBUTES = 0x7 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEFcfb121b4 2d e9 f0 4f ad f6 ac 4d df f8 44 4e df f8 44 3e -..O...M..DN..D>svc_mprotect leave==========================================
用memdumper64(github上有,速度挺快) dump出so,用Sofixer修復so文件。
打開跳轉到JNI_Onload(0x1E5DC),發現ida沒有自動創建函數,按p會報錯The function has undefined instruction/data at the specified address。
用idapython強制創建函數。
ida_funcs.add_func(0x281B4,0x2A5CC)
隨便打開一個函數,發現是這樣的:
?
.data:00085E24 DD F9 97 E4 off_85E24 DCD 0xE497F9DD ; DATA XREF: sub_DF08+8↑r
cat /proc/18395/maps | grep e49看一下這個地址.

發現是libc.so,把這個libc.so拖出來放到ida分析。
計算一下0xE497F9DD-0xe494b000 = 0x349dd
看一下libc.so,所以這個函數就是strcpy。

感覺可以寫一個idapython腳本去修復一下。
然后就寫了一下,先從libc.so中提取函數地址和函數名。
from idautils import *from idaapi import *from idc import *f = open("./func.txt",'w')for func_addr in Functions(0,0x5B18BC): func_name = get_func_name(func_addr) print(func_addr , func_name) f.write(str(func_addr) + "," + func_name + "") # f.writelines()f.close()
效果:

然后從.data段中找到相應地址,相減得到libc.so中地址的偏移,然后對應起來,去修改函數名。
from idautils import *from idaapi import *from idc import *f = open(r"CryptoTest_32\CryptoTest\lib\func.txt",'r')func_info = {}while True: info = f.readline().strip('') if not info: break addr, func_name = info.split(',') # print(addr + func_name) func_info[int(addr,10)] = func_name# print(func_info)f.close()textStart = 0xA2984textEnd = 0xC2000# textStart = 0xA2DE0# textEnd = 0xA2E04libc_dump_base = 0xe494b000for i in range(textStart,textEnd,4): dword_ = get_dword(i) if dword_ > libc_dump_base: libc_func = dword_ - libc_dump_base # print(dword_,libc_func) func_name = func_info.get(libc_func) if not func_name: func_name = func_info.get(libc_func-1) #thumb if not func_name: continue raw_name_off = get_name(i) patch_name_off = func_name + "_ptr_" + raw_name_off set_name(i,patch_name_off) xrefaddrs = XrefsTo(i, flags=0) for xrefaddr in xrefaddrs: raw_name = get_func_name(xrefaddr.frm) #拿到函數原名稱 patch_fun_addr = get_name_ea_simple(raw_name) #拿到函數地址 # print(get_func_name(xrefaddr.frm)) if raw_name and patch_fun_addr: break if raw_name and patch_fun_addr: patch_name = func_name + "_" + raw_name print("patch_name : ",patch_name) set_name(patch_fun_addr,patch_name) print(dword_,func_name)
效果如下:

這樣就容易分析得多,其實不止libc.so,還有libdl.so等,不過這個函數少,就手動恢復了。
三、init_array
地址:0x11720

sub_13E48:打開libc.so,通過dlsym獲取了mprotect、mmap、munmap、fopen、fclose、fgets、fwrite、fread、sprintf、pthread_create函數指針。
接著跟著frida的log,程序運行到了case 2。
流程是case 2 -> case 5 -> case 4(讀cmdline) -> case 1 -> case 5 -> case 4循環讀取。
這里主要是記錄包名的長度,存在v8里。

最終執行到case 0,讀包名,然后和/system/bin/dex2oat對比,這里我包名和/system/bin/dex2oat不匹配,不進入下面的步驟(這個過程看不懂它要干啥)。
然后進入到JNI_Onload。
四、JNI_Onload
字符串解密
剛看到JNI_Onload,發現用了sub_12B94函數大量解密字符串。

于是采用frida hook這個函數,打印出相應的信息(比如解密后的函數,返回地址),本來是只想解密字符串,但是字符串的解密順序其實幫助了分析流程的過程,解密字符串的函數不止一個,具體的可以看看附件,寫得很亂,需要注意的是這個hook的時機應該是在JNI_Onload解密之后,不然可能會出問題。
function hook_decode_str(){ let base_secShell = Module.findBaseAddress("libSecShell.so"); let decode_str = base_secShell.add(0x12B94+1); Interceptor.attach(decode_str, { onEnter: function(args) { console.log("=======decode_str========="+ " size = " + args[1] + " op = " + args[2]," return addr = " + this.context.lr.sub(base_secShell)) this.args0 = args[0] this.args1 = args[1] }, onLeave: function(){ console.log(hexdump(this.args0,{length:this.args1.toInt32()})) // console.log(hexdump(args[0],{length:0x10})) } })} function hook_svc_mprotect() { let base_secShell = Module.findBaseAddress("libSecShell.so"); if (base_secShell != null) { console.log("base_secShell : " + base_secShell) }else{ return ; } let svc_mprotect = base_secShell.add(0xC0778);//32位 // let svc_mprotect = base_secShell.add(0x1541A0);//64位 //private native void jniLoadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously); Interceptor.attach(svc_mprotect, { onEnter: function(args) { console.log("==========================================") console.log("svc_mprotect: start = " + args[0] + " , len = " + args[1] + " , ATTRIBUTES = " + args[2]) mprotect_cnt += 1 console.log(hexdump(base_secShell.add(0x281B4))) }, onLeave: function(){ console.log("svc_mprotect leave") console.log("==========================================") if(mprotect_cnt == 2){ hook_decode_str() // hook_elf_hook() // sleep(1000000) } } })}
大概流程
先執行case 0:初始化JNIEnv,解密得到com/SecShell/SecShell/H字符串
然后case8(0x29e00):
跳到sub_13E48,獲取libc.so一些函數指針,從java類獲取PKGNAME = "com.example.cryptotest",
后面在case8里的case分支干了一些不知道在干啥,好像是在配置環境
然后是case9:
調用android/app/ActivityThread類的currentActivityThread方法
調用ActivityThread對象的getSystemContext方法
調用ContextImpl的getPackageManager方法
調用PackageManager的getPackageInfo方法
獲取PackageInfo對象的applicationInfo字段
獲取ApplicationInfo對象的sourceDir字段
獲取ApplicationInfo對象的nativeLibraryDir字段
拼接出/proc/%d/fd/%d,遍歷fd找到base.apk路徑
然后是case2:對小米手機進行適配
然后是case3:創建了線程(沒執行到),驗證了簽名
case1->case10
case10:打開/proc/self/maps,找到lib/libart.so,比較是否是r-xp權限,通過格式化字符串%lx-%lx讀取地址
case11:把libart.so改為可讀寫,兩個箭頭前后對比

case13:拼接出各種字符串,比如/data/app/~~yqfNRTFBNC4L6gA2oycp-g==/com.example.cryptotest-VtwyTKkuWOlQLYKSpK7Z5Q==/oat/arm/base.odex
然后到case13里面的case10,打開這個base.odex
會打開打開classes.dve進行校驗
然后會執行sub_260BC,這里會調用0x4D7DC(hook_libc_so_func),對hook部分說的那些函數進行hook,然后讀classes.jar寫到內存里的時候就調用這些函數進行解密(dex加載)
case4:把libart.so權限改回去
case12:弄了好多inlinehook,但是好像也沒有執行(不知道是不是我系統版本過高)
具體見附件給的idb吧
dex加載
sub_1DFB0調用了com/SecShell/SecShell/H的f方法加載/data/user/0/com.example.cryptotest/.cache/classes.jar
調用com/SecShell/SecShell/H的ff加載/data/user/0/com.example.cryptotest/.cache/v1filter.jar
通過dump maps來比較加載前和加載后的差異

可以直接把這個直接dump下來,發現解析不了,有點尷尬。
于是比較一下frida-dexdump dump下來的文件,發現后面多了幾百個字節,刪掉就可以解析了。

inlineHook
地址:0x53E30是inline hook函數

交叉引用可以看到很多hook的地方。
比如hook了libc.so的pread64、ftruncate64、write、read、munmap、msync、__open、__openat、__mmap2。
運行的時候發現其他的hook沒有觸發,之后用ida動態調試了一下確實是只hook了這些,其他地方不知道是不是有啥其他辦法能讓我斷不下來。
這些沒有執行的地方就不過多分析了。

函數p208CA25EFD02F087E334CA562B3F8423:

檢測
地址0x60C5C:(似乎沒有執行,發現這些check函數好像都沒有執行)
xposed檢測,fart檢測等


check_usb:0x25508
check_root:0x17D9C
其他
函數地址:0x53E30,對華為和榮耀手機進行適配
is_miuiinstaller_process對小米手機進行適配
JNI_Onload里兼容性適配:

JNI函數注冊:sub_16028(通過字符串解密log很容易發現)

用ida動態調試的時候,可能會遇到函數不會自動解析成函數,在下面框框輸入這段腳本,然后用createFunction函數就可以創建函數了。
def createFunction(start,end): len_func = end - start begin = start del_items(start,0,len_func) #先undefine while len_func: cnt = idc.create_insn(begin) if cnt == 0: break #遇到比如off_31F40 DCD __stack_chk_guard_ptr - 0x31D78這種就不解析了,一般是連續的,如果不連續需要跳過,繼續解析 begin += cnt len_func -= cnt print(len_func) #idc.create_insn(start) return idc.add_func(start,end)
【參考文獻】分析一下梆x加固:https://bbs.pediy.com/thread-266247.htm
好像超過上傳大小了,兩個文件,apk傳不上去,所有文件放在百度網盤:
鏈接:https://pan.baidu.com/s/1Wdjp431IhhoCbcICQCJRAg
提取碼:kxuc