android逆向奇技淫巧十八:x音so層代碼花指令防護分析(三)


  上次找到了兩個關鍵的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機器碼查詢

         

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM