@[TOC](IOS 逆向开发(二十六) 汇编-寄存器总结)
IOS 逆向开发(二十六) 汇编-寄存器总结
- 本篇主要讲解关于寄存器相关的知识,我们在逆向的过程中会经常和寄存器打交道。如果我们逆向不懂汇编将不会研究得很深入,如果我们能够看懂汇编代码,将会大大提高我们调试代码和逆向的能力。
先来回顾一些历史性的问题。
- 汇编语言有哪些种类?
- 8086汇编 ,16位
- x86汇编, 32位
- x64汇编, 64位
- ARM 汇编, 主要是在嵌入式手持设备中,我们做IOS逆向,就是主要研究这个。
那么做IOS逆向,最主要的汇编语言是什么呢?
- AT & T 汇编, 对应的是IOS模拟器
- ARM汇编, 对应的是IOS真机
我们知道Objective C源文件(.m)的编译器是Clang + LLVM,Swift源文件的编译器是swift + LLVM。所以借助clang命令,我们可以查看一个.c或者.m源文件的汇编结果:
x86架构的汇编:
clang -S KYLDemo.m
ARM64我们可以借助xcrun:
xcrun --sdk iphoneos clang -S -arch arm64 KYLDemo.m
前面也提到,做IOS逆向经常要用到的就是寄存器地址分析,那么我们常见的寄存器有哪些呢?
- 16个常用寄存器:
- rax、rbx、rcx 、rdx、rsi、rdi、rbp、rsp
- r8、r9、r10、r11、r12、r13、r14、r15
其中:
rax
: 常作为函数返回值使用
rdi、rsi、rdx、rcx、r8、r9
等寄存器常用于存放函数参数
rsp、rbp
用于栈操作
rip作为指令指针: 存储着CPU下一条要执行的指令的地址. 一旦CPU读取一条指令,rip会自动指向下一条指令(存储下一条指令的地址)
这里只是简单概括一下寄存器的种类,下面会详细介绍。
我们了解寄存器后是不够的,逆向时还需要理解各种汇编指令,下面列举一些常见的汇编指令:
指令名称/作用 | AT & T 汇编语法 | Intel汇编语法 | 说明 |
---|---|---|---|
寄存器命名 | %rax | rax | |
操作数顺序 | movq %rax, %rdx | mov rdx, rax | 将rax的值赋值给rdx |
常数/立即数 | movq $0x10, %rax | mov rax, 0x10 | 将0x10赋值给rax |
内存赋值 | movq $0xa, 0x1ff7(%rip) | mov qword ptr [rip+0x1ff7], 0xa | 将0xa赋值给地址为rip+0x1ff7的内存空间 |
取内存地址 | leaq - 0x18(%rbp), %rax | lea rax, [rbp - 0x18] | 将rbp - 0x18这个地址值赋值给rax |
jmp指令 | jmp *%rdx | jmp rdx | call和jmp写法类似 |
操作数长度 | leaw 0x10(%dx), %ax | lea ax, [dx + 0x10] | b = byte(8位) , s= short(16位整型或者32位浮点类型), w = word (16位整型), l = long(32为整型或64位浮点类型), q = quad (64位) |
理解了汇编指令后,我们很多时候做逆向的时候,依赖于命令行调试,所以了解一些常用的lldb调试指令是必须的。
指令作用或名称 | 用法 | 举例说明 |
---|---|---|
读取寄存器的值 | register read/格式, | register read/x |
修改寄存器的值 | register write 寄存器名称 数值 | register write rax 0 |
读取内存中的值 | x/数量-格式-字节大小 内存地址 | x/3xw 0x0000010 |
修改内存中的值 | memory write 内存地址 数值 | memory write 0x0000010 10 |
格式 | x是16进制,f是浮点,d是十进制 | |
字节大小 | b – byte 1字节, h – half word 2字节,w – word 4字节, g – giant word 8字节 | |
expression 表达式 | 可以简写:expr 表达式 | expression $rax, expression $rax = 1 |
po 表达式 | po 内存地址 | po &0x1000010 |
打印信息 print 表达式 | po/x $rax | po (int)$rax |
指令作用或名称 | 用法 | 举例说明 |
---|---|---|
thread step-over、next、n | 单步运⾏行行,把子函数当做整体⼀一步执⾏行行(源码级别) | |
thread step-in、step、s | 单步运⾏行行,遇到子函数会进⼊入子函数(源码级别) | |
thread step-inst-over、nexti、ni | 单步运⾏行行,把子函数当做整体⼀一步执⾏行行(汇编级别) | |
thread step-inst、stepi、si | 单步运⾏行行,遇到子函数会进⼊入子函数(汇编级别) | |
thread step-out、finish | 直接执⾏行行完当前函数的所有代码,返回到上一个函数(遇到断点会卡住) |
寄存器
因为我们主要是研究IOS的逆向需要的汇编知识,所以重点是ARM架构。ARM的全称是Advanced RISC Machine,翻译过来是高级精简指令集机器。
iOS设备CPU架构都是基于ARM的,做过早起IOS开发的肯定对IOS的系统架构非常熟悉了,arm64,arm7…它们指的都是CPU指令集。iPhone 5s及以后的iOS设备的CPU都是ARM 64架构的。
通用寄存器
通用寄存器可用于传送和暂存数据,也可参与算术逻辑运算,并保存运算结果。除此之外,它们还各自具有一些特殊功能。通用寄存器的长度取决于机器字长,汇编语言程序员必须熟悉每个寄存器的一般用途和特殊用途,只有这样,才能在程序中做到正确、合理地使用它们。
16位cpu通用寄存器共有 8 个:AX,BX,CX,DX,BP,SP,SI,DI. 这八个寄存器都可以作为普通的数据寄存器使用。但有的有特殊的用途:AX为累加器,CX为计数器,BX,BP为基址寄存器,SI,DI为变址寄存器,BP还可以是基
指针,SP为堆栈指针。
32位cpu通用寄存器共有 8 个: EAX,EBX,ECX,EDX,EBP,ESP,ESI,EDI功能和上面差不多
而我们用的IPhone 设备中,ARM64常见的的通用寄存器31个64bit,命名为x0-x30。
通用寄存器主要分为5大类:
- 数据寄存器
- 变址寄存器
- 指针寄存器
- 段寄存器
- 指令指针寄存器
接下来我们看看这些通用寄存器的作用:
寄存器 | 特殊用途 | 作用 | 说明 |
---|---|---|---|
SP | 堆栈指针 | 可以把栈理解为存储数据的容器,而Stack Pointer告诉你这个容器有多高,你可以通过移动Stack Pointer来增加/减少你的容器容量。 | |
x30 | LR | 链接寄存器 | 在子程序调用的时候保存下一个要执行指令的内存地址 |
x29 | FP | 帧指针 | 保存函数栈的基地址 |
x19…x28 | Callee-saved寄存器 | ||
x18 | 平台保留寄存器,应用不可以使用。 | ||
x17 | IP1 | 第二个过程内调用临时寄存器(可以被调用单板和PLT代码使用);在其他时候可以用作临时寄存器。 | |
x16 | IP0 | 第一个内部过程调用的scratch寄存器(可以被调用单板和PLT代码使用);在其他时候可以用作临时寄存器。 | |
x9…r15 | 临时寄存器 | ||
x8 | 间接返回值寄存器,在一些特殊情况下,函数的返回值是通过x8返回的。 | ||
x0…x7 | 用来参数传递给子程序或者从函数中返回值,也可以用来存储中间值 | ||
寄存器 | 特殊用途 | 作用 | 说明 |
---|---|---|---|
PC寄存器 | PC寄存器保存下一条将要执行的指令的地址,正常情况下PC指令加1,顺序执行下一跳指令,PC按条件执行指令(比如依次执行指令1,指令5,指令3),是条件分支(比如if/while)的理论基础。 | ||
FLAGS程序状态寄 | FLAGS程序状态寄存器,保存若干flags,数据处理的指令会修改这些状态,条件分支指令会读取flag,决定跳转。 | ||
XZR,和WZR | 代表零寄存器。 | ||
寄存器AX | 寄存器AX乘、除运算,字的输入输出,中间结果的缓存 | ||
AL | AL字节的乘、除运算,字节的输入输出,十进制算术运算 | ||
AH | |||