這次的 APP 是 2015 年移動安全挑戰賽(看雪&阿里主辦)中的第二題,網上已經有很多關于這個 APP 的破解方法,但是我跟著這些教程里的步驟來做,有些步驟總是不能成功,有些步驟也是半知半解,所以想把這個過程詳細地記錄下來
1. 逆向 APK####
首先用 jadx 大致看看程序的結構和流程
程序由 4 個類組成,大致瀏覽之后將注意力集中在 MainActivity 上,這個 Activity 有一個 native 方法,securityCheck,并且通過該方法來判斷輸入是否正確,我們要做的就是逆向這個方法來找到正確的輸入
2. 靜態分析####
打開 IDA,載入對應的 so 文件,找到 Java_com_yaotong_crackme_MainActivity_securityCheck 函數
導入 JNINativeMethod 和 JNINativeInterface 結構,進行簡單處理后按下 F5
留意到影響函數返回值的實際上在最后的 while(1)
循環中,在該循環中將 v5 和 v7 的值進行比較,如果不相等就跳出循環返回 0 (即 false),而 v7 的值是 v6 存放的地址中的值,查看 v6 存放的地址處的值
看到一個很像答案的字符串 “Wojiushidanan”,然而試過之后發現并不是。這就說明前面亂七八糟的代碼對這個字符串做了一定的處理,而具體是怎么處理的看起來太復雜了,于是來試試動態分析
3. 動態分析####
要用 IDA 對某個 APP 進行動態分析的前提是要有一臺 root 過的手機,我之前用的華為 Mate 7 是 Android 6.0 的系統,root 起來特別麻煩,root 之后調試 APP 的效果也不太好,推薦使用低版本的 Android。模擬器好像也是不行的,我試了好幾個,一運行就閃退,可能是程序對運行的環境也有檢測,也可能是架構問題。反正最好是用一部裝有低版本 Android 系統而且已 root 的真機
在有了滿足條件的手機以后,將 IDA 安裝目錄下的 dbgsrv 目錄下的 android_server 拷貝到手機上,拷貝的方法有很多,用 QQ 直接傳也可以,用 adb push 也可以,只要能找到存儲路徑就行。接著用 adb shell 進入系統的 shell,然后在 root 權限下用 chmod 755 命令將 android_server 修改為可運行,修改好后運行 android_server,如果運行成功會顯示:
說明現在 android_server 在手機的 23946 端口監聽
現在要做的是把手機的 23946 端口映射到電腦上,用 adb forward tcp:23946 tcp:23946
就可以把手機的 23946 端口映射到電腦的 23946 端口
接下來在手機上運行該 APP,然后嘗試用 IDA 進行連接,在 Debugger 選項卡中選擇 Attach to,找到這個 APP,點擊 OK 后發現程序閃退了,那就說明在程序中進行了判斷,不允許動態調試
一般的反調試原理是這樣的:IDA 使用 android_server 在 root 環境下注入到被調試的進程中,其用的技術是 Linux 中的 ptrace。在 Android 中如果一個進程被另一個進程 ptrace 之后,在它的 status 文件中的一個字段 TracePid 就標識了是那個進程 trace 了它自己。因此,只要在程序運行的過程中循環檢測進程中的 TracePid 標識就可以知道程序是否被調試。需要注意的是,這種方法不僅可以用在程序啟動的時候,在程序正常流程中間隨機插入這樣的檢測代碼(暗樁),往往可以給調試帶來更大的難度。
再回過來看這個程序,這個程序是一開始運行就退出了調試界面,說明這個檢測的執行時機比較早,已知的比較早運行的兩個函數是 .init_array 和 JNI_onload,.init_array 是一個 so 最先加載的段信息,時機最早,一般的 so 解密操作都是在這里做的;而 JNI_onload 是在 so 被 System.loadLibrary 加載后緊接著調用的,時機早于 native 方法,但是在 .init_array 方法之后
先試試是不是在 JNI_onload 中進行的反調試檢測
- 首先看看 AndroidManifest.xml 里面有沒有添加
android:debuggable="true"
,發現沒有,把它加到 application 標簽下,最后重新打包簽名成 APK 再安裝到手機上 - 用 am 以調試的方式啟動 MainActivity,命令如下:
am start -D -n com.yaotong.crackme/.MainActivity
,-D
的意思是以調試的方式啟動,具體用法可參見 Android AM 命令及使用
使用adb jdwp
命令可以看到當前可被調試的程序的 pid,如果該命令沒有輸出說明第一步出錯了
-
用 IDA Attach 上調試方式啟動的程序,在此之前要修改 Debug Options 修改為:
不在 library 加載之前掛起那 library 執行后程序就退出啦
- 按 F9 讓程序跑起來,然后用 jdb 連接,具體命令為:
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
這里的 port=8700 是在 DDMS 中看到的(好像要打開 DDMS 才能讓 jdb 命令生效)
-
在靜態分析 so 文件中查看 libcrack.so 中 JNI_onload 函數的偏移
偏移是 1B9C
-
在動態調試中按下 Ctrl + S 查看段信息
看到帶有 X 屬性的 libcrackme.so (至于為什么要找帶有 X 屬性的 so,我個人理解是只有這個 so 才是會執行的)的起始地址是 7651D000,加上 1B9C 就是 7651EB9C
-
按下 G 跳轉到 7651EB9C,然后按下 F2 下個斷點
- 按下 F9,讓程序跑起來,程序沒有閃退,說明沒有在 .init_array 做反調試檢測
- 接下來單步一步步往下走,看看是哪里讓程序退出的。在單步往下走的過程中,最好在每個跳轉指令的地方下個斷點,防止程序退出以后又要重新定位退出點
- 試了幾次之后,發現跑到
BLX R7
的時候,R7 存放是地址是 pthread_create 的地址,進一步分析發現,這里新建線程的目的就是循環檢測程序是否在被調試
- 記下這行指令的地址,再減去 libcrackme.so 的起始地址,在 WinHex 中定位到該語句,將這部分十六進制的字節碼修改為 NOP,即
00 F0 20 E3
,然后保存修改、重新打包 APK 并簽名,再安裝到手機上運行 - APP 成功運行后,用 IDA 附加到該進程上
-
在靜態的 libcrackme.so 中定位到 Java_com_yaotong_crackme_MainActivity_securityCheck 的地址,加上 libcrackme.so 的起始地址就得到了該函數在進程中的地址
-
隨便輸入一個注冊碼,然后運行程序,在 IDA 中跳轉到
Java_com_yaotong_crackme_MainActivity_securityCheck,進行單步步過
- 運行到下圖時
發現,程序出現了分支,在 R1 和 R3 不相等的時候,執行MOV R1, #0
,然后接著執行MOV R0, R1
,這就說明把返回值(R0)置成了 false。此時 R1 和 R3 一個存放的是我們的輸入的第一個字符的 ASCII 碼,另一個存放的就是正確的注冊碼的字符的 ASCII 碼。而 R1 和 R3 是通過LDRB R3, [R2]
和LDRB R1, [R0]
賦值的,查看 R0 和 R2,剛好一個是輸入,一個是正確的注冊碼,如下:
最后推薦一個ARM 轉機器碼的網站
參考文檔:
深入理解 JNI
IDA 調試 Android native(Crackme)
Android 逆向之動態調試總結
Android AM 命令及使用