當前位置:
首頁 > 新聞 > Android APP破解利器Frida之反調試對抗

Android APP破解利器Frida之反調試對抗

在我發表了關於Frida的第二個博文後不久,@muellerberndt決定發布另外一個新的OWASP Android 破解APP,我很想知道我是否可以再次使用Frida解決這個CrackMe。如果你想跟隨我一起操作,你需要做以下準備:


· OWASP Level2的CrackMe APK


· Android SDK和模擬器(我使用的是Android 7.1 x64映像)


· Frida安裝(以及frida-server二進位文件)


· ByteCode viewer

· radare2(或你喜歡使用的其他一些反彙編程序)


· apktool


如果你需要知道如何安裝Frida,請查看Frida的相關 文檔。對於Frida的使用,你還可以查看本教程系列的第I部分。我想你在繼續本文的操作之前已經擁有所有需要的東西,並且基本上熟悉了Frida的使用。另外,確保Frida可以連接到你的設備或模擬器(例如使用frida -ps -U 命令)。


警告:本教程不僅僅是一個快速破解Crackme的練習。相反,我將向你展示各種方法來克服所遇到的具體問題。如果你只是尋找一個快速的破解方案,請在本教程末尾查看相關的Frida腳本。


注意:如果你遇到了下面這個錯誤:


Error: access violation accessing 0xebad8082


或者使用Frida時會出現了類似的錯誤,再模擬器中擦除用戶數據,重新啟動並重新安裝該apk可能有助於解決此類錯誤問題。


做好多次嘗試的準備。該應用程序可能會崩潰,模擬器也可能會重新啟動,一切也可能會搞砸,但是,它依舊可以正常工作。


先把APP跑起來


在開始時,我們做的事情和之前破解UnCrackable1是一樣的,先運行應用程序:接下來,當你在模擬器中運行它時,它會檢測到它是在有root許可權的設備上運行的。

Android APP破解利器Frida之反調試對抗



我們可能會嘗試像之前破解UnCrackable 1一樣Hook OnClickListener。但首先我們來看看我們是否可以連接到Frida來進行篡改:


michael@sixtyseven:~/Development$ frida -U sg.vantagepoint.uncrackable2 ____ / _ | Frida 9.1.27 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/Failed to attach: ambiguous name; it matches: sg.vantagepoint.uncrackable2 (pid: 5184), sg.vantagepoint.uncrackable2 (pid: 5201)


這是什麼東西?有兩個名稱相同的進程?我們可以使用frida -ps -U命令來驗證一下:


奇怪。我們試著將Frida注入到父進程:


michael@sixtyseven:~/Development$ frida -U 5184 ____ / _ | Frida 9.1.27 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/Failed to attach: unable to access process with pid 5184 due to system restrictions; try `sudo sysctl kernel.yama.ptrace_scope=0`, or run Frida as root


看起來它並不起作用,因為當我們使用root許可權運行Frida時,我們得到了相同的結果,所以提出的解決方案並沒有什麼幫助。這裡到底發生了什麼?我們來看看應用程序吧。解壓縮apk並使用ByteCodeViewer 查看classes.dex並進行反編譯(例如CFR-Decompiler):

Android APP破解利器Frida之反調試對抗


Android APP破解利器Frida之反調試對抗


Android APP破解利器Frida之反調試對抗



我們注意到一個調用static的靜態調用System.load載入了foo庫(參見[1])。該應用程序還在其onCreate方法的第一行調用了this.init()(參見[3]),該方法被聲明為一種native方法(參見[2]),因此它可能是foo的一部分。


現在讓我們來看看foo庫。在radare2中,打開這個庫文件(你可以在lib文件夾中找到各種架構的多個庫文件,我在這裡使用的是lib/x86_64),分析並列出其導出函數:

Android APP破解利器Frida之反調試對抗



我們注意到,該庫出口2個有趣的導出函數:


Java_sg_vantagepoint_uncrackable2_MainActivity_init和Java_sg_vantagepoint_uncrackable2_CodeCheck_bar

(對這些方法的具體命名可以查看Java介面NATIV JNI)。


我們來看看Java_sg_vantagepoint_uncrackable2_MainActivity_init:


這是一個相當短的函數:

Android APP破解利器Frida之反調試對抗



它調用了另一個函數sub.fork_820,在這個函數中還有更多的函數調用:

Android APP破解利器Frida之反調試對抗


Android APP破解利器Frida之反調試對抗


Android APP破解利器Frida之反調試對抗



我們看到有調用fork,pthread_create,getppid,ptrace和waitpid。不需要花費太多的時間來進行反彙編,我們就可以猜測出,主進程使用ptrace作為調試器來fork了一個附加到它的子進程。這是一個基本的常見的反調試技術,你可以在這裡閱讀到更多信息。


由於Frida 使用 ptrace用於初始的注入操作,所以這就解釋了為什麼我們沒有成果連接到父進程:附加調試進程被阻止,因為已經有其他的進程作為調試器進行了連接。


繞過反調試解決方案1:Frida


Frida來解決問題的目的不是將Frida注入正在運行的進程,而是我們可以讓它為我們生成進程。使用-f選項,我們告訴Frida注入Zygote並開始啟動應用程序。關閉設備上的應用程序,看看當我們啟動Frida時會發生什麼:


我們得到如下輸出:


michael@sixtyseven:~/Development/UnCrackable2/lib/x86_64$ frida -U -f sg.vantagepoint.uncrackable2 --no-pause ____ / _ | Frida 9.1.27 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/Spawned `sg.vantagepoint.uncrackable2`. Resuming main thread! [USB::Android Emulator 5554::[ sg.vantagepoint.uncrackable2 ]]->


好吧!Frida注入到了Zygote,產生了我們的進程並等待輸入。(我承認,告訴你在啟動Frida時添加-f選項是一個很長的介紹,不過你已被警告過...)


我們現在準備繼續破解, 但是在我們繼續之前,我們將要檢查另一個解決方案來克服這個破解的調試保護。

繞過反調試解決方案2:補丁


除了讓Frida生成進程之外,我們還可以通過對應用打補丁來解決我們遇到的問題。這意味著我們需要反彙編應用程序,對修改的apk進行重建和簽名。但是,在這種情況下,這將會給我們麻煩帶來麻煩。不過我還是會告訴你如何解決這個麻煩,我們稍後會注意到這個問題。


我們可以通過apktool來實現補丁:


michael@sixtyseven:~/Disassembly$ /opt/apktool/apktool.sh -r d UnCrackable-Level2.apkI: Using Apktool 2.2.0 on UnCrackable-Level2.apkI: Copying raw resources...I: Baksmaling classes.dex...I: Copying assets and libs...I: Copying unknown files...I: Copying original files...


(我跳過了資源的提取, 因為-r參數在重新編譯apk時引起了新的問題,我們在這裡不需要APP的資源。)


看一下smali/sg/vantagepoint/uncrackable2/MainActivity.smali 文件里的smali代碼。你可以在82行處找到調用init的代碼,並且可以將其注釋掉:


重新編譯apk(忽略致命錯誤...):


michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ /opt/apktool/apktool.sh bI: Using Apktool 2.2.0I: Checking whether sources has changed...I: Smaling smali folder into classes.dex...[Fatal Error] AndroidManifest.xml:1:1: Content ist nicht zul?ssig in Prolog.I: Checking whether resources has changed...I: Copying raw resources...I: Copying libs... (/lib)I: Building apk file...I: Copying unknown files/dir...


對齊:


michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ zipalign -v 4 dist/UnCrackable-Level2.apk UnCrackable2.recompiled.aligned.apkVerifying alignment of UnCrackable2.recompiled.aligned.apk (4)... 49 AndroidManifest.xml (OK - compressed) 914 classes.dex (OK - compressed) 269899 lib/arm64-v8a/libfoo.so (OK - compressed) 273297 lib/armeabi-v7a/libfoo.so (OK - compressed) 279346 lib/armeabi/libfoo.so (OK - compressed)

簽名(注意:你需要有一個密鑰和密鑰庫):


michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ jarsigner -verbose -keystore ~/.android/debug.keystore UnCrackable2.recompiled.aligned.apk signkeyEnter Passphrase for keystore: adding: META-INF/MANIFEST.MF adding: META-INF/SIGNKEY.SF adding: META-INF/SIGNKEY.RSA signing: AndroidManifest.xml signing: classes.dex signing: lib/arm64-v8a/libfoo.so signing: lib/armeabi-v7a/libfoo.so signing: lib/armeabi/libfoo.so signing: lib/mips/libfoo.so[...]


你可以在「 OWASP移動安全測試指南」中找到更廣泛的描述。卸載掉原始的apk並安裝打過補丁的apk:


再次啟動該應用程序。運行frida-ps後我們發現現在只有一個進程:


Frida連接也沒有問題:


michael@sixtyseven:~/Disassembly/UnCrackable-Level2$ frida -U sg.vantagepoint.uncrackable2 ____ / _ | Frida 9.1.27 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/[USB::Android Emulator 5554::sg.vantagepoint.uncrackable2]->


比起給Frida增加 -r選項更為繁瑣一點,但也更普遍的適用。


如前所述,當我們使用補丁版本後(我會告訴你如何克服這個,所以不要把它丟棄),我們就不能輕易地提取這個秘密字元串了。但是現在我們繼續使用原來的apk。確保你按照正確的方式安裝了原始的apk。


繼續解惑


在我們找到了一些擺脫反調試的可能性之後,我們來看看我們接下來該如何進行。一旦我們按下OK按鈕,應用程序就會在模擬器的運行時中進行root許可權的檢測並退出。我們已經知道UnCrackable1的一些行為。另外,我們可以對這種檢測行為打補丁,刪除System.exit的調用代碼,但是我們試圖用Frida來解決這個問題。再看看反編譯的代碼,我們看到沒有OnClickListener類,只是一個匿名的內部類。由於onClickListener的實現中調用了System.exit所以我們簡單地Hook住該函數並無卵用。

這是Frida腳本:


再次關閉任何正在運行的UnCrackable2實例,並再次在Frida的幫助下啟動它:


等到App啟動後,Frida 在控制台中顯示了Hooking calls...的消息。然後按「確定」。你應該會得到這樣的輸出信息:


michael@sixtyseven:~/Development/frida$ frida -U -f sg.vantagepoint.uncrackable2 --no-pause -l uncrackable2.js ____ / _ | Frida 9.1.27 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/Spawned `sg.vantagepoint.uncrackable2`. Resuming main thread! [USB::Android Emulator 5554::[ sg.vantagepoint.uncrackable2 ]]-> [*] Hooking calls to System.exit[*] System.exit called


該應用程序不再退出。我們可以輸入一個秘密字元串試試:

Android APP破解利器Frida之反調試對抗



但是我們應該在這裡輸入什麼呢?看看MainActivity的Android代碼,檢查正確的輸入邏輯如下:


this.m = new CodeCheck();[...]//in method: public void verifyif (this.m.a(string)) { alertDialog.setTitle((CharSequence)"Success!"); alertDialog.setMessage((CharSequence)"This is the correct secret.");}


這是CodeCheck類代碼:

package sg.vantagepoint.uncrackable2;public class CodeCheck { private native boolean bar(byte[] var1); public boolean a(String string) { return this.bar(string.getBytes()); //Call to a native function }}


我們注意到,我們的文本欄位的輸入也就是我們的「秘密字元串」 被傳遞給一個native函數bar。我們可以在libfoo.so庫中找到這個函數。搜索函數地址(如我們之前使用的init函數),並用radare2進行反彙編:

Android APP破解利器Frida之反調試對抗



看看反彙編代碼,我們注意到有一些字元串比較的邏輯,另外我們還注意到一個有趣的明文字元串Thanks for all t.測試這個字元串作為我們的crackme的解決方案後,我們發現它並不起作用。我們必須繼續向前。


看看0x000010d8這個地址處的反彙編代碼:


0x000010d8 83f817 cmp eax, 0x17 0x000010db 7519 jne 0x10f6 ;[1]


這裡有一個eax寄存器與0x17的比較,其十進位格式為23。如果這個比較不成功,strncmp在這裡就不會調用。我們也注意到了0x17已作為參數傳遞給了在


記住,根據linux 64位的調用約定,函數參數在寄存器中傳遞至少1到6個參數。尤其是前3個參數要按照這個順序在RDI,RSI和RDX中傳遞(參見[PDF],第20頁)。Strncmp函數聲明如下:


int strncmp ( const char * str1, const char * str2, size_t num );


所以我們的strncmp函數將比較0x17 = 23個字元。我們可以推斷,我們的秘密字元串的長度應該是23個字元。


讓我們最後嘗試Hook住strncmp函數,並簡單地列印出它的參數。我們可以期望這裡給我們直接輸出解密的輸入字元串。我們必須


1. 查找strncmp函數在libfoo.so中的內存地址


2. 使用 Interceptor.attach Hook libfoo.so的strncmp函數和轉儲參數


如果你這樣做了,你會發現有很多地方調用了strncmp,我們將進一步限制輸出。這是Frida代碼片段:


var strncmp = undefined;imports = Module.enumerateImportsSync("libfoo.so");for(i = 0; i < imports.length; i++) }Interceptor.attach(strncmp, { onEnter: function (args) { if(args[2].toInt32() == 23 && Memory.readUtf8String(args[0],23) == "01234567890123456789012") { console.log("[*] Secret string at " + args[1] + ": " + Memory.readUtf8String(args[1],23)); } }});


對這個腳本的一些說明:


1. 該腳本調用Module.enumerateImportsSync從libfoo.so(查閱文檔)中獲取有關導入信息的對象數組。我們遍歷這個數組,直到找到strncmp並檢索其地址。然後我們將攔截器附加到它上面。


2. Java中的字元串不會已空字元終止。當我們使用Frida的Memory.readUtf8String方法訪問strncmp字元串指針的內存位置,並且不提供長度時,Frida會期望一個終止符,否則會輸出一些內存垃圾。因為它不知道字元串在哪裡結束。如果我們指定要讀取的字元數量作為第二個參數,我們就可以解決掉這個警告。


3. 如果我們沒有限制我們轉儲strncmp參數的條件,我們會得到很多輸出。所以我們只輸出strncmp中當第三個參數的參數size_t為23且第一參數的字元串指針指向我們在輸入框輸入的01234567890123456789012這個字元串作為過濾條件


我是如何知道args[0]也就是我們的輸入和args[1]也就是秘密字元串的?我其實並不知道,我只是在測試它,並將大量的輸出轉儲到屏幕上,以找到我的輸入。如果你不想跳過,你可以在上述腳本中移除if語句,並使用Frida的hexdump輸出:


buf = Memory.readByteArray(args[0],32);console.log(hexdump(buf, { offset: 0, length: 32, header: true, ansi: true}));buf = Memory.readByteArray(args[1],32);console.log(hexdump(buf, { offset: 0, length: 32, header: true, ansi: true}));


每次調用strncmp時都會輸出很多hexdumps,所以要提醒你一下。


以下是完整版本的腳本,可以更好地輸出參數:


setImmediate(function() { Java.perform(function() { console.log("[*] Hooking calls to System.exit"); exitClass = Java.use("java.lang.System"); exitClass.exit.implementation = function() { console.log("[*] System.exit called"); } var strncmp = undefined; imports = Module.enumerateImportsSync("libfoo.so"); for(i = 0; i < imports.length; i++) { if(imports[i].name == "strncmp") { strncmp = imports[i].address; break; } } Interceptor.attach(strncmp, { onEnter: function (args) { if(args[2].toInt32() == 23 && Memory.readUtf8String(args[0],23) == "01234567890123456789012") { console.log("[*] Secret string at " + args[1] + ": " + Memory.readUtf8String(args[1],23)); } }, }); console.log("[*] Intercepting strncmp"); });});


現在,啟動Frida並載入腳本:


輸入字元串並按驗證:

Android APP破解利器Frida之反調試對抗



在控制台,你會得到:


michael@sixtyseven:~/Development/frida$ frida -U -f sg.vantagepoint.uncrackable2 --no-pause -l uncrackable2.js ____ / _ | Frida 9.1.27 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/Spawned `sg.vantagepoint.uncrackable2`. Resuming main thread! [USB::Android Emulator 5554::[ sg.vantagepoint.uncrackable2 ]]-> [*] Hooking calls to System.exit[*] Intercepting strncmp[*] System.exit called[*] Secret string at 0x7fffa628f010: Thanks for all the fish


沒錯,輸出內容很好很簡單:完整的秘密字元串。將其輸入到輸入框中並享受成功吧

Android APP破解利器Frida之反調試對抗



修復補丁的解決方案


最後,是有一些關於補丁修復的說明,以及為什麼我們在使用打過補丁的apk時不會得到秘密字元串。Libfoo中的init函數包含一些初始化邏輯,阻止應用程序根據我們的輸入檢查或解碼秘密字元串。


如果我們再次看看init函數的反彙編,我們會看到有趣的一行代碼:


在bar函數的後面會檢查相同的變數libfoo,如果未設置,則代碼會跳過strncmp:


所以在後面它可能是一個布爾變數,如果init函數運行,它將被設置。如果我們想要在我們的apk的修補版本中調用strncmp那麼我們必須設置此變數,或者至少阻止它跳過實際的strncmp調用。


我們現在可以再次進行修補,反編譯apk,覆蓋jmp指令,並重新編譯一次。的確有些餓笨重。 由於這是Frida教程,我們將使用Frida來動態更改內存。


因此,我們需要:


1. 獲取載入foo庫的基址


2. 找到相對於庫基地址的變數(通過反彙編中,我們知道從基地址的偏移量是0x400C位元組)


3. 將變數設置為1


所以,在Frida中的相關代碼如下:


//Get base address of libraryvar libfoo = Module.findBaseAddress("libfoo.so");//Calculate address of variablevar initialized = libfoo.add(ptr("0x400C"));//Write 1 to the variableMemory.writeInt(initialized,1);


以下是該修補程序版本的完整腳本:


setImmediate(function() { Java.perform(function() { console.log("[*] Hooking calls to System.exit"); exitClass = Java.use("java.lang.System"); exitClass.exit.implementation = function() { console.log("[*] System.exit called"); } var strncmp = undefined; imports = Module.enumerateImportsSync("libfoo.so"); for(i = 0; i < imports.length; i++) { if(imports[i].name == "strncmp") { strncmp = imports[i].address; break; } } //Get base address of library var libfoo = Module.findBaseAddress("libfoo.so"); //Calculate address of variable var initialized = libfoo.add(ptr("0x400C")); //Write 1 to the variable Memory.writeInt(initialized,1); Interceptor.attach(strncmp, { onEnter: function (args) { if(args[2].toInt32() == 23 && Memory.readUtf8String(args[0],23) == "01234567890123456789012") { console.log("[*] Secret string at " + args[1] + ": " + Memory.readUtf8String(args[1],23)); } }, }); console.log("[*] Intercepting strncmp"); });});


現在運行應用程序,通過frida載入腳本,再次輸入01234567890123456789012。按驗證按鈕。應用程序會調用strncmp並且會記錄秘密字元串:


root@sixtyseven:/home/michael/Development/frida# frida -U sg.vantagepoint.uncrackable2 -l uncrackable2-final.js ____ / _ | Frida 9.1.27 - A world-class dynamic instrumentation framework | (_| | > _ | Commands: /_/ |_| help -> Displays the help system . . . . object? -> Display information about object . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/[USB::Android Emulator 5554::sg.vantagepoint.uncrackable2]-> [*] Hooking calls to System.exit[*] Intercepting strncmp[*] System.exit called[*] Secret string at 0x7fffd52c6570: Thanks for all the fish


希望你在使用Frida後能體驗到更多的樂趣。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

從404到默認頁面,通過.cshtml拿到webshel??l
http81殭屍網路預警:專門攻擊攝像頭,國內5萬台設備已淪陷
DNSPod客服審核不嚴,導致國內某技術論壇域名被劫持一小時

TAG:嘶吼RoarTalk |

您可能感興趣

Facebook 開源其調試平台 Sonar,支持 Android 與 iOS
使用Visual Studio Code編譯、調試Apollo項目
如何使用Ghostscript調試PostScript
A 站受黑客攻擊,近千萬條用戶數據外泄;Facebook 開源其調試平台 Sonar,支持 Android與iOS
使用 Visual Studio Code 搭建 C/C+開發和調試環境
Servlet 調試
Android遠程調試Web頁面
Undo 發布用於 Linux 調試的 Live Recorder 5.0
如何使用curl調試openstack的api
如何在 Linux 或者 UNIX 下調試 Bash Shell 腳本
Eclipse Debug 調試
PyTorch代碼調試利器:自動print每行代碼的Tensor信息
linux性能調試之iostat
喲,寫Bug呢?Facebook發布AI代碼調試工具SapFix
Chrome 調試技巧
挖掘IntelliJ IDEA的調試功能
使用pdb進行Python調試
strace幫助你調試PHP代碼
教你使用Vue.js的DevTools來調試你的vue項目
使用systemtap調試工具分析MySQL的性能