作者論壇帳號:Se8s0n
前言從OWASP CrackMe學Android逆向(一)感覺用題目來學習會比之前學得內容多很多, 然後這些題目有多種解法, 我也學到很多姿勢, 下面就來分析UnCrackable-Level2。這個題目涉及.so動態調試的知識。
UnCrackable-Level2同樣的, 先將UnCrackable-Level2.apk放到JEB中分析, 一進到MainActivity, 就發現了一個神奇的東西, 這個應用加載了libfoo.so文件。
能感覺出來這個應用和之前level1不同的是, check輸入的關鍵代碼應該是在libfoo.so中, 所以不能使用Xposed去處理這個CrackMe了, 但是root和debug的檢測還是Java層調用System.exit(0)退出。所以上次的cracker.js, 重載系統exit函數部分保留。
將UnCrackable-Level2.apk解壓之後在UnCrackable-Level2\lib\arm64-v8a目錄下找到ibfoo.so, 把它拖到IDA裡面分析一下。查看Export表導出的函數, 發現存在含有關鍵詞CodeCheck的函數。
按下F5查看其反編譯的代碼, 發現存在可疑的數據xmmword_EA0。
雙擊xmmword_EA0, 發現了一個傳入v7的是一個大數, 使用Hex-View查看其Ascii碼, 發現了一句不完整的話Thank for all t
CheckCode中還有v8參數, 雖然現在暫時不知道它有什麼用, 還是先將它最後處理的過程寫出來, 詳情可以看注釋, 最後我們知道v8的數據轉換為ascii碼之後為"eish", 暫時不知道這個的用處, 所以先放著。根據下面的result獲取, 我們也就知道了, 我們最終想要的字符串是存放在v6參數中的。
分析libfoo.so中的其他的函數, 發現應用限制了attach, 還是通過輪詢的方法監視應用是否被調試。也就是說, 如果想使用IDA動態調試這個應用的話, 還需要我們繞過這種反調試的機制。
事先說明,上面靜態分析的過程都是對lib\arm64-v8a文件夾下的libfoo.so文件進行分析的。在分析代碼的過程中,存在一些函數調用的過程讓我們看起來不是很舒服。實際上這些調用的函數都是JNI函數, 我們可以通過導入jni.h文件(File->Load File->Parse C header file...)——jni.h文件一般安裝了Android Studio都有, 我的存放在Android Studio\jre\include目錄下, 選中v4指針,選中後按一下」y」鍵,然後將類型聲明為JNIEnv*即可。
修改之後效果如下:
那什麼時候我們可以使用上面的方法還原函數名呢?在文章安卓動態調試七種武器之孔雀翎 – Ida Pro中有提及, 如果出現一個指針加上一個數字,比如v3+676。然後將這個地址作為一個方法指針進行方法調用,並且第一個參數就是指針自己,比如(v3+676)(v3…)。這實際上就是我們在JNI裡經常用到的JNIEnv方法。
靜態分析就分析到這裡了。現在, 我們需要開始動手了。
靜態分析代碼因為我手機arm版本為arm64-v8a(使用命令 adb shell getprop ro.product.cpu.abi可查看arm版本), 所以前面的分析都是基於lib\armeabi-v7a\libfoo.so。當我將lib\armeabi-v7a\libfoo.so拖到32位的IDA中分析代碼的時候找到含有CodeCheck的函數, 發現密鑰以明文字符串的形式出現了。
這時候我們輸入"Thanks for all the fish", 發現我們成功找到密鑰。
即便如此簡單, 我們還是需要學習通過動態調試的方法獲取密鑰。以防遇到加了密或者其他更難分析的情況,我們無從下手。
IDA動態調試代碼繞過反調試機制通過上面的分析(不包含靜態分析arm64-v7a目錄的libfoo.so文件的內容), 靜態分析到現在也沒找到完整的23個字母長度的密鑰, 我們要通過IDA調試的方法獲得密鑰,。嘗試動態調試.so文件的時候會發現應用會閃退, 所以我們需要來過反調試機制才能讓應用正常運行並且獲得密鑰。那我們怎麼做才能成功繞過反調試機制呢?
根據分析,我們要繞過的反調試機制分別處於Java層和Native層。
我們下面先修改應用的smali源碼, 不然我們設置完AndroidManifest.xml文件debuggable="true"之後, 還要面對下面的彈窗。
還記得我們最開始分析的Java層的代碼不, 現在我們來回憶一下。發現應用在root環境或可調試的環境下運行之後, 會調用MainActivity類中的a函數。雙擊該函數, 分析代碼邏輯。
發現這個函數會在我們做了點擊OK的操作之後調用系統的exit函數退出。
反編譯apk之後, 定位到剛剛的a函數。發現函數最後返回為return-void, 那我們直接把中間的代碼刪除就ok。接下來就是常規的反編譯+重籤名的操作, 這裡就不贅述了。安裝修改之後的應用, Java層的反調試就繞過了。
下面講一下稍有難度的繞過Native層的代碼, 用IDA反編譯libfoo.so。從IDA左側的Function窗口雙擊Java_sg_vantagepoint_uncrackable2_MainActivity_init, 發現會調用sub_918方法。雙擊sub_918函數分析代碼。
通過代碼我們可以知道, sub_918是起反調試作用的關鍵函數。接下來我們分析一下這個函數是如何實現反調試的,該函數會先fork自身, 產生一個子進程, 父進程在運行的過程中使用了輪詢機制, 通過ptrace函數監視自身是否處於被調試的狀態。子進程和fock失敗的父進程, 會調用waitpid一般情況下, ptrace這種類型的函數是不會被混淆的, 所以我們直接將調用ptrace函數的部分nop掉就好了。
使用IDA View-A打開sub_918函數, 可以看到有兩個BL .ptrace。選中其中的一個.ptrace, 然後打開Hex View-1頁面。
按下快捷鍵F2, 輸入1F 20 03 D5(NOP), 將原本的9C FF FF 97替換掉, patch結束之後按F2完成修改(兩個BL .ptrace都用這種方法修改)。發現網上有部分文章使用指令00 00 00 00和00 00 A0 E1(mov r0,r0)修改, 我按照那種方式修改之後, 發現代碼結構被破壞掉了, 應用不能正常運行, 最後直接使用NOP指令才能讓應用正常運行, 並繞過反調試的檢測。
最後按下圖選中下圖Apply patches to input file...., 就可以保存我們修改之後的結果。我們只需要patch我們設備使用到的libfoo.so文件, 比如我設備的arm版本是arm64-v8a。
這裡只針對這個應用的反調試作了介紹, 如果想了解更多反調試和繞過反調試的內容, 可以看看文章【SO殼】17種安卓native反調試收集。
在正式開始之前, 我們需要做點準備工作。首先打開目錄<你IDA安裝的目錄下>\IDA\dbgsrv, 將該目錄下的android_serverpush進手機裡面並添加權限。
$ adb push android_server64
$ adb forward tcp:23946 tcp:23946
$ adb shell
$ su
$ cd /data/local/tmp
$ chmod +x android_server64
$ ./android_server
經過測試,發現應用不能進行調試。我們先解決AndroidManifest.xml上的反調試,有4種方法:
反編譯APK, 修改AndroidManifest.xml, 使debuggable="true"
從手機內核中提取出boot.img並修改, 使ro.debuggable=1, 或者可以使用mprop, 但是有版本限制。
在裝有Xposed的手機上安裝BDOpener, 重啟激活該模塊即可。
設置全局變量, 使ro.debuggable=1(每次開機重啟後失效)
使用上面的方法讓應用處於可調試的狀態, 用DDMS打開, 效果如下
先輸入命令adb shell dumpsys activity top | findstr ACTIVITY, 通過輸出可知應用的包名及當前的Activity, 輸入命令adb shell am start -D -n owasp.mstg.uncrackable2/sg.vantagepoint.uncrackable2.MainActivity可讓Uncrackable2這個應用處於被調試狀態。這時候查看DDMS, 我們可以看到應用的狀態改變了。
接下來要用IDA對.so文件進行動態調試, 打開64位的IDA,打開debug選項Debugger->attach->Remoute Android debugger。
這時候會出現一個彈窗, 點擊Debug option, 勾選上框出來的3項。點擊OK之後, 在hostname裡面填寫127.0.0.1即可。
之後會出現手機加載的進程, 我們只需要選中我們想調試的uncrackable2, 就可以進入IDA的調試界面。
之後使用jdb使應用繼續運行, 因為上面的DDMS幾次重新打開, 所以埠映射部分會不一致。我們在使用jdb調試的時候, 最好還是先打開DDMS, 查看應用真正映射的埠, 否則會出現無法附加到目標 VM的錯誤, 按照下面的顯示結果, 我們需要輸入命令jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8605(8700埠被DDMS進程佔用, 改為8605埠, 不開DDMS就不用改)。
雙開IDA計算進程動態運行時我們想要定位的函數的地址, 在動態調試的IDA按下F9快捷鍵讓程序正常運行, 這時候我們就能在IDA右側Module板塊搜索到libfoo.so動態加載的地址0x0000007B11291000, 之後我們從分析靜態libfoo.so的IDA中獲取函數CodeCheck函數的偏移量為0xDAC, 所以最後我們能夠計算出我們要跳轉到0x0000007B11291DAC。
按下快捷鍵G, 跳轉到我們計算的CodeCheck函數開始的地址(0x0000007B11291DAC), 發現代碼不能解析出來, 選中部分代碼, 用快捷鍵C解析代碼(一次不行就先按C再按下Force)。然後在地址0x0000007B11291DAC下斷點, 在手機應用界面上輸入隨便輸入長度為23的字符串之後點擊Verify, 就可以運行到如下位置。
在動態調試彙編代碼的過程中, 會發現我們很難根據變量名獲取其中的值, 只能在調試的過程中看到寄存器中存儲的內容, 將靜態調試的IDA中的彙編代碼和動態調試過程中的IDA的代碼進行比對, 我們可以找出一些函數, 比如這裡的strncmp的函數的位置, 也可以通過靜態分析的IDA找到strncmp函數的位置(0x0000007B11291000+0xE5C=0x0000007B11291E5C), 選中unk_7B11281820, 使用快捷鍵N可將地址名重命名為.strncmp。
IDA右側有General Registers窗口, 但是這個窗口只會出現地址, 而我們需要獲取寄存器中的值, View->Open subviews->Hex dump可以查看內存中的數值。
F8單步調試到BL .strncmp處, 這時候我們就能看到傳遞的參數寄存器X1和X0存放的地址。滑鼠移至X0存放的地址, 右鍵選中Select All, 複製地址。
轉到Hex View-3的窗口, 按下快捷鍵G, 跳轉到剛剛我們複製的地址處。可以看到我們剛剛在應用中輸入的數值, 想要獲取X1的值可以用一樣的方法, 最終我們得到要輸入的密碼為Thanks for all the fish。
IDA動態調試的過程就到這裡結束了, 下面還有一種不patch apk的方法可以實現繞過反調試和各種檢測的方法, 使用到了Radare2, 感覺重要的是學習思路而不是工具如何使用這句話, 還是很有道理的。
結語實際上最近有很多事情要做, 配置Radare2及其插件上, 我花了很多時間, 現在還有一個小插件沒安裝好。為了推著自己完成這篇系列文章, 就把UnCrackable2完成的部分先發出來。希望我能儘快完成環境的配置還有之後的文章吧
--官方論壇
www.52pojie.cn
--推薦給朋友
公眾微信號:吾愛破解論壇
或搜微信號:pojie_52