逆向工程

iOS逆向:ARM64汇编基础

2020-10-16  本文已影响0人  码小菜

目录
一,基本知识
二,搭建环境
三,通用寄存器
四,基础指令
五,跳转指令
六,内存指令
七,堆栈
八,实战练习

一,基本知识

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,说明
2,图解
3,打印
通用寄存器 x0和w0

四,基础指令

1,mov(move的缩写):将第二个赋值给第一个
2,add和sub
3,cmp(compare的缩写)
4,参数和返回值
// 声明
int test(int a, int b); 
// 实现
_test:
add x0, x0, x1
ret
// 调用
NSLog(@"%d", test(2, 3));
// 打印
5

五,跳转指令

1,b:不带返回的跳转

常用条件
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的本质区别

1>第9行指令在执行跳转之前会将第10行指令的地址存入lr寄存器中
2>第6行指令在执行返回之前会将lr寄存器中的地址赋值给pc寄存器
3>在执行返回之后系统就会执行pc寄存器中的地址对应的指令,这样就能顺利的回到第10行

六,内存指令

1,从内存中读取数据(load)

1>打印变量地址

2>读取之前将变量b的地址存入第二个寄存器中

3>读取之后打印第一个寄存器中的数据

1>打印变量地址

2>读取之前将变量c的地址存入第二个寄存器中

3>读取之后打印第一个寄存器中的数据

1>打印变量地址

2>读取之前将变量d的地址存入第三个寄存器中

3>读取之后打印第一个和第二个寄存器中的数据

2,往内存中写入数据(store)

1>打印变量地址

2>写入之前将变量a的地址存入第二个寄存器中

3>写入之后打印第二个寄存器中的地址(变量a的值就变成了5)

1>打印变量地址

2>写入之前将变量a的地址存入第二个寄存器中

3>写入之后打印第二个寄存器中的地址(变量b的值就变成了7)

1>打印变量地址

2>写入之前将变量b的地址存入第三个寄存器中

3>写入之后打印第三个寄存器中的地址(变量a的值就变成了7,变量b的值就变成了9)

3,零寄存器(Zero Register):存储的值固定为0,包括wzr(32位)和xzr(64位)两个

七,堆栈

1,基本介绍
2,叶子函数(内部没有调用其他函数)
void test()
{
    int a = 3;
    int b = 5;
}
_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,非叶子函数(内部有调用其他函数)
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

1>从iPhone上导出APP的可执行文件

2>用Hopper打开可执行文件

1>找到相关指令

2>删除指令(需要先选中指令)

3>删除结果

1>用MachOView查看_age的地址(全局变量存储在数据段中)

2>在Hopper中找到_age:Navigate -> Go To Address -> 输入地址

3>修改_age的值

1>从Hopper中导出新的可执行文件

2>将新的可执行文件替换iPhone中旧的可执行文件

3>重新运行APP

上一篇 下一篇

猜你喜欢

热点阅读