利用FRIDA攻击Android应用程序(一)
前言
直到去年參加RadareCon大會時,我才開始接觸動態代碼插樁框架Frida。最初,我感覺這玩意還有點意思,后來發現這種感覺是不對的:應該是非常有意思。您還記得游戲中的上帝模式嗎?面對本地應用程序的時候,一旦擁有了Frida,也就擁有了這種感覺。在這篇文章中,我們重點介紹Frida在Android應用方面的應用。在本文的第二篇中,我們將會介紹如何利用Frida來應付Android環境下的crackme問題。
什么是動態代碼插樁??
動態二進制插樁(DBI)意味著將外部代碼注入到現有的(正在運行的)二進制文件中,從而讓它們執行以前沒有做過的事情。注意,這并非漏洞利用,因為代碼的注入無需借助于漏洞。同時,它不是調試,因為你不必將二進制代碼附加到一個調試器上面,當然,如果你非要這么做的好也未嘗不可。那么DBI可以用來做什么呢?實際上,它可以用來做許多很酷的事情:
訪問進程內存
在應用程序運行時覆蓋函數
從導入的類調用函數
在堆上查找對象實例并使用它們
Hook、跟蹤和攔截函數等。
當然,調試器也能完成所有這些事情,但是會比較麻煩。例如。在Android平臺中,應用程序必須先進行反匯編和重新編譯處理,才能進行調試。一些應用程序會嘗試檢測并阻止調試器,這時你必須先克服這一點,才能進行調試。然而,這一切做起來都會非常麻煩。在DBI與Frida的幫助下,這些事情都不是我們要關心的,所以調試會變得更加便捷。
FRIDA入門
Frida“允許您在Windows、macOS、Linux、iOS、Android和QNX的本機應用程序中注入JavaScript或自己的庫代碼?!弊铋_始的時候,它是基于谷歌的V8 Javascript運行時的,但是從版本9開始,Frida已經開始使用其內部的Duktape運行時了。不過,如果你需要V8的話,仍然可以切換回去。Frida可以通過多種操作模式與二進制程序進行交互(包括在非root的設備上給應用程序“插樁”),但是這里我們只介紹最簡單的情形,同時也不關心其內部運行原理。
為了完成我們的實驗,你需要
????Frida
????您可以從這里下載frida服務器的二進制代碼(截止寫作本文為止,最新版本為frida-server-9.1.16-android-arm.xz)
????Android模擬器或已經獲得root權限的設備。雖然Frida是在Android 4.4 ARM上面開發的,不過應該同樣適用于更高的版本。就本文來說,使用Android 7.1 ARM完全沒有一點問題。對于第二部分的crackme來說,則需要使用比Android 4.4更高的版本。
這里假設以linux系統作為主機操作系統,所以如果你使用Windows或Mac的話,有些命令可能需要進行相應的調整。
Frida的啟動方式花樣繁多,包括各種API和方法。您可以使用命令行界面或類似frida-trace的工具來跟蹤底層函數(例如libc.so中的“open”函數),以便快速運行。同時,你還可以使用C、NodeJS或Python綁定完成更復雜的任務。但是在其內部,Frida使用Javascript的時候較多,換句話說,你可以通過這種語言完成大部分的插樁工作。所以,如果你像我一樣不太喜歡Javascript的話(除了XSS功能),Frida倒是一個讓你進一步了解它的理由。
首先,請安裝Frida,具體如下所示(此外,您還可以通過查看README了解其他安裝方式):?
pip install frida
npm install frida
啟動模擬器或連接設備,確保adb正在運行并列出您的設備:?
michael@sixtyseven:~$ adb devices
List of devices attached
emulator-5556device
然后,開始安裝frida-server。先進行解壓,并將二進制文件放入設備中:?
adb push /home/michael/Downloads/frida-server-9.1.16-android-arm /data/local/tmp/frida-server
在設備上打開一個shell,切換到root用戶,并啟動frida:?
adb shell
su
cd /data/local/tmp
chmod 755 frida-server
./frida-server
(注意事項1:如果frida-server沒有啟動,請檢查當前是否為root用戶,以及文件是否在傳輸過程中發生損壞。當文件傳輸而導致文件損壞的時候,經常會出現一些讓人奇怪的錯誤提示。注意事項2:如果你想以后臺進程的方式啟動frida-server的話,則需要使用./frida-server&)
您可以另一個終端的常規操作系統shell中檢查Frida是否正在運行,并列出Android上的進程:?
frida-ps -U
-U代表USB,允許Frida檢查USB設備,同時還可用于仿真器。這時,您將看到一個如下所示進程列表:?
michael@sixtyseven:~$ frida-ps -U
?PID ?Name
---- ?--------------------------------------------------
?696 ?adbd
5828 ?android.ext.services
6188 ?android.process.acore
5210 ?audioserver
5211 ?cameraserver
8334 ?com.android.calendar
6685 ?com.android.chrome
6245 ?com.android.deskclock
5528 ?com.android.inputmethod.latin
6120 ?com.android.phone
6485 ?com.android.printspooler
8355 ?com.android.providers.calendar
5844 ?com.android.systemui
7944 ?com.google.android.apps.nexuslauncher
6416 ?com.google.android.gms
[...]
您將看到進程標識(PID)和正在運行的進程(名稱)?,F在,您可以通過Frida掛鉤到任何一個進程并對其進行“篡改”了。
例如,您可以跟蹤由Chrome使用的特定調用(如果還沒有運行該瀏覽器的話,請首先在模擬器中啟動它):?
frida-trace -i "open" -U com.android.chrome
輸出結果如下所示:?
michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome
Instrumenting functions... ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop. ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ?/* TID 0x2740 */
? ?282 ms ?open(pathname=0xa843ffc9, flags=0x80002)
? ? ? ? ? ?/* TID 0x2755 */
? ?299 ms ?open(pathname=0xa80d0c44, flags=0x2)
? ? ? ? ? ?/* TID 0x2756 */
? ?309 ms ?open(pathname=0xa80d0c44, flags=0x2)
? ? ? ? ? ?/* TID 0x2740 */
? ?341 ms ?open(pathname=0xa80d06f7, flags=0x2)
? ?592 ms ?open(pathname=0xa77dd3bc, flags=0x0)
? ?596 ms ?open(pathname=0xa80d06f7, flags=0x2)
? ?699 ms ?open(pathname=0xa80d105e, flags=0x80000)
? ?717 ms ?open(pathname=0x9aff0d70, flags=0x42)
? ?742 ms ?open(pathname=0x9ceffda0, flags=0x0)
? ?758 ms ?open(pathname=0xa63b04c0, flags=0x0)
frida-trace命令會生成一個小巧的javascript文件,然后Frida會將其注入到進程中,并跟蹤特定的調用。您可以觀察一下在__handlers __ / libc.so/open.js路徑下面生成的open.js腳本。它將鉤住libc.so中的open函數并輸出參數。使用Frida的情況下,這非常簡單:?
[...]
onEnter: function (log, args, state) {
? ? log("open(" + "pathname=" + args[0] + ", flags=" + args[1] + ")");
},
[...]
請注意Frida是如何訪問Chrome內部調用的open函數的調用參數(args [0],args [1]等)的?,F在,讓我們對這個腳本稍做修改。如果我們輸出以純文本形式打開的文件的路徑,而不是存儲這些路徑的內存地址,那不是更好嗎? 幸運的是,我們可以直接訪問內存。為此,您可以參考Frida API和Memory對象。我們可以修改腳本,讓它將內存地址中的內容作為UTF8字符串輸出,這樣結果會更加一目了然?,F在修改腳本,具體為:?
onEnter: function (log, args, state) {
? ? log("open(" + "pathname=" + Memory.readUtf8String(args[0])+ ", flags=" + args[1] + ")");
},
(我們只是添加了Memory.readUtf8String函數)我們會得到如下所示輸出:?
michael@sixtyseven:~$ frida-trace -i open -U -f com.android.chrome
Instrumenting functions... ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
open: Loaded handler at "/home/michael/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop. ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ?/* TID 0x29bf */
? ?240 ms ?open(pathname=/dev/binder, flags=0x80002)
? ? ? ? ? ?/* TID 0x29d3 */
? ?259 ms ?open(pathname=/dev/ashmem, flags=0x2)
? ? ? ? ? ?/* TID 0x29d4 */
? ?269 ms ?open(pathname=/dev/ashmem, flags=0x2)
? ? ? ? ? ?/* TID 0x29bf */
? ?291 ms ?open(pathname=/sys/qemu_trace/process_name, flags=0x2)
? ?453 ms ?open(pathname=/dev/alarm, flags=0x0)
? ?456 ms ?open(pathname=/sys/qemu_trace/process_name, flags=0x2)
? ?562 ms ?open(pathname=/proc/self/cmdline, flags=0x80000)
? ?576 ms ?open(pathname=/data/dalvik-cache/arm/system@app@Chrome@Chrome.apk@classes.dex.flock, flags=0x42)
Frida打印出了路徑名。這很容易,對吧?
另一個要注意的是,你可以先啟動一個應用程序,然后讓Frida注入它的magic,或者傳遞-f選項給Frida,讓它創建進程。
現在,我們來考察Fridas的命令行接口frida-cli:?
frida -U -f com.android.chrome
這將啟動Frida和Chrome應用。但是,仍啟動Chrome的主進程。這是為了讓您可以在應用程序啟動主進程之前注入Frida代碼。不幸的是,在我實驗時,它總是導致應用程序2秒后自動終止。這不是我們想要的結果。您可以利用這2秒鐘時間輸入%resume,并讓應用程序啟動其主進程;或者,直接使用--no-pause選項啟動Frida,這樣就不會中斷應用程序了,并將生成的進程的任務留給Frida。
無論使用哪種方法,你都會得到一個shell(不會被殺死),這樣就可以使用它的Javascript API向Frida寫命令了。通過TAB可以查看可用的命令。此外,這個shell還支持命令自動完成功能。
它提供了非常詳盡的文檔說明。對于Android,請檢查JavaScript-API的Java部分(這里將討論一個“Java API”,雖然從技術上說應該是一個訪問Java對象的Javascript包裝器)。在下面,我們將重點介紹這個Java API,因為在跟Android應用程序打交道的時候,這是一種更加方便的方法。不同于掛鉤libc函數,實際上我們可以直接使用Java函數和對象。
作為使用Java API的第一步,不妨從顯示Frida的命令行界面運行的Android的版本開始:?
[USB::Android Emulator 5556::['com.android.chrome']]-> Java.androidVersion
"7.1.1"
或者列出加載的類(警告:這會輸出大量內容,下面我會對代碼進行相應的解釋):?
[USB::Android Emulator 5556::['com.android.chrome']]-> Java.perform(function(){Java.enumerateLoadedClasses({"onMatch":function(className){ console.log(className) },"onComplete":function(){}})})
org.apache.http.HttpEntityEnclosingRequest
org.apache.http.ProtocolVersion
org.apache.http.HttpResponse
org.apache.http.impl.cookie.DateParseException
org.apache.http.HeaderIterator
我們在這里輸入了一個比較長的命令,確切地說是一些嵌套的函數代碼。首先,請注意,我們輸入的代碼必須包裝在Java.perform(function(){...})中,這是Fridas的Java API的硬性要求。
下面是我們在Java.perform包裝器中插入的函數體:?
Java.enumerateLoadedClasses(
? {
? "onMatch": function(className){?
? ? ? ? console.log(className)?
? ? },
? "onComplete":function(){}
? }
)
上面的代碼非常簡單:我們使用Fridas API的Java.enumerateLoadedClasses枚舉所有加載的類,并使用console.log將匹配的類輸出到控制臺。這種回調對象在Frida中是一種非常常見的模式。你可以提供一個回調對象,形式如下所示?
{
? "onMatch":function(arg1, ...){ ... },
? "onComplete":function(){ ... },
}
當Frida找到符合要求的匹配項時,就會使用一個或多個參數來調用onMatch;當Frida完成匹配工作時,就會調用onComplete。
現在,讓我們進一步學習Frida的magic,并通過Frida覆蓋一個函數。此外,我們還將介紹如何從外部腳本加載代碼,而不是將代碼鍵入cli,因為這種方式更方便。首先,將下面的代碼保存到一個腳本文件中,例如chrome.js:?
Java.perform(function () {
? ? var Activity = Java.use("android.app.Activity");
? ? Activity.onResume.implementation = function () {
? ? ? ? console.log("[*] onResume() got called!");
? ? ? ? this.onResume();
? ? };
});
上面的代碼將會覆蓋android.app.Activity類的onResume函數。它會調用Java.use來接收這個類的包裝對象,并訪問其onResume函數的implementation屬性,以提供一個新的實現。在新的函數體中,它將通過this.onResume()調用原始的onResume實現,所以應用程序依然可以繼續正常運行。
打開您的模擬器和Chrome,然后通過-l選項來注入這個腳本:?
frida -U -l chrome.js com.android.chrome
一旦觸發了onResume——例如切換到另一個應用程序并返回到模擬器中的Chrome——您將收到下列輸出:?
[*] onResume() got called!
很好,不是嗎?我們實際上覆蓋了應用程序中的一個函數。這就給控制目標應用程序的行為提供了可能性。但是,實際上我們可以繼續發揮:還能夠利用Javaschoose查找堆中已經實例化的對象。
需要注意的是,當你的模擬速度較慢的時候,Frida經常會超時。為了防止這種情況,請將腳本封裝到函數setImmediate中,或將它們導出為rpc。RPC在Frida默認情況下不超時(感謝@oleavr給予的提示)。在修改腳本文件后,setImmediate將自動重新運行你的腳本,所以這是相當方便的。同時,它還在后臺運行您的腳本。這意味著你會立刻得到一個cli,即使Frida仍然在忙著處理你的腳本。請繼續等待,不要離開cli,直到Frida顯示腳本的輸出為止。然后,再次修改chrome.js:?
setImmediate(function() {
? ? console.log("[*] Starting script");
? ? Java.perform(function () {
? ? ? ? Java.choose("android.view.View", {?
? ? ? ? ? ? ?"onMatch":function(instance){
? ? ? ? ? ? ? ? ? console.log("[*] Instance found");
? ? ? ? ? ? ?},
? ? ? ? ? ? ?"onComplete":function() {
? ? ? ? ? ? ? ? ? console.log("[*] Finished heap search")
? ? ? ? ? ? ?}
? ? ? ? });
? ? });
});
運行frida -U -l chrome.js com.android.chrome,這時應該會產生以下輸出:?
[*] Starting script
[*] Instance found
[*] Instance found
[*] Instance found
[*] Instance found
[*] Finished heap search
我們在堆上找到了4個android.view.View對象的實例。讓我們看看能用這些搞點什么事情。首先,我們可以調用這些實例的對象方法。這里,我們只是為console.log輸出添加instance.toString()。由于我們使用了setImmediate,所以現在只需修改我們的腳本,然后Frida會自動重新加載它:?
setImmediate(function() {
? ? console.log("[*] Starting script");
? ? Java.perform(function () {
? ? ? ? Java.choose("android.view.View", {?
? ? ? ? ? ? ?"onMatch":function(instance){
? ? ? ? ? ? ? ? ? console.log("[*] Instance found: " + instance.toString());
? ? ? ? ? ? ?},
? ? ? ? ? ? ?"onComplete":function() {
? ? ? ? ? ? ? ? ? console.log("[*] Finished heap search")
? ? ? ? ? ? ?}
? ? ? ? });
? ? });
});
返回的結果為:?
[*] Starting script
[*] Instance found: android.view.View{7ccea78 G.ED..... ......ID 0,0-0,0 #7f0c01fc app:id/action_bar_black_background}
[*] Instance found: android.view.View{2809551 V.ED..... ........ 0,1731-0,1731 #7f0c01ff app:id/menu_anchor_stub}
[*] Instance found: android.view.View{be471b6 G.ED..... ......I. 0,0-0,0 #7f0c01f5 app:id/location_bar_verbose_status_separator}
[*] Instance found: android.view.View{3ae0eb7 V.ED..... ........ 0,0-1080,63 #102002f android:id/statusBarBackground}
[*] Finished heap search
Frida實際上為我們調用了android.view.View對象實例的toString方法??釘懒?#xff01;所以,在Frida的幫助下,我們可以讀取進程內存、修改函數、查找實際的對象實例,并且所有這些只需寥寥幾行代碼就可以搞定。
現在,我們已經對Frida有了一個基本的了解,如果想要進一步深入了解它的話,可以自學其文檔和API。為了使得這篇文章更加全面,本文還將介紹兩個主題,即Frida的綁定和r2frida。但是在此之前,需要首先指出一些注意事項。
注意事項
當使用Frida時,經常會出現一些不穩定的情形。首先,將外部代碼注入另一個進程容易導致崩潰,畢竟應用程序是以其非預期的方式被觸發,來執行某些額外的功能的。第二,Frida本身貌似仍然處于實驗階段。它的確非常有用,但是許多時候我們必須嘗試各種方式才能獲得所需的結果。例如,當我嘗試從命令行加載腳本然后生成一個命令的進程時,Frida總是崩潰。所以,我不得不先生成進程,然后讓Frida注入腳本。這就是為什么我展示Frida的使用和防止超時的各種方法的原因。當然,許多時候您要根據自己的具體情況來找出最有效的方法。?
Python綁定
若想利用Frida進一步提升自己工作的自動化程度的話,你應該學習應用性更高的Python、C或NodeJS綁定,當然,前提是你已經熟悉了Frida的工作原理。例如,要從Python注入chrome.js腳本的話,可以使用Frida的Python綁定。首先,創建一個chrome.py腳本:?
#!/usr/bin/python import?frida #?put?your?javascript-code?here jscode=?""" console.log("[*]?Starting?script"); Java.perform(function()?{ ???var?Activity?=?Java.use("android.app.Activity"); ????Activity.onResume.implementation?=?function?()?{ ????????console.log("[*]?onResume()?got?called!"); ????????this.onResume(); ????}; }); """ #?startup?frida?and?attach?to?com.android.chrome?process?on?a?usb?device session?=?frida.get_usb_device().attach("com.android.chrome") #?create?a?script?for?frida?of?jsccode script?=?process.create_script(jscode) #?and?load?the?script
script.load()
更多的例子,請參考Frida的文檔。
Frida和Radare2:r2frida
如果我們還可以使用類似Radare2之類的反匯編框架來檢查應用程序的內存的話,那不是更好嗎?別急,我們有r2frida。您可以使用r2frida將Radare2連接到Frida,然后對進程的內存進行靜態分析和反匯編處理。不過,我們這里不會對r2frida進行詳細的介紹,因為我們假設您已經了解了Radare2的相關知識(如果您對它還比較陌生的話,建議您抽時間學習一下,我認為這是非常值得的)。無論如何,您都沒有必要過于擔心,因為這個軟件的用法非常容易上手,看看下面的例子您就知道此言不虛。
您可以使用Radare2的數據包管理程序來安裝r2frida(假設您已經安裝了Radare2):?
r2pm install r2frida
回到我們的frida-trace示例,刪除或重命名我們修改的腳本,讓frida-trace再次生成默認的腳本,并重新查看日志:?
michael@sixtyseven:~$?frida-trace?-i?open?-U?-f?com.android.chrome Instrumenting?functions...?????????????????????????????????????????????? open:?Loaded?handler?at?"/home/michael/__handlers__/libc.so/open.js" Started?tracing?1?function.?Press?Ctrl+C?to?stop.??????????????????????? ???????????/*?TID?0x2740?*/ ???282?ms??open(pathname=0xa843ffc9,?flags=0x80002) ???????????/*?TID?0x2755?*/ ???[...]
使用r2frida的話,您可以輕松地檢查所顯示的內存地址的內容并讀取路徑名(在本例中為/ dev / binder):?
root@sixtyseven:~# r2 frida://emulator-5556/com.android.chrome
?-- Enhance your graphs by increasing the size of the block and graph.depth eval variable.
[0x00000000]> s 0xa843ffc9
[0xa843ffc9]> px
- offset - ? 0 1 ?2 3 ?4 5 ?6 7 ?8 9 ?A B ?C D ?E F ?0123456789ABCDEF
0xa843ffc9 ?2f64 6576 2f62 696e 6465 7200 4269 6e64 ?/dev/binder.Bind
0xa843ffd9 ?6572 2069 6f63 746c 2074 6f20 6f62 7461 ?er ioctl to obta
0xa843ffe9 ?696e 2076 6572 7369 6f6e 2066 6169 6c65 ?in version faile
0xa843fff9 ?643a 2025 7300 4269 6e64 6572 2064 7269 ?d: %s.Binder dri
[...]
訪問進程以及讓r2frida執行注入操作的語法如下所示:?
r2 frida://DEVICE-ID/PROCESS
下面展示以=!為前綴的情況下,有哪些可用的r2frida命令,其中,您可以快速搜索內存區域中特定的內容或對任意內存地址執行寫入操作:?
[0x00000000]> =!?
r2frida commands available via =!
? ? ? ? ? ? ? ? ? ? ? ? ? ?Show this help
?V ? ? ? ? ? ? ? ? ? ? ? ? Show target Frida version
/[x][j] <string|hexpairs> ?Search hex/string pattern in memory ranges (see search.in=?)
/w[j] string ? ? ? ? ? ? ? Search wide string
[...]
小結
在這篇文章中,我們重點介紹Frida在Android應用方面的應用。在本教程的第二篇中,我們將介紹如何通過Frida輕松搞定crackme。
*?原文出處:https://www.codemetrix.net/hacking-android-apps-with-frida-1/,轉載自安全客
總結
以上是生活随笔為你收集整理的利用FRIDA攻击Android应用程序(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Android Native层中创建J
- 下一篇: 利用FRIDA攻击Android应用程序