目录
一,基本知识
二,搭建环境
三,通用寄存器
四,基础指令
五,跳转指令
六,内存指令
七,堆栈
八,实战练习
一,基本知识
1,真机是arm64
汇编,模拟器是x86
汇编
2,汇编的三个主要内容:寄存器,指令,堆栈
二,搭建环境
1,新建.h
文件
#ifndef Arm64_h
#define Arm64_h
void test(void);
#endif /* Arm64_h */
2,新建.s
文件
.text // 存储在代码段
.global _test // 将函数公开
_test: // 函数开始
ret // 函数结束(return的缩写)
3,main
函数
#import "AppDelegate.h"
#import "Arm64.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
test();
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
三,通用寄存器
1,说明
- 64位:
x0
~x28
- 32位:
w0
~w28
(属于x0
~x28
的低32位)
2,图解
3,打印
四,基础指令
1,mov
(move的缩写):将第二个赋值给第一个
2,add和sub
-
add
:将第二个加上第三个赋值给第一个 -
sub
:将第二个减去第三个赋值给第一个
3,cmp
(compare的缩写)
- 将第一个减去第二个的结果存入
cpsr
(Current Program Status Register)寄存器的标志位中 - 如果结果为0,则第30位(零位)为1
- 如果结果为负,则第31位(负数位)为1
4,参数和返回值
- 参数:用
x0
~x7
传递,更多参数用堆栈传递 - 返回值:用
x0
传递
// 声明
int test(int a, int b);
// 实现
_test:
add x0, x0, x1
ret
// 调用
NSLog(@"%d", test(2, 3));
// 打印
5
五,跳转指令
1,b:不带返回的跳转
- 无条件跳转:
[b 标记]
- 有条件跳转:
[b.条件 标记]
,条件会判断cpsr
寄存器的标志位,所以需要与cmp
配合使用
常用条件
eq
(equal):相等
ne
(no equal):不相等
gt
(great than):大于
ge
(great equal):大于等于
lt
(less than):小于
le
(less equal):小于等于
2,bl
:带返回的跳转(类似于函数调用)
3,pc
(Program Counter):程序计数器,存储当前正在执行指令的地址
4,lr
(Link Register):链接寄存器,也称为x30
,存储函数的返回地址,也就是函数执行完后下一条指令的地址
- 函数下一条指令的地址
- 函数的返回地址
5,b和bl的本质区别
-
b
的本质:第5行指令只会执行跳转
-
bl
的本质
1>第9行指令在执行跳转之前会将第10行指令的地址存入
lr
寄存器中
2>第6行指令在执行返回之前会将lr
寄存器中的地址赋值给pc
寄存器
3>在执行返回之后系统就会执行pc
寄存器中的地址对应的指令,这样就能顺利的回到第10行
六,内存指令
1,从内存中读取数据(load)
-
ldr
:从第二个寄存器中存储的地址开始读取4或8个字节的数据存入第一个寄存器中
1>打印变量地址
2>读取之前将变量b的地址存入第二个寄存器中
3>读取之后打印第一个寄存器中的数据
-
ldur
:与ldr
用法一样,先偏移再读取;不同的是ldr
用于往高地址偏移,而ldur
用于往低地址偏移
1>打印变量地址
2>读取之前将变量c的地址存入第二个寄存器中
3>读取之后打印第一个寄存器中的数据
-
ldp
:从第三个寄存器中存储的地址开始依次读取4或8个字节的数据存入第一个和第二个寄存器中
1>打印变量地址
2>读取之前将变量d的地址存入第三个寄存器中
3>读取之后打印第一个和第二个寄存器中的数据
2,往内存中写入数据(store)
-
str
:将第一个寄存器中的数据写入第二个寄存器中存储的地址,占据4或8个字节
1>打印变量地址
2>写入之前将变量a的地址存入第二个寄存器中
3>写入之后打印第二个寄存器中的地址(变量a的值就变成了5)
-
stur
:与str
用法一样,也是用于往低地址偏移
1>打印变量地址
2>写入之前将变量a的地址存入第二个寄存器中
3>写入之后打印第二个寄存器中的地址(变量b的值就变成了7)
-
stp
:依次将第一个和第二个寄存器中的数据写入第三个寄存器中存储的地址,每个占据4或8个字节
1>打印变量地址
2>写入之前将变量b的地址存入第三个寄存器中
3>写入之后打印第三个寄存器中的地址(变量a的值就变成了7,变量b的值就变成了9)
3,零寄存器(Zero Register):存储的值固定为0,包括wzr
(32位)和xzr
(64位)两个
- 打印变量地址
- 写入之前将变量b的地址存入第二个寄存器中
- 写入之后打印第二个寄存器中的地址(变量a的值就变成了0,变量b的值也变成了0)
七,堆栈
1,基本介绍
堆栈是用来为局部变量分配内存空间的
堆栈指针:
sp
(Stack Pointer),fp
(Frame Pointer,也称为x29
)
2,叶子函数(内部没有调用其他函数)
- C代码
void test()
{
int a = 3;
int b = 5;
}
- 汇编代码(用
clang
进行转换)
_test:
sub sp, sp, 16 ; 将sp指针往低地址偏移16个字节(分配内存空间)
orr w8, wzr, 3 ; 将3赋值给w8
str w8, [sp, 12] ; 将3存储到sp+12的位置
mov w8, 5 ; 将5赋值给w8
str w8, [sp, 8] ; 将5存储到sp+8的位置
add sp, sp, 16 ; 将sp指针往高地址偏移16个字节(释放内存空间)
ret
- 图解
- 说明
1>偏移的16个字节就是为test函数分配的内存空间
2>局部变量存储在栈上,并且是从高地址开始分配的
3>释放内存空间只需将sp
指针移回即可,无需清空数据
3,非叶子函数(内部有调用其他函数)
- C代码
void test2()
{
int c = 7;
int d = 9;
test();
}
- 汇编代码
_test2:
sub sp, sp, 32 ; 将sp指针往低地址偏移32个字节(分配内存空间)
stp x29, x30, [sp, 16] ; 将fp和lr依次存储到sp+16的位置(保护现场)
add x29, sp, 16 ; 将sp+16赋值给fp
orr w8, wzr, 7 ; 将7赋值给w8
stur w8, [x29, -4] ; 将7存储到fp-4的位置
mov w8, 9 ; 将9赋值给w8
str w8, [sp, 8] ; 将9存储到sp+8的位置
bl _test ; 跳转到test函数
ldp x29, x30, [sp, 16] ; 从sp+16的位置依次取出fp和lr(恢复现场)
add sp, sp, 32 ; 将sp指针往高地址偏移32个字节(释放内存空间)
ret
- 图解
- 说明
1>16个字节用来存储test2函数的局部变量,16个字节用来保护现场,所以需要偏移32个字节
2>假设是test3函数调用的test2函数,那么test3函数也是非叶子函数,也会用到fp
指针,所以在进入test2函数时先将fp
指针保存起来,在退出时再取出,这样在test2函数中对fp
指针的操作就不会影响到test3函数中fp
指针的指向
3>lr
寄存器与fp
指针同理
八,实战练习
1,动态调试微信
- 打印点击朋友圈调用的所有方法
- 给其中一个方法设置断点
1>获取
ASLR
地址2>获取方法地址
3>设置断点
- 点击朋友圈进入断点
- 打印方法调用者、名称和参数
方法调用都会转换为
objc_msgSend
函数的调用,该函数的第一个参数是方法调用者,第二个参数是方法名称,从第三个参数开始都是方法参数,这些参数都存放在通用寄存器中
2,破解APP
- 实例代码
@implementation ViewController
int _age = 10;
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self addLabel];
[self showAlert];
}
- (void)addLabel {
UILabel *label = [[UILabel alloc] init];
label.text = [NSString stringWithFormat:@"my age is %d", _age];
label.frame = CGRectMake(30.0, 30.0, 100.0, 30.0);
[self.view addSubview:label];
}
- (void)showAlert {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示"
message:@"很抱歉,暂时无法操作!"
preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:alert
animated:YES
completion:nil];
}
@end
- 目标:移除弹窗并将10改为20
- 准备工作
1>从iPhone上导出APP的可执行文件
2>用
Hopper
打开可执行文件
- 移除弹框
1>找到相关指令
2>删除指令(需要先选中指令)
3>删除结果
- 将10改为20
1>用
MachOView
查看_age的地址(全局变量存储在数据段中)2>在
Hopper
中找到_age:Navigate
->Go To Address
->输入地址
3>修改_age的值
- 保存修改并查看结果
1>从
Hopper
中导出新的可执行文件2>将新的可执行文件替换iPhone中旧的可执行文件
3>重新运行APP