iOS 逆向之ARM汇编

最近对iOS逆向工程很感兴趣。html

目前iOS逆向的书籍有: 《Hacking and Securing IOS Applications》, 《iOS Hacker's Handbook》中文书籍有《iOS应用逆向工程:分析与实战》ios

中文博客有: 程序员念茜的《iOS安全攻防系列》 英文博客有:Prateek Gianchandani的iOS 安全系列博客程序员

这些资料中都涉及到有ARM汇编,但都只是很泛地用到,并无对iOS上的ARM汇编进行比较详细的讲解。所以,通过一系列的学习对iOS下的ARM有了必定的理解。在此打算用几篇博文记录下来,备忘之,分享之, 限于本人水平有限,若有错误请不吝赐教。安全

 

咱们先讲一些ARM汇编的基础知识。(咱们以ARMV7为例,最新iPhone5s上的64位暂不讨论)app

基础知识部分:jsp

 

首先你介绍一下寄存器:ide

R0-R3:用于函数参数及返回值的传递函数

R4-R6, R8, R10-R11:没有特殊规定,就是普通的通用寄存器布局

R7:栈帧指针(Frame Pointer).指向前一个保存的栈帧(stack frame)和连接寄存器(link register, lr)在栈上的地址。学习

R9:操做系统保留

R12:又叫IP(intra-procedure scratch ), 要说清楚要费点笔墨,参见http://blog.csdn.net/gooogleman/article/details/3529413

R13:又叫SP(stack pointer),是栈顶指针

R14:又叫LR(link register),存放函数的返回地址。

R15:又叫PC(program counter),指向当前指令地址。

CPSR:当前程序状态寄存器(Current Program State Register),在用户状态下存放像condition标志中断禁用等标志的。

     在其它系统状态中断状等状态下与CPSR对应还有一个SPSR,在这里不详述了。

另外还有VFP(向量浮点运算)相关的寄存器,在此咱们略过,感兴趣的能够从后面的参考连接去查看。

 

基本的指令:

add 加指令

sub 减指令

str 把寄存器内容存到栈上去

ldr  把栈上内容载入一寄存器中

.w是一个可选的指令宽度说明符。它不会影响为此指令的行为,它只是确保生成 32 位指令。Infocenter.arm.com的详细信息

bl 执行函数调用,并把使lr指向调用者(caller)的下一条指令,即函数的返回地址

blx 同上,可是在ARM和thumb指令集间切换。

bx  bx lr返回调用函数(caller)。

 

 

接下来是函数调用的一些规则。

一. 在iOS中你须要使用BLX,BX这些指令来调用函数,不能使用MOV指令(具体意义下面会说)

二. ARM使用一个栈来来维护函数的调用及返回。ARM中栈是向下生长(由高地址向低地址生长的)。

函数调用先后栈的布局如图一(引用的苹果iOS ABI Reference):

              图(一)

SP(stack pointer)指向栈顶(栈低在高地址)。栈帧(stack frame)其实就是经过R7及存在栈上的旧R7来标识的栈上的一块一块的存储空间。栈帧包括:

  1. 参数区域(parameter area),存放调用函数传递的参数。对于32位ARM,前4个参数经过r0-r3传递,多余的参数经过栈来传递,就是存放在这个区域的。
  2. 连接区域(linkage area),存放调用者(caller)的下一条指令。
  3. 栈帧指针存放区域(saved frame pointer),存放调用函数的栈帧的底部,标识着调用者(caller)栈帧的结束及被调用函数(callee)的栈帧开始。
  4. 局部变量存储区(local storage area)。用于存被调函数(callee)的局部变量及在被调用函数(callee)结束后反回调用函数(call)以前须要恢复的寄存器内容。
  5. 寄存器存储区(saved registers area)。Apple的文档中是这样说的。但我认为这个区域和local storage area相邻且干的事也是存放须要恢复的寄存器内容,所以我以为要不就把这个区域在概念上不区分出来,要不就把存放须要恢复的寄存器这项功能从local storage area中分出来。 固然这些都只是概念上的,其实实质上是没有区别的。

接下来看看在调用子函数开始及结尾时所要作的事情。(官方叫序言和结语, prologs and epilogs)

调用开始:

  1. LR入栈
  2. R7入栈
  3. R7 = SP地址。在通过前面两条入栈指令后,SP指向的地址向下移动,再把SP赋值给R7, 标志着caller栈帧的结束及callee的栈帧的开始
  4. 将callee会修改且在返回caller时须要恢复的寄存器入栈。
  5. 分配栈空间给子程序使用。因为栈是从高地址向低地址生长,因此一般使用sub sp, #size来分配。

调用结尾:

  1. 释放栈空间。add sp, #size指令。
  2. 恢复所保存的寄存器。
  3. 恢复R7
  4. 将以前存放的LR从栈上弹出到PC,这样函数就返回了。

-----------------------------------------------------------华丽的分割线-------------------------------------------------------------

实战部分(一):

用XCode建立一个Test工程,新建一个.c文件,添加以下函数:

#include <stdio.h>

int func(int a, int b, int c, int d, int e, int f)
{
    int g = a + b + c + d + e + f;
    return g;
}

查看汇编语言:

在XCode左上角选中targe 在真机下编译,这样产生的才是ARM汇编,否则在模拟器下生成的是x86汇编。

点击 XCode => Product => Perform Action => Assemble file.c 生成汇编代码。

代码不少,有不少"."开头的".section", ".loc"等,这些是汇编器须要的,咱们不用去管。把这些"."开头的及注释增掉后,代码以下:

_func:
	.cfi_startproc
Lfunc_begin0:
	add	r0, r1
Ltmp0:
	ldr.w	r12, [sp]
	add	r0, r2
	ldr.w	r9, [sp, #4]
	add	r0, r3
	add	r0, r12
	add	r0, r9
	bx	lr
Ltmp2:
Lfunc_end0:

 _func:表示接下来是func函数的内容。Lfunc_begin0及Lfunc_end0标识函数定义的起止。函数起止通常是"xxx_beginx:"及"xxx_endx:"

下面来一行行代码解释:

  1. add r0, r1                 将参数a和参数b相加再把结果赋值给r0
  2. ldr.w r12, [sp]           把最的一个参数f从栈上装载到r12寄存器
  3. add r0, r2                 把参数c累加到r0上
  4. ldr.w r9, [sp, #4]       把参数e从栈上装载到r9寄存器
  5. add r0, r3                 累加d累加到r0
  6. add r0, r12               累加参数f到r0
  7. add r0, r9                 累加参数e到r0

至此,所有的a到f 共6个值所有累加到r0寄存器上。前面说了r0是存放返回值的。

bx lr: 返回调用函数。

-----------------------------------------------------------华丽的分割线-------------------------------------------------------------

实战部分(二):

为了让你们看清楚函数调用时栈上的变化,下面以一个有三个函数,两个调用的C代码的汇编代码为例讲解一下。

上代码:

#include <stdio.h>

__attribute__((noinline))
int addFunction(int a, int b, int c, int d, int e, int f) {
    int r = a + b + c + d + e + f;
    return r;
}

__attribute__((noinline))
int fooFunction(int a, int b, int c, int d, int f) {
    int r = addFunction(a, b, c, d, f, 66);
    return r;
}

int initFunction()
{
    int r = fooFunction(11, 22, 33, 44, 55);    
    return r;
}

因为咱们是要看函数调用及栈的变化的,因此在这里咱们加上__attribute__((noinline))防止编译器把函数内联(若是你不懂内联,请google之)。

在XCode左上角选中targe 在真机下编译,这样产生的才是ARM汇编,否则在模拟器下生成的是x86汇编。

点击 XCode => Product => Perform Action => Assemble file.c 生成汇编代码, 以下:

为了能更符合咱们人的思考方式,咱们从调用函数讲起。

 initFunction:

_initFunction:
	.cfi_startproc
Lfunc_begin2:
@ BB#0:
	push	{r7, lr}
	mov	r7, sp
	sub	sp, #4
	movs	r0, #55
	movs	r1, #22
Ltmp6:
	str	r0, [sp]
	movs	r0, #11
	movs	r2, #33
	movs	r3, #44
	bl	_fooFunction
	add	sp, #4
	pop	{r7, pc}
Ltmp7:
Lfunc_end2:

仍是一行行的解释:

  1. push {r7, lr}                      就是前面基础知识部分说的函数调用的序言(prologs)部分的1, 2两条,将lr, r7 存到栈上去
  2. mov r7, sp                         序言(prolog)之3。
  3. sub sp, #4                         在栈上分配一个4字节空间用来存放局部变量, 即参数。前面咱们说过,r0-r3能够传递4个参数,但超过的只能经过栈来传递。
  4. movs r0, #55                     把当即数55存入r0
  5. movs r1, #22                     把22存入r1
  6. str r0, [sp]                         把r0的值存入栈指针sp指向的内存。即栈上存了参数55
  7. 接下来三条指令 moves r0, #11   moves r2, #33   moves r3, #44  把相应的当即数存入指定的寄存器。  到目前为止,r0-r3分别存放了11, 22, 33,44共4个当即数参数,栈上存放了55这一个参数。
  8. bl _fooFunction                   调用fooFunction, 调用后跳转到fooFunction中的状况下面再分析。
  9. add sp, #4                         栈指针向上移动4个字节,回收第3个指令 sub sp, #4分配的空间。
  10. pop {r7, pc}                       恢复第一条指令push {r7, lr}到栈中的值, 把以前的lr值赋给pc。注意:在进入initFunction的时候lr是调用initFunction的函数的下一条指令,因此如今把当时的lr中的值赋给pc程序计数器,这样执行lr指向的这一条指令,函数就反回了。

指令1,2, 3是函数序言(prologs),指令9, 10是结语(epilogs)。这基本上是一个套路,看多了天然就知道了,都不用停下来一条条分析。

为了方便和栈的变化联系起来,咱们画出指令8,  bl __fooFunction时的栈布局如图二:

          图(二)

在上面的initFunction调用第8条指令bl _fooFunction以后,进入fooFunction, 其它汇编以下:

 fooFunction:

_fooFunction:
	.cfi_startproc
Lfunc_begin1:
	push	{r4, r5, r7, lr}
	add	r7, sp, #8
	sub	sp, #8
	ldr	r4, [r7, #8]
	movs	r5, #66
	strd	r4, r5, [sp]
	bl	_addFunction
	add	sp, #8
	pop	{r4, r5, r7, pc}
Lfunc_end1:

同样,咱们一行行来看:

  1. push {r4, r5, r7, lr}             你应该发现了,此次和initFunction不一样,除了lr和r7也把r4, r5 push到栈上去了,这是由于咱们下面会用到r4, r5,因此咱们先把r4,r5存到栈上,这样咱们在退出fooFunction返回initFunction的时候好恢复r4, r5的值。push到栈上的顺序是lr, r7, r4, r5。 
  2. add r7, sp, #8                     在initFunction中咱们没有push r4, r5因此sp指向的位置正好是新的r7的值,可是这里咱们把r4, r5也push到栈上了,如今sp指向栈上的r4的位置,而栈是向下生长的,因此咱们把sp + #8个字节就是存放旧r7的位置。
  3. sub sp, #8                          在栈上分配8个字节。
  4. ldr r4, [r7, #8]                    r7加8个字节,在栈上的位置正好是在initFunction中咱们存放的参数55的位置。所以,这里是把55赋值给r4
  5. movs  r5, #66                     当即数赋值,不解释了
  6. strd r4, r5, [sp]                   把r4, r5中的值存到栈上。咱们在initFunction中已经把11,22,33,44这4个参数存放到了r0-r3,如今55,66咱们存放在栈上
  7. bl _addFunction                   参数已经准备好了,所以如今调用addFunction。
  8. add sp, #8                          回收栈空间
  9. pop {r4, r5, r7, pc}              这最后两条指令和 initFunction相似,只是多了个恢复r4,r5。不过也是一个指令就完事。

在指令bl _addFunction 调用addFunction后,栈的布局如图(三):

            图(三)

上面的fooFunction第7条指令bl _addFunction以后,进入addFunction。汇编代码以下:

addFunction:

_addFunction:
	.cfi_startproc
Lfunc_begin0:
	add	r0, r1
	ldr.w	r12, [sp]
	add	r0, r2
	ldr.w	r9, [sp, #4]
	add	r0, r3
	add	r0, r12
	add	r0, r9
	bx	lr
Lfunc_end0:

逐行解释之:

  1. add r0, r1              r0 += r1
  2. ldr.w r12, [sp]           把sp指向的内容load到r12寄存器。从图(三)咱们知道sp指向66,所以r12存的66
  3. add r0, r2                 r0 += r2
  4. ldr.w r9, [sp, #4]       从图(三) sp加4个字节存的是55, r9存的55
  5. add r0, r3                 r0 += r3
  6. add r0, r12               r0 += r12
  7. add r0, r9             r0 += r9。 至此r0-r4存的11,22,33,44,及栈上存的55,66想加存到了r0上。
  8. bx lr                         返回。

你们应该有注意到由于addFunction没有调用其它的函数,序言和结语与initFunction和fooFunction不同。由于咱们不调用其它函数,就不会有bl, blx这样的指令,因此不会个性lr, 因此咱们没有push lr。  

 

在这里咱们用了r9, r12为何不须要保存与恢复,我还没大搞明白,大侠们若能赐教,将不胜感激。

 

 iOS ABI Reference上是这样说的:

关于R9:

In iOS 2.x, register R9 is reserved for operating system use and must not be used by application code. Failure to do so can result in application crashes or aberrant behavior. However, in iOS 3.0 and later, register R9 can be used as a volatile scratch register. These guidelines differ from the general usage provided for by the AAPCS document.  

 

关于R12

R12 is the intra-procedure scratch register, also known as IP. It is used by the dynamic linker and is volatile across all function calls. However, it can be used as a scratch register between function calls. 

 

这是C函数的汇编。下篇讲obj-c函数的汇编,包括objc block。

 

参考:http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001l/QRC0001_UAL.pdf

   http://simplemachines.it/doc/arm_inst.pdf

   iOS ABI Reference

相关文章
相关标签/搜索