前言
关于应用层开发人员而言,仅仅把握Objective-C和体系结构即可较好的完成开发,但在涉及到应用加固、逆向剖析等内容时仅有应用层开发技术就会显得十分的无力,因而把握汇编关于打破iOS开发水平的瓶颈十分有效。
一个比如
以反调试为例,咱们知道,经过调用ptrace函数可以阻止调试器依靠。
ptrace(31,0,0,0)
这种办法可以被函数hook容易破解,例如运用facebook的fishhook。为了避免函数被hook,咱们可以将函数调用转为经过汇编发起体系调用,即运用下面的代码。
mov x0,#31
mov x1,#0
mov x2,#0
mov x3,#0
mov x16,#26
svc #0x80
其间x0-x3存储的为函数入参,x16存储的为函数编号,经过Apple供给的System Call Table 可以查出ptrace的编号为26,最终一句指令发起了体系调用。经过运用_asm_指令可以将汇编代码嵌入咱们的函数中,构成反调试办法。
// 运用inline办法将函数在调用途强制展开,避免被hook和追寻符号
static __attribute__((always_inline))void anti_debug(){
// 判断是否是ARM64处理器指令集
#ifdef __arm64__
// volatile修饰符可以避免汇编指令被编译器疏忽
__asm__ __volatile__(
"mov x0, #31\n"
"mov x1, #0\n"
"mov x2, #0\n"
"mov x3, #0\n"
"mov x16, #26\n"
"svc #0x80\n"
);
#endif
}
尽管上面的反调试机制并不完善,可是比直接调用ptrace要好上很多倍,从这一点来看,把握汇编技术关于iOS应用安全和底层研讨十分有利。
入门攻略
iOS设备主要运用的为ARM64汇编,因而本文主要介绍ARM64汇编的入门技巧。汇编入门最难的地方在于对栈的了解,汇编的一切指令操作都是围绕栈完成的,在汇编中,没有变量的概念,只需寄存器和内存。
栈
汇编中的栈是由高地址向低地址生长的数据结构,sp指针永久指向栈顶,需求记住的是,在某位置进行存储时,是向高地址进行的,下面以一个简略的比如解说汇编的栈操作。咱们以一段简略的C代码为例。
// hello.c
#include<stdio.h>
int test(int a,int b){
int res = a + b;
return res;
}
int main(){
int res = test(1,2);
return0;
}
运用clang可以将其编译为特定指令集的汇编代码,这儿咱们将其编译为ARM64指令集的汇编代码。
clang -S -arch arm64 -isysroot `xcrun --sdk iphoneos --show-sdk-path` hello.c
完好的汇编代码如下。
.section __TEXT,__text,regular,pure_instructions
.ios_version_min 11,2
.globl _test
.p2align 2
_test:;@test
; BB#0:
sub sp, sp,#16 ; =16
str w0,[sp,#12]
str w1,[sp,#8]
ldr w0,[sp,#12]
ldr w1,[sp,#8]
add w0, w0, w1
str w0,[sp,#4]
ldr w0,[sp,#4]
add sp, sp,#16 ; =16
ret
.globl _main
.p2align 2
_main:;@main
; BB#0:
sub sp, sp,#32 ; =32
stp x29, x30,[sp,#16] ; 8-byte Folded Spill
add x29, sp,#16 ; =16
orr w0, wzr,#0x1
orr w1, wzr,#0x2
stur wzr,[x29,#-4]
bl _test
mov w1,#0
str w0,[sp,#8]
mov x0, x1
ldp x29, x30,[sp,#16] ; 8-byte Folded Reload
add sp, sp,#32 ; =32
ret
.subsections_via_symbols
本节咱们只评论栈操作,因而疏忽main函数和printf调用部分,咱们只看对test函数的调用,节选这一段汇编代码如下。
sub sp, sp,#16 ; =16
str w0,[sp,#12]
str w1,[sp,#8]
ldr w0,[sp,#12]
ldr w1,[sp,#8]
add w0, w0, w1
str w0,[sp,#4]
ldr w0,[sp,#4]
add sp, sp,#16 ; =16
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函数。
int test(int a,int b){
int res = a + b;
return res;
}
这儿涉及了3个int变量,分别是a、b、res,int变量占有4个字节,因而一共需求12个字节,但ARM64汇编为了提高访问效率要求按照16字节进行对齐,因而需求16byte的空间,也便是需求在栈上开辟16字节的空间,咱们来看汇编的榜首句,正是将sp指针下移16字节。
sub sp, sp,#16
sp下移16后,留下了4个4字节的内存空格,合计16字节,咱们继续看下面的句子。
str w0,[sp,#12]
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
,则栈的结构变为下图。
按照“上帝视角”,接下来test函数应该将a和b相加,需求留意的是,只需寄存器才干参加运算,因而接下来的汇编代码又将变量的值从内存中读出,进行相加运算。
ldr w0,[sp,#12]
ldr w1,[sp,#8]
add w0, w0, w1
由此可见先存储再读取后运算其实是剩余的,这是没有进行编译优化的结果,学习不进行编译优化的汇编更能让咱们了解其工作机制。
接下来的代码将w0存入了sp+4,也便是res变量的内存区域。
str w0,[sp,#4]
接下来就要进行回来了,在比如中咱们说到,函数的回来值一般存储在x0寄存器中回来,因而咱们需求将res的值载入x0寄存器。
ldr w0,[sp,#4]
这儿之所以运用w寄存器,是因为int为4字节,这也便是类型转换时带来信息丢失的原因,例如从long到int的转换就类似于将x寄存器的值以w的形式进行存储。最终的代码为将栈复原,并回来到函数调用途继续向下执行。
add sp, sp,#16
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/反调试及绕过