美文网首页逆向工程
iOS逆向:ARM64汇编基础

iOS逆向:ARM64汇编基础

作者: 码小菜 | 来源:发表于2020-10-16 21:56 被阅读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,说明
    • 64位:x0 ~ x28
    • 32位:w0 ~ w28(属于x0 ~ x28的低32位)
    2,图解
    3,打印
    通用寄存器 x0和w0

    四,基础指令

    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

    相关文章

      网友评论

        本文标题:iOS逆向:ARM64汇编基础

        本文链接:https://www.haomeiwen.com/subject/ifunyktx.html