31

iOS汇编入门教程(一)ARM64汇编基础

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI0MTcwNDcyMw%3D%3D&%3Bmid=2247484282&%3Bidx=1&%3Bsn=5607653a98fa6c513aa35b01841ea52a
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

对于应用层开发人员而言,仅仅掌握Objective-C和系统框架即可较好的完成开发,但在涉及到应用加固、逆向分析等内容时仅有应用层开发技能就会显得非常的无力,因此掌握汇编对于突破iOS开发水平的瓶颈十分有效。

一个例子

以反调试为例,我们知道,通过调用ptrace函数可以阻止调试器依附。

这种方式能够被函数hook轻易破解,例如使用facebook的fishhook。为了防止函数被hook,我们可以将函数调用转为通过汇编发起系统调用,即使用下面的代码。

其中x0-x3存储的为函数入参,x16存储的为函数编号,通过Apple提供的System Call Table 可以查出ptrace的编号为26,最后一句指令发起了系统调用。通过使用_ asm_ 指令能够将汇编代码嵌入我们的函数中,构成反调试方法。

虽然上面的反调试机制并不完善,但是比直接调用ptrace要好上很多倍,从这一点来看,掌握汇编技能对于iOS应用安全和底层研究非常有利。

入门攻略

iOS设备主要使用的为ARM64汇编,因此本文主要介绍ARM64汇编的入门技巧。汇编入门最难的地方在于对栈的理解,汇编的所有指令操作都是围绕栈实现的,在汇编中,没有变量的概念,只有寄存器和内存。

汇编中的栈是由高地址向低地址生长的数据结构,sp指针永远指向栈顶,需要记住的是,在某位置进行存储时,是向高地址进行的,下面以一个简单的例子讲解汇编的栈操作。我们以一段简单的C代码为例。

使用clang可以将其编译为特定指令集的汇编代码,这里我们将其编译为ARM64指令集的汇编代码。

完整的汇编代码如下。

本节我们只讨论栈操作,因此忽略main函数和printf调用部分,我们只看对test函数的调用,节选这一段汇编代码如下。

首先介绍一下基本指令和指令的学习方式,要查询某个指令如何使用,最好的方式是去查询ARM公司提供的官方文档,在官方文档页面可以直接搜索指令并查看用法和例程,本文会简单讲解上面的汇编代码中出现的指令。

sub用于对寄存器实施减法, suba,b,c 等价于 a=b-c ,在ARM汇编中,目的操作数一般出现最前方,例如 mov ra,rb 代表将rb寄存器的值 复制 到ra寄存器。add和sub同理,只是将减法变成了加法。

str和ldr是一对指令,str的全称是store register,即将寄存器的值存储到内存中,ldr的全称是load register,即将内存中的值读到寄存器,因此他们的第一个参数都是寄存器,第二个参数都是内存地址。 [sp,#12] 代表 sp+12 这个地址,同理 [sp,#-12] 代表 sp-12 这个地址。注意这里的数字都是以字节为单位的偏移量,以 str w0,[sp,#12] 为例,w是4字节的寄存器,这个指令代表将w0寄存器的值存储在sp+12这个地址上,由于w0有4个字节,所以存储后会占据 sp+12~sp+16 这个内存区域。

下面将分段讲解这段汇编代码,在编译器生成汇编时,首先会计算需要的栈空间大小,并利用sp指针向低地址开辟相应的空间,我们再来看一下test函数。

这里涉及了3个int变量,分别是a、b、res,int变量占据4个字节,因此一共需要12个字节, 但ARM64汇编为了提高访问效率要求按照16字节进行对齐 ,因此需要16byte的空间,也就是需要在栈上开辟16字节的空间,我们来看汇编的第一句,正是将sp指针下移16字节。

uaIVzeZ.png!web

sp下移16后,留下了4个4字节的内存空格,共计16字节,我们继续看下面的句子。

这两句的含义是将w0存储在sp+12的格子中,w1存储在sp+8的格子中,上面的例子中提到 x0, x1等寄存器将顺序存放函数的入参,x0和w0是同一个寄存器的不同大小体现,x0为8字节,w0为x0的前4个字节,因此w0是函数的第一个入参a,w1是函数的第二个入参b,由于存储是从低地址到高地址的,所以a将占据 sp+12~sp+16 ,同理b将占据 sp+8~sp+12 ,则栈的结构变为下图。

zaaquu6.png!web

按照“上帝视角”,接下来test函数应该将a和b相加,需要注意的是,只有寄存器才能参与运算,因此接下来的汇编代码又将变量的值从内存中读出,进行相加运算。

由此可见先存储再读取后运算其实是多余的,这是没有进行编译优化的结果,学习不进行编译优化的汇编更能让我们理解其工作机制。

接下来的代码将w0存入了sp+4,也就是res变量的内存区域。

m6z2qq7.png!web

接下来就要进行返回了,在例子中我们提到,函数的返回值一般存储在x0寄存器中返回,因此我们需要将res的值载入x0寄存器。

这里之所以使用w寄存器,是因为int为4字节,这也就是类型转换时带来信息丢失的原因,例如从long到int的转换就类似于将x寄存器的值以w的形式进行存储。最后的代码为将栈还原,并返回到函数调用处继续向下执行。

显然,经过这样的操作,栈被完全还原到了函数调用以前的样子,需要注意的细节是,栈空间中的内存单元并未被清空,这也就导致下一次使用低地址的栈时,未初始化单元的值是不确定的,这也就是局部变量不初始化值随机的根本原因。

通过上面的例子,我们对栈有了基本的认识,汇编的操作基本都是对栈进行的,只要理解了栈机制,只需要学习各种指令,即可掌握足够使用的汇编技能。

深入

在了解了栈以后,就可以看一些较为复杂的汇编片段来进行学习了,初级阶段可以尝试看着函数写汇编代码,高级阶段要求能够看着汇编还原成函数逻辑,本文仅仅介绍入门基础,下面推荐一些大牛的博客供大家深入学习汇编技能。

1.知兵的知乎专栏

2.刘坤的汇编入门文章

总结

掌握ARM汇编能够帮助开发者更好地了解编译器和CPU的工作原理,除了能够指导编码外,还能够扩宽视野,通过反编译分析一些闭源代码的逻辑或是进行一些安全加固,因此在汇编上付出时间是十分值得的。

参考资料

1.知兵. iOS调试进阶 https://zhuanlan.zhihu.com/c_142064221

2.ARM官方文档 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0802a/STUR_fpsimd.html

3.反调试和绕过 http://jmpews.github.io/2017/08/09/darwin/反调试及绕过

Fn6j6re.gif如果感觉这篇文章不错可以点击在看:point_down:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK