####
hook技術
1,hook,java層,這個相對簡單,都是java代碼,
2,hook,native層,so庫,這個難度大,里面都是匯編,
####
frida框架介紹:
frida是一個逆向框架,
目前逆向框架比較知名的有兩個,Xposed,frida
Xposed,是java開發的,要會java
frida,是python和js,開發的,這個比較適合我,frida是一個js注入技術,python主要是一個架子,主要還是用js注入,
兩個工具對比,
frida比Xposed的適用性更廣一些,Xposed只能hook安卓,java層,
而frida可以hook,安卓,ios,windows,還可以hook,安卓的native層,
###
frida中python只是調用,主要是寫js,做js注入,
###
使用frida開發的前提是設備要root
###
使用frida開發hook 的前提條件,
你首先要知道hook哪一個方法,所以你要先解決脫殼的問題,獲取到源代碼,
###
frida組成部分,
1,frida-server,這個是一個包,需要放到手機設備上,
2,frida,python模塊
3,frida-tools,提供cli命令,和frida-server做交互,
第二個和第三個是可以只用一個的,你可以用python,直接調用js,也可以用客戶端cli命令,調用js,
###
有空經常去看官方文檔,
很多時候,有很多問題,你解決不了,你去百度不到的,必須要養成看官方文檔的習慣,
對於這些先進的東西,生態還不是很豐富的,你必須要要看官方文檔,
###
Frida安裝配置
第一步:電腦安裝frida和frida-tools
pip install frida
pip install frida-tools
--注意:這兩個我安裝到了python虛擬環境里面了,所以要先進入python虛擬環境,然后再執行
mac安裝本來是兩個命令的事情,但是安裝的時候總是報錯,
sudo pip install --upgrade certifi
open /Applications/Python\ 3.7/Install\ Certificates.command
如果是證書問題,就執行這兩句,然后重新啟動電腦,我的就是這樣好的,
####
第二步,手機執行frida server
frida server 放在手機上執行 要注意手機CPU型號
https://github.com/frida/frida/releases
下載的時候,注意是x86,還是x86_64,還是arm,
手機基本都是arm
模擬器一般都是x86的,而且是32位的,但是這不是絕對的,需要查看一下,
adb shell getprop ro.product.cpu.abi
使用這個命令來查看,
####
在手機上執行:
首先下載的frida-server解壓,
推入手機里,adb push frida-server-15.0.18-android-x86_64 /data/local/temp/frida-server
adb shell
cd /data/local/temp
確定手機當前用戶是root用戶或擁有root權限
chmod 777 frida-server --變成可讀可寫可執行,
./frida-server
執行完畢后為運行狀態。
保留此窗口 shell,以保證服務運行,關閉該shell 或者停止ctrl+c 則服務關閉。接下來的操作可另起shell 或該步驟命令另起 shell 執行。
###
第三步,端口轉發:
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043
27042 用於與frida-server通信的默認端口號,之后的每個端口對應每個注入的進程,檢查27042端口可檢測 Frida 是否存在。
你想要在python里面做開發,還要做端口轉發,
不然,你python做開發,是會報錯,告訴你沒有連接上,
####
第四步,檢查是否成功
電腦上運行
先進入python虛擬環境,里面安裝了frida tools
frida-ps -U 電腦上運行 android.process.acore 字樣表示成功
這個命令是驗證一下,在手機端的frida-server是否運行起來了,
注意這一句要進入你安裝frida的虛擬環境里面執行,
###
經過上面的配置,你就可以做frida開發了,
###
frida基礎開發示例:
###
frida開發的固定套路:三段論
第一段:有一個打印的python函數,用來打印日志,不使用這個,使用console.log("version: "+v)也可以,只是自己寫可以加一些標志,
第二段:有一段js腳本,
第三段:下面有一段python代碼,
主要是做了,獲取設備,獲取設備里面的app包名,執行js腳本,注入到app里面取,調用第一段的打印方法,最后一句是不要讓程序斷掉,
第一段和第二段,基本不用動,主要是動中間的js腳本,
python主要是一個架子,主要還是用js注入,
####
要先在模擬器把app運行起來然后再hook
運行這個py文件,可以打印出來安卓系統的版本,打印所有調用的方法
###
frida提供了什么方法,可以看官方文檔,
https://www.frida.re/docs/javascript-api
具體的js腳本的api,看這個官方文檔,
逆向開發很少有很詳細的教程,
所以如果你遇到解決不了的問題,你要常常看官方文檔,靜下來心看文檔,這是你進階的必經之路,
###
Frida 常用模塊API:
Java 模塊:Hook Java 層的類 方法 相關
Process 模塊:處理當前線程相關
Interceptor 模塊:操作指針相關,多用來Hook Native 相關
Memory 模塊:內存操作相關
Module 模塊:處理so相關
###
python代碼:
import frida import sys def on_message(message,data): if message["type"] == "send": print("[*]{0}".format(message["payload"])) else: print(message) jscode = """ // 獲取安卓版本 var v = Java.androidVersion; send("version: "+v) //獲取該應用的加載的類 var classnames = Java.enumerateLoadedClassesSync(); // for循環 for(var i = 0; i<classnames.length; i++){ send("class name :" + classnames[i]); } """ process = frida.get_usb_device().attach("自毀程序密碼") script = process.create_script(jscode) script.on("message",on_message) print("[*] running CIF") script.load() sys.stdin.read()
#####
#####
frida進階開發:
###
###
作為一個開發已經要了解一個分層的思想,
看上面這個圖,一開始就只有系統層的AIP,也就是so層,這一層的API可以讓程序員調用,
但是你使用這一層,對程序員很不友好,所以封裝出來了應用層API,安卓層,這一層對程序員比較友好,開發效率也會提升,
###
一般的程序員是有有流水線的培訓的,所以在市面上有很多培訓機構,
系統層,so層,這個對程序員的要求很高,對技術的深度和廣度都有要求,沒有辦法批量培訓,
系統層的問題都比較復雜,所以現在很多分層了之后,系統層的問題你就不用管了,只需要去考慮應用層,去調用接口去開發app,關注業務邏輯,
應用層對程序員比較友好,
###
另外主要最上層的app,不是只能調用安卓層,還能直接掉SO層,這是可以的,
因為及時你調用的是安卓層,最終也會傳導到系統層,然后執行,
###
理解了上面的分層,
你就會知道,為什么你有時候hook應用層的時候不生效,
比如打開文件,你hook應用層不生效,這個時候你就要考慮是不是要hook系統層,os層,
因為我知道不管你怎么打開文件,一定會經過系統層,那我就直接hook系統層的打開文件的函數,就一定能hook到,
有時候你會發現,直接hook系統SO層,還會更方便,
###
簡單了解so庫,---ida工具
如何看到SO庫文件里面的內容,就要使用到一個神器,IDA工具,
使用ida工具,需要安裝一下軟件,
這個工具的使用就是調試代碼的,
目前最優秀的靜態反編譯軟件之一,這款mac安全工具它不僅可以應用在反編譯和動態調試等強大的逆向工程領域,還支持對多種處理器不同類型的可執行模塊進行反匯編處理,軟件具有方便直觀的操作界面,在為用戶呈現出簡潔清晰源代碼的同時,也完美的減少了反匯編工作的難度,極大的提升了用戶在反編譯工作中的使用體驗。
可以使用這個工具看到so庫里面的內容,都是一個一個的函數,供app調用,
###
具體軟件使用要自己學習了,
###
怎么hook這個so庫呢?
下面介紹幾個操作so庫的frida的API使用
hello.so
printhello()
假設你要hook一個so庫,hello.so,
里面有一個方法printhello()
第一步:使用module模塊,都是操作內存地址,
module.findBaseAddress(name)
使用memory模塊,這個是操作內存,
可以把內存的內存拿出來,轉成字符串,
使用intercept模塊,監聽模塊
可以把方法監聽到,拿到函數的操作權,就靠這個模塊了,
####
怎么hook,app啟動階段的方法?
#####
有很多時候我們需要在APP的打開之前做hook,完全打開之后就hook不到了,
所以要在啟動階段hook,
有哪些是app頁面在完全打開之前做的:
比如oncreate方法,
###
用之前的hook方法是不行的,這次要換一個,
上面是之前的寫法,下面是新的寫法,
獲取到pid,然后resume重啟這個進程,就可以hook到app啟動階段的函數調用了,
注意,這種方法,也要把APP處於啟動階段,中間的重啟app是自動進行的,
###
啟動的代碼不動,只改動一下,下面的幾行,和jscode的幾行,就可以打印出來i am here
對應的jscode:
所以你看到有一個細節,就是進行了方法重寫,
這樣我們只是增加了一個oncreate方法的打印,其他的都沒有動,
###
怎么hook系統層的so文件?
####
這個是經驗得來的,
這是打開文件的庫,
hook這個,就知道打開了什么庫了,
####
jscode:
###
frida進階開發:hook加密類--使用引力播app
怎么hook,java的加密類,首先你要知道java里面有那些加密類,比如md5
我們能這樣不看代碼就去hook加密的前提,是因為加密算法一般公司會使用現成的,而不是自己去發明一套,這樣我們就知道去找找java里面的加密類去hook一下
嘗試一下就知道這個app是否使用了這個加密算法,
這個方法比較笨,因為這個你要知道java里面所有的加密算法,
這就是hook重寫的技巧,很重要,
這樣的寫法不會破壞原有的原理,但是又可以增加我們想要的代碼,
如果沒有返回值,就是不需要return ,
如果有返回值,就可以使用變量來接收,
具體有沒有返回值,需要在api里面來判斷,
代碼解釋:
第一點,為什么hook,update方法,因為這個加密算法,有多個update方法,所以我們把四個update都hook一下,具體可以看下這個加密類的API
我們知道了這個加密類之后,還不能盲目去hook,還需要查一下這個類的api,下面是查api的結果,
如果你使用overlord,會報錯的,
了解安卓API/Java API---可以通過這些網站
http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
https://developer.android.google.cn/reference/packages
https://www.android-doc.com/reference/android/content/pm/PackageInfo.html
###
第二點:有一個地方傳入的字節,我們要把他轉行成為字符串,下面是轉換的js代碼:
###
第三點:為什么要打印參數,因為既然你加密是用的這個算法類,那肯定要把待加密的字符串拿到,
這就是加密之前的字符串,我們就hook到了
有三段,是設備號,時間戳,第三段是固定的(多運行幾次就知道了)
###
第四點,我們把getinstance方法,也打印出來了,這個需要傳遞是什么類型的加密,就是使用的md5加密
###
第五點:我們拿到這個加密,但是還能加密之后做了位運算,
所以我們要hook這個加密函數的調用棧,我們可以找到加密的地方,
怎么打印調用棧呢?
下面的寫法是固定的,直接哪來用就可以了,
我們拿到了堆棧之后,才之后,是這個a調用的加密,否則混淆過的代碼,我們根本不知道這個是哪里調用的,
通過這個方式,我們沒有使用jadx進行搜索,但是我們通過hook加密類,就得到了這個加密的代碼位置,
這樣我們就可以直接去找這個函數,看看怎么加密的,
下一步使用jadx打開這個app,這個icity是要脫殼的,
完整的python代碼
import frida import sys def on_message(message, data): if message['type'] == 'send': print("[*]{0}".format(message['payload'])) else: print(message) jscode = """ Java.perform(function(){ var Testsig=Java.use('com.yaotong.crackme.MainActivity') Testsig.onCreate.overload('android.os.Bundle').implementation=function(v){ send('I am here'); this.onCreate(); return true ; } }) """ # 打印輸出基礎地址 jscode1=""" var base_address = Module.findBaseAddress('libc.so'); send('base_address:'+base_address); var mod_address=Module.findExportByName('libc.so','dlopen'); send('mod_address:'+mod_address); var lib_module=Process.findModuleByAddress(base_address); send('lib_module_name:'+lib_module.name); Interceptor.attach(mod_address,{ onEnter: function(args){ send("open("+Memory.readUtf8String(args[0])+","+args[1]+")"); }, onLeave: function(retval){ send('retval:'+retval); } }) """ #hook md5加密 jscode2=""" //打印調用堆棧 function printstact(){ send(Java.use('android.util.log').getStackTraceString(Java.use('java.lang.Exception').$new())); } //array 轉成 string function array2string(array){ var buffer=Java.array('byte',array); var result=''; for (var i = 0;i<buffer.length;i++){ result +=(String.fromCharCode(buffer[i])) } return result; } Java.perform(function(){ var MessageDigest=Java.use('java.security.MessageDigest'); MessageDigest.update.overload('[B').implementation= function (bytesarray) { send ('I am here 0'); send('ori:'+ array2string(bytesarray)); printstact() ; send('md5:'+this.update('bytesarray')); }, MessageDigest.update.overload('byte').implementation=function(bytesarray){ send ('I am here 1'); send('ori:'+array2string(bytesarray)); printstact(); send('md5:'+this.update('bytesarray')); }, MessageDigest.update.overload('java.nio.ByteBuffer').implementation=function(bytesarray){ send ('I am here 2'); send('ori:'+array2string(bytesarray)); printstact(); send('md5:'+this.update('bytesarray')); }, MessageDigest.update.overload('[B','int','int').implementation=function(bytesarray){ send ('I am here 3'); send('ori:'+array2string(bytesarray)); printstact(); send('md5:'+this.update('bytesarray')); }, MessageDigest.getInstance.overloads[0].implementation=function(algorithm){ send('call->getInstance for ' + algorithm); return this.getInstance.overloads[0].apply(this,arguments); }; } ); """ deveice = frida.get_usb_device() pid = deveice.spawn(['cn.soulapp.android']) process = deveice.attach(pid) # 創建運行腳本 script = process.create_script(jscode2) # 輸入打印,寫死 script.on('message', on_message) print('[*] Running CTF') # 寫死 script.load() # 重啟程序 deveice.resume(pid) # 寫死 sys.stdin.read() 最后打印結果,看出加密方式
###
注意,執行的時候,運行一下app,否則打印不出內容,
###
通過這個方法還是找到加密的入口函數,
不再使用jadx搜索的方式來找加密入口了,
之前是怎么找這個入口的,是關鍵詞搜索的方式,這一步通常很難,因為代碼反混淆之后,你看不懂他的代碼,搜索不到,這是一步一步的從上到下分析,
###
我們hook的目的,不是要完全的破解,而是要破解加密的token,還有加密的參數,能去調用接口,
這個過程我們會遇到很多困難,
如果是加固了,我們要脫殼,
如果混淆了,我們這個時候直接拿到加密參數的字段去搜索就搜不到,否則我們可以試試搜索一下,看看是否可以看到,
既然混淆了,我們就要有手段,可以讓我們的分析更加有效一點,
上面的這個加密算法的hook,就是基於這個目的,就是為了解密參數,
###
使用上面這種盲狙的方法,怎么提升成功率,就是擴大加密類的覆蓋,覆蓋的多,就可以大概率的能打印出來,
###
同樣一個app,引力播,
第一次,我們通過脫殼,然后搜索signature來找到加密的地方,
第二次,我們脫殼之后,hook,加密類,打印調用堆棧,然后來找到加密的地方,
很多地方都可以用這種方法,
這次是hook加密類,這是我們要解決參數加密的問題,
我們還可以hook,網絡請求庫,因為我們要解決抓包的問題, 同樣的思路,
###
但是這種思路,主要還是要hook的面范圍大,才可以覆蓋大,
###
前期使用frida工具,能hook到類,方法,並且打印出參數,打印出日志,打印出堆棧就可以了,
后續會有更加復雜的操作,
####