显示标签为“ANDREV EXAMPLE”的博文。显示所有博文
显示标签为“ANDREV EXAMPLE”的博文。显示所有博文

2017年5月11日星期四

ANDROID 逆向实例(八)- 乐固加固脱壳(2017.01)

虽然乐固做了一些反调试的东西,不过感觉比 Ali 更好分析一点,很多情况并不是反调试越厉害加固就越好。感觉 Ali 在加固方面做的比乐固有意思,Ali 利用 SP 来储存返回地址和参数所以整个流程很混乱,基本看上去就是在各种 JUMP,而乐固仍然是常规规的函数调用和返回方式,流程清晰很多。

另外想说一下,之前把对 Ali 壳的分析发到看雪了,可能因为我只把大概的流程放上去了所以有评论觉得这是一个很没技术的秒脱壳子,至于究竟这个壳子怎么样,好不好脱,完全没有自己尝试过。个人感觉 Ali 的壳子对于初学者来说有很深的学习意义,有不少有意思的细节,建议分析。

* 为了阅读这篇文章时更好理解文中所说内容,下面大多形如 [sub_] [unk_xxx] [loc_xxx] 的函数都经过了重命名以帮助理解。

下面言归正传,下图是反编译 apk 后的 smali 差异,另外还新增了一个 lib 文件夹。
lib 文件夹的格式如下所示。

bugly 相关的可以不用管了,是腾讯的一个类似上报 bug 之类的玩意和加固没有关系。smali 层面只做了一些简单的混淆,不做过多分析了(其实是时间太久忘了 ....),直接进入 Native 层。

直接动态 IDA 挂上,然后在 Linker 和 libdvm.so 对应调用 .init / .init_array / JNI_Onload 的偏移地址下好断点,这样下断点的好处是可以无视 .so 的 ELF 头被修改的情况直达想要的位置,也上个图简单示意一下。

触发断点之后说明运行的到了 .init / .init_array / JNI_Onload 的出发点,然后 F7 即可进入对应 .so ELF 内执行的代码位置。

.init Segment

根据上面说的来到 .init 在 .so 里的位置,偏移量是 [+ 14D4],IDA 静态 + F5 之后得到伪代码,下面是完善函数名之后的伪代码。

int func_init()
{
  unsigned int v0; // ST44_4@11
  unsigned int v1; // ST40_4@11
  unsigned __int64 v2; // kr00_8@13
  _BYTE v3; // ST27_1@15
  _DWORD j; // [sp+14h] [bp-44h]@13
  int v6; // [sp+18h] [bp-40h]@13
  _DWORD v7; // [sp+1Ch] [bp-3Ch]@13
  char v8; // [sp+2Eh] [bp-2Ah]@13
  char v9; // [sp+2Fh] [bp-29h]@13
  char v10; // [sp+30h] [bp-28h]@13
  char v11; // [sp+33h] [bp-25h]@13
  unsigned int v12; // [sp+34h] [bp-24h]@4
  int v13; // [sp+38h] [bp-20h]@4
  unsigned int v14; // [sp+48h] [bp-10h]@4
  unsigned int i; // [sp+4Ch] [bp-Ch]@1

  for ( i = (unsigned int)func_init & 0xFFFFF000; *(_DWORD *)i != 1179403647; i -= 4096 )
    ;
  v14 = 0;
  v13 = i + *(_DWORD *)(i + 28);
  v12 = 0;
  while ( v12 < *(_WORD *)(i + 44) )
  {
    if ( *(_DWORD *)v13 != 1 || *(_DWORD *)(v13 + 24) != 5 )
    {
      if ( *(_DWORD *)v13 == 1 && *(_DWORD *)(v13 + 24) == 6 )
      {
        v0 = *(_DWORD *)(v13 + 8) & 0xFFFFF000;
        v1 = (*(_DWORD *)(v13 + 8) + *(_DWORD *)(v13 + 16) + 4095) & 0xFFFFF000;
        break;
      }
    }
    else
    {
      v14 = (*(_DWORD *)(v13 + 8) + *(_DWORD *)(v13 + 16) + 4095) & 0xFFFFF000;
    }
    ++v12;
    v13 += 32;
  }
  v11 = 43;
  v10 = -103;
  v9 = 32;
  v8 = 21;
  v2 = (unsigned __int64)word_A010[0] << 16;
  v7 = LOWORD(word_A010[0]);
  v6 = LOWORD(word_A010[0]) - (word_A010[0] >> 16);
  mmprotect(
    (void *)(i + HIDWORD(v2)),
    (void *)((v6 + 4095) & 0xFFFFF000),
    &bad_offset_80003911[2],
    (void *)(i + HIDWORD(v2)));
  for ( j = HIDWORD(v2); j <= v7; ++j )
  {
    v3 = *(_BYTE *)(i + j);
    *(_BYTE *)(i + j) ^= (unsigned __int8)((j ^ (v10 - v9)) + v8) ^ v11;
    *(_BYTE *)(i + j) += v10 ^ v9 & v8;
    v11 += v3 & (v10 + v9 - v8) & j;
    v10 += v3 ^ (v11 + j);
    v9 ^= (unsigned __int8)(v3 - v11) ^ (unsigned __int8)j;
    v8 -= v3 + v11 - j;
  }
  mmprotect(
    (void *)(i + HIDWORD(v2)),
    (void *)((v6 + 4095) & 0xFFFFF000),
    (char *)&off_4 + 1,
    (void *)(i + HIDWORD(v2)));
  cacheflush_syscall(i + HIDWORD(v2), v6);
  word_A010[0] = i;
  dword_A380 = i;
  unk_A384 = v14;
  return sub_8630();
}
略过很多字节变换,略过重命名后的 mmprotect() 和 cacheflush_syscall(),整段其实就执行了一个函数 [sub_8630],跟进之后发现伪代码如下。

int sub_8630()
{
  int result; // r0@1
  int v1; // [sp+Ch] [bp-4h]@1

  v1 = 0;
  dword_A378 = 0;
  result = pthread_create(&v1, 0, anti_init, dword_A380);
  unk_A37C = 1;
  return result;
}
可以看出是 pthread_create 新开了一个线程执行 anti_init 函数,进入 F5 查看 anti_init 函数伪代码。


int __fastcall anti_init(int a1)
{
  int v2; // r0@3
  int v3; // r0@3
  int v4; // r1@3
  int v5; // r2@7
  int v6; // r0@10
  int *v7; // r3@14
  int *v8; // r1@15
  int *v9; // r3@15
  int v10; // r2@15
  unsigned int v11; // r0@15
  int *v12; // r3@16
  int v13; // [sp+0h] [bp-480h]@3
  int v14; // [sp+4h] [bp-47Ch]@16
  int v15; // [sp+8h] [bp-478h]@16
  int v16; // [sp+Ch] [bp-474h]@15
  int v17; // [sp+10h] [bp-470h]@14
  int v18; // [sp+14h] [bp-46Ch]@14
  int v19; // [sp+18h] [bp-468h]@12
  int v20; // [sp+1Ch] [bp-464h]@12
  int v21; // [sp+20h] [bp-460h]@9
  int v22; // [sp+24h] [bp-45Ch]@7
  int v23; // [sp+28h] [bp-458h]@7
  int v24; // [sp+2Ch] [bp-454h]@3
  int v25; // [sp+30h] [bp-450h]@3
  int fd_1; // [sp+34h] [bp-44Ch]@3
  int v27; // [sp+38h] [bp-448h]@3
  int v28; // [sp+3Ch] [bp-444h]@1
  void *v29; // [sp+40h] [bp-440h]@1
  char *v30; // [sp+44h] [bp-43Ch]@1
  char *v31; // [sp+48h] [bp-438h]@1
  char v32; // [sp+4Ch] [bp-434h]@1
  char v33; // [sp+4Dh] [bp-433h]@1
  char v34; // [sp+4Eh] [bp-432h]@1
  char v35; // [sp+4Fh] [bp-431h]@1
  char v36; // [sp+50h] [bp-430h]@1
  char v37; // [sp+51h] [bp-42Fh]@1
  char v38; // [sp+52h] [bp-42Eh]@1
  char v39; // [sp+53h] [bp-42Dh]@1
  char v40; // [sp+54h] [bp-42Ch]@1
  char v41; // [sp+55h] [bp-42Bh]@1
  char v42; // [sp+56h] [bp-42Ah]@1
  char v43; // [sp+57h] [bp-429h]@1
  char v44; // [sp+58h] [bp-428h]@1
  char v45; // [sp+59h] [bp-427h]@1
  char v46; // [sp+5Ah] [bp-426h]@1
  char v47; // [sp+5Bh] [bp-425h]@1
  int fd; // [sp+5Ch] [bp-424h]@3
  char v49; // [sp+60h] [bp-420h]@7
  int v50; // [sp+460h] [bp-20h]@4
  int v51; // [sp+464h] [bp-1Ch]@1
  int v52; // [sp+468h] [bp-18h]@1
  int v53; // [sp+46Ch] [bp-14h]@1

  v53 = a1;
  ++dword_A378;
  v31 = (char *)&GLOBAL_OFFSET_TABLE_;
  v30 = &v32;
  v29 = &GLOBAL_OFFSET_TABLE_;
  unk_A388 = malloc(1024);
  v28 = sub_6DEC();
  unk_A38C = sub_6EE0(0, v53, unk_A384);
  v52 = getppid();
  v38 = unk_A2B7 ^ 0x96;
  v37 = unk_A2B6 ^ 0xF7;
  v46 = unk_A2BF ^ 0xE1;
  v39 = unk_A2B8 ^ 0x9E;
  v34 = unk_A2B3 ^ 0x85;
  v41 = unk_A2BA ^ 0x98;
  v47 = unk_A2C0;
  v32 = unk_A2B1 ^ 0xE9;
  v35 = unk_A2B4 ^ 0x95;
  v44 = unk_A2BD ^ 0xCA;
  v42 = unk_A2BB ^ 0xEE;
  v36 = unk_A2B5 ^ 0xEA;
  v33 = unk_A2B2 ^ 0x97;
  v45 = unk_A2BE ^ 0xD3;
  v43 = unk_A2BC ^ 0xA5;
  v40 = unk_A2B9 ^ 0xFB;
  v51 = opendir(&v32);
  if ( v51 )
  {
    v27 = 0xFFF;
    fd = inotify_init();
    fd_1 = fd;
    v2 = fcntl(fd, 3, 0);
    v3 = fcntl(fd_1, 4, v2 | 0x800);
    v4 = fd;
    *((_BYTE *)&v13 - 4) = byte_A2CD ^ 0x8D;
    *((_BYTE *)&v13 - 16) = unk_A2C1 ^ 0x91;
    *((_BYTE *)&v13 - 7) = byte_A2CA ^ 0xC5;
    *((_BYTE *)&v13 - 5) = byte_A2CC ^ 0x80;
    *((_BYTE *)&v13 - 13) = byte_A2C4 ^ 0xD3;
    *((_BYTE *)&v13 - 11) = byte_A2C6 ^ 0xE1;
    *((_BYTE *)&v13 - 9) = byte_A2C8 ^ 0xCA;
    *((_BYTE *)&v13 - 8) = byte_A2C9 ^ 0xE1;
    *((_BYTE *)&v13 - 2) = byte_A2CF;
    *((_BYTE *)&v13 - 3) = byte_A2CE ^ 0xC8;
    *((_BYTE *)&v13 - 15) = byte_A2C2 ^ 0x87;
    *((_BYTE *)&v13 - 14) = byte_A2C3 ^ 0x96;
    *((_BYTE *)&v13 - 6) = byte_A2CB ^ 0xDA;
    *((_BYTE *)&v13 - 12) = byte_A2C5 ^ 0xD7;
    *((_BYTE *)&v13 - 10) = byte_A2C7 ^ 0xE2;
    v25 = v3;
    v24 = inotify_add_watch(v4, (int)(&v13 - 4), v27);// "/proc/self/mem"
    while ( 1 )
    {
      v50 = readdir(v51);
      if ( !v50 )
        break;
      if ( *(_BYTE *)(v50 + 18) & 4 && 46 != *(_BYTE *)(v50 + 19) )
      {
        v5 = v50 + 19;
        *((_BYTE *)&v13 - 11) = byte_A2DD ^ 0x8D;
        *((_BYTE *)&v13 - 22) = byte_A2D2 ^ 0x9E;
        *((_BYTE *)&v13 - 12) = byte_A2DC ^ 0xAD;
        *((_BYTE *)&v13 - 5) = byte_A2E3 ^ 0x86;
        *((_BYTE *)&v13 - 14) = byte_A2DA ^ 0xE1;
        *((_BYTE *)&v13 - 8) = byte_A2E0 ^ 0xB1;
        *((_BYTE *)&v13 - 10) = byte_A2DE ^ 0xCF;
        *((_BYTE *)&v13 - 15) = byte_A2D9 ^ 0xB1;
        *((_BYTE *)&v13 - 24) = unk_A2D0 ^ 0xA7;
        *((_BYTE *)&v13 - 13) = byte_A2DB ^ 0xE0;
        *((_BYTE *)&v13 - 7) = byte_A2E1 ^ 0xA2;
        *((_BYTE *)&v13 - 3) = byte_A2E5 ^ 0xAB;
        *((_BYTE *)&v13 - 17) = byte_A2D7 ^ 0x8B;
        *((_BYTE *)&v13 - 2) = byte_A2E6;
        *((_BYTE *)&v13 - 23) = byte_A2D1 ^ 0xBD;
        *((_BYTE *)&v13 - 19) = byte_A2D5 ^ 0xCC;
        *((_BYTE *)&v13 - 9) = byte_A2DF ^ 0xA7;
        *((_BYTE *)&v13 - 6) = byte_A2E2 ^ 0xE6;
        *((_BYTE *)&v13 - 16) = byte_A2D8 ^ 0xA0;
        *((_BYTE *)&v13 - 18) = byte_A2D6 ^ 0x83;
        *((_BYTE *)&v13 - 21) = byte_A2D3 ^ 0x98;
        *((_BYTE *)&v13 - 20) = byte_A2D4 ^ 0xD2;
        *((_BYTE *)&v13 - 4) = byte_A2E4 ^ 0xC2;
        v23 = sprintf(&v49, &v13 - 6, v5);
        v22 = inotify_add_watch(fd, (int)&v49, 0xFFF);// "/proc/self/task/{pid}/mem"
      }
    }
    v21 = closedir(v51);
    while ( 1 )
    {
      *((_BYTE *)&v13 - 12) = byte_A2FC ^ 0x8D;
      *((_BYTE *)&v13 - 7) = byte_A301;
      *((_BYTE *)&v13 - 10) = byte_A2FE ^ 0xD9;
      *((_BYTE *)&v13 - 14) = byte_A2FA ^ 0xD2;
      *((_BYTE *)&v13 - 17) = byte_A2F7 ^ 0xD5;
      *((_BYTE *)&v13 - 19) = byte_A2F5 ^ 0x81;
      *((_BYTE *)&v13 - 22) = byte_A2F2 ^ 0xB8;
      *((_BYTE *)&v13 - 13) = byte_A2FB ^ 0xC7;
      *((_BYTE *)&v13 - 15) = byte_A2F9 ^ 0xB2;
      *((_BYTE *)&v13 - 18) = byte_A2F6 ^ 0x81;
      *((_BYTE *)&v13 - 24) = unk_A2F0 ^ 0x9C;
      *((_BYTE *)&v13 - 9) = byte_A2FF ^ 0x95;
      *((_BYTE *)&v13 - 20) = byte_A2F4 ^ 0x82;
      *((_BYTE *)&v13 - 23) = byte_A2F1 ^ 0x81;
      *((_BYTE *)&v13 - 16) = byte_A2F8 ^ 0x88;
      *((_BYTE *)&v13 - 8) = byte_A300 ^ 0xC;
      *((_BYTE *)&v13 - 11) = byte_A2FD ^ 0xC8;
      *((_BYTE *)&v13 - 21) = byte_A2F3 ^ 0x8B;
      v6 = chk_status_trace((int)(&v13 - 6));
      if ( v6 != v52 )
      {
        *((_BYTE *)&v13 - 16) = byte_A2F8 ^ 0x88;
        *((_BYTE *)&v13 - 11) = byte_A2FD ^ 0xC8;
        *((_BYTE *)&v13 - 24) = unk_A2F0 ^ 0x9C;
        *((_BYTE *)&v13 - 9) = byte_A2FF ^ 0x95;
        *((_BYTE *)&v13 - 8) = byte_A300 ^ 0xC;
        *((_BYTE *)&v13 - 10) = byte_A2FE ^ 0xD9;
        *((_BYTE *)&v13 - 20) = byte_A2F4 ^ 0x82;
        *((_BYTE *)&v13 - 18) = byte_A2F6 ^ 0x81;
        *((_BYTE *)&v13 - 7) = byte_A301;
        *((_BYTE *)&v13 - 23) = byte_A2F1 ^ 0x81;
        *((_BYTE *)&v13 - 19) = byte_A2F5 ^ 0x81;
        *((_BYTE *)&v13 - 14) = byte_A2FA ^ 0xD2;
        *((_BYTE *)&v13 - 21) = byte_A2F3 ^ 0x8B;
        *((_BYTE *)&v13 - 12) = byte_A2FC ^ 0x8D;
        *((_BYTE *)&v13 - 13) = byte_A2FB ^ 0xC7;
        *((_BYTE *)&v13 - 22) = byte_A2F2 ^ 0xB8;
        *((_BYTE *)&v13 - 15) = byte_A2F9 ^ 0xB2;
        *((_BYTE *)&v13 - 17) = byte_A2F7 ^ 0xD5;
        if ( chk_status_trace((int)(&v13 - 6)) )
        {
          *((_BYTE *)&v13 - 6) = byte_A304 ^ 0xAF;
          *((_BYTE *)&v13 - 8) = unk_A302 ^ 0x8B;
          *((_BYTE *)&v13 - 5) = byte_A305 ^ 0xF3;
          *((_BYTE *)&v13 - 7) = byte_A303 ^ 0xCB;
          *((_BYTE *)&v13 - 4) = byte_A306;
          *((_BYTE *)&v13 - 13) = byte_A30A ^ 0xA7;
          *((_BYTE *)&v13 - 5) = byte_A312 ^ 0xDF;
          *((_BYTE *)&v13 - 9) = byte_A30E ^ 0xB5;
          *((_BYTE *)&v13 - 14) = byte_A309 ^ 0xAC;
          *((_BYTE *)&v13 - 4) = byte_A313 ^ 0x80;
          *((_BYTE *)&v13 - 8) = byte_A30F ^ 0xFD;
          *((_BYTE *)&v13 - 12) = byte_A30B ^ 0xC4;
          *((_BYTE *)&v13 - 7) = byte_A310 ^ 0xE7;
          *((_BYTE *)&v13 - 15) = byte_A308 ^ 0xC5;
          *((_BYTE *)&v13 - 16) = unk_A307 ^ 0xA9;
          *((_BYTE *)&v13 - 3) = byte_A314;
          *((_BYTE *)&v13 - 6) = byte_A311 ^ 0x9E;
          *((_BYTE *)&v13 - 10) = byte_A30D ^ 0x93;
          *((_BYTE *)&v13 - 11) = byte_A30C ^ 0xE3;
          v20 = log(6, (int)(&v13 - 2), (int)(&v13 - 4), (int)(&v13 - 4));// log("anti", "tracer antied")
          v19 = j_raise(0x11);
        }
      }
      if ( read(fd, &v49, dword_400) > 0 )
      {
        *((_BYTE *)&v13 - 5) = byte_A305 ^ 0xF3;
        *((_BYTE *)&v13 - 4) = byte_A306;
        *((_BYTE *)&v13 - 6) = byte_A304 ^ 0xAF;
        *((_BYTE *)&v13 - 8) = unk_A302 ^ 0x8B;
        *((_BYTE *)&v13 - 7) = byte_A303 ^ 0xCB;
        v7 = &v13 - 4;
        *(_BYTE *)v7 = unk_A315 ^ 0xB2;
        *((_BYTE *)&v13 - 14) = unk_A317 ^ 0xA6;
        *((_BYTE *)&v13 - 11) = unk_A31A ^ 0xBF;
        *((_BYTE *)&v13 - 13) = unk_A318 ^ 0xBA;
        *((_BYTE *)&v13 - 15) = unk_A316 ^ 0xAF;
        *((_BYTE *)&v13 - 10) = unk_A31B ^ 0x92;
        *((_BYTE *)&v13 - 7) = unk_A31E ^ 0xE7;
        *((_BYTE *)&v13 - 9) = unk_A31C ^ 0x9E;
        *((_BYTE *)&v13 - 8) = unk_A31D ^ 0xB3;
        *((_BYTE *)&v13 - 12) = unk_A319 ^ 0xDD;
        *((_BYTE *)v7 + 10) = unk_A31F;
        v18 = log(6, (int)(&v13 - 2), (int)(&v13 - 4), (int)(&v13 - 4));// log("anti", "mem antied")
        v17 = j_raise(17);
      }
      v8 = (int *)&v31[(_DWORD)&dword_438[2]];
      v9 = (int *)&v31[(_DWORD)&dword_438[4]];
      ++*((_DWORD *)v31 + 269);
      v10 = *v8;
      v16 = *v9;
      v11 = sub_6EE0(0, v53, v10);
      if ( v16 != v11 )
      {
        *((_BYTE *)&v13 - 5) = byte_A305 ^ 0xF3;
        *((_BYTE *)&v13 - 4) = byte_A306;
        *((_BYTE *)&v13 - 7) = byte_A303 ^ 0xCB;
        *((_BYTE *)&v13 - 8) = unk_A302 ^ 0x8B;
        *((_BYTE *)&v13 - 6) = byte_A304 ^ 0xAF;
        v12 = &v13 - 2;
        *(_BYTE *)v12 = unk_A320 ^ 0xCD;
        *((_BYTE *)&v13 - 6) = unk_A322 ^ 0x87;
        *((_BYTE *)&v13 - 7) = unk_A321 ^ 0xDF;
        *((_BYTE *)&v13 - 3) = unk_A325 ^ 0xF5;
        *((_BYTE *)&v13 - 5) = unk_A323 ^ 0xFC;
        *((_BYTE *)&v13 - 2) = unk_A326;
        *((_BYTE *)v12 + 4) = unk_A324 ^ 0x1A;
        v15 = log(6, (int)(&v13 - 2), (int)(&v13 - 2), (int)(&v13 - 2));// log("anti", "antied")
        v14 = j_raise(17);
      }
      v13 = sleep(1);
    }
  }
  return 0;
}
一个 while(1) 的循环里面根据不同的条件执行了 3 个 raise() 函数,不用管具体是根据什么样的 3 个条件执行的 raise() 操作,直接把 [BL  j_raise] 对应 arm 十六进制码修改成 [Mov R1, R1] 就可以达到 Bypass 反调试的目的,这个地方殊途同归的 Bypass 方法很多。

JNI_Onload

还是同样的方法来到 JNI_Onload 在 .so 里的位置,偏移量是 [+ 65A8],IDA 静态查看,F5 之后得到对应的伪代码,动态 + 静态结合调试可以还原出大部分函数的真实目的。

void *__fastcall JNI_Onload(JavaVM *J_vm_1, JNIEnv *J_env)
{
  char v2; // r0@5
  int v3; // r0@9
  int v4; // lr@9
  int v5; // r3@9
  char *v6; // r12@9
  int v8; // [sp+0h] [bp-450h]@8
  int (__fastcall *J_dlsym_1)(_DWORD, _DWORD); // [sp+4h] [bp-44Ch]@10
  int v10; // [sp+8h] [bp-448h]@9
  int v11; // [sp+Ch] [bp-444h]@9
  int v12; // [sp+10h] [bp-440h]@9
  int *v13; // [sp+14h] [bp-43Ch]@9
  int a1a; // [sp+18h] [bp-438h]@9
  int v15; // [sp+1Ch] [bp-434h]@8
  int v16; // [sp+20h] [bp-430h]@4
  char *v17; // [sp+24h] [bp-42Ch]@1
  int v18; // [sp+28h] [bp-428h]@1
  int (__fastcall *J_dlsym)(_DWORD, _DWORD); // [sp+2Ch] [bp-424h]@9
  int v20; // [sp+30h] [bp-420h]@9
  int v21; // [sp+34h] [bp-41Ch]@2
  JNIEnv *J_env_1; // [sp+434h] [bp-1Ch]@1
  JavaVM *J_vm; // [sp+438h] [bp-18h]@1
  void *v24; // [sp+43Ch] [bp-14h]@10

  J_vm = J_vm_1;
  J_env_1 = J_env;
  v18 = 0;
  v17 = (char *)&GLOBAL_OFFSET_TABLE_;
  while ( sub_69D4((int)&v21) )
    ;
  if ( !*(_DWORD *)&v17[(_DWORD)dword_438] )
    v16 = j_raise(9);
  while ( 1 )
  {
    v2 = 0;
    if ( *((_DWORD *)v17 + 269) )
      v2 = 1;
    if ( !(((unsigned __int8)v2 ^ 1) & 1) )
      break;
    *((_BYTE *)&v8 - 7) = byte_A025 ^ 0xB5;
    *((_BYTE *)&v8 - 8) = unk_A024 ^ 0x81;
    *((_BYTE *)&v8 - 4) = byte_A028 ^ 0xEC;
    *((_BYTE *)&v8 - 6) = byte_A026 ^ 0xD4;
    *((_BYTE *)&v8 - 5) = byte_A027 ^ 0xF5;
    *((_BYTE *)&v8 - 3) = byte_A029;
    *((_BYTE *)&v8 - 13) = byte_A207 ^ 0xBA;
    *((_BYTE *)&v8 - 6) = byte_A20E;
    *((_BYTE *)&v8 - 14) = byte_A206 ^ 0x88;
    *((_BYTE *)&v8 - 8) = byte_A20C ^ 0xD3;
    *((_BYTE *)&v8 - 9) = byte_A20B ^ 0xC9;
    *((_BYTE *)&v8 - 12) = byte_A208 ^ 0xF7;
    *((_BYTE *)&v8 - 11) = byte_A209 ^ 0xED;
    *((_BYTE *)&v8 - 16) = unk_A204 ^ 0xD1;
    *((_BYTE *)&v8 - 10) = byte_A20A ^ 0xEB;
    *((_BYTE *)&v8 - 15) = byte_A205 ^ 0xCE;
    *((_BYTE *)&v8 - 7) = byte_A20D ^ 0x91;
    v15 = log(6, (int)(&v8 - 2), (int)(&v8 - 4), (int)(&v8 - 4));
  }
  a1a = 6;
  v13 = &v21;
  v12 = (int)&GLOBAL_OFFSET_TABLE_;
  v11 = J_unzip((int)&v21, dword_A020);
  v3 = linker_method_dlopen((int)v13, 0);
  v20 = v3;
  v4 = v12 + 715;
  *((_BYTE *)&v8 - 6) = *(_BYTE *)(v12 + 725);
  *((_BYTE *)&v8 - 14) = *(_BYTE *)(v4 + 2) ^ 0x8B;
  *((_BYTE *)&v8 - 7) = *(_BYTE *)(v4 + 9) ^ 0xB0;
  *((_BYTE *)&v8 - 8) = *(_BYTE *)(v4 + 8) ^ 0x92;
  *((_BYTE *)&v8 - 9) = *(_BYTE *)(v4 + 7) ^ 0xCD;
  *((_BYTE *)&v8 - 11) = *(_BYTE *)(v4 + 5) ^ 0x84;
  *((_BYTE *)&v8 - 16) = *(_BYTE *)v4 ^ 0xC3;
  *((_BYTE *)&v8 - 12) = *(_BYTE *)(v4 + 4) ^ 0xA4;
  *((_BYTE *)&v8 - 13) = *(_BYTE *)(v4 + 3) ^ 0xB1;
  *((_BYTE *)&v8 - 10) = *(_BYTE *)(v4 + 6) ^ 0x9E;
  *((_BYTE *)&v8 - 15) = *(_BYTE *)(v4 + 1) ^ 0xDB;
  J_dlsym = (int (__fastcall *)(_DWORD, _DWORD))linker_method_dlsym(v3, &v8 - 4);
  v5 = v12;
  v6 = &byte_E0[v12];
  *((_BYTE *)&v8 - 7) = byte_E0[v12 + 1] ^ 0xB5;
  *((_BYTE *)&v8 - 4) = v6[4] ^ 0xEC;
  *((_BYTE *)&v8 - 3) = v6[5];
  *((_BYTE *)&v8 - 8) = *v6 ^ 0x81;
  *((_BYTE *)&v8 - 5) = v6[3] ^ 0xF5;
  *((_BYTE *)&v8 - 6) = v6[2] ^ 0xD4;
  *((_BYTE *)&v8 - 13) = *(_BYTE *)(v5 + 729) ^ 0xAA;
  *((_BYTE *)&v8 - 12) = *(_BYTE *)(v5 + 730) ^ 0xBE;
  *((_BYTE *)&v8 - 16) = *(_BYTE *)(v5 + 726) ^ 0x84;
  *((_BYTE *)&v8 - 15) = *(_BYTE *)(v5 + 727) ^ 0xC9;
  *((_BYTE *)&v8 - 6) = *(_BYTE *)(v5 + 736);
  *((_BYTE *)&v8 - 7) = *(_BYTE *)(v5 + 735) ^ 0xC7;
  *((_BYTE *)&v8 - 14) = *(_BYTE *)(v5 + 728) ^ 0xA9;
  *((_BYTE *)&v8 - 11) = *(_BYTE *)(v5 + 731) ^ 0x95;
  *((_BYTE *)&v8 - 9) = *(_BYTE *)(v5 + 733) ^ 0x9D;
  *((_BYTE *)&v8 - 10) = *(_BYTE *)(v5 + 732) ^ 0xDC;
  *((_BYTE *)&v8 - 8) = *(_BYTE *)(v5 + 734) ^ 0xC2;
  v10 = log(a1a, (int)(&v8 - 2), (int)(&v8 - 4), v5);// log("txtag", "load done!")
  if ( J_dlsym )
  {
    J_dlsym_1 = J_dlsym;
    v24 = (void *)J_dlsym(J_vm, J_env_1);
  }
  else
  {
    v24 = &unk_10004;
  }
  return v24;
}

这里需要重点关注的就是 dlopen 和 dlsym 这两个函数了,其他都是一些字符串操作的内容,对脱壳没有什么太大的帮助。其实 dlopen 都不需要关注了,直接在最后 J_dlsym 这里下断,F7 。

跳到 .so 外的一段内存,很明显是程序 mmap 进来的内容,至于怎么 mmap mmprotect 之类的不需要做太多关注,看看这里指向代码的功能。

debug150:7860E800 ; signed int __fastcall sub_7860E800(int a1, int)
debug150:7860E800 sub_7860E800
debug150:7860E800
debug150:7860E800 var_14= -0x14
debug150:7860E800
debug150:7860E800 PUSH            {R0,R1,R4-R6,LR}
debug150:7860E802 ADD             R5, SP, #0x18+var_14
debug150:7860E804 MOVS            R4, #0
debug150:7860E806 MOVS            R1, R5
debug150:7860E808 LDR             R2, =0x10006
debug150:7860E80A MOVS            R6, R0
debug150:7860E80C STR             R4, [SP,#0x18+var_14]
debug150:7860E80E BL              J_getEnv
debug150:7860E812 CMP             R0, R4
debug150:7860E814 BEQ             loc_7860E844
debug150:7860E816 MOVS            R0, R6
debug150:7860E818 MOVS            R1, R5
debug150:7860E81A LDR             R2, =0x10004
debug150:7860E81C BL              J_getEnv
debug150:7860E820 CMP             R0, R4
debug150:7860E822 BEQ             loc_7860E848
debug150:7860E824 MOVS            R0, R6
debug150:7860E826 MOVS            R1, R5
debug150:7860E828 LDR             R2, =0x10002
debug150:7860E82A BL              J_getEnv
debug150:7860E82E CMP             R0, R4
debug150:7860E830 BEQ             loc_7860E84C
debug150:7860E832 MOVS            R0, R6
debug150:7860E834 MOVS            R1, R5
debug150:7860E836 LDR             R2, =0x10001
debug150:7860E838 BL              J_getEnv
debug150:7860E83C CMP             R0, R4
debug150:7860E83E BNE             loc_7860E85E
debug150:7860E840 LDR             R4, =0x10001
debug150:7860E842 B               loc_7860E84E
debug150:7860E844 ; ---------------------------------------------------------------------------
debug150:7860E844
debug150:7860E844 loc_7860E844                            ; CODE XREF: sub_7860E800+14 j
debug150:7860E844 LDR             R4, =0x10006
debug150:7860E846 B               loc_7860E84E
debug150:7860E848 ; ---------------------------------------------------------------------------
debug150:7860E848
debug150:7860E848 loc_7860E848                            ; CODE XREF: sub_7860E800+22 j
debug150:7860E848 LDR             R4, =0x10004
debug150:7860E84A B               loc_7860E84E
debug150:7860E84C ; ---------------------------------------------------------------------------
debug150:7860E84C
debug150:7860E84C loc_7860E84C                            ; CODE XREF: sub_7860E800+30 j
debug150:7860E84C LDR             R4, =0x10002
debug150:7860E84E
debug150:7860E84E loc_7860E84E                            ; CODE XREF: sub_7860E800+42 j
debug150:7860E84E                                         ; sub_7860E800+46 j ...
debug150:7860E84E LDR             R3, [SP,#0x18+var_14]
debug150:7860E850 CMP             R3, #0
debug150:7860E852 BEQ             loc_7860E85E
debug150:7860E854 BL              sub_7860EFB8
debug150:7860E858 LDR             R0, [SP,#0x18+var_14]
debug150:7860E85A BL              sub_7860E7C4
debug150:7860E85E
debug150:7860E85E loc_7860E85E                            ; CODE XREF: sub_7860E800+3E j
debug150:7860E85E                                         ; sub_7860E800+52 j
debug150:7860E85E MOVS            R0, R4
debug150:7860E860 POP             {R1,R2,R4-R6,PC}
debug150:7860E860 ; End of function sub_7860E800

还是一堆的赋值操作略过不表,看到执行了 [sub_7860EFB8] [sub_7860E7C4] 两个函数,分别看下这两个函数的作用。

signed int sub_7860EFB8()
{
  signed int result; // r0@1

  dword_78626068 = (int)"java/lang/String";
  dword_7862606C = (int)"getBytes";
  dword_78626070 = (int)"()[B";
  dword_78626074 = (int)"android/os/Build$VERSION";
  dword_78626078 = (int)"SDK_INT";
  dword_7862607C = (int)"I";
  .......
}

[sub_7860EFB8] 全是赋值操作,跳过。下面是 F5 之后的 [sub_7860E7C4],主要执行了 [sub_7860E780] 然后打了个 registerNatives 相关的 log,相信作安卓逆向的同学看到 registerNative 这个都会比较激动吧,跟进 [sub_7860E780]。

int __fastcall sub_7860E7C4(int a1)
{
  int v1; // r4@1

  v1 = sub_7860E780(a1, (int)"com/tencent/StubShell/TxAppEntry", (int)&dword_78626004, 5);
  if ( v1 )
    v1 = 1;
  else
    log(3, (int)"SecShell", (int)"registerNatives Fail");
  return v1;
}
F5 后的伪代码如下,亮眼的 0x35C 刚好是 registerNatives 相对于 JNINativeInterface 的偏移量,在这下断。

int __fastcall sub_7860E780(int a1, int a2, int a3, int a4)
{
  int v4; // r5@1
  int v5; // r4@1
  int v6; // r6@1
  int J_env; // r1@1
  int v8; // r0@2

  v4 = a4;
  v5 = a1;
  v6 = a3;
  J_env = (*(int (**)(void))(*(_DWORD *)a1 + 0x18))();
  if ( J_env )
  {
    v8 = (*(int (__fastcall **)(int, int, int, int))(*(_DWORD *)v5 + 0x35C))(v5, J_env, v6, v4);// registerNatives
    J_env = 1;
    if ( v8 < 0 )
    {
      ((void (__fastcall *)(signed int, const char *, const char *))log)(3, "SecShell", "register nativers error");
      J_env = 0;
    }
  }
  return J_env;
}
触发断点之后查看 R2 寄存器的值。

可以看到这里的 RegisterNative 是用来注册 native load(Landroid/content/Context;)V ,函数指针则是 [0x78615839],至于为什么是这样可以参考我 *这一篇文章。

接着跳入 [0x78615839],这里我直接贴 F5 + 重命名函数之后的伪代码了,如图。
可以看到先检查了 * sdk 版本,* dalvik Or art,* 是否存在 zjdroid,然后在分别进行了两个操作,因为这里我用的是 dalvik,所以跟进 [sub_78614CD0]。

_DWORD *__fastcall sub_78614CD0(int a1, int a2)
{
  int v2; // r6@1
  int v3; // r0@2
  int v4; // r4@2
  int v5; // r0@2
  int v6; // r0@2
  int v7; // r0@2
  int v8; // r7@2
  int v9; // ST00_4@3
  int v10; // ST00_4@3
  int v11; // r0@5
  int v12; // ST00_4@5
  const char *v13; // r1@6
  const char *v14; // r2@6
  int v15; // r0@16
  int v16; // r5@16
  _DWORD *v17; // r3@16
  int v18; // ST00_4@17
  int v19; // ST00_4@18
  const char *v20; // r1@20
  const char *v21; // r2@20
  int v22; // r4@21
  int v23; // r1@23
  const char *v24; // r1@24
  const char *v25; // r2@24
  int v26; // r4@33
  int v27; // ST14_4@37
  int v28; // r0@37
  int v29; // r1@38
  int v30; // r1@41
  int v31; // r5@44
  int v32; // ST00_4@44
  int v33; // r0@45
  int v34; // ST00_4@46
  int v35; // r5@46
  int v36; // r4@50
  int v37; // r4@54
  int v38; // r2@58
  int v39; // r3@59
  int v40; // r3@61
  int v41; // r3@62
  int v42; // r3@63
  int v43; // r3@64
  int v44; // r5@64
  int v45; // r2@65
  int v46; // r5@77
  int v47; // r2@77
  int v48; // r0@79
  int v49; // r0@80
  int v50; // r4@82
  int v51; // r0@84
  int v52; // r5@84
  int m; // r4@84
  int v54; // r0@86
  int v55; // r2@87
  const char *v56; // r3@87
  int v57; // ST00_4@87
  int v58; // r1@87
  int v59; // r0@87
  int v60; // r0@76
  int i; // r4@76
  int v62; // r0@90
  int v63; // r5@92
  int v64; // r0@92
  int j; // r4@92
  int v66; // r0@94
  int v67; // r5@95
  int v68; // r4@95
  int v69; // r0@95
  int k; // r4@95
  int v71; // r0@97
  int v72; // r5@98
  int v73; // r4@98
  int v74; // r7@98
  int v75; // r0@98
  int v76; // r0@98
  int l; // r4@98
  int v78; // r0@100
  int v79; // r4@101
  int v80; // r0@101
  int v81; // ST00_4@102
  int v82; // ST00_4@103
  _DWORD *result; // r0@105
  int v84; // [sp+10h] [bp-120h]@3
  int v85; // [sp+10h] [bp-120h]@76
  int v86; // [sp+14h] [bp-11Ch]@5
  signed int v87; // [sp+14h] [bp-11Ch]@37
  int v88; // [sp+14h] [bp-11Ch]@95
  int v89; // [sp+18h] [bp-118h]@3
  int v90; // [sp+18h] [bp-118h]@13
  int v91; // [sp+1Ch] [bp-114h]@1
  signed int v92; // [sp+20h] [bp-110h]@33
  int v93; // [sp+20h] [bp-110h]@51
  int v94; // [sp+24h] [bp-10Ch]@3
  int v95; // [sp+24h] [bp-10Ch]@95
  int v96; // [sp+28h] [bp-108h]@2
  signed int v97; // [sp+28h] [bp-108h]@51
  int v98; // [sp+2Ch] [bp-104h]@3
  int v99; // [sp+30h] [bp-100h]@13
  int v100; // [sp+34h] [bp-FCh]@13
  int v101; // [sp+38h] [bp-F8h]@2
  int v102; // [sp+3Ch] [bp-F4h]@13
  int v103; // [sp+40h] [bp-F0h]@37
  int v104; // [sp+44h] [bp-ECh]@3
  int v105; // [sp+4Ch] [bp-E4h]@92
  int v106; // [sp+54h] [bp-DCh]@2
  _DWORD *v107; // [sp+5Ch] [bp-D4h]@1
  int v108; // [sp+60h] [bp-D0h]@34
  char v109; // [sp+64h] [bp-CCh]@37
  char v110; // [sp+74h] [bp-BCh]@50
  int v111; // [sp+88h] [bp-A8h]@50
  char v112; // [sp+8Ch] [bp-A4h]@50
  int v113; // [sp+A0h] [bp-90h]@50
  char v114; // [sp+A4h] [bp-8Ch]@34
  int v115; // [sp+B8h] [bp-78h]@34
  signed int v116; // [sp+C4h] [bp-6Ch]@37
  int v117; // [sp+114h] [bp-1Ch]@1

  v2 = a1;
  v117 = *(_DWORD *)dword_78625DA4;
  v107 = (_DWORD *)dword_78625DA4;
  v91 = J_GC_object_method(
          a1,
          a2,
          (int)"android/content/Context",
          (int)"getClassLoader",
          (int)"()Ljava/lang/ClassLoader;");
  if ( !v91 )
    goto LABEL_105;
  v3 = getEnv(v2, "com/tencent/StubShell/TxAppEntry");
  v4 = v3;
  v5 = ((int (__fastcall *)(int, int, const char *, const char *))unk_78610864)(
         v2,
         v3,
         "mSrcPath",
         "Ljava/lang/String;");
  v101 = ((int (__fastcall *)(int, int, int))unk_78610872)(v2, v4, v5);
  v96 = J_copy_array_bytes(v2, v101);
  v6 = ((int (__fastcall *)(int, int, const char *, const char *))unk_78610864)(v2, v4, "mPKName", "Ljava/lang/String;");
  v7 = ((int (__fastcall *)(int, int, int))unk_78610872)(v2, v4, v6);
  v106 = J_copy_array_bytes(v2, v7);
  v8 = unk_78625DA8;
  if ( J_sdk_int > 10 )
  {
    v9 = *(_DWORD *)(unk_78625DA8 + 248);
    v104 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EE40)(
             v2,
             v91,
             *(_DWORD *)(unk_78625DA8 + 240),
             *(_DWORD *)(unk_78625DA8 + 244));
    v10 = *(_DWORD *)(v8 + 220);
    v98 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EE40)(
            v2,
            v104,
            *(_DWORD *)(v8 + 212),
            *(_DWORD *)(v8 + 216));
    v94 = J_getArrayLength(v2, v98);
    v89 = 0;
    v84 = 0;
    while ( 1 )
    {
      if ( v89 >= v94 )
      {
        v90 = 0;
        v102 = 0;
        v100 = 0;
        v99 = 0;
        goto LABEL_33;
      }
      v11 = ((int (__fastcall *)(int, int, int))unk_7860E8C8)(v2, v98, v89);
      v12 = *(_DWORD *)(v8 + 232);
      v86 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860EE40)(
              v2,
              v11,
              *(_DWORD *)(v8 + 224),
              *(_DWORD *)(v8 + 228));
      if ( !v86 )
        break;
      v84 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F770)(
              v2,
              v86,
              *(_DWORD *)(v8 + 236),
              *(_DWORD *)(v8 + 424));
      if ( !v84 )
      {
        v84 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F950)(
                v2,
                v86,
                *(_DWORD *)(v8 + 236),
                *(_DWORD *)(v8 + 424));
        if ( !v84 )
        {
          v13 = "SecShell";
          v14 = "SetDexClassLoaderCookie GetIntField fail";
          goto LABEL_10;
        }
      }
      if ( *(_DWORD *)v84 && !((int (__cdecl *)(_DWORD))unk_7861EC2C)(*(_DWORD *)v84) )
      {
        v90 = 0;
        v102 = 0;
        v100 = 0;
        v99 = 0;
        goto LABEL_33;
      }
LABEL_14:
      ++v89;
    }
    v13 = "SecShell";
    v14 = "SetDexClassLoaderCookie GetObjectField fail:pDexPathListElementsClassName";
LABEL_10:
    log(3, (int)v13, (int)v14);
    goto LABEL_14;
  }
  v15 = getEnv(v2, *(_DWORD *)(unk_78625DA8 + 456));
  v16 = ((int (__fastcall *)(int, int, int))unk_7861080E)(v2, v91, v15);
  v17 = (_DWORD *)(v8 + 204);
  if ( v16 )
  {
    v18 = *(_DWORD *)(v8 + 208);
    v99 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860EE40)(v2, v91, *(_DWORD *)(v8 + 456), *v17);
    v100 = ((int (__fastcall *)(int, int, _DWORD, const char *))unk_7860EE40)(v2, v91, *(_DWORD *)(v8 + 456), "mPaths");
    v102 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
             v2,
             v91,
             *(_DWORD *)(v8 + 456),
             "mFiles");
    v90 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
            v2,
            v91,
            *(_DWORD *)(v8 + 456),
            "mZips");
  }
  else
  {
    v19 = *(_DWORD *)(v8 + 208);
    v99 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860EE40)(v2, v91, *(_DWORD *)(v8 + 200), *v17);
    v102 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
             v2,
             v91,
             *(_DWORD *)(v8 + 200),
             "mFiles");
    v100 = 0;
    v90 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EE40)(
            v2,
            v91,
            *(_DWORD *)(v8 + 200),
            "mZips");
  }
  if ( !v99 )
  {
    v20 = "SecShell";
    v21 = "SetDexClassLoaderCookie GetObjectField fail:pmDexs";
    goto LABEL_48;
  }
  v22 = 0;
  v94 = J_getArrayLength(v2, v99);
  v84 = 0;
  while ( 1 )
  {
    if ( v22 >= v94 )
    {
      v98 = 0;
      v104 = 0;
      goto LABEL_33;
    }
    v23 = ((int (__fastcall *)(int, int, int))unk_7860E8C8)(v2, v99, v22);
    if ( !v23 )
    {
      v24 = "SecShell";
      v25 = "SetDexClassLoaderCookie GetObjectArrayElement fail";
LABEL_27:
      log(3, (int)v24, (int)v25);
      goto LABEL_30;
    }
    v84 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F770)(
            v2,
            v23,
            *(_DWORD *)(v8 + 236),
            *(_DWORD *)(v8 + 424));
    if ( !v84 )
    {
      v24 = "SecShell";
      v25 = "SetDexClassLoaderCookie GetIntField fail";
      goto LABEL_27;
    }
    if ( *(_DWORD *)v84 && !((int (__cdecl *)(_DWORD))unk_7861EC2C)(*(_DWORD *)v84) )
      break;
LABEL_30:
    ++v22;
  }
  v98 = 0;
  v104 = 0;
LABEL_33:
  v26 = sub_786123A4(v106, (int)"classes.dex", 0);
  v92 = 0;
  if ( !v26 )
  {
    ((void (__fastcall *)(char *, int, int *))unk_786124E8)(&v114, v96, &v108);
    log(3, (int)"SecShell", (int)"strSrcPath:%s");
    ((void (__fastcall *)(char *))unk_78612A54)(&v114);
    log(3, (int)"SecShell", (int)"strSrcPath:%s");
    v26 = sub_786123A4(v115, (int)"classes.dex", 0);
    ((void (__fastcall *)(char *))unk_786124A4)(&v114);
    if ( v26 )
    {
      v92 = 0;
    }
    else
    {
      v26 = ((int (__fastcall *)(int))unk_786117A4)(v84) - 40;
      v92 = 1;
    }
  }
  v27 = ((int (__fastcall *)(int))unk_786108CA)(v26 + 40);
  log(3, (int)"SecShell", (int)"orgDexOffset:%d");
  j_j_memset_0((int)&v114, 0, 112);
  memcpy((int)&v114, v26 + v27 + 40);
  j_j_memset_0((int)&v109, 0, 16);
  ((void (__fastcall *)(char *, char *, signed int, signed int))unk_78617E4C)(&v109, &v114, 112, 32);
  v103 = v26 + v27 + 40;
  v87 = v116;
  v28 = log(3, (int)"SecShell", (int)"fileSize:%d");
  if ( v92 )
  {
    v29 = v87;
    if ( v87 & 0xFFF )
      v29 = (v87 / 4096 + 1) << 12;
    v28 = ((int (__fastcall *)(int, int, signed int))unk_7861EC7C)(v26, v29, 3);
    if ( v28 )
    {
      v30 = v87;
      if ( v87 & 0xFFF )
        v30 = (v87 / 4096 + 1) << 12;
      v28 = ((int (__fastcall *)(int, int, signed int))unk_7861EC7C)(v26, v30, 5);
    }
  }
  v31 = ((int (__fastcall *)(int))unk_78610CD4)(v28);
  v32 = *(_DWORD *)errno();
  log(3, (int)"SecShell", (int)"mRes:%d error:%d");
  if ( v31 != -1
    || (v33 = log(3, (int)"SecShell", (int)"wrong code1"), ((int (__fastcall *)(int))unk_78610D24)(v33) != -1) )
  {
LABEL_50:
    ((void (__fastcall *)(char *, int, signed int, signed int))unk_78617E4C)(&v109, v103, 112, 32);
    ((void (__fastcall *)(char *, const char *, int *))unk_786124E8)(&v110, "/data/data/", &v108);
    ((void (__fastcall *)(char *, int))loc_7861261A)(&v110, v106);
    ((void (__fastcall *)(char *, char *, const char *))unk_786127D4)(&v112, &v110, "/mix.so");
    ((void (__fastcall *)(char *, const char *))loc_7861261A)(&v110, "/mix.dex");
    v36 = sub_7860ECA4(v2, v111);
    sub_7860ECA4(v2, v113);
    if ( ((int (__fastcall *)(int, int, int))unk_786112A0)(v2, v101, v111)
      && (v93 = J_GC_static_method(
                  v2,
                  (int)"dalvik/system/DexFile",
                  (int)"loadDex",
                  (int)"(Ljava/lang/String;Ljava/lang/String;I)Ldalvik/system/DexFile;")) != 0 )
    {
      v101 = v36;
      v97 = 0;
    }
    else
    {
      log(3, (int)"SecShell", (int)"load mix.dex failed");
      ((void (__fastcall *)(char *, int))unk_78612738)(&v110, v96);
      v93 = J_GC_static_method(
              v2,
              (int)"dalvik/system/DexFile",
              (int)"loadDex",
              (int)"(Ljava/lang/String;Ljava/lang/String;I)Ldalvik/system/DexFile;");
      log(3, (int)"SecShell", (int)"load org.dex end");
      v97 = 1;
    }
    v37 = ((int (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860F770)(
            v2,
            v93,
            *(_DWORD *)(v8 + 236),
            *(_DWORD *)(v8 + 424));
    if ( !v37 )
    {
      v37 = ((int (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860F950)(
              v2,
              v93,
              *(_DWORD *)(v8 + 236),
              *(_DWORD *)(v8 + 424));
      if ( !v37 )
        log(3, (int)"SecShell", (int)"testCookie is null");
    }
    if ( v97 )
    {
      v38 = J_sdk_int;
    }
    else
    {
      v38 = J_sdk_int;
      if ( J_sdk_int > 10 )
      {
        v39 = *(_DWORD *)(*(_DWORD *)(v37 + 8) + 4);
        goto LABEL_64;
      }
    }
    v40 = *(_DWORD *)(v37 + 12);
    if ( v38 == 8 )
      v41 = *(_DWORD *)(v40 + 36);
    else
      v42 = *(_DWORD *)(v40 + 40);
LABEL_64:
    v108 = 0;
    ((void (__fastcall *)(int, int, signed int, int *))unk_78611690)(v2, v103, v87, &v108);
    v43 = v108;
    v44 = *(_DWORD *)(v108 + 4);
    if ( v97 )
    {
      *(_DWORD *)(v37 + 8) = v108;
      *(_BYTE *)(v37 + 4) = 1;
      if ( J_sdk_int == 10 )
        *(_DWORD *)(v84 + 16) = v103;
    }
    else
    {
      v45 = J_sdk_int;
      if ( J_sdk_int > 10 )
      {
        if ( J_sdk_int <= 18 && chk_yunos() )
          ((void (__fastcall *)(_DWORD, int, int))unk_78615CF0)(*(_DWORD *)(*(_DWORD *)(v37 + 8) + 4), v44, J_sdk_int);
        else
          ((void (__fastcall *)(_DWORD, int, int))unk_78615D04)(*(_DWORD *)(*(_DWORD *)(v37 + 8) + 4), v44, J_sdk_int);
LABEL_75:
        if ( J_sdk_int <= 10 )
        {
          v60 = getEnv(v2, "dalvik/system/DexFile");
          v85 = ((int (__fastcall *)(int, int, int, _DWORD))unk_78610880)(v2, v94 + 1, v60, 0);
          ((void (__fastcall *)(int, int, _DWORD, int))unk_7860E8D6)(v2, v85, 0, v93);
          for ( i = 0; i < v94; ((void (__fastcall *)(int, int, int, int))unk_7860E8D6)(v2, v85, i, v62) )
            v62 = ((int (__fastcall *)(int, int, int))unk_7860E8C8)(v2, v99, i++);
          if ( v100 )
          {
            v63 = J_getArrayLength(v2, v100);
            v64 = getEnv(v2, "java/lang/String");
            v105 = ((int (__fastcall *)(int, int, int, _DWORD))unk_78610880)(v2, v63 + 1, v64, 0);
            ((void (__fastcall *)(int, int, _DWORD, int))unk_7860E8D6)(v2, v105, 0, v101);
            for ( j = 0; j < v63; ((void (__fastcall *)(_DWORD, _DWORD, int, _DWORD))unk_7860E8D6)(v2, v105, j, v66) )
              v66 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v100, j++);
          }
          v67 = J_getArrayLength(v2, v102);
          v68 = getEnv(v2, "java/io/File");
          v88 = ((int (__fastcall *)(_DWORD, int, _DWORD, _DWORD))unk_78610880)(v2, v67 + 1, v68, 0);
          v69 = J_getObjectClass(v2, v68, "", "(Ljava/lang/String;)V");
          v95 = J_NewObjectV(v2, v68, v69, v101);
          ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v88, 0, v95);
          for ( k = 0; k < v67; ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v88, k, v71) )
            v71 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v102, k++);
          v72 = J_getArrayLength(v2, v90);
          v73 = getEnv(v2, "java/util/zip/ZipFile");
          v74 = ((int (__fastcall *)(_DWORD, int, _DWORD, _DWORD))unk_78610880)(v2, v72 + 1, v73, 0);
          v75 = J_getObjectClass(v2, v73, "", "(Ljava/io/File;)V");
          v76 = J_NewObjectV(v2, v73, v75, v95);
          ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v74, 0, v76);
          for ( l = 0; l < v72; ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v74, l, v78) )
            v78 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v90, l++);
          v79 = unk_78625DA8;
          v80 = getEnv(v2, *(_DWORD *)(unk_78625DA8 + 456));
          if ( ((int (__fastcall *)(int, int, int))unk_7861080E)(v2, v91, v80) )
          {
            v81 = *(_DWORD *)(v79 + 208);
            ((void (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 456),
              *(_DWORD *)(v79 + 204));
            ((void (__fastcall *)(int, int, _DWORD, const char *))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 456),
              "mPaths");
            ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 456),
              "mFiles");
            v55 = *(_DWORD *)(v79 + 456);
            v59 = v2;
            v58 = v91;
            v56 = "mZips";
          }
          else
          {
            v82 = *(_DWORD *)(v79 + 208);
            ((void (__fastcall *)(int, int, _DWORD, _DWORD))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 200),
              *(_DWORD *)(v79 + 204));
            ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, const char *))unk_7860EEFC)(
              v2,
              v91,
              *(_DWORD *)(v79 + 200),
              "mFiles");
            v55 = *(_DWORD *)(v79 + 200);
            v59 = v2;
            v58 = v91;
            v56 = "mZips";
          }
          goto LABEL_88;
        }
        v46 = getEnv(v2, "dalvik/system/DexPathList$Element");
        v47 = J_getObjectClass(v2, v46, "", "(Ljava/io/File;Ljava/util/zip/ZipFile;Ldalvik/system/DexFile;)V");
        if ( v47
          || (((void (__fastcall *)(int))unk_78610804)(v2),
              (v47 = J_getObjectClass(v2, v46, "", "(Ljava/io/File;Ljava/io/File;Ldalvik/system/DexFile;)V")) != 0) )
        {
          v48 = J_NewObjectV(v2, v46, v47, 0);
        }
        else
        {
          ((void (__fastcall *)(int))unk_78610804)(v2);
          v49 = J_getObjectClass(v2, v46, "", "(Ljava/io/File;ZLjava/io/File;Ldalvik/system/DexFile;)V");
          if ( !v49 )
          {
            v50 = 0;
LABEL_84:
            v51 = getEnv(v2, "dalvik/system/DexPathList$Element");
            v52 = ((int (__fastcall *)(int, int, int, _DWORD))unk_78610880)(v2, v94 + 1, v51, 0);
            ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v52, 0, v50);
            for ( m = 0; m < v94; ((void (__fastcall *)(_DWORD, _DWORD, _DWORD, _DWORD))unk_7860E8D6)(v2, v52, m, v54) )
              v54 = ((int (__fastcall *)(_DWORD, _DWORD, int))unk_7860E8C8)(v2, v98, m++);
            v55 = *(_DWORD *)(v8 + 212);
            v56 = *(const char **)(v8 + 216);
            v57 = *(_DWORD *)(v8 + 220);
            v58 = v104;
            v59 = v2;
LABEL_88:
            ((void (__fastcall *)(int, int, int, const char *))unk_7860EEFC)(v59, v58, v55, v56);
            dword_78626714 = (*(int (__fastcall **)(int, int))(*(_DWORD *)v2 + 84))(v2, v93);
            log(3, (int)"SecShell", (int)&unk_78622BD8);
            ((void (__fastcall *)(char *))unk_786124A4)(&v112);
            ((void (__fastcall *)(char *))unk_786124A4)(&v110);
            goto LABEL_105;
          }
          v48 = J_NewObjectV(v2, v46, v49, 0);
        }
        v50 = v48;
        goto LABEL_84;
      }
      *(_BYTE *)(v37 + 4) = 1;
      *(_DWORD *)(v37 + 8) = v43;
      if ( v45 == 10 )
        *(_DWORD *)(v84 + 16) = v103;
    }
    *(_DWORD *)(v37 + 12) = 0;
    goto LABEL_75;
  }
  log(3, (int)"SecShell", (int)"wrong code");
  v34 = ((int (__fastcall *)(const char *, signed int))unk_7861EDBC)("/dev/zero", 2);
  v35 = ((int (__cdecl *)(_DWORD, signed int))unk_7861EEEC)(0, v87);
  ((void (__fastcall *)(int))unk_7861EE2C)(v34);
  if ( v35 )
  {
    memmove();
    v103 = v35;
    goto LABEL_50;
  }
  v20 = "SecShell";
  v21 = "mmap fail";
LABEL_48:
  log(3, (int)v20, (int)v21);
LABEL_105:
  result = v107;
  if ( v117 != *v107 )
    _stack_chk_fail(v107);
  return result;
}
*ClassLoader, *loadDex(), *multidex 这些关键字是不是想到了什么 ?

Surprise

其实 QEver 的脚本可以直接 dump 出加载后正确完整的 dex,不知道为啥乐固没想到这一点。
Read More

2017年4月25日星期二

ANDROID 逆向实例(七)- Ali 加固(2017.01)

也算是第一次独立完成壳子的分析大概花了两周的时间,通过这个壳子学到了很多,感觉学习价值还不错 ~

先 apktool 看一下加固前后的目录结构,对比图如下,看出来主要修改的地方有两个,lib 下的 libdemolish.so 和 libdemolishdata.so,另一处是 com.ali.fixHelper 类。
这里还有一个需要留意的地方,原来的 Java 类都没有消失,目录结构也没有变化,先看一下加固后的 AndroidMainfest.xml。这里不贴图了,比较之后发现源 Mainfest 和加固后的 Mainfest 没有区别,入口还是 com.example.helloworld.MainActivity 类,看一下加固后的这个类长什么样。
乍一看和之前的类很像,只不过多了一个 [static constructor <clinit>()V] 方法, 而其他原函数都变成了 native 函数,可以看到这里比较关键的是调用了 [Lcom/ali/fixHelper;->fixfunc([I)V],不贴具体的 fixHelper 类代码了没什么实质性的内容,主要操作 load 了 libdemo.so 然后执行了 [public static native soInit()V] 方法。

不在 Java 层做过多的停留了,直接跳到 Native 层,IDA 打开 .so 看看。果然,无法正确打开,尝试了一下动态 dump 出来,用工具修复再静态加载到 IDA 还是报错,而且载入之后 IDA 的符号表为空,不过好在可以载入,虽然数据比较混乱。算了,不要函数表了,从零开始。

因为这里 dump 的没有函数表,所以动态调试的时候就算载入 libdemo.so 之后也没有办法通过 IDA 常规办法的 [Module -> Name] 来找到 JNI_Onload 函数下断点。[greadelf -a dump.so],可以看到这里是有 .init_array 的,不过这里的 .init_array 并没有什么用,不赘述了。
既然常规简单的办法不行,还可以通过逆向 libdvm.so 找到调用 JNI_Onload 的位置,我这 libdvm.so 调用 JNI_Onload 的位置在 +53A08,在这下个断点,然后动态调试进去看看。
在 +53A08 下好断点之后可以看到调用 JNI_Onload 时确实停下来了,F7,又回到了 libdemolish.so。这里因为没有函数表,先简单过一遍把 BL、BLX、PC 操作都列出来,可以 C 的地方都 make code,这里比较繁琐,需要一点耐心具体的过程不一个个说了,下面是我当时简单列的一个操作表。

+ 4214  # SDK_INT

+ 6920 # android.app.ActivityThread
+ 6B1C # currentActivityThread

+ 6B36 -> +8AB0 -> +8AD8 # new_obj_ActivityThread -> +8728
+ 6B48 -> +8AB0 -> +8AD8 # new_obj_CurrentActivityThread -> +8728

+ 6AFC -> +663E (IMP) -> JNI 操作
+ 6AAC -> +673A (IMP)-> JNI 操作

+ 6DD8 ->  +8BA4 # getString # "/data/app/com.example.helloworld-1.apk"
+ 6BC2 ->  检查是否存在 mobiledata.so
+ 70A6 # open("/data/app-lib/com.example.helloworld-1/libdemolishdata.so")
+ 6EEA # stat("mobiledata") -> +7028 # close()
+ 46E0 # malloc -> memset
+ 477C # malloc -> memset -> android.os.Binder
+ 4812 # malloc -> memset
+ 883e # 字符变换
+ 6FE8 # log("Ali", "parse data success")

+ 4298 # 检查 yunos
+ 3F86 # BLX R3 -> JNI 调用
+ 4a14 # check dvm OR art
+ 3418 # log("sdk_version: %d, dalvik, yunos: %d")

+ 3570 clock()
+ 3468 log("init success, total (%.2f ms)")

+ 8678 memset()
+ 8682 memcpy(0xBEA2B06C, ptr_dex, 0x70) # 上面的 dex 片段

while(times < 3){
+ 73EA 失败触发 -> log("find codeoffset by dexfileid error") # find_dex_codeoff

+ 74DC # strlen("Lcom/example/helloworld/MainActivity;") -> + 752C # calloc(len + 2)
+ 753E # strcpy("740A40E8", "Lcom/example/helloworld/MainActivity;")

+ 7668 # strlen("") -> + 760A # calloc(len + 2) -> memcpy(740A4E40, "", 6)
+ 7B2E # strlen("V") -> + 7ACC # strlen("V") -> memcpy(740A4E52, "V",) -> "()V"
+ 71FE 失败触发 -> log("") # chk_find_class_method
}

虽然比较乱,不过从上面的表可以看到整个流程已经很清楚了,先做了一系列初始化操作,检查 libdemolishdata.so 是否存在、检查是否 yunos、检查 dvm Or art,等等。初始化不赘述了,然后可以看到执行了三遍 [+ 73EA # find_dex_codeoff](+ 73EA 对应的是 libdemolish.so 的偏移量,find_dex_codeoff 是我方便理解取的名字下同),F5 + 还原后的伪代码如图所示。
如果执行失败则 [log("find codeoffset by dexfileid error")],看到 codeoff 其实 ali 的加固流程已经很明显了,通过 static 方法最先执行的特性 load 一个 libdemolish.so 然后再动态修复 odex 执行时对应 method 的 codeoff。

也正是这个原因,所以之前看到的加固后的类结构并没有发生变化只是原有的方法变成了 native 方法,其实这里这个 native 也只不过是吓唬人的纸老虎罢了。

然后跟进 [+ 71FE # chk_find_class_method],F5 + 修复之后如下所示,检查是否有对应的类和方法,如果没有的话 log 失败日志。
而之所以 find_dex_codeoff 和 chk_find_class_method 在 while 里执行了三遍,是因为加固前的 MainActivity 类里一共有三个方法[init, onCreate, onCreateOptions] 在加固后的 codeoff 都被修改了。

那么现在思路已经很清楚了,动态调试时在 [chk_find_class_method] 执行结束的位置下断点,然后看后续的操作。
我这里在对 [MainActivity->onCreate] 的 [chk_find_class_method] 结束后下断电,然后一路单步执行到这里,看到这个 R3 很可疑,因为它指向的是内存 libdemolishdata.so 片段,这里用的是 [R3 + #0x10] 位置的内容,即 0x73861068。

0x73861068 指向的是 [6f 20 1c 00 21 00 15 00 03 7f 6e 20 b6 13 01 00 0e 00] 这么一串内容,而这一串恰巧是原 onCreate 的 dalvik opcode!下图是通过 010Editor 看到加固前 onCreate 的 insns。
通过 dalvik opcode 对查表可以还原出 onCreate 的 smali 代码。

使用相同的方法可以对应恢复其他 Class 和 Method,这里不一一说明了,Crack 加固成功。
Read More

2017年2月27日星期一

ANDROID 逆向实例(六)- 梆梆加固(2015.08)

还是从网上找到的样本,拿来做加固分析的例子。

可以看到,加固之后多了两个 .so 文件、一个 com.edog 的 smali 文件夹,My.XuanAo.LiuYao 这个目录是本身 apk 的代码结构,所以猜测加固全是在 com.edog 和 .so 文件里做的。

看下 AndroidManifest.xml。
可以看到原 apk 的 activity 没变,还是 .main,但是 application 变成新增的 com.edog.AppWrapper,因为 application 会在 activity 之前加载,所以判断加固正是在这里做的。

看下 smali,这里比较简单,不做过多的展开,直接跳到关键的内容。


可以看到这里在 libedog.so 里定义了一个 native 函数 d1,然后在 Utils.smali 调用了这个函数,IDA 动态调试跟进看一下。
在加载 libedog.so 的时候下断电,可以看到右侧的的 Java_com_edog_Elibrary_d1 函数,并且这个 .so 加载之后没有 .init(_array) 和 JNI_Onload 段,所以该函数就是之前定义的 native d1。

F5。
可以看到 d1 的大体结构,还算比较简单,具体的每个函数的功能不详细分析了,有反调试的,有解密的,等等。比较关键的是 restore 这个函数。

“dvmResolveClass”,看到这个可以明确使用的是 Dex 的类抽取做加固,通过动态地替换 DexCode 里的内容(见 Android 逆向学习笔记 (八)- Classes.dex 结构简单介绍)打到加固的目的。

如果这种加固在还原正确的 DexCode 之后没有擦出 insns 的话,可以使用 Qever 提供的一个小脚本来脱壳。

__author__ = 'QEver'

DUMP_FILE_PREFIX = r'd:/'
USER_DEX_FILES_OFFSET = 0x334
LOADED_CLASSES_OFFSET = 0xAC
JAR_NAME_OFFSET = 0x24
SIZE_OF_DEY_HEADER = 0x28
DESC_OFFSET_OF_CLASS_OBJECT = 0x18
METHOD_OFFSET_OF_CLASS_OBJECT = 0x60
SIZE_OF_STRUCT_METHOD = 0x38

import os
import idaapi
import binascii


def read_data(ea, size):
    return idaapi.dbg_read_memory(ea, size)


def read_dword(ea):
    val = int(binascii.hexlify(idaapi.dbg_read_memory(ea, 4)), 16)
    r = (val & 0xff) << 24 | (val & 0xff00) << 8 | (val & 0xff0000) >> 8 | (val & 0xff000000) >> 24
    return r


def read_bool(ea):
    val = int(binascii.hexlify(idaapi.dbg_read_memory(ea, 1)), 16)
    return val


def read_str(ea, max=256):
    c = ''
    while True:
        x = idaapi.dbg_read_memory(ea, 1)
        ea = ea + 1
        max = max - 1
        if x == '\0' or max < 0:
            break
        c += x
    return c


class DvmDex:
    def __init__(self, ea):
        self.ea = ea
        self.pDexFile = read_dword(ea)
        self.baseAddr = read_dword(self.pDexFile + 0x2c)

    def p(self):
        print 'pDexFile = %x' % self.pDexFile
        print 'baseAddr = %x' % self.baseAddr


class ClassObject:
    def __init__(self, ea):
        self.ea = ea
        self.desc_addr = read_dword(ea + DESC_OFFSET_OF_CLASS_OBJECT)

    def get_descriptor(self):
        return read_str(self.desc_addr)


class DexOrJar:
    def __init__(self, ea):
        self.ea = ea
        self.filename = read_dword(ea)
        self.isDex = read_bool(ea + 4)
        self.pRawDexFile = read_dword(ea + 8)
        self.pJarFile = read_dword(ea + 12)

    def get_filename(self):
        if self.isDex == 0:
            return read_str(read_dword(self.pJarFile + JAR_NAME_OFFSET))
        return read_str(self.filename)

    def get_dvmdex(self):
        if self.isDex == 0:
            return read_dword(self.pJarFile + JAR_NAME_OFFSET + 4)
        return read_dword(self.pRawDexFile + 4)

    def p(self):
        print 'filename = %s' % read_str(self.filename)
        print 'isDex = %d' % self.isDex
        print 'pRawDexFile = %x' % self.pRawDexFile
        print 'pJarFile = %x' % self.pJarFile


class HashTable:
    def __init__(self, ea):
        self.ea = ea
        self.tableSize = -1
        self.numEntries = -1
        self.pEntries = None
        self.do_init()

    def do_init(self):
        self.tableSize = read_dword(self.ea)
        self.numEntries = read_dword(self.ea + 4)
        self.pEntries = read_dword(self.ea + 12)

    def get_table_size(self):
        return self.tableSize

    def get_num_entries(self):
        return self.numEntries

    def get_pentries(self):
        return self.pEntries

    def p(self):
        print 'tableSize = %d' % self.tableSize
        print 'numEntries = %d' % self.numEntries
        print 'pEntries = %x' % self.pEntries


class Method:
    def __init__(self, ea):
        self.ea = ea

    def get_name(self):
        addr = self.ea + 0x10
        return read_str(read_dword(addr))

    def get_insns(self):
        addr = self.ea + 0x20
        return read_dword(addr)

    def get_address(self):
        return self.ea


def get_gdvm_address():
    return idaapi.get_debug_name_ea("gDvm")


def dump_all_dex(prefix=DUMP_FILE_PREFIX):
    gdvm = get_gdvm_address()
    print '[*] gDvm = 0x%x' % gdvm
    user_dex_files = read_dword(gdvm + USER_DEX_FILES_OFFSET)
    print '[*] gDvm.user_dex_files = 0x%x' % user_dex_files

    ht = HashTable(user_dex_files)

    max_size = ht.get_table_size()
    size = ht.get_num_entries()
    p = ht.get_pentries()
    print '[*] Found %s items in Dex Table' % size
    for i in range(max_size):
        x = read_dword(p)

        p += 8
        if x == 0:
            continue
        doj = DexOrJar(x)
        print '[*] Dex in Address 0x%x, isDex = %d' % (x, doj.isDex)
        addr = doj.get_dvmdex()
        name = doj.get_filename()
        print '[*] found file : %s , dvmdex = 0x%x' % (name, addr)

        dd = DvmDex(addr)
        addr = dd.baseAddr
        base = addr - SIZE_OF_DEY_HEADER
        size = read_dword(addr + 0x20) + SIZE_OF_DEY_HEADER
        flag = read_str(base, 3)
        if flag != 'dey':
            base = addr
            size = size = read_dword(addr + 0x20)

        print '[*] found odex file = 0x%x <%s>, size = 0x%x' % (base, read_str(base, 7).replace('\n', '.'), size)

        name = os.path.basename(name)
        path = os.path.join(prefix, name)

        print '[*] Write to %s' % path
        data = read_data(base, size)

        f = open(path, 'wb')
        f.write(data)
        f.close()
        print '[*] Finish Write'


def find_class(name):
    gDvm = get_gdvm_address()
    loaded_classes = read_dword(gDvm + LOADED_CLASSES_OFFSET)

    ht = HashTable(loaded_classes)
    max_size = ht.get_table_size()
    size = ht.get_num_entries()
    p = ht.get_pentries() + 4
    print 'Finding for %d items, may take a long time...' % max_size
    for i in range(max_size):
        x = read_dword(p)
        p = p + 8
        if x == 0:
            continue
        c = ClassObject(x)
        s = c.get_descriptor()
        if s == name:
            print '[*] Found Class <%s> : 0x%x' % (name, x)
            return x
        if s.find(name) != -1:
            print '[*] Found Class <%s> : 0x%x' % (s, x)


def list_method(class_addr):
    x = ClassObject(class_addr)
    print '[*] List All Method of %s' % x.get_descriptor()
    m = class_addr + METHOD_OFFSET_OF_CLASS_OBJECT
    directMethodCount = read_dword(m)
    directMethodTable = read_dword(m + 4)
    virtualMethodCount = read_dword(m + 8)
    virtualMethodTable = read_dword(m + 12)

    for i in range(directMethodCount):
        method = Method(directMethodTable)
        directMethodTable = directMethodTable + SIZE_OF_STRUCT_METHOD
        print method.get_name(), hex(method.get_address())

    for i in range(virtualMethodCount):
        method = Method(virtualMethodTable)
        virtualMethodTable = virtualMethodTable + SIZE_OF_STRUCT_METHOD
        print method.get_name()


def list_all_class():
    gDvm = get_gdvm_address()
    loaded_classes = read_dword(gDvm + LOADED_CLASSES_OFFSET)

    ht = HashTable(loaded_classes)
    max_size = ht.get_table_size()
    size = ht.get_num_entries()
    p = ht.get_pentries() + 4
    for i in range(max_size):
        x = read_dword(p)
        p = p + 8
        if x == 0:
            continue
        c = ClassObject(x)
        print c.get_descriptor()

if __name__ == '__main__':
    #list_all_class()
    #find_class('alibaba')
    #list_method(class_addr) 
    dump_all_dex('c:/test/')

使用这个脚本有一个需要注意的问题,这里是通过对应的 gDvm.userDexFile 来找到不同方法的 DexCode,所以使用不同型号、版本的测试机时 USER_DEX_FILES_OFFSET 需要做对应修改。

另外,也可以使用 find_class 来实现查找对应 Method 的 DexCode。

使用 QEver 的脚本脱壳具有一定的局限性,如果 Method 调用之后擦除 insns 那么就没法使用该脚本,不过可以通过 DexHunter 脱壳,他们的原理类似,只不过 DexHunter 增加了动态脱壳时机的判断。

其实,每一种脱壳都有 easy way and hard way,上面说的都是 easy way,hard way 则是通过理解 dvmResolveClass 的实现方式,通过对应的解密方式来还原。这种方式不在此讨论,详细脱壳方法可以在下面参考链接中看到。

Reference

* QEver: 一个dex脱壳脚本
* APK加固之类抽取分析与修复
Read More

2017年1月19日星期四

ANDROID 逆向实例(五)- 360 QhCrackMe

分析一个从看雪上拿过来的 apk。

看名字像是某次数字出的题目?不过毕竟年代久远,比较简单。

baksmail 反编译,就两个需要注意的文件 MainActivity.smali 和 MainActivity$1.smali 其实就是一个 MainActivity 类了,先进入程序看下是什么样的 CrackMe。


大概就长这样,可以看出来程序因为是有一个隐藏的方法,把用户输入的三个内容进行比较是否正确,看看 smali 文件先。没什么太多东西,不贴代码了,主要就是注册了一个 Native 函数 verify,然后把 verify 和 OnClick 绑定。

只有 libqihoo.so 所以也不用去区分是哪个 so 文件里的方法,IDA 看一下。


静态看不了,动态调试一下看看。


动态调试没问题了,不用关系代码是怎么还原的,反正,还原了就好,F5 。


看到这个 860 就知道了,对应的是 RegisterNatives 的偏移 0x35C,所以此时这个 &unk_78068004 肯定保存的就是动态注册 verify 时需要的指针。(为什么可以看之前的逆向笔记)跟进看到。


三个地址,分别指向的是动态注册的:函数名、函数类型、函数代码,前面两个不看了,直接看第三个指针指向的 0x78061FF4。


这里 F5 的时候出现一个 sp 不平的问题,怎么解决这个问题呢,定位到指针不平的地址,然后通过 Alt + k 快捷键,将不平的地方配平就可以 F5 了。


最后的 verify 函数就是这样了,里面掉用了一些其他的函数,这些函数也没做特殊的处理,至此 verify 原型已经出来了,有兴趣的可以继续往下分析。
Read More

2016年12月13日星期二

ANDROID 逆向实例(四)- com.qtfreet.crackme001

现在很多 apk 在开发中为了保证代码不被轻易逆向出来会对关键函数采用 ndk 的方式用 c 语言和 java 代码进行交互,所以学会分析 .so 文件(加载流程、方式等)对安卓逆向来说是比较重要的。

这里借助一个 CrackMe.apk 实例来帮忙分析。反编译 apk 文件得到 smali 代码,提取关键代码如下:

# direct methods
.method static constructor ()V
    .locals 2

    .prologue
    .line 15
    sget v0, Landroid/os/Build$VERSION;->SDK_INT:I

    const/16 v1, 0x14

    if-ge v0, v1, :cond_0

    .line 16
    const-string v0, "qtfreet"

    invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

    .line 20
    :cond_0
    return-void
.end method

...

# virtual methods
.method public native check(Ljava/lang/String;)Z
.end method

分析后可以看到调用了 .so 文件里定义的 check 函数,然后这个 check 函数又和 button 的 OnClick 事件绑定,可以判断 check 则是判断用户输入 key 是否正确的函数。

用 IDA 对 libqtfreet.so 进行分析,首先先看看有没有 JAVA_com_qtfreet_crackme001_check 这样类似的函数,如果存在的话就说明 .so 文件里面没有对 check 函数进行动态注册,JAVA_com_qtfreet_crackme001_check 就是 smali 里面调用的 check 函数原型,直接分析即可。

如果没有发现上面类似函数的话则说明 smali 调用的 check 函数进行了动态注册,此时需要找到注册的位置。

这里需要注意一个问题,就是安卓加载 .so 文件的流程。安卓对 .so 文件加载时首先会查看  .init 或 .init_array 段是否存在,如果存在那么就先运行这段的内容,如果不存在的话那么就检查是否存在 JNI_Onload 存在则执行。

另外,因为 .init 或者 .init_array 在 IDA 动态调试的时候是不会显示出来的,所以需要静态分析出这两段的偏移量然后动态调试的时候计算出绝对位置,然后在 make code(快捷键:c),这样才可以看到该段内的代码内容。

查看 .init_array 段的地址有两种办法:
1.可以使用 IDA 加载 .so 文件,新建 “Segments” 视图,这里会列出不同类型的代码段信息,如下图所示。



2.可以使用二进制工具 readelf 来查看 .so 文件的结构,在 OS X 上面可以使用 greadelf 代替。


可以看到这里两个都指示 .init_array 存在,并且偏移量为 0x20790,IDA 定位到该位置查看如图。


定义了一些东西,这个不必太在意具体是什么内容后面用到的话再看就行了, .init_array 执行完之后就跳到 JNI_Onload 里面执行,IDA 跳到 JNI_Onload 之后 F5 查看伪代码。

这里会有一些小问题,就是 jni.h 头文件里的结构体因为 IDA 里面缺乏参考所以不能正确的对应到他们的类型,需要手动修改数据类型(IDA 快捷键 Y),例如 JNI_Onload(int a1) 这里的 a1 就应该修改成 JavaVM*,后面的一些则需要修改成 JNIEnv*。修改好之后的代码如下图所示:


看到高亮的 RegisterNatives,进去第三个参数 off_21458 位置查看保存的内容。


可以看到确实是 check 函数进行动态注册的地方,并且可以知道 check 真正的函数代码在 sub_8A64+1 位置(具体为什么可以参考 RegisterNative 源码),IDA 跟进 F5。


可以看到这里最后执行了一个 memcmp 所以可以确定 sub_8A64 开始是关键的判断代码,直接将 memcmp 的返回值修改即可成功破解 crackme。如果需要逆出 key 则需要结合之前的初始化变量和函数来看,可以自己试一试逆向 key 不过多阐述了。

ps: 文中省略了一些 Anti Anti-Debug 的内容,因为这个 crackme 的反调试并不复杂而且通过单一函数实现,可以直接将反调试函数 “movs r0,r0” 掉。

* crackme.apk 可以在 Github 上找到。
Read More

2016年11月24日星期四

ANDROID 逆向实例(三)- Gamex 恶意软件

流程太多 就不写了... 其实倒不是很复杂 代码没有做混淆和加壳还是比较好分析的

自己释放了几次得到最后的恶意 apk 执行 有兴趣的可以试试看 建议分析以加强对 smali 和安卓的了解 ~

* 相关文件上传到 Github 了

Reference

Read More

2016年10月11日星期二

ANDROID 逆向实例(二)- XDCTF Mobile 100

刚刚接触安卓逆向不久看见 XDCTF 有部分安卓逆向的题目,拿过来看看,试试手,第一次分析 CTF 相关的题目确实用了不少时间,不过同时也学到了不少东西。

常规手段还原出 smali 文件(这里偷了个懒,直接用的 apktool),没有混淆也没有 so 加固,只需要理出 smali 处理流程即可。根据 AndroidManifest.xml 找到 com.example.ring.myapplication.MainActivity, 故从对应的 MainActivity.smali 作为起点分析。

进行了一系列的初始化操作,然后理出比较重要的代码:进入 p 函数、绑定类 d 为点击事件。

.method protected onCreate(Landroid/os/Bundle;)V
    ...
    invoke-direct {p0}, Lcom/example/ring/myapplication/MainActivity;->p()V
    ...
    new-instance v1, Lcom/example/ring/myapplication/d;
    invoke-direct {v1, p0}, Lcom/example/ring/myapplication/d;->(Lcom/example/ring/myapplication/MainActivity;)V
    invoke-virtual {v0, v1}, Landroid/widget/Button;->setOnClickListener(Landroid/view/View$OnClickListener;)V
.end method

跟进一下 p() 函数:

.method private p()V
    ...
    const-string v1, "url.png"
    invoke-virtual {v0, v1}, Landroid/content/res/AssetManager;->open(Ljava/lang/String;)Ljava/io/InputStream;
    ...
    invoke-virtual {v0, v2, v3, v1}, Ljava/io/InputStream;->read([BII)I
    ...
    const/16 v1, 0x90
    const/4 v3, 0x0
    const/16 v4, 0x10
    invoke-static {v2, v1, v0, v3, v4}, Ljava/lang/System;->arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V
    ...
    iput-object v1, p0, Lcom/example/ring/myapplication/MainActivity;->v:Ljava/lang/String;
    ...
.end method

可以看到这里打开了 asset 目录下的 “url.png”,然后读取了 0x90 ~ 0xa0 位置的内容保存到 MainActivity->v 里,下面看下 d 类:

.method public onClick(Landroid/view/View;)V
    ...
    invoke-virtual {v0}, Landroid/widget/EditText;->getText()Landroid/text/Editable;
    move-result-object v0
    invoke-virtual {v0}, Ljava/lang/Object;->toString()Ljava/lang/String;
    move-result-object v0 
    ...
    # 下行是用来获取 MainActivity->v 的
    invoke-static {v2}, Lcom/example/ring/myapplication/MainActivity;->a(Lcom/example/ring/myapplication/MainActivity;)Ljava/lang/String;
    move-result-object v2
    invoke-static {v1, v2, v0}, Lcom/example/ring/myapplication/MainActivity;->a(Lcom/example/ring/myapplication/MainActivity;Ljava/lang/String;Ljava/lang/String;)Z
    move-result v0
    ...
    if-eqz v0, :cond_0
    ...
    const-string v2, "Congratulations!"

好的,发现成功条件了,当 MainActivity->a(String,String) 返回结果为真时程序成功,看下这里的两个 String,一个是 MainActivity->v(上面说过,其实是 url.png 的 0x90 ~ 0xa0 位置), 一个是用户输入的字符串(即为 Flag),下面先看一下 MainActivity->v :


xxd 可以看到 MainActivity->v 即是 "this_is_the_key.",然后跟进 MainActivity->a(String,String) 函数,看后续流程,由下面的代码可以看出,当用户输入的内容经过转换后和 array_0 位置的内容相同为成功条件。

.method private a(Ljava/lang/String;Ljava/lang/String;)Z
    ...
    new-instance v0, Lcom/example/ring/myapplication/c;
    invoke-direct {v0}, Lcom/example/ring/myapplication/c;->()V
    invoke-virtual {v0, p1, p2}, Lcom/example/ring/myapplication/c;->a(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
    ...
    const/16 v1, 0x20
    new-array v1, v1, [B
    fill-array-data v1, :array_0
    new-instance v2, Ljava/lang/String;    
    invoke-direct {v2, v1}, Ljava/lang/String;->([B)V      # 将 array_0 位置的内容转化成字符串
    invoke-virtual {v0, v2}, Ljava/lang/String;->equals(Ljava/lang/Object;)
    move-result v0
    return v0
.end method

在比较之前,函数初始化了类 c,然后执行 c->a(String,String) ,这里有点复杂,偷个懒使用 jeb 还原 java 代码看,这里。



可以看到做的主要的事情有:进入 c->a(String) 函数将 bytes 分为两个一组,然后组内交换顺序,那么现在的 MainActivity->v 就变成了 "htsii__sht_eek.y";另外初始化了类 a,并执行了 a->a(byte[]) 和 a->b(byte[]) 操作,看下类 a 的代码。


可以看出这里其实是以 MainActivity->v 作为了 AES 加密 Key,加密了用户输入的字符串这个操作。所以最后的比较即是:AES 密文和 array_0 位置数据的比较,写了个解密代码,运行出来了 Flag。

import java.io.*;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.Cipher;

class test  
{
 public static void main (String[] args) throws java.lang.Exception
 { 
         String key = "htsii__sht_eek.y";
         SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
         Cipher cipher = Cipher.getInstance("AES");// 创建密码器  
         byte[] bye = {21,-93,-68,-94,86,117,-19,-68,-92,33,50,118,16,13,1,-15,-13,3,4,103,-18,81,30,68,54,-93,44,-23,93,98,5,59};
         /*
            byte[] byteContent = content.getBytes("utf-8");  
            cipher.init(Cipher.ENCRYPT_MODE, keySpec);// 初始化   
            byte[] result = cipher.doFinal(byteContent);  
            String str2 = new String(result,"UTF-8");
            System.out.println(str2); // 加密   
            */
            
            cipher.init(Cipher.DECRYPT_MODE, keySpec);
            byte[] result2 = cipher.doFinal(bye);
            String str3 = new String(result2, "utf-8");
            System.out.println(str3);
 }
}


# LCTF{1t's_rea1ly_an_ea3y_ap4}

* 逆向的 apk 上传到了 Github 。
Read More