iOS 逆向之ARM汇编

发布者:支持中文最新更新时间:2019-12-03 来源: eefocus关键字:iOS  逆向  ARM汇编 手机看文章 扫描二维码
随时随地手机看文章

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

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

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

这些资料中都涉及到有ARM汇编,但都只是很泛地用到,并没有对iOS上的ARM汇编进行比较详细的讲解。因此,经过一系列的学习对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 ), 要说清楚要费点笔墨,参见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文件,添加如下函数:

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:"

下面来一行行代码解释:

  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代码的汇编代码为例讲解一下。

上代码:

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
11
12
13
14
15
16
17
18
19
_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:

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:

一样,我们一行行来看:

  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:

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:

逐行解释之:

  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

[1] [2]
关键字:iOS  逆向  ARM汇编 引用地址:iOS 逆向之ARM汇编

上一篇:Linux及Arm-Linux程序开发笔记(零基础入门篇)
下一篇:ARM-GCC-LD脚本

推荐阅读最新更新时间:2024-11-17 01:42

Keil下ARM汇编程序建立与调试简介
1. 新建工程 选择目标处理器 比如:SAMSUNG /S3C2410 2. 新建文件 保存SAVE 3. 把文件添加入工程 或者双击组文件夹,选择你保存的文件 4. 设置工程属性 5. 编辑代码 6. build 7. debug 8. 调试方法简介 窗口1汇编代码编辑窗口 窗口2单步调试工作栏,也可以按快捷键F11或F10 窗口3 是程序执行时的寄存器窗口,Supervisor 高黑,说明当前CPU处于Supervisor工作模式,(CPU的工作模式介绍参考博文:arm处理器工作模式)Current代表当前模式下各个寄
[单片机]
Keil下<font color='red'>ARM汇编</font>程序建立与调试简介
ARM汇编指令集之六——加载/存储指令
ARM微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据传送到寄存器,存储指令则完成相反的操作。常用的加载存储指令如下: 1、LDR指令 LDR指令的格式为: LDR{条件} 目的寄存器, 存储器地址 LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,且寻址方式灵活多样,请读者认真掌握。 指令示例: LDR R0, ;将存储器地址为R1的字
[单片机]
苹果眼镜未来将把虚拟屏与iOS设备叠加 以增强隐私性
苹果的iPhone和iPad可以提供一个隐私屏幕,通过增强现实技术,只有 “苹果眼镜 ”佩戴者才能看到屏幕上的信息。苹果一再表示,隐私不是一些额外的附加功能,它从一开始就已经被植入,而它似乎正通过即将推出的 “苹果眼镜 ”来证明这一点。新的专利申请显示,苹果正在探索如何利用苹果AR有效地创造更多保护隐私的方法。   “隐私屏幕 ”是一项新的专利申请,它提出了一种限制,除了设备所有者以外的任何人都无法看清屏幕。它的具体目的是为了摆脱对偏光屏盖的需求,从而避免可视角度问题。传统的隐私屏幕在垂直于显示屏的方向上衰减了一些光线,从而降低了用户感知到的亮度,为了补偿,用户可能会在不担心隐私问题时移除隐私屏幕,在这种情况下,用户必须存
[手机便携]
微软不应该学苹果iOS的围墙花园模式
     10月26日发布的Windows 8,对世界最流行操作系统而言预示着一个新时代的到来,平板首次得到了与传统桌面电脑同等的待遇。微软大幅修改Windows旨在回应苹果iPad和iOS的成功,但 Ars的一篇评论认为微软是从一个错误的方向模仿苹果。     未来的计算机是作为一种自由的工具还是一种审查的工具?苹果iOS的围墙花园模式存在根本性的缺陷,微软应该重新考虑他们的Windows RT计划。     封闭的应用程序商店模式有其安全和便捷的优势,但在全面考虑用户自由、安全和便捷性之后,作者认为Mac OS X和Android提供了更好的模式:     它们同样默认启用围
[工业控制]
苹果iOS 12暗藏玄机 Apple Watch 4曝光
   昨天,苹果发布了 iOS 12 第二个开发者测试版(Beta 2),在首个测试版的基础上,对 iOS 12 中的多个新功能进行了调整和更改,而很多人没注意到的是,iOS 12里赫然提到了Apple Watch 4。 苹果iOS 12暗藏玄机 Apple Watch 4曝光(图片来自新浪微博)   在iOS 12的系统中,我们发现了Watch4-1、Watch4-2、Watch4-3和Watch4-4的标识符,这和目前Apple Watch Series 3使用的Watch3,1到Watch3,4高度一致,可以说Apple Watch Series 4已经没啥悬念了。   同时还有与新设备相对应的型号,比如MTUD2、MTUK
[手机便携]
自由切换WiFi和3G iOS6让网络体验更自由
    市场传言 苹果  (Apple)将于下季推出的最新版 iOS   作业系统 将包含一个功能──让使用者无论任何时候都拥有最佳的 网路连接 。 这个想法称之为“无线网路附加 蜂巢式 网路”(  Wi-Fi Plus Cellular),同时,根据AppleInsider网站,这意味着当你连接到一个Wi-Fi时,若该网路繁忙,则你的iPhone将会自行选择,为你找到更好的连接,即使那意味着将会回到蜂巢式网路。 这也代表着你的资料和应用程式都会维持同步,而且,如果你使用的是iPhone 4S或更先进产品,你的FaceTime视讯会议会将不再断线。 从我们的角度来看,这种新功能听来还算
[手机便携]
iOS 11正式版更新率上涨,逐渐被用户接受
iOS 11正式版发布后,一直存在不小的问题,导致它的更新率迟迟上不去,这让苹果很头疼。 Mixpanel监控到的最新数据显示,iOS 11的更新率出现了不小的上涨,其正式版在发布3周后终于超越了iOS 10(更新率46%),目前它的更新率已经达到了47%。 不得不说,iOS 11今年的更新率要比iOS 10慢不少,要知道iOS 10正式版发布后只用了2周时间,就在更新率上超越了iOS 9,而后者目前的占有率为6.7%(也包含其他版本)。 为了改善iOS 11的小问题,苹果也是不断推出小更新率来解决问题,截至目前,苹果已经为iOS 11发布了三个小幅升级更新,都是解决设备的一些小问题,比如iPhone 8、8 Plus的听筒噼
[嵌入式]
通杀iOS与Android 闪迪两款U盘直插手机实战
    我们一直在思考一个问题,Android手机可以直插OTG线连接U盘以传输数据,那么苹果的iPhone为什么不可以?的确,苹果不支持OTG功能,但一向以周边配件齐全著称的iPhone没有好用的接驳U盘设备实在说不过去。一些厂商曾经开发出iPhone、iPad专用的外接U盘,但雷声大雨点小,并没有获得广泛的普及。如今闪迪也做了一个iPhone、iPad专用外接U盘,名为“欢欣i享闪存盘”,还有一块Android用的OTG USB 3.0 闪存盘,它们是否能引起人们对手机外接U盘的兴趣呢? 用U盘替代iTunes   苹果的系统是封闭的,无论iPhone、iPad还是iPod在传输数据时都需要通过电脑上的iTunes软件进行。
[手机便携]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved