iOS 逆向学习(arm64 汇编入门)
iOS汇编
iOS汇编语音有很多钟。常见的有8086汇编、arm汇编、x86汇编等等。
arm汇编
iOS的架构从最初的armv6发展到后来的armv7和armv7s,最后发展到现在的arm64,不管是armv6还是后来的armv7,以及arm64都是arm处理器的指令集。armv7和armv7s是真机32位处理器使用的架构,而arm64是真机64位处理器使用的架构。 iPhone 5C是最后一款arm32位版本的iPhone,在iPhone5s之后,所有的iPhone设备都采用arm64架构。arm64汇编在真机上使用,如下:
x86汇编
x86汇编是模拟器使用的汇编语言,它的指令和arm64汇编的语法不同,如下
为什么要学习arm64汇编?
代码调试
在平常开发中,在调试程序的时候,如果程序crash,通常会定位到具体的崩溃代码。但是有时候也会遇到一些比较诡异的crash,比如说崩溃在了系统库中,这个时候定位到具体的crash原因会非常困难。如果利用汇编调试技巧来进行调试,可能会让我们事半功倍。
逆向调试
在逆向别人App过程中,我们可以通过LLDB对内存地址进行断点操作,但是当执行到断点时,LLDB展现给我们的是汇编代码,而不是OC代码,所以想要逆向并且动态调试别人的App,就需要学习汇编的知识。
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术,iOS开发者一起交流学习成长!
arm64汇编入门
想要学习arm64汇编,需要从以下三个方面入手,寄存器、指令和堆栈。
寄存器
arm64中有34个寄存器,如下
通用寄存器
64 bit的通用寄存器有29个,分别是x0 ~ x28
32 bit的也有29个,分别是w0 ~ w28(属于x0 ~ x28的低32位)
其中x0 ~ x7通常拿来存放函数的参数,如果参数更多,则采用堆栈来进行传递
x0中通常存放函数的返回值
程序计数器
pc (Program Counter)寄存器,它记录着当前CPU正在执行的指令的地址,通过register read pc查看寄存器中存储的值
堆栈指针
sp (Stack Pointer)
fp (Frame Pointer),也就是之前所说的x29
链接寄存器
程序状态寄存器
arm体系中包含一个当前程序状态寄存器cpsr (Current Program Status Register)和五个备份的程序状态寄存器spsr (Saved Program Status Registe),备份的程序状态寄存器用来进行异常处理。
程序状态寄存器的每一位都有特定的用途,此处只介绍几种常用的标志位
其中N、Z、C、V均为条件码标志位,他们的内容可被算数或者逻辑运算的结果所改变,并且可以决定某条指令是否被执行。条件码标志各位的具体含义如下
指令
mov指令
mov指令可以将另一个寄存器、被移位的寄存器或者将一个立即数加载到目的寄存器
mov指令在arm64汇编中的实际使用
在xcode中新建test.s文件,在test.s文件中添加以下代码
在xcode中新建test.h头文件,将test.s中的_test方法暴露出来
在viewDidLoad中调用test()函数,然后在LLDB中使用register read x0 读取寄存器中存放的值
通过对汇编指令增加断点,一步一步调试可以看出,在执行完mov指令后,x0和x1寄存器的值都被修改了
ret
ret指令表示函数的返回,而且它还有一个非常重要的作用,就是将lr(x30)寄存器的值赋值给pc寄存器
在viewDidLoad中调用test()函数,在test()函数上打上断点,执行程序如下
使用register read 查看lr和pc寄存器的值
此时,lr寄存器和pc寄存器的值都是test()函数起始地址
使用si指令跳转到test()函数中
再次查看lr和pc寄存器的值,发现lr的值变成了test()函数的下一条指令的地址,也就是test()函数执行完成之后,主程序需要执行的下一条指令。pc寄存器保存了当前即将执行的指令的地址,如下
执行完test()函数,发现程序跳转到了lr寄存器所保存的指令地址,也就是0x00000001021965a8,此时再次查看lr和pc寄存器的值,发现pc寄存器存放的地址已经变成了lr寄存器存放的地址
add指令
add指令是将两个操作数相加,并将结果存放到目标寄存器中。具体说明如下
在arm64汇编中,相应的就是操作x0~x28,执行如下汇编代码
执行完test()函数,通过register read查询x0的值,最后可以看到x0存放的值为7,如下
sub指令
sub指令是将操作数1减去操作数2,再减去cpsr中的C条件标志位的反码,并将结果存放到目标寄存器中
cmp指令
cmp指令是把一个寄存器的内容和另一个寄存器的内容或者立即数做比较,同时会更新CPSR寄存器中条件标志位的值
执行如下汇编代码
在执行cmp代码之前和之后各打印一次CPSR寄存器的值如下
可以发现,在执行cmp操作之后,cpsr寄存器的值变成了0x20000000,转换成16进制后,得到32位标志位如下
可以发现第31位,也就是N位的值为0,同时第30位,也就是Z位的值也为0,这就表示,x0和x1寄存器相比较之后的值为非零非负,而使用x0 - x1得到的结果是1,符合非零非负的条件。
修改汇编代码,调换x0和x1寄存器的位置,如下
再次在cmp代码执行前后读取CPSR寄存器的值
这个时候,cpsr寄存器的值变成了0x80000000,转换成16进制后,如下
可以看出,第31位N位的值变成了1,第30位Z位的值为0,这表示,x0和x1寄存器相比较之后的值为非零负数,使用x1-x0得到的结果是-1,符合非零负数的条件
B指令
B指令是最简单的跳转指令,一旦遇到B指令,程序会无条件跳转到B之后所指定的目标地址处执行。
BL指令
BL指令是另一个跳转指令,但是在跳转之前,它会先将当前标记位的下一条指令存储在寄存器lr(x30)中,然后跳转到标记处开始执行代码,当遇到ret时,会将lr(x30)中存储的地址重新加载到PC寄存器中,使得程序能返回标记位的下一条指令继续执行。
首先执行以下汇编代码
断点到bl label指令时,读取lr寄存器和PC寄存器的值
执行bl label指令,跳转到label标记处,再次读取lr(x30)寄存器和PC寄存器的值,这个时候会发现lr(x30)寄存器存放的地址已经变成mov x1, #0x3这条指令的内存地址
执行完label标记中的所有代码,发现程序再次回到lr寄存器所存储的地址,也就是mov x1, #0x3这句指令继续执行,并且此时pc寄存器所存储的地址也变成了mov x1, #0x3这句指令的地址。
想了解更多逆向相关知识不妨动动小手,添加一下咱们的交流群642363427来为你的技术多添一份光彩。
评论