上次找到了兩個關鍵的so:sscronet和metasec_ml,本想着用jni trace看看jni函數的加載順序、參數、地址等關鍵信息,結果大失所望:一個都沒有....... 仔細想想原因:要么是沒用到,要么是加密了!
繼續用ida打開mestasec_ml:發現導出函數列表發現大量的函數名都加密了(還有幾個明顯沒加密的函數名大家都看到了么? man are you sss bbb? 字節的同學真幽默( ̄▽ ̄)")!
很多字符串也被加密:
init_array發現大量函數:
1、好奇心驅使我挨個點進去查看,第一個就吃了閉門羹: 試圖F5查看源碼的時候,直接彈窗報”3BC6E: positive sp value has been found“錯誤,和我上次F5 jni_onload遇到的問題一毛一樣;仔細看代碼,發現這里有問題,如下:
(1)開始的時候還很正常:入棧保存寄存器,然后通過sub sp,sp 0x6c開辟棧空間來存放參數、局部變量等;
.text:0003BC10 000 PUSH {R4-R7,LR} .text:0003BC12 014 ADD R7, SP, #0xC .text:0003BC14 014 PUSH.W {R8-R11} .text:0003BC18 024 SUB SP, SP, #0x6C .text:0003BC1A 090 MOV.W R0, #0x172
但是到了下一個分支就是這樣的了:突然一個add指令讓sp大幅增加,而且分支中根本沒用到棧空間,分支結束時也沒回復棧平衡;
text:0003BC6C loc_3BC6C ; CODE XREF: sub_3BC10+3CC↓j .text:0003BC6C 090 ADD SP, SP, #0x180 .text:0003BC6E -F0 NOP .text:0003BC70 -F0 LDR R0, =0x5F1D4716 .text:0003BC72 -F0 B loc_3BFD6
.text:0003BD42 loc_3BD42 ; CODE XREF: sub_3BC10+3FA↓j .text:0003BD42 -F0 ADD SP, SP, #0xDC .text:0003BD44 -1CC LDR R0, =0xBDA61CFA .text:0003BD46 -1CC B loc_3BFD6
text:0003BD72 loc_3BD72 ; CODE XREF: sub_3BC10+40A↓j .text:0003BD72 -1CC ADD SP, SP, #0x15C .text:0003BD74 -328 MOV R0, R2 .text:0003BD76 -328 B loc_3BFD6
.text:0003BDA0 loc_3BDA0 ; CODE XREF: sub_3BC10+41A↓j .text:0003BDA0 -328 ADD SP, SP, #0x15C .text:0003BDA2 -484 NOP .text:0003BDA4 -484 LDR R0, =0x743ECA69 .text:0003BDA6 -484 B loc_3BFD6
.text:0003BDA8 loc_3BDA8 ; CODE XREF: sub_3BC10+422↓j .text:0003BDA8 -484 ADD SP, SP, #0x104 .text:0003BDAA -588 NOP .text:0003BDAC -588 MOV R0, R9 .text:0003BDAE -588 B loc_3BFD6
.text:0003BDC8 loc_3BDC8 ; CODE XREF: sub_3BC10+432↓j .text:0003BDC8 -588 ADD SP, SP, #0xF4 .text:0003BDCA -67C NOP .text:0003BDCC -67C LDR R0, =0x2E70D3EB .text:0003BDCE -67C B loc_3BFD6
直到整個函數結束前還在add sp的值,最后pop了函數剛開始時入棧的寄存器值,始終未通過sub sp讓棧重新平衡!
.text:0003C08E -67C BNE loc_3BFD6 .text:0003C090 -67C ADD SP, SP, #0x6C .text:0003C092 -6E8 POP.W {R8-R11} .text:0003C096 -6F8 POP {R4-R7,PC} .text:0003C096 ; End of function sub_3BC10
所以這里總結一下這個libmetasec_ml.so的防護方式之一:
- 增加一些無用的分支,在分支中破壞棧平衡,然后跳轉到原本有用的分支繼續執行;
(2)繼續看init_array的其他函數,從第二個函數開始都能順利F5反編譯了,就第一個不行,這里又是欲蓋彌彰:肯定很重要,所以才防護,第一個函數有必要好好跟蹤一下!
通過代碼分析,發現這些add sp的分支在其他代碼中有被引用,但都是在cmp條件中引用的,而這些條件都是不成立的,換句話說:這些add sp的分支都不會被執行的,存粹是為了反IDA搞的鬼!仔細想想也是:正經的編譯器是不會干這種事的,干這種事的都不是正經的編譯器!為了重新平衡棧,這里借助010editor把額外add的地方都NOP掉,方式如下:
010Editor比較貼心,把我手動改的地方全都標紅了:一共改了6處,全都NOP掉了!
把這些add sp代碼全量nop掉后,第一個函數能正常F5了,部分代碼片段(代碼太長了,放不下)如下:發現又是OLLVM混淆;
signed int sub_3BC10() { int v0; // r1 signed int result; // r0 int v2; // r1 bool v3; // zf signed int v4; // r1 char v5; // nf int v6; // r1 int v7; // r1 int v8; // r1 char v9; // r11 int v10; // r1 char v11; // r0 int v12; // r1 int v13; // r1 signed int v14; // r1 char v15; // [sp+61h] [bp-27h] char v16; // [sp+63h] [bp-25h] int v17; // [sp+64h] [bp-24h] char v18; // [sp+6Bh] [bp-1Dh] sub_82B38(547604, 19); if ( v0 ) LOBYTE(v0) = 1; v15 = v0; result = -224184235; do { while ( 1 ) { do { while ( 1 ) { while ( 1 ) { while ( 1 ) { do { while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { do { while ( 1 ) { while ( 1 ) { while ( 1 ) { v14 = result; if ( result != -1786743035 ) break; result = 1595754262; } if ( result != -1766343261 ) break; *(_DWORD *)((char *)R2bC6xH3fE6sH5rZ6gG + ((((~(unsigned int)sub_3BC10 | 0xA021040) & 0xA061440) + ((unsigned int)sub_40400 & (unsigned int)sub_3BC10 | 0x1010104)) ^ 0xF5F5F57C)) = 563; sub_82B38(49730, 7); result = 1701695347; } if ( result != -1732828906 ) break; sub_82B38(53825, 7); v3 = v2 == 0; v4 = -1113187078; result = -964039141; if ( !v3 ) result = -1113187078; v5 = 1;
先找些函數點進去看看都是干啥的,發現有個sub_40400的函數F5也是報同樣的錯,這里只能繼續把add sp指令NOP掉(甚至連pop代碼也要去掉,因為函數的入口就沒有push),如下:
這次棧是平衡了,F5反編譯還是出錯;回過頭來想:這么加安全防護,不擔心改變業務以往的邏輯么?繼續往上回溯代碼,發現這個分支也是在cbz條件內部,但這個條件根本不成立,所以這個分支永遠不會被執行!和上面的花指令方式如出一轍!
.text:00040D7E 000 30 46 MOV R0, R6 .text:00040D80 000 00 21 MOVS R1, #0 .text:00040D82 000 CE F7 BB FF BL sub_FCFC .text:00040D86 000 B0 B3 CBZ R0, loc_40DF6
................................................................................... .text:00040DB6 000 30 46 MOV R0, R6 .text:00040DB8 000 00 21 MOVS R1, #0 .text:00040DBA 000 CE F7 F3 FE BL sub_FBA4 .text:00040DBE 000 D0 B1 CBZ R0, loc_40DF6
除了init_array,還有另外一個重要的函數Jni_onload,用了同樣的sp不平衡的方式反IDA的F5,即使我在函數末尾改sp為0,還是報同樣的錯!而且我也不知道還有多少地方都是這樣的,挨個去找太費勁了!
.text:0003A76C 108 0D 9A LDR R2, [SP,#0x100+var_CC] .text:0003A76E 108 12 68 LDR R2, [R2] .text:0003A770 108 51 1A SUBS R1, R2, R1 .text:0003A772 108 02 BF ITTT EQ .text:0003A774 108 39 B0 ADDEQ SP, SP, #0xE4 .text:0003A776 024 BD E8 00 0F POPEQ.W {R8-R11} .text:0003A77A 014 F0 BD POPEQ {R4-R7,PC} .text:0003A77C 000 00 BF NOP .text:0003A77E 000 00 BF NOP .text:0003A77E ; END OF FUNCTION CHUNK FOR JNI_OnLoad
至此: 靜態分析基本上到頭了!原因有兩個:(1)很多地方都人為讓棧不平衡反ida的F5,挨個去找很麻煩,我個人沒那么多時間,耗不起! (2)就算F5成功了,還面臨OLLVM的控制流混淆、字符串加密,這種情況下靜態分析根本沒轍!
有同學又會問了:既然靜態分析不行,就動態調試唄! 剛開始我確實也是這樣想的,嘗試后發現ida經常彈窗說捕獲異常(如下圖),讓我選擇怎么處理,導致我連指令或block的trace都不行(個人猜測可能是估計加了反動態調試)!
好吧,截至目前靜態分析不行,動態調試也不行,我就只剩這么條路可以走了:
- frida 去hook關鍵字符串,看看內存中解密后的字符串都是啥
- unicorn、androidNativeEmu、unidbg這些模擬器來運行so了
- 魔改artMethod類的registerNative、prettyMethod、JniMethodStart等方法trace函數的執行順序(https://www.cnblogs.com/theseventhson/p/14952092.html)
未完待續,下周繼續更新!
心得:
1、調試器、模擬器、解釋器、虛擬機等沒本質區別,核心功能都是一樣的!
2、一旦編譯成匯編語言,C++相比C,本質就是個“大號”的結構體(C++類名義上有成員函數,但編譯后函數在代碼段,對象在棧或堆上,調用成員函數時第一個參數是this指針)!
參考:
1、https://armconverter.com/ arm機器碼查詢