什么是動態二進制插樁?
動態二進制插樁(DBI)意味著將外部代碼注入到現有的(正在運行的)二進制文件中,使它們能夠做一些以前沒有做過的事情。這個過程不是利用了漏洞,因為代碼注入并不是通過你之前必須搞清楚的一些漏洞所導致的。它也不是調試,因為你沒有將調試器附加到二進制文件上,盡管你可以做類似調試的一些事情。你能用DBI做些什么呢?這是一個很酷的東西:)
訪問進程的內存
在應用程序運行時覆蓋一些功能
從導入的類中調用函數
在堆上查找對象實例并使用這些對象實例
Hook,跟蹤和攔截函數等等
當然,你也可以使用調試器完成所有這些操作,不過使用調試器會帶來各種麻煩。例如在Android中,你必須反匯編并重新編譯應用程序以使其可用于調試。一些應用程序會檢測并嘗試阻止調試器的調試過程,你需要擺脫這些反調試邏輯。可能你會成功,但是整個過程非常麻煩。使用Frida的DBI可以讓你使用黑盒進程快速啟動Android應用程序。
FRIDA
Frida可以“將你自己的JavaScript代碼片段或代碼庫注入到Windows,MACOS,Linux, iOS,Android和QNX 的本地應用中”。這是第一款基于谷歌的V8 JavaScript引擎運行的應用程序,在Frida的第九個版本中使用的是Duktape,但它仍然允許你切換回V8引擎,如果你需要這么做的話。Frida有很多可以與二進制文件進行交互(包括在無root權限的設備上對應用程序進行插樁的可能性)的操作模式,不過我們在本文這里會使用最常見的用戶操作,并且現在我們不需要關心其內部實現。
在開始本文的破解教程之前,你需要做以下幾個事情:
1.?Frida(我在本教程中使用的9.1.16版本)
2. frida-server可以二進制發布頁面下載到( 在本文發表時為frida-server-9.1.16-android-arm.xz,frida-serve的版本應該與Frida版本一致。)
3. Android模擬器或已ROOT的安卓設備。Frida已經開發了Android 4.4 ARM對應的版本,但它應該適用于更高的版本。我在本教程中成功使用了Android 7.1.1 ARM。對于第二部分的破解,我們需要用到比Android 4.4版本更高的一些東西。
我使用linux系統作為宿主機的操作系統,如果你使用的是Windows或Mac,那你可能需要調整一些命令。
如果你想在解決OWASP Unbreakable Crackme Level 1中的問題,你可以在本系列教程的第二部分中找到破解方法,同時,你也應該下載下面的幾個程序:
1.?OWASP Unbreakable Crackme Level 1(APK)
2.?BytecodeViewer
3.?dex2jar
Frida提供各種API和開始破解的方法。你可以使用命令行界面或frida-trace跟蹤低級功能的工具(如libc.so中的“open”函數的調用)進行快速運行。你可以使用C,NodeJS或Python綁定更復雜的東西。在Frida內部,會更多的使用Javascript工作,你也將使用這種語言完成大部分插樁工作。所以如果你和我一樣,不喜歡使用Javascript(除了XSS功能之外),那Frida或許是讓你熟悉JavaScript的另一個原因。
如果沒有,請安裝Frida(請參閱README以獲得其他的安裝方式):
pip?install?frida
npm?install?frida
啟動你的模擬器或連接你的設備,并確保adb正在運行并列出了你的設備:
michael@sixtyseven:~$?adb?devices
List?of?devices?attached
emulator-5556???device
然后安裝frida-server。解壓文檔并將二進制文件push到設備上:
adb push /home/michael/Downloads/frida-server-9.1.16-android-arm /data/local/tmp/frida-server
使用adb打開設備上的一個shell,切換到root用戶并啟動frida-server:
adb?shell
su
cd?/data/local/tmp
chmod?755?frida-server
./frida-server
(注1:如果frida-server沒有啟動,請確保你使用的是root用戶,并且該文件已經正確push到設備中。我之前就遇到過文件傳輸損壞導致的各種奇怪的錯誤。注2:如果要啟動frida-server作為后臺進程,可以使用這個命令./frida-server &)
在另一個常規的OS shell終端中,檢查Frida是否正在運行,并列出在Android上的進程:
frida-ps?-U
參數-U代表USB,并讓Frida檢查USB設備,但它也可以與模擬器一起工作。你應該得到一個這樣的進程列表:
michael@sixtyseven:~$?frida-ps?-UPID??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)和運行的進程(名稱)。使用Frida你現在可以Hook這些進程,并且可以篡改進程。
例如,你可以跟蹤Chrome完成的特定通話(如果Chrome未運行,請先啟動模擬器里的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會將這些JavaScript文件注入到進程中并跟蹤特定的調用。看看生成的open.js腳本文件(__handlers__/libc.so/open.js)。它Hook了libc.so中的“open”函數并輸出一些參數。這在Frida中很容易就可以實現:
[...]
onEnter:?function?(log,?args,?state)?{log("open("?+?"pathname="?+?args[0]?+?",?flags="?+?args[1]?+?")");
},
[...]
請注意,Frida可以訪問到Chrome內部調用的open函數(args [0],args [1]等等)的一些調用參數。讓我們修改一下這個腳本。如果我們明文輸出打開的文件的路徑,而不是存儲這些路徑的內存地址,這樣不是更好嗎?幸運的是,我們可以直接用Frida訪問內存。看看Frida API和Memory對象。我們可以修改腳本,將內存地址所指向的內容輸出為UTF8字符串,可以獲得更清楚明了的輸出。修改腳本后,如下所示:
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進行注入,或者使用-f選項,讓Frida自動生成進程。
現在我們來看看Frida的命令行界面frida-cli:
frida?-U?-f?com.android.chrome
這將啟動Frida和Chrome應用。但是,它還沒有啟動Chrome的主進程。這意味著可以在應用程序的主進程啟動之前注入Frida代碼。不幸的是,在我自己的嘗試中總是遇到一個問題——應用程序在啟動2秒鐘后自動卡死。這不是我們想要的。你可以使用這兩秒來鍵入%resume,如cli輸出的建議,讓應用程序啟動其主進程。或者你直接啟動Frida,使用–no-pause參數選擇不中斷應用程序的啟動,仍然將進程的產生留給Frida去做。
在這兩種情況下,你都可以獲得一個shell(不會被殺死),你現在可以使用其Javascript API向Frida寫入命令。按TAB鍵可以查看可用的命令。shell還支持命令自動完成。
你想做的大多數事情在文檔中都是有據可查的。對于Android,特別要檢查Javascript-API?的Java部分(我將在此討論一個“Java API”,盡管在技術上說,是一個用于訪問Java對象的Javascript封裝)。我們將重點關注Java API,因為這是使用Android應用程序更為方便的方式。我們可以直接使用Java函數和對象,而不是Hook libc中的函數。(注意:如果你對Frida的其他的Java API能做什么很感興趣,那么你可以使用frida-trace Hook Android里面的更低級的C函數,并且查看文檔里的函數部分,我不會按照文檔所述那樣在本文中進行描述。)
要開始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(){}}
)
很簡單:我們使用Java.enumerateLoadedClassesFridas API 枚舉所有加載的類,并將每個匹配到的類使用console.log輸出到控制臺。這種回調對象的模式你會經常在Frida找到。你需要提供一個回調對象的模板。
{"onMatch":function(arg1,?...){?...?},"onComplete":function(){?...?},
}
一旦Frida匹配到你的請求,就會使用一個或多個參數調用onMatch并且當Frida完成迭代可能的匹配時,將會調用onComplete。
現在我們深入了解了Frida的魔法,并且使用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接收此類的包裝對象并訪問implementation的onResume函數的屬性以提供新的實現。在新的函數主體內,它this.onResume()這樣的方式調用原來的onResume的實現,所以應用程序可以繼續正常運行。
打開你的模擬器,打開Chrome并用-l選項注入腳本:
frida?-U?-l?chrome.js?com.android.chrome
一旦你觸發了onResume的執行—— 例如通過在模擬器中切換到另一個應用程序并返回到Chrome后你將得到下面的輸出:
[*]?onResume()?got?called!
很好用,不是嗎?我們實際上重寫了應用程序的一個函數。這給了我們控制目標應用程序行為的很多可能性。但是我們可以做更多的事情:我們也可以在堆上查找實例化的對象Java.choose。
在我們繼續之前,我有一個警告:當你的模擬器變得有點慢的時候,Frida有時候會有時間超時的提示。為了防止這種情況,請將腳本包裝在setImmediate函數中或將其導出為rpc。RPC在Frida中默認情況下不會超時( 感謝@oleavr的這些提示)。在你修改腳本文件后,setImmediate會自動重新運行Frida腳本,這樣很方便。它也會在后臺運行你的腳本。這意味著你可以立即得到一個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對象的實例。讓我們看看我們可以做些什么。也許我們可以調用這些對象實例的方法。我們這次只添加instance.toString()到我們的console.log進行輸出(由于我們使用了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方法。很酷哦。因此,通過Frida的幫助,我們可以讀取進程內存,修改函數,查找實際的對象實例,并使用少量的幾行代碼。
現在你應該對Frida有了基本的了解了,并能夠自己深入了解其文檔和API。為了完成這篇文章,我想再談兩個主題,Frida的綁定和r2frida。但首先會有一點警告。
警告
當你嘗試使用Frida時,你會注意到有一些不穩定。首先,將外部代碼注入到另一個進程中容易導致崩潰,因為應用程序被以意想不到的方式觸發運行。第二,Frida本身仍然讓人感覺為一個實驗品。有時候它的確在正常工作,但你經常需要嘗試多種方式來獲得你所需的結果。例如,當我嘗試加載一個腳本并在命令行中執行一個命令生成一個進程時,Frida就會一直崩潰。相反,我必須首先啟動該進程,然后讓Frida去注入腳本。這就是為什么我向你展示了使用Frida的各種方法,并防止超時失敗的提示出現。你可能需要弄清楚在你的實際情況下哪個才是最有效的方法。
Python綁定
一旦了解了Frida的工作原理之后,如果你想要使用Frida更自動化的完成你的工作,你應該查看易于使用的Python,C或NodeJS 綁定。例如從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?=?session.create_script(jscode)
#?and?load?the?script
script.load()
如果要結束Frida會話并銷毀本次會話的腳本,可以調用session.detach()。
有關更多的例子,還是一如以往的請查看Frida的文檔。
Frida和Radare2:r2frida
如果我們也可以使用像Radare2這樣的反匯編框架來檢查我們的應用程序的內存,那不是很好嗎?在這里是r2frida。你可以使用r2frida將Radare2連接到Frida,并進行進程內存的靜態分析和反匯編。我不會在這里詳細介紹r2frida,因為它的使用本身就預先假定了使用者具備Radare2的知識(如果你沒有一定的相關知識,那非常值得一看。),但是我仍然想給你一個使用簡便的方式。
你可以使用Radare2的包管理器來安裝r2frida(假設你已經安裝了Radare2):
r2pm?install?r2frida
回到我們的frida跟蹤示例,刪除或重命名我們修改過的腳本,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
[...]
更多
如果這讓你感到好奇,可以看看下面的內容:
?1.?Frida的項目頁面
?2. @oleavr在r2con的演講視頻 和David Weinstein對Frida簡介的演講。
?3. Frida的Twitter帳號@fridadotre
?4. Frida的Telegram頻道
?5. AppMon——基于Frida的應用程序監視和注入的GUI工具(由@dpnishant提供)
在本教程的第二部分,我們將使用Frida來輕松解決一些小問題。