导语:在学习iOS逆向开发里面,反汇编是一个非常重要的过程,拿到别人app的可执行文件,无论是通过ida hopper打开,都是汇编代码,虽然汇编分很多种类,iOS逆向分析使用的主要是ARM64,今天就来简单了解一下ARM64汇编。
ARM64汇编
-
处理iOS开发主要了解三方面汇编知识
-
寄存器
-
指令
-
堆栈
-
寄存器
-
64bit通用寄存器
x0
x1
...x27
x28
-
x0~x7
,通常是放函数参数,超出部分使用堆栈来传递 -
x0
通常是函数返回值 -
32bit通用寄存器(64bit向下兼容)
w0
w1
...w7
w28
(是64bit的低32bit)
这里的32bit通用寄存器并非armv7等32bit里的寄存器
如果是armv7的汇编,寄存器是 r1,r2,r3...
-
-
程序计数器
pc
(program Counter)- 记录CPU当前指令,储存着当前正在执行的指令地址
- 类似于8086汇编的ip寄存器
-
堆栈指针
sp
(Stack Pointer)fp
(Frame Pointer),也就是x29
-
链接寄存器
-
lr
(link Register), 也就是x30
是用于存放子程序的返回地址
-
-
程序状态寄存器
-
cpsr (Current Program Status Register)
-
spsr (Saved Program Status Register),异常状态下使用
-
指令
-
mov
mov w10, #0xa
:意思就是将10放到w10寄存器中 -
ret
函数返回
将lr (x30)寄存器的值赋值给pc寄存器
-
add
add w10, w10, w11
:w10的值 + w11值,结果保存到w10寄存器中 -
sub
subs w10, w10, w11
: w10的值 - w 11的值,结果保存到w10中 -
cmp
cmp w10, w11
: 比较两个寄存器里值得大小通过 w10 - w11 的结果得出结论
相减结果会影响到cpsr(程序状态寄存器)寄存器的标志位
-
b
跳转指令(如果和ret指令一起用,无法返回都正确位置)
如果跟cmp配合使用,带条件跳转(如 eq(相等):
beq
相等就跳转) -
bl
-
带返回的跳转指令(调用函数,跟ret配合返回到调用函数指令的下一条指令)
首先将下一条指令的地址储存到lr (即x30链接寄存器)
然后根据地址跳转执行代码
-
-
条件域
-
EQ: equal 相等
-
NE:not equal 不相等
-
GT:great than 大于
-
GE:great equal 大于等于
-
LT:less than 小于
-
LE:less equal 小于等于
-
-
内存操作指令
-
load:从内存中读取数据
-
ldr
ldr w10, [sp, #0x8]
:从[sp, #0x8]地址中读取值保存到w10寄存器中 -
ldur
ldur w10, [sp, #-0x8]
:从[sp, #-0x8]读取数据保存到w10寄存器 -
ldp
ldur w10, w11, [sp, #0x8]
:把值分段储存在w10,w11寄存器中
ldr与ldur不同的是一个是地址加,一个是地址减
-
-
store:向内存写入数据
-
str
str w10, [sp, #0x8]
:注意:是把w10寄存器的值写入[sp, #0x8]地址中 -
stur
同上,不过地址是减
-
stp
stp x10, x11 [sp, #0x20]
:x10,x11寄存器的值分段写入对应内存地址
-
-
零寄存器,里面储存的值是0
-
wzr (32bit, Word Zero Register)
-
xzr (64bit)
-
-
函数的堆栈平衡
-
叶子函数
//函数内没有其他函数 void test1(){ int a = 3; int b = 4; }
//汇编 ArmAssembly`test1: 0x1047b20dc <+0>: sub sp, sp, #0x10 ; =0x10 0x1047b20e0 <+4>: mov w8, #0x3 0x1047b20e4 <+8>: str w8, [sp, #0xc] 0x1047b20e8 <+12>: mov w8, #0x4 0x1047b20ec <+16>: str w8, [sp, #0x8] 0x1047b20f0 <+20>: add sp, sp, #0x10 ; =0x10 0x1047b20f4 <+24>: ret
sub sp, sp, #0x10
:sp = sp - 0x10 sp指针向低地址偏移16个字节,相当于分配了16个字节空间mov w8, #0x3
w8, [sp, #0xc]
:这两句把3保存到内存空间的后四个字节mov w8, #0x4
w8, [sp, #0x8]
:把4保存到比3低地址的4个字节里,挨着3add sp, sp, #0x10
: 恢复sp指针位置,还原现场ret
:返回 -
非叶子函数
//函数内部包括其他函数 void test2(){ int c = 5; int d = 6; test1(); }
//汇编 ArmAssembly`test2: 0x104dd20cc <+0>: sub sp, sp, #0x20 ; =0x20 0x104dd20d0 <+4>: stp x29, x30, [sp, #0x10] 0x104dd20d4 <+8>: add x29, sp, #0x10 ; =0x10 0x104dd20d8 <+12>: mov w8, #0x5 0x104dd20dc <+16>: stur w8, [x29, #-0x4] 0x104dd20e0 <+20>: mov w8, #0x6 0x104dd20e4 <+24>: str w8, [sp, #0x8] 0x104dd20e8 <+28>: bl 0x1000060b0 ; test1 at main.m:11 0x104dd20ec <+32>: ldp x29, x30, [sp, #0x10] 0x104dd20f0 <+36>: add sp, sp, #0x20 ; =0x20 0x104dd20f4 <+40>: ret
sub sp, sp, #0x20
分配空间,sp指针偏移stp x29, x30, [sp, #0x10]
:x29(fp),x30(lr) 把原来的fp,lr寄存器的数据保存到分配好的空间的后16个字节里面add x29, sp, #0x10
: fp指针放到 sp+#0x10 的位置上。。。
bl 0x1000060b0
:1.保存下一条指令的地址到x30(lr)寄存器中 2.跳转 3. 等test1执行完之后根据lr跳转回来ldp x29, x30, [sp, #0x10]
: 取出原来的数据,放回fp(x29),lr(x30)中,fp指针回到原来的位置add sp, sp, #0x20
:sp指针归位,恢复现场在非叶子函数里面如果画一个内存图,可以发现,数据保存是在sp地址到fp地址之间,sp-fp决定数据保存位置,所以叫做堆栈指针(个人理解)
汇编当然有很多知识点,不过不在今天的讨论之中,学习这些是为了分析反汇编代码,有时间精力的朋友当然可以深入研究一下。
由于带有个人学习理解,不免有错漏,如发现请一定更正,谢谢。