ios 逆向工程与arm汇编_第1页
ios 逆向工程与arm汇编_第2页
ios 逆向工程与arm汇编_第3页
ios 逆向工程与arm汇编_第4页
ios 逆向工程与arm汇编_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

iOS 逆向之 ARM 汇编 我们先讲一些 ARM 汇编的基础知识。 (我们以 ARMV7 为例,最新 iPhone5s 上的 64 位暂不讨论) 基础知识部分: 首先介绍一下寄存器: R0-R3:用于函数参数及返回值的传递 R4-R6, R8, R10-R11:没有特殊规定,就是普通的通用寄存器 R7:栈帧指针 (Frame Pointer).指向前一个保存的栈帧(stack frame)和链接寄存器(link register, lr)在栈上的地址。 R9:操作系统保留 R12:又叫 IP(intra-procedure scratch ), 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 位指令。 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 来标识的栈上的一块一块的存储空间。栈帧包括: 参数区域(parameter area),存放调用函数传递的参数。对于 32 位 ARM,前 4 个参数 通过 r0-r3 传递,多余的参数通过栈来传递,就是存放在这个区域的。 链接区域(linkage area),存放调用者(caller)的下一条指令。 栈帧指针存放区域(saved frame pointer),存放调用函数的栈帧的底部,标识着调用者 (caller)栈帧的结束及被调用函数 (callee)的栈帧开始。 局部变量存储区(local storage area)。用于存被调函数(callee)的局部变量及在被调用函 数(callee) 结束后反回调用函数 (call)之前需要恢复的寄存器内容。 寄存器存储区(saved registers area)。Apple 的文档中是这样说的。但我认为这个区域和 local storage area 相邻且干的事也是存放需要恢复的寄存器内容,因此我觉得要不就把这个 区域在概念上不区分出来,要不就把存放需要恢复的寄存器这项功能从 local storage area 中 分出来。 当然这些都只是概念上的,其实实质上是没有区别的。 接下来看看在调用子函数开始及结尾时所要做的事情。(官方叫序言和结语, prologs and epilogs) 调用开始: LR 入栈 R7 入栈 R7 = SP 地址。在经过前面两条入栈指令后,SP 指向的地址向下移动,再把 SP 赋值 给 R7, 标志着 caller 栈帧的结束及 callee 的栈帧的开始 将 callee 会修改且在返回 caller 时需要恢复的寄存器入栈。 分配栈空间给子程序使用。由于栈是从高地址向低地址生长,所以通常使用 sub sp, #size 来分配。 调用结尾: 释放栈空间。add sp, #size 指令。 恢复所保存的寄存器。 恢复 R7 将之前存放的 LR 从栈上弹出到 PC,这样函数就返回了。 -华丽的分割线- - 实战部分(一) : 用 XCode 创建一个 Test 工程,新建一个.c 文件,添加如下函数: 1 2 3 4 5 6 7 #include 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“等,这些是汇编器需要的,我们不用去管。 把这些“.“开头的及注释增掉后,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 _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:“ 下面来一行行代码解释: add r0, r1 将参数 a 和参数 b 相加再把结果赋值给 r0 ldr.w r12, sp 把最的一个参数 f 从栈上装载到 r12 寄存器 add r0, r2 把参数 c 累加到 r0 上 ldr.w r9, sp, #4 把参数 e 从栈上装载到 r9 寄存器 add r0, r3 累加 d 累加到 r0 add r0, r12 累加参数 f 到 r0 add r0, r9 累加参数 e 到 r0 至此,全部的 a 到 f 共 6 个值全部累加到 r0 寄存器上。前面说了 r0 是存放返回值的。 bx lr: 返回调用函数。 -华丽的分割线- - 实战部分(二) : 为了让大家看清楚函数调用时栈上的变化,下面以一个有三个函数,两个调用的 C 代 码的汇编代码为例讲解一下。 上代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include _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: 1 2 3 4 5 6 7 8 9 10 _initFunction: .cfi_startproc Lfunc_begin2: BB#0: push r7, lr mov r7, sp sub sp, #4 movs r0, #55 movs r1, #22 Ltmp6: 11 12 13 14 15 16 17 18 19 str r0, sp movs r0, #11 movs r2, #33 movs r3, #44 bl _fooFunction add sp, #4 pop r7, pc Ltmp7: Lfunc_end2: 还是一行行的解释: push r7, lr 就是前面基础知识部分说的函数调用的序言(prologs)部分的 1, 2 两条,将 lr, r7 存到栈上去 mov r7, sp 序言(prolog)之 3。 sub sp, #4 在栈上分配一个 4 字节空间用来存放局部变量, 即参数。前面 我们说过,r0-r3 可以传递 4 个参数,但超过的只能通过栈来传递。 movs r0, #55 把立即数 55 存入 r0 movs r1, #22 把 22 存入 r1 str r0, sp 把 r0 的值存入栈指针 sp 指向的内存。即栈上存了参数 55 接下来三条指令 moves r0, #11 moves r2, #33 moves r3, #44 把相应的立即数存入指 定的寄存器。 到目前为止, r0-r3 分别存放了 11, 22, 33,44 共 4 个立即数参数,栈上存放 了 55 这一个参数。 bl _fooFunction 调用 fooFunction, 调用后跳转到 fooFunction 中的情况下面 再分析。 add sp, #44 栈指针向上移动 4 个字节,回收第 3 个指令 sub sp, #4 分配的 空间。 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 _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: 一样,我们一行行来看: push r4, r5, r7, lr 你应该发现了,这次和 initFunction 不同,我们除把 r4, r5 也 push 到栈上去了,这是因为我们下面会用到 r4, r5,所以我们先把 r4,r5 存到栈上,这样我 们在退出 fooFunction 返回 initFunction 的时候好恢复 r4, r5 的值。push 到栈上的顺序是 lr, r7, r4, r5。 add r7, sp, #8 在 initFunction 中我们没有 push r4, r5 所以 sp 指向的位置正好 是新的 r7 的值,但是这里我们把 r4, r5 也 push 到栈上了,现在 sp 指向栈上的 r4 的位置, 而栈是向下生长的,所以我们把 sp + #8 个字节就是存放旧 r7 的位置。 sub sp, #8 在栈上分配 8 个字节。 ldr r4, r7, #8 r7 加 8 个字节,在栈上的位置正好是在 initFunction 中我们存 放的参数 55 的位置。因此,这里是把 55 赋值给 r4 movs r5, #66 立即数赋值,不解释了 strd r4, r5, sp 把 r4, r5 中的值存到栈上。我们在 initFunction 中已经把 11,22,33,44 这 4 个参数存放到了 r0-r3,现在 55,66 我们存放在栈上 bl _addFunction 参数已经准备好了,因此现在调用 addFunction。 add sp, #8 回收栈空间 pop r4, r5, r7, pc 这最后两条指令和 initFunction 类似,只是多了个恢复 r4,r5。不过也是一个指令就完事。 在指令 bl _addFunction 调用 addFunction 后,栈的布局如图 (三): 图(三) 上面的 fooFunction 第 7 条指令 bl _addFunction 之后,进入 addFunction。汇编代码如 下: addFunction: 1 2 3 4 5 6 7 8 9 10 11 12 _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: 逐行解释之: add r0, r1 r0 += r1 ldr.w r12, sp 把 sp 指向的内容 load 到 r12 寄存器。从图( 三)我们知道 sp 指向 66,因 此 r12 存的 66 add r0, r2 r0 += r2 ldr.w r9, sp, #4 从图(三) sp 加 4 个字节存的是 55, r9 存的 55 add r0, r3 r0 += r3 add r0, r12 r0 += r12 add r0, r9 r0 += r9。 至此 r0-r4 存的 11,22,33,44,及栈上存的 55,66 想加存到 了 r0 上。 bx lr 返回。 大家应该有注意到因为 addFunction 没有调用其它的函数,序言和结语与 initFunction 和 fooFunction 不一样。因为我们不调用其它函数,就不会有

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论