這次的 APP 還是 2015 年移動安全挑戰(zhàn)賽(看雪&阿里主辦)中的第二題局待,通用的解法參考這個系列的 上一篇文章伪朽,這篇文章再介紹一種取巧的解法碳蛋,這種解法并不通用
其實(shí)在別的文章里也有提到過這種取巧的解法滔灶,但是同樣不夠詳細(xì)糟红,經(jīng)過摸索并成功實(shí)踐之后瘦癌,我想再記錄下來
直接跳到 IDA 載入 SO 的那一步猪贪,前面的步驟跟之前的都是一樣的,留意到這里調(diào)用了 __android_log_print
該函數(shù)的原型是:
int __android_log_print(int prio, const char *tag, const char *fmt, ...)
第一個參數(shù)是 LOG 的優(yōu)先級讯私。取值可以是
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;
這個枚舉類型里的任意一個
第二個參數(shù)是 LOG 的標(biāo)簽的指針热押,指針指向的標(biāo)簽通常用于過濾 LOG
第三個參數(shù)是 LOG 的格式的指針,該指針指向的通常就是 LOG 的內(nèi)容
回憶一下 ARM 里調(diào)用函數(shù)時傳參的規(guī)則斤寇,如果不是對象調(diào)用類的方法桶癣,在普通的函數(shù)調(diào)用里,caller 會把前四個參數(shù)分別放到 R0, R1, R2, R3 這四個寄存器中娘锁,這里也不例外牙寞。所以在調(diào)用 __andoid_log_print 的時候,R0 對應(yīng)的是 LOG 的優(yōu)先級莫秆,通過 MOV R0, #4
知道這里的優(yōu)先級是 ANDROID_LOG_INFO间雀;R1 對應(yīng)的是 LOG 的標(biāo)簽,通過計算得出這里 R1 的地址為 0x6340镊屎,跳轉(zhuǎn)過去發(fā)現(xiàn)是 bss 段惹挟,bss 段保存的是未初始化的全局變量,所以 R1 的值應(yīng)該是程序執(zhí)行過程中通過賦值符賦值的缝驳;同樣连锯,對應(yīng)于 LOG 內(nèi)容指針的 R2 也是如此。
來看看這里的這個 __android_log_print 會輸出什么
運(yùn)行 APP用狱,打開 DDMS运怖,隨便輸入一個密碼,點(diǎn)擊 “輸入密碼” 按鈕夏伊,然后查看 logcat 選項卡輸出的 LOG摇展,發(fā)現(xiàn)輸出的 LOG 太多了,過濾一下署海,得到如下 LOG
再來分析下函數(shù)的流程
通過對上圖開始處的 R3 和 R1 值的比較分別進(jìn)入不同的分支吗购,具體為:如果 R3 和 R1 一直相等(一個字符一個字符比較)医男,程序進(jìn)入左邊的分支,最后返回 1(true)捻勉,如果 R3 和 R1 有一次不相等镀梭,直接進(jìn)入右邊的分支,返回 0(false)
而 R3 的值是 R2 保存的地址中的值踱启,往上查看 R2 的地址中保存的值
就是我們之前看到的那個錯誤的答案
很巧报账,R2 保存的是既是我們要的答案的地址,也是 __android_log_print 函數(shù)的第三個參數(shù)埠偿,因此我們只需要把 __android_log_print 函數(shù)往下移透罢,讓它在將答案的地址賦給 R2 之后再調(diào)用就可以通過 LOG 來知道答案了。
具體的做法是:將之前的 __android_log_print 調(diào)用和之后的對 R0, R1, R2 的無關(guān)緊要的賦值 NOP 掉冠蒋,然后在將 R2 的值賦為答案的之后再調(diào)用 __android_log_print羽圃。這里有幾點(diǎn)是需要注意的:
- 答案的地址是受到 R1 的影響的,因此要把先前的 R1 保存下來抖剿,我的做法是把下面的 R1 改成 R3朽寞,然后把
LDR R2, [R1, R7]
改成LDR R2, [R3, R7]
,這樣 R2 的值不受影響斩郎,也能保證 tag 依然是之前的那個 tag
- 如何才能重新調(diào)用 __android_log_print 呢脑融? 首先要明確這里能用調(diào)用 __android_log_print 的方法來輸出答案的原因是程序本身調(diào)用了這個函數(shù),因此我們可以通過 PLT 來獲得這個函數(shù)在這個程序中的偏移缩宜,該偏移是位置無關(guān)的肘迎。關(guān)于 GOT 和 PLT,這里引用一下 通過 GDB 調(diào)試?yán)斫?GOT/PLT
GOT(Global Offset Table):全局偏移表用于記錄在 ELF 文件中所用到的共享庫中符號的絕對地址锻煌。在程序剛開始運(yùn)行時妓布,GOT 表項是空的,當(dāng)符號第一次被調(diào)用時會動態(tài)解析符號的絕對地址然后轉(zhuǎn)去執(zhí)行,并將被解析符號的絕對地址記錄在 GOT 中,第二次調(diào)用同一符號時工猜,由于 GOT 中已經(jīng)記錄了其絕對地址顽频,直接轉(zhuǎn)去執(zhí)行即可(不用重新解析)。
PLT(Procedure Linkage Table):過程鏈接表的作用是將位置無關(guān)的符號轉(zhuǎn)移到絕對地址圆兵。當(dāng)一個外部符號被調(diào)用時跺讯,PLT 去引用 GOT 中的其符號對應(yīng)的絕對地址,然后轉(zhuǎn)入并執(zhí)行殉农。
雙擊 __android_log_print
回到我們要調(diào)用的地方刀脏,是在 R2 的值賦為答案的之后
最后修改的結(jié)果:
打包簽名并重新運(yùn)行程序仙逻,并查看 LOG
看到答案啦~