2016年10月25日星期二

Android 逆向学习笔记 (五)- ARM 和 Thumb 指令集学习

学习安卓逆向,ARM 和 Thumb 是两个绕不开的内容。Thumb 可以看作是 ARM 指令集压缩形式的子集,是针对指令长度问题而提出的,它具有 16 位的指令长度(ARM:32 位),但操作数、指令地址这两项 Thumb 均和 ARM 相同,为 32 位,所以如果想要区分当前指令是 ARM 还是 Thumb 通过指令长度就可以知道。

Thumb 不是一个完整的指令体系结构,必须和 ARM 指令集结合才能正常工作。安卓逆向总是由 CODE32 声明的 ARM 指令集开始,执行的过程中可以通过 BX 指令切换到 CODE16 声明的 Thumb 指令集。

ARM 程序分析(I)

源码:

#include 
#include 

void (*func)();
  
void sub() {
  void *p = dlopen("libxxx.so", RTLD_NOW);
  if (!p) {
    return;
  }
  else {
    // myfn是libxxx.so的一个函数,作用是打印"call myfn...!"
    func = (void (*)())dlsym(p,"myfn");
    func();
  }
}

int main()
{
  printf("RTLD_NOW=%d\n", RTLD_NOW);
  sub();
  return 0;
}

反汇编之后:



sub:
1.    84a0:  4808        ldr  r0, [pc, #32]  ; (84c4 )
; r0 = [pc + 32 + 4] = [0x84a0 + 0x24] = [0x84c4] = 0x48
; 这里之所以多 +4 是因为 Thumb 涉及 PC 时需要 +4,而 ARM 计算涉及 PC 时需要 +8

2.    84a2:  2100        movs  r1, #0
; r1 = 0

3.    84a4:  b510        push  {r4, lr}
; r4 和 lr 入栈,r4 属于局部变量而 lr 保存了 sub 函数的返回地址

4.    84a6:  4c08        ldr  r4, [pc, #32]  ; (84c8 )
; r4 = [pc + 32 + 4] = [0x84a6 + 0x24] = [0x8aca] = [0x84c8] = 0x0c26
; 这里的 [0x8aca] 不属于标准起始,必须对齐到 (0, 4, 8, c),所以 [0x84ca] 变成 [0x84c8]

5.    84a8:  4478        add  r0, pc
; r0 = r0 + pc = 0x48 + 0x84a8 + 4 = 0x84f4 -> [0x84f4] = 'libxxx.so' 

6.    84aa:  447c        add  r4, pc
; r4 = r4 + pc = 0x0c26 + 0x84aa + 4 = 0x90d4

7.    84ac:  f7ff efc8   blx  8440 
; 执行 dlopen 函数,可以看到此时的指令长度为 (0x84ac ~ 0x84b0) = 4 bytes = 32 bit 为 ARM 指令
; 而之前指令长度可以看到尾 2 bytes = 16 bit,为 Thumb 指令

8.    84b0:  b138        cbz  r0, 84c2 
; r0 不为 0 则跳转执行 0x84c2 位置的 printf 函数,此时 r0 为上一条指令 dlopen 的返回结果

9.    84b2:  4906        ldr  r1, [pc, #24]  ; (84cc )
; r1 = [0x84b2 + 24 + 4] = [0x84b2 + 0x1c] = [0x84ce] = [0x84cc] = 0x46

10.   84b4:  4479        add  r1, pc
; r1 = 0x46 + 0x84b4 + 4= 0x84fe -> [0x84fe] = 'myfn'

11.   84b6:  f7ff efca   blx  844c 
; 切换到 ARM 指令执行 dlsym

12.   84ba:  4905        ldr  r1, [pc, #20]  ; (84d0 )
; r1 = [pc + 20 + 4] = [0x84ba + 0x18] = [0x84d2] = [0x84d0] = 0xfffffffc

13.   84bc:  5863        ldr  r3, [r4, r1]
; r3 = [r4 + r1] = [0x90d4 + 0xfffffffc] = [0x90d4 - 0x04] = [0x90d0] = 0x90f0
; 图中没有给出 [0x90d0] 的内容,原文中截图截少了,这里的 0x90f0 即是 myfn 函数的地址,下面传入 r0 并执行

14.   84be:  6018        str  r0, [r3, #0]
15.   84c0:  4780        blx  r0
16.   84c2:  bd10        pop  {r4, pc}

ARM 程序分析(II)

#include <stdio.h>
#include <string.h>
int main(int argc,char *argv[])
{
  char name[]="helloworld";
  int  keys[]={0xb,0x1f,0x19,0x19,0x49,0xb,0xb,0xb,0x31,0x53};
  char Thekeys[11];
  int i;
  for(i=0;i<10;i++)
  {
    keys[i]^=7;
    keys[i]=keys[i]/6;
    keys[i]+=22;
    keys[i]-=24;
    keys[i]^=name[i];
  }
  for(i=0;i<10;i++)
  {
    Thekeys[i]=keys[i];
  }
  Thekeys[i]=0;
  if(!strcmp(Thekeys,argv[1]))
    printf("Good Work,you have Successed!");
  else
    printf("NO,you are failed!");
  return 0;
}

反编译之后,流程图下所示,可以看到起点是 start 函数,但是没有发现名称为 main 的函数。那怎么确定改名之后的 main 函数呢?可以通过搜索 “__libc_init” ,定位到调用位置,代码类似如下:
.text:0000847C start
.text:0000847C
.text:0000847C var_14          = -0x14
.text:0000847C var_10          = -0x10
.text:0000847C var_C           = -0xC
.text:0000847C var_8           = -8
.text:0000847C
.text:0000847C                 LDR     R12, =(_GLOBAL_OFFSET_TABLE_ - 0x8498)
.text:00008480                 STMFD   SP!, {R11,LR}
.text:00008484                 LDR     R3, =(off_AF98 - 0xAFCC)
.text:00008488                 ADD     R11, SP, #4
.text:0000848C                 SUB     SP, SP, #0x10
.text:00008490                 ADD     R12, PC, R12 ; _GLOBAL_OFFSET_TABLE_
.text:00008494                 LDR     R3, [R12,R3] ; unk_AE90
.text:00008498                 STR     R3, [R11,#var_14]
.text:0000849C                 LDR     R3, =(off_AF9C - 0xAFCC)
.text:000084A0                 ADD     R0, R11, #4
.text:000084A4                 LDR     R3, [R12,R3] ; unk_AE88
.text:000084A8                 STR     R3, [R11,#var_10]
.text:000084AC                 LDR     R3, =(off_AFA0 - 0xAFCC)
.text:000084B0                 MOV     R1, #0
.text:000084B4                 LDR     R3, [R12,R3] ; unk_AE80
.text:000084B8                 STR     R3, [R11,#var_C]
.text:000084BC                 LDR     R3, =(off_AFA4 - 0xAFCC)
.text:000084C0                 LDR     R3, [R12,R3] ; unk_AE98
.text:000084C4                 STR     R3, [R11,#var_8]
.text:000084C8                 LDR     R3, =(off_AFA8 - 0xAFCC)
.text:000084CC                 LDR     R2, [R12,R3] ; sub_850C
.text:000084D0                 SUB     R3, R11, #-var_14
.text:000084D4                 BL      __libc_init
.text:000084D8                 SUB     SP, R11, #4
.text:000084DC                 LDMFD   SP!, {R11,PC}
.text:000084DC ; End of function start

根据 __libc_init 函数的定义,可以知道调用时保存在寄存器 R2 中的地址即使 main 函数的地址,此时根据 “LDR R2, [R12, R3] ; sub_850C” 这一行即可知道 main 函数为 sub_850C,跟进 F5 看看。

int __fastcall sub_850C(int a1, int a2)
{
  int *v2; // r3@1
  char *v3; // r0@1
  int v4; // t1@2
  signed int *v5; // r3@3
  char *v6; // r2@3
  signed int v7; // t1@4
  const char *v8; // r1@5
  int result; // r0@7
  int v10; // [sp+0h] [bp-60h]@3
  int v11; // [sp+4h] [bp-5Ch]@1
  signed int v12; // [sp+8h] [bp-58h]@1
  signed int v13; // [sp+Ch] [bp-54h]@1
  signed int v14; // [sp+10h] [bp-50h]@1
  signed int v15; // [sp+14h] [bp-4Ch]@1
  signed int v16; // [sp+18h] [bp-48h]@1
  signed int v17; // [sp+1Ch] [bp-44h]@1
  signed int v18; // [sp+20h] [bp-40h]@1
  signed int v19; // [sp+24h] [bp-3Ch]@1
  signed int v20; // [sp+28h] [bp-38h]@1
  int v21; // [sp+2Ch] [bp-34h]@1
  signed int v22; // [sp+30h] [bp-30h]@1
  __int16 v23; // [sp+34h] [bp-2Ch]@1
  char v24; // [sp+36h] [bp-2Ah]@1
  char v25; // [sp+37h] [bp-29h]@3
  char v26; // [sp+38h] [bp-28h]@5
  char v27; // [sp+42h] [bp-1Eh]@5
  int v28; // [sp+44h] [bp-1Ch]@1

  v21 = 1819043176;
  v22 = 1919907695;
  v23 = 'dl';
  v24 = 0;
  v11 = 11;
  v12 = 31;
  v13 = 25;
  v14 = 25;
  v15 = 73;
  v16 = 11;
  v17 = 11;
  v18 = 11;
  v2 = &v11;
  v28 = _stack_chk_guard;
  v19 = 49;
  v20 = 83;
  v3 = (char *)&v20 + 3;
  do
  {
    v4 = (unsigned __int8)(v3++)[1];
    *v2 = ((*v2 ^ 7) / 6 - 2) ^ v4;
    ++v2;
  }
  while ( v2 != &v21 );
  v5 = &v10;
  v6 = &v25;
  do
  {
    v7 = v5[1];
    ++v5;
    (v6++)[1] = v7;
  }
  while ( v5 != &v20 );
  v8 = *(const char **)(a2 + 4);
  v27 = 0;
  if ( !strcmp(&v26, v8) )
    printf("Good Work,you have Successed!");
  else
    printf("NO,you are failed!");
  result = 0;
  if ( v28 != _stack_chk_guard )
    _stack_chk_fail(0);
  return result;
}

可以看到做了一系列变换之后进行判断,不涉及其他位置的函数了,不详细分析下去了。

参考链接:
IDA 如何识别 ARM 的 main 函数
ARM 指令和 Thumb 指令的区别 
关于__stack_chk_guard_ptr的理解
ARM 汇编版 CRACKME 分析