這次的 APP 還是 2015 年移動安全挑戰賽(看雪&阿里主辦)中的第二題,通用的解法參考這個系列的 上一篇文章,這篇文章再介紹一種取巧的解法,這種解法并不通用
其實在別的文章里也有提到過這種取巧的解法,但是同樣不夠詳細,經過摸索并成功實踐之后,我想再記錄下來
直接跳到 IDA 載入 SO 的那一步,前面的步驟跟之前的都是一樣的,留意到這里調用了 __android_log_print
該函數的原型是:
int __android_log_print(int prio, const char *tag, const char *fmt, ...)
第一個參數是 LOG 的優先級。取值可以是
typedef enum android_LogPriority { ANDROID_LOG_UNKNOWN = 0, ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */ ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL, ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */ } android_LogPriority;
這個枚舉類型里的任意一個
第二個參數是 LOG 的標簽的指針,指針指向的標簽通常用于過濾 LOG
第三個參數是 LOG 的格式的指針,該指針指向的通常就是 LOG 的內容
回憶一下 ARM 里調用函數時傳參的規則,如果不是對象調用類的方法,在普通的函數調用里,caller 會把前四個參數分別放到 R0, R1, R2, R3 這四個寄存器中,這里也不例外。所以在調用 __andoid_log_print 的時候,R0 對應的是 LOG 的優先級,通過 MOV R0, #4
知道這里的優先級是 ANDROID_LOG_INFO;R1 對應的是 LOG 的標簽,通過計算得出這里 R1 的地址為 0x6340,跳轉過去發現是 bss 段,bss 段保存的是未初始化的全局變量,所以 R1 的值應該是程序執行過程中通過賦值符賦值的;同樣,對應于 LOG 內容指針的 R2 也是如此。
來看看這里的這個 __android_log_print 會輸出什么
運行 APP,打開 DDMS,隨便輸入一個密碼,點擊 “輸入密碼” 按鈕,然后查看 logcat 選項卡輸出的 LOG,發現輸出的 LOG 太多了,過濾一下,得到如下 LOG
再來分析下函數的流程
通過對上圖開始處的 R3 和 R1 值的比較分別進入不同的分支,具體為:如果 R3 和 R1 一直相等(一個字符一個字符比較),程序進入左邊的分支,最后返回 1(true),如果 R3 和 R1 有一次不相等,直接進入右邊的分支,返回 0(false)
而 R3 的值是 R2 保存的地址中的值,往上查看 R2 的地址中保存的值
就是我們之前看到的那個錯誤的答案
很巧,R2 保存的是既是我們要的答案的地址,也是 __android_log_print 函數的第三個參數,因此我們只需要把 __android_log_print 函數往下移,讓它在將答案的地址賦給 R2 之后再調用就可以通過 LOG 來知道答案了。
具體的做法是:將之前的 __android_log_print 調用和之后的對 R0, R1, R2 的無關緊要的賦值 NOP 掉,然后在將 R2 的值賦為答案的之后再調用 __android_log_print。這里有幾點是需要注意的:
- 答案的地址是受到 R1 的影響的,因此要把先前的 R1 保存下來,我的做法是把下面的 R1 改成 R3,然后把
LDR R2, [R1, R7]
改成LDR R2, [R3, R7]
,這樣 R2 的值不受影響,也能保證 tag 依然是之前的那個 tag
- 如何才能重新調用 __android_log_print 呢? 首先要明確這里能用調用 __android_log_print 的方法來輸出答案的原因是程序本身調用了這個函數,因此我們可以通過 PLT 來獲得這個函數在這個程序中的偏移,該偏移是位置無關的。關于 GOT 和 PLT,這里引用一下 通過 GDB 調試理解 GOT/PLT
GOT(Global Offset Table):全局偏移表用于記錄在 ELF 文件中所用到的共享庫中符號的絕對地址。在程序剛開始運行時,GOT 表項是空的,當符號第一次被調用時會動態解析符號的絕對地址然后轉去執行,并將被解析符號的絕對地址記錄在 GOT 中,第二次調用同一符號時,由于 GOT 中已經記錄了其絕對地址,直接轉去執行即可(不用重新解析)。
PLT(Procedure Linkage Table):過程鏈接表的作用是將位置無關的符號轉移到絕對地址。當一個外部符號被調用時,PLT 去引用 GOT 中的其符號對應的絕對地址,然后轉入并執行。
雙擊 __android_log_print
回到我們要調用的地方,是在 R2 的值賦為答案的之后
最后修改的結果:
打包簽名并重新運行程序,并查看 LOG
看到答案啦~