前言

关于应用层开发人员而言,仅仅把握Objective-C和体系结构即可较好的完成开发,但在涉及到应用加固、逆向剖析等内容时仅有应用层开发技术就会显得十分的无力,因而把握汇编关于打破iOS开发水平的瓶颈十分有效。

一个比如

以反调试为例,咱们知道,经过调用ptrace函数可以阻止调试器依靠。

  1. ptrace(31,0,0,0)

这种办法可以被函数hook容易破解,例如运用facebook的fishhook。为了避免函数被hook,咱们可以将函数调用转为经过汇编发起体系调用,即运用下面的代码。

  1. mov x0,#31

  2. mov x1,#0

  3. mov x2,#0

  4. mov x3,#0

  5. mov x16,#26

  6. svc #0x80

其间x0-x3存储的为函数入参,x16存储的为函数编号,经过Apple供给的System Call Table 可以查出ptrace的编号为26,最终一句指令发起了体系调用。经过运用_asm_指令可以将汇编代码嵌入咱们的函数中,构成反调试办法。

  1. // 运用inline办法将函数在调用途强制展开,避免被hook和追寻符号

  2. static __attribute__((always_inline))void anti_debug(){

  3. // 判断是否是ARM64处理器指令集

  4. #ifdef __arm64__

  5. // volatile修饰符可以避免汇编指令被编译器疏忽

  6. __asm__ __volatile__(

  7. "mov x0, #31\n"

  8. "mov x1, #0\n"

  9. "mov x2, #0\n"

  10. "mov x3, #0\n"

  11. "mov x16, #26\n"

  12. "svc #0x80\n"

  13. );

  14. #endif

  15. }

尽管上面的反调试机制并不完善,可是比直接调用ptrace要好上很多倍,从这一点来看,把握汇编技术关于iOS应用安全和底层研讨十分有利。

入门攻略

iOS设备主要运用的为ARM64汇编,因而本文主要介绍ARM64汇编的入门技巧。汇编入门最难的地方在于对栈的了解,汇编的一切指令操作都是围绕栈完成的,在汇编中,没有变量的概念,只需寄存器和内存。

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

  1. // hello.c

  2. #include<stdio.h>

  3. int test(int a,int b){

  4. int res = a + b;

  5. return res;

  6. }

  7. int main(){

  8. int res = test(1,2);

  9. return0;

  10. }

运用clang可以将其编译为特定指令集的汇编代码,这儿咱们将其编译为ARM64指令集的汇编代码。

  1. clang -S -arch arm64 -isysroot `xcrun --sdk iphoneos --show-sdk-path` hello.c

完好的汇编代码如下。

  1. .section __TEXT,__text,regular,pure_instructions

  2. .ios_version_min 11,2

  3. .globl _test

  4. .p2align 2

  5. _test:;@test

  6. ; BB#0:

  7. sub sp, sp,#16 ; =16

  8. str w0,[sp,#12]

  9. str w1,[sp,#8]

  10. ldr w0,[sp,#12]

  11. ldr w1,[sp,#8]

  12. add w0, w0, w1

  13. str w0,[sp,#4]

  14. ldr w0,[sp,#4]

  15. add sp, sp,#16 ; =16

  16. ret

  17. .globl _main

  18. .p2align 2

  19. _main:;@main

  20. ; BB#0:

  21. sub sp, sp,#32 ; =32

  22. stp x29, x30,[sp,#16] ; 8-byte Folded Spill

  23. add x29, sp,#16 ; =16

  24. orr w0, wzr,#0x1

  25. orr w1, wzr,#0x2

  26. stur wzr,[x29,#-4]

  27. bl _test

  28. mov w1,#0

  29. str w0,[sp,#8]

  30. mov x0, x1

  31. ldp x29, x30,[sp,#16] ; 8-byte Folded Reload

  32. add sp, sp,#32 ; =32

  33. ret

  34. .subsections_via_symbols

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

  1. sub sp, sp,#16 ; =16

  2. str w0,[sp,#12]

  3. str w1,[sp,#8]

  4. ldr w0,[sp,#12]

  5. ldr w1,[sp,#8]

  6. add w0, w0, w1

  7. str w0,[sp,#4]

  8. ldr w0,[sp,#4]

  9. add sp, sp,#16 ; =16

  10. ret

首要介绍一下根本指令和指令的学习办法,要查询某个指令怎么运用,最好的办法是去查询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函数。

  1. int test(int a,int b){

  2. int res = a + b;

  3. return res;

  4. }

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

  1. sub sp, sp,#16

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

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

  1. str w0,[sp,#12]

  2. str w1,[sp,#8]

这两句的意义是将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,则栈的结构变为下图。

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

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

  1. ldr w0,[sp,#12]

  2. ldr w1,[sp,#8]

  3. add w0, w0, w1

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

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

  1. str w0,[sp,#4]

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

接下来就要进行回来了,在比如中咱们说到,函数的回来值一般存储在x0寄存器中回来,因而咱们需求将res的值载入x0寄存器。

  1. ldr w0,[sp,#4]

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

  1. add sp, sp,#16

  2. ret

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

经过上面的比如,咱们对栈有了根本的知道,汇编的操作根本都是对栈进行的,只需了解了栈机制,只需求学习各种指令,即可把握足够运用的汇编技术。

深化

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

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/反调试及绕过

iOS 汇编入门教程(一):ARM64 汇编基础
如果感觉这篇文章不错可以点击在看