zoukankan      html  css  js  c++  java
  • 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机器码查询

             

  • 相关阅读:
    领域驱动设计实践,精通业务,面向对象编程,面条编程,过程编程
    日志聚合与全链路监控
    Spring Security OAuth2 之token 和 refresh token
    Web开发技术发展历程(笔记)
    JDBC 数据处理 总结
    Idea创建Maven多模块项目
    中国 / 省市区县 / 四级联动 / 地址选择器(京东商城地址选择)
    左膀mongostat,右臂mongotop——MongoDB的监控之道
    制定项目章程
    挣值分析
  • 原文地址:https://www.cnblogs.com/theseventhson/p/15027667.html
Copyright © 2011-2022 走看看