最近对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来标识的栈上的一块一块的存储空间。栈帧包括:
接下来看看在调用子函数开始及结尾时所要作的事情。(官方叫序言和结语, prologs and epilogs)
调用开始:
调用结尾:
-----------------------------------------------------------华丽的分割线-------------------------------------------------------------
实战部分(一):
用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:"
下面来一行行代码解释:
至此,所有的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,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:
同样,咱们一行行来看:
在指令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:
逐行解释之:
你们应该有注意到由于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