這篇文章最后以分析UnCrackable-Level1.apk介紹frida腳本的使用,如果大佬們對(duì)前面介紹的adb、frida安裝已經(jīng)清楚,則可以直接拉到最后看UnCrackable-Level1.apk的分析。
一、安裝frida
-
在電腦上安裝adb
安裝adb是為了在電腦上通過adb與手機(jī)進(jìn)行交互。手機(jī)需要通過usb連接(adb也支持通過wifi連接,但最開始都需要通過usb連接一次)
windows電腦安裝adb,可以通過安裝android sdk,然后在android sdk目錄中的platform-tools目錄中找到adb.exe,將該目錄設(shè)置為系統(tǒng)環(huán)境變量,即可在命令行中使用adb,具體安裝方法可以百度或者Google一下參考
mac安裝adb可以參考如下鏈接
http://www.itdecent.cn/p/52e9b44460d0
安裝完adb后,通過數(shù)據(jù)線將手機(jī)與電腦進(jìn)行連接,手機(jī)設(shè)置允許usb調(diào)試,然后執(zhí)行adb devices命令查看手機(jī)是否已經(jīng)跟電腦連接成功,如下所示
image -
在電腦上安裝python3
windows電腦可以在https://www.python.org/downloads/windows/下載python3安裝包進(jìn)行安裝,如下所示
imagemac電腦安裝python3,可以通過brew install python3指令進(jìn)行安裝,如下所示
image -
在電腦上安裝frida-tools
windows電腦與mac電腦都可以通過如下指令安裝frida-tools
pip3 install frida-tools
如下所示
image -
下載frida-server發(fā)送到手機(jī)中并啟動(dòng)
電腦上配置完后,現(xiàn)在需要下載frida-server保存到手機(jī)中,并啟動(dòng)frida-server,這樣電腦上的frida客戶端才能與手機(jī)中的frida服務(wù)端進(jìn)行交互
frida-server下載,可在github中下載,鏈接如下所示
https://github.com/frida/frida/releases
下載的frida-server可根據(jù)設(shè)備cpu的型號(hào)進(jìn)行下載,如下所示
image如果覺得github下載速度慢,可以在公眾號(hào)回復(fù)“frida-server”百度云下載,四個(gè)版本都打包一起哈
下載完android設(shè)備相對(duì)應(yīng)的frida-server后,通過adb push將frida-server發(fā)送到android設(shè)備中(通常將frida-server保存在/data/local/tmp目錄中),如下所示
image修改frida-server的權(quán)限,使frida-server具有執(zhí)行權(quán)限,如下所示
image以后臺(tái)模式運(yùn)行frida-server,這樣電腦上的frida CLI就能夠與frida-server進(jìn)行交互,我們可以在電腦上使用frida來(lái)與android設(shè)備中的app交互(當(dāng)然前提是手機(jī)要通過usb數(shù)據(jù)線連接到電腦),如下所示
imageimage
二、frida的使用
-
frida tools主要有Frida CLI、frida-ps、frida-trace、frida-discover、frida-ls-devices、frida-kill等命令工具
具體的命令工具使用可以參考上篇文章 ios逆向之frida安裝與使用,鏈接如下
-
除了frida命令行使用外,frida還可以通過python及JavaScript腳本來(lái)hook android設(shè)備中的應(yīng)用程序
接下來(lái)我們主要介紹如何通過python及JavaScript腳本來(lái)hook android應(yīng)用程序,以分析UnCrackable-Level1.apk為例,找到UnCrackable-Level1.apk需要的正確的校驗(yàn)字符串
-
下載安裝UnCrackable-Level1.apk
可以通過如下github鏈接下載
https://github.com/OWASP/owasp-mstg/tree/master/Crackmes/Android/Level_01
如果覺得github下載速度慢,也可以在公眾號(hào)回復(fù)"UnCrackable-Level1"進(jìn)行下載。下載完以后通過adb指令 adb install UnCrackable-Level1.apk 將apk安裝到手機(jī)中,如下所示
image -
運(yùn)行UnCrackable-Level1,觀察app的功能,可以發(fā)現(xiàn)該app主要是要校驗(yàn)我們輸入的字符串是否正確。并出現(xiàn)了一個(gè)"Root detected!"彈框,表示app檢測(cè)到我們的android設(shè)備已root,如下所示
image 確認(rèn)我們需要完成任務(wù)即繞過app的root檢測(cè)和找出app校驗(yàn)的正確的字符串是什么,現(xiàn)在可以開始分析app
-
靜態(tài)分析app,可以通過jeb、jadx、Androidkiller等工具進(jìn)行分析,這里我就使用jadx靜態(tài)分析app。我們先分析app的檢測(cè)android設(shè)備是否root的功能,如下所示
imageimageimageimage順帶看一下app中的檢測(cè)app是否被調(diào)試的方法
image靜態(tài)分析完app的root檢測(cè)方法后,我們現(xiàn)在需要通過frida腳本繞過app的檢測(cè)android設(shè)備是否root功能,代碼如下所示(我貼出所有代碼,包括后面字符串校驗(yàn)的代碼)
主要有兩種方式繞過root檢測(cè)方法
方法一:hook root檢測(cè)方法使它們的返回值始終是false,這樣則檢測(cè)不到我們的android設(shè)備已經(jīng)root
方法二:hook System.exit函數(shù),即app雖然檢測(cè)到我們的設(shè)備已經(jīng)root,但是不讓它退出,繼續(xù)運(yùn)行
script.js
setImmediate(function(){ //防止超時(shí)
console.log("[*] Staring script");
Java.perform(function(){ //JavaScript代碼成功被附加到目標(biāo)進(jìn)程時(shí)調(diào)用,我們要hook app的代碼都在Java.perform底下寫,是個(gè)固定格式
//我們通過兩種方法來(lái)繞過app的檢測(cè)android設(shè)備是否root,如果root則退出app
//方法一 修改我們上面分析的3個(gè)root檢測(cè)函數(shù)的返回值 使他們始終返回false
console.log("[*] Hooking calls to root detect");
//1.修改方法a的返回值,使返回值為false。方法a通過檢測(cè)PATH(android系統(tǒng)環(huán)境變量)中是否有su文件來(lái)判斷android系統(tǒng)是否被root
var rootDetect = Java.use("sg.vantagepoint.a.c"); //Java.use用于聲明一個(gè)Java類 這里我們聲明root檢測(cè)的類
//類.函數(shù).overload(參數(shù)類型).implementation = function(形參名稱){
rootDetect.a.implementation = function(){ //這里我們需要hook的是rootDetect類中的a方法 a方法沒有參數(shù)因此overload可以不用寫
//function中不用寫形參名稱
console.log("-----hook su finder-----");
var suFinder = this.a(); //執(zhí)行a方法并返回a的返回值Boolean值
console.log("su finder original return value is: ",suFinder.toString()); //打印返回值
suFinder = false; //修改返回值為false
console.log("su finder new return value is: ",suFinder.toString()); //打印修改的返回值
return suFinder; //返回我們修改的值 使a函數(shù)始終返回false
}
//2.修改方法b的返回值,使返回值為false。方法b通過檢測(cè)Build.TAGS中是否包含字符串"test-keys"來(lái)判斷android系統(tǒng)是否被root
rootDetect.b.implementation = function(){ //這里我們需要hook的是rootDetect類中的b方法 b方法沒有參數(shù)因此overload可以不用寫
//function中不用寫形參名稱
console.log("-----hook test-keys finder------");
var testKeysFinder = this.b(); //執(zhí)行b方法并返回b的返回值Boolean值
console.log("test-keys finder original return value is: ",testKeysFinder.toString()); //打印返回值
testKeysFinder = false; //修改返回值為false
console.log("test-keys finder new return value is: ",testKeysFinder.toString()); //打印修改的返回值
return testKeysFinder; //返回我們修改的值 使b函數(shù)始終返回false
}
//3.修改方法c的返回值,使返回值為false。方法c通過檢測(cè)指定路徑下是否包含指定的文件來(lái)判斷android系統(tǒng)是否被root
rootDetect.c.implementation = function(){ //這里我們需要hook的是rootDetect類中的c方法 c方法沒有參數(shù)因此overload可以不用寫
//function中不用寫形參名稱
console.log("-----hook superuser file finder-----");
var superuserFileFinder = this.c(); //執(zhí)行c方法并返回c的返回值Boolean值
console.log("superuser file finder original return value is: ",superuserFileFinder.toString()); //打印返回值
superuserFileFinder = false; //修改返回值為false
console.log("superuser file finder new return value is: ",superuserFileFinder.toString()); //打印修改的返回值
return superuserFileFinder; //返回我們修改的值 使c函數(shù)始終返回false
}
//方法二 修改System.exit函數(shù)使app不退出 當(dāng)app檢測(cè)到android設(shè)備root時(shí)則會(huì)調(diào)用System.exit函數(shù)退出app
/*console.log("[*] Hooking calls to System.exit");
var exitClass = Java.use("java.lang.System"); //聲明System類
exitClass.exit.implementation = function(){ //這里我們需要hook的是System類中的exit方法 exit方法沒有參數(shù)因此overload可以不用寫
//function中不用寫形參名稱
console.log("[*] System.exit called");
}*/
//hook app正確字符串生成算法得到解密后的正確字符串
console.log("[*] Hooking calls to AES decrypt");
var aesDecrypt = Java.use("sg.vantagepoint.a.a");
aesDecrypt.a.overload('[B',"[B").implementation = function(arg1,arg2){
console.log("[*]Hook a.Class");
var decryptValue = this.a(arg1,arg2);
var secret = '';
for(var i=0;i<decryptValue.length;i++){
secret+=String.fromCharCode(decryptValue[i]);
}
console.log("secret is : " + secret);
return decryptValue;
}
});
})
verifyString.py
# -*- coding: utf-8 -*-
import frida
import sys
PACKAGE = 'owasp.mstg.uncrackable1' #需要hook的app包名
if __name__ == '__main__':
jscode = open('script.js','r',encoding='UTF-8').read() #獲取js腳本內(nèi)容
device = frida.get_usb_device(1000) #連接usb設(shè)備 1000表示超時(shí)
pid = device.spawn([PACKAGE]) #啟動(dòng)指定包名的app
session = device.attach(pid) #附加到app
script = session.create_script(jscode) #創(chuàng)建frida javaScript腳本
script.load() #加載腳本
device.resume(pid) #恢復(fù)app運(yùn)行
sys.stdin.read() #阻塞接收數(shù)據(jù)
-
下面是執(zhí)行繞過app root檢測(cè)方法腳本后,得到的結(jié)果,如下所示
imageimageimageimage -
為了驗(yàn)證app root檢測(cè)方法的正確性,我們也可以到android設(shè)備中上面檢測(cè)方法涉及的目錄查看是否存在相應(yīng)的文件,如下所示
imageimageimage -
我們已經(jīng)分析完app的root檢測(cè)功能,現(xiàn)在分析app的verify算法,查看app字符串校驗(yàn)的算法,確認(rèn)app需要的正確的字符串是啥,如下所示
imageimageimage 分析完verify校驗(yàn)算法后,我們即可通過frida hook最后的aes解密算法,得到它解密后的返回值再轉(zhuǎn)成String后,就是我們想得到的正確的校驗(yàn)字符串,代碼如下
//hook app正確字符串生成算法得到解密后的正確字符串
console.log("[*] Hooking calls to AES decrypt");
var aesDecrypt = Java.use("sg.vantagepoint.a.a");
aesDecrypt.a.overload('[B',"[B").implementation = function(arg1,arg2){
console.log("[*]Hook a.Class");
var decryptValue = this.a(arg1,arg2);//執(zhí)行原函數(shù)得到返回值byte[]
var secret = '';
for(var i=0;i<decryptValue.length;i++){//將返回值byte[]轉(zhuǎn)成String
secret+=String.fromCharCode(decryptValue[i]);
}
console.log("secret is : " + secret);//打印正確的字符串
return decryptValue;
}
-
下面是執(zhí)行完aes解密方法腳本后,得到的結(jié)果,如下所示
圖片.png
圖片.png
圖片.png
- 綜上所述,我們已經(jīng)通過frida腳本完成了繞過app的檢測(cè)及hook app校驗(yàn)字符串的方法從而得到真正的字符串。感興趣的大佬可以試一試,完成項(xiàng)目代碼可以在公眾號(hào)回復(fù)“codeUncrackable1Test”,通過百度云下載。


