系列文章目录
第一章 汇编学习记录-二进制炸弹-x86架构
第二章 汇编学习记录-二进制炸弹-arm64架构
第三章 汇编学习记录-简单总结
前言
这里对二进制炸弹破解过程中常用的汇编命令及其用法进行一点总结。并不是很详细,只是用来看懂(而不是自己写)汇编代码。
一、x86汇编简单总结
1、寄存器
其中有几个特殊的通用寄存器我们需要知道其功能
-
rdi,rsi,rdx,rcx,r8,r9用来放函数的参数,更多参数存在栈中
-
rax用来存函数的返回值
-
程序计数器 rip记录当前执行哪条指令
-
堆栈指针 rsp栈指针
2、mov指令(及寻址方式)
movX src,dst
将src(可以说寄存器,立即数,内存)送到dst(可以是寄存器,内存,但不能直接在两个内存间传送),相当于c语言的dst=src
寻址方式
最一般的形式:
D(Rb,Ri,S)
含义:Mem[Reg[Rb]+S*Reg[Ri]+ D]
- D——常量,表示位移量(displacement): 1, 2, or 4 字节
- Rb——基址寄存器(Base register): 所有16位整数寄存器
- Ri——变址寄存器(Index register): 不可用%rsp
- S ——比例因子(Scale): 1, 2, 4, or 8
常用的形式
(Rb) //Mem[Reg(Rb)]
例如 movq (%rcx),%rax 相当于rax=*rcx
D(Rb) //Mem[Reg[Rb]+D]
例如 movq 8(%rbp),%rdx 相当于 rdx = *(rbp+8)
(Rb,Ri) //Mem[Reg[Rb]+Reg[Ri]]
D(Rb,Ri) //Mem[Reg[Rb]+Reg[Ri]+D]
(Rb,Ri,S) //Mem[Reg[Rb]+S*Reg[Ri]]
这一条一般用来访问数组 S即为数组元素大小,Rb即为数组首地址,Ri即为索引
即相当于访问数组元素Rb[Ri]
条件传送指令
一般形式
cmovcc src, dst
- c:表示这是条件传送
- cc:表示条件
- src: r16, r32, r64
- dst: r/m16, r/m32, r/m64
条件在后面与跳转指令一起说,这里只需要知道条件传送指令会根据cmp或test的结果决定是否传送
3、取地址指令
leaq Src, Dst
- Src 地址模式表达式
- 将表达式对应的地址保存到Dst中
举个例子
leaq (%rdi,%rdi,2), %rax
我们知道(%rdi,%rdi,2)的意义应为Mem[R[rdi]+2R[rdi]]
对其取地址即为R[rdi]+2R[rdi]
所以该指令相当于rax= 2rax+rax=3rax
4、算术运算指令
双操作数指令
- addq Src,Dest #Dest = Dest + Src
- subq Src,Dest # Dest = Dest - Src
- imulq Src,Dest # Dest = Dest * Src
移位运算
- salq Src,Dest # Dest = Dest << Src同shlq
- sarq Src,Dest # Dest = Dest >> Src算术移位
- shrq Src,Dest # Dest = Dest >> Src逻辑移位
位运算
- xorq Src,Dest # Dest = Dest ^ Src
- andq Src,Dest # Dest = Dest & Src
- orq Src,Dest # Dest = Dest | Src
单操作数指令
- incq Dest # Dest = Dest + 1
- decq Dest # Dest = Dest - 1
- negq Dest # Dest = - Dest(取负)
- notq Dest # Dest = ~Dest(求反)
5、cmp指令
cmp 源操作数 目的操作数
CMP指令执行隐含的减法操作:目的操作数-源操作数,
并设置标志位,但不保存减法的结果,两个操作数都不
会被修改
格式与AND相同,修改OF、SF、ZF、CF、AF和PF
条件码
OF、SF、ZF、CF、AF和PF
执行某些指令会修改条件码,条件跳转指令,条件传送指令会根据条件码决定是否执行。
具体的条件码形式在这里我们不需要深究。只要记住助记符即可。
图上是设置条件码的指令setXX,助记符XX及其对应意义与条件传送,条件跳转的条件助记符意义相同
6、跳转指令
格式:
jX <目标地址>
- X表示根据条件码跳转
7、函数调用
call func_label
- 返回地址入栈(Push)
- 跳转到func_label (函数名字就是函数代码段的起始地址)
- 返回地址:
紧随call指令的下一条指令的地址 (考虑PC——RIP的含义)
ret
- 从栈中弹出返回地址(pop)
- 跳转到返回地址
其他
跳转表
需要注意的是,当我们(C语言中)使用switch语句时
编译器会构造一个跳转表
如图,跳转表在地址为0x4031c0的位置
由因为地址(指针)占有8个字节
所以语句jmpq *0x4031c0(,%rax,8)
意味跳转到首地址为0x4031c0的地址数组中第rax项
的位置
二、arm64汇编简单总结
1、寄存器
通用寄存器
64位:x0 ~ x28
32位:w0 ~ w28 (x0 ~ x28 的低位,如图)
其中有几个特殊的通用寄存器我们需要知道其功能
- x0 ~ x7用来放函数的参数,更多参数存在栈中,类比x86架构的rdi,rci,rdx,rcx,r8,r9
- x0用来存函数的返回值,类比rax
程序计数器
- pc记录当前执行哪条指令,类比rip
堆栈指针
- sp Stack Pointer栈指针,类比rsp
- fp Frame Pointer也就是x29
链接寄存器
- lr Link Register存储函数返回地址,也就是x30
一般在函数的开头与结尾会有这样的代码,实现函数的调用与返回
程序状态寄存器
- cpsr:Current Program Status Register 程序状态寄存器
cmp指令的结果会放到这里 - spsr:Saved Program Status Register,异常状态下使用
只是看懂正常汇编代码的话,这两个其实不太重要
零寄存器
- wzr:4字节
- xzr:8字节
用于清零操作
2、mov指令
mov x0, #10
立即数->寄存器 将立即数10存到寄存器x0 基本相当于C语言中 x0=10
mov x0, x1
寄存器->寄存器 将寄存器x1中的值存到寄存器x0 基本相当于C语言中 x0=x1
3、add、sub指令
add
add x0, x1, #5 //相当于C语言中 x0=x1+5
add x0, x1, x2 //相当于C语言中 x0=x1+x2
add x0, x1, x2, LSL #5 //相当于C语言中 x0=x1+(x2<<5)
sub
sub x0, x1, x2 //相当于C语言中 x0=x1-x2
sub x0, x1, x2, LSL #5 //相当于C语言中 x0=x1-(x2<<5)
4、位运算
按位与
AND R0, R0, #5 //5的二进制表示为0101,保持R0的第0位和第2位,其余位清0
按位或
ORR R0, R0, #5 //R0的第0位和第2位设置为1,其余位不变
按位异或
EOR R0, R0,#5 //R0的第0位和第2位取反,其余位不变
5、cmp指令
这里会用到cpsr
程序状态寄存器
cmp x0 ,x1
会根据x0-x1的结果,修改cpsr的值。
若只想看懂汇编代码,不需要知道具体是怎样修改的,若想要了解,请参阅其他文献
6、跳转指令
无条件跳转
b <目标地址>
无条件跳转到标记处,类比jmp
带条件的跳转
例如
b.eq <label>
当上一条cmp指令的结果是相等时跳转
另外比较常见的是NE,LS,GE,LT,GT,LE
记住这几个的意义一般就够用了
函数调用
bl <函数名>
调用函数 类比call
比较+跳转
CBNZ
CBNZ X1,label //如果X1!= 0则跳转到label
CBZ
CBZ X1,label //如果X1== 0则跳转到label
TBNZ
TBNZ X1,#3, label //若位X1[3]!=0,(将其二进制从左往右数,从0数到3)则跳转到label
也就相当于
if(x1>=8)goto label;
TBZ
TBZ X1,#3, label //若位X1[3]==0,则跳转到label
7、加载指令(及寻址方式)
ldr
通用格式为
LDR 目的寄存器,<存储器地址>
举例
寄存器间接寻址
- LDR R0,[R1]
将存储器地址为x1的字数据读入寄存器R0。相当于C语言的R0=*R1
基址变址寻址
前索引
LDR R0,[R1,R2]
将存储器地址为R1+R2的字数据读入寄存器R0。相当于C语言的R0=*(R1+R2)
LDR R0,[R1,#8]
将存储器地址为R1+8的字数据读入寄存器R0。相当于C语言的R0=*(R1+8)
LDR R0,[R1,R2,LSL#2]
将存储器地址为R1+R2×4的字数据读入寄存器R0相当于C语言的R0=*(R1+R2<<2);
一般我们通过这种方式访问数组 LSL #2代表数组元素大小为4字节 该语句为将数组元素R1[R2]放入R0
自动索引
LDR R0,[R1,R2] !
将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1。相当于C语言的R0=*(R1+R2);R1+=R2;
LDR R0,[R1,#8] !
将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。相当于C语言的R0=*(R1+8);R1+=8;
LDR R0,[R1,R2,LSL#2]!
将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。相当于C语言的R0=*(R1+R2<<2);R1+=R2<<2;
后索引
LDR R0,[R1],R2
将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。相当于C语言的R0=*R1;R1+=R2;
LDR R0,[R1],R2,LSL#2
将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
- 另外还有ldrX------X指某一个特定字母
ldrb 从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。
ldrh 用于从存储器中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。
ldur
ldur与ldr的区别:
ldr用于正数(偏移值是正数);
ldur用于负数(偏移值是负数)。
ldp
根据地址读取一对数据,按地址顺序赋值给一对寄存器:
1、先赋值给第一个寄存器;
2、偏移地址后,取值赋值给第二个寄存器。
load pair w0,w1 [x2 ,#0x10]
该指令一般用在程序结尾,作为出栈指令。例如
ldp x29, x30, [sp, #0x10] // 将 sp 偏移 16 个字节的值取出来,存入寄存器 x29 和寄存器 x30
至于具体存放顺序,这里我们不需要深究,因为我们只是需要读懂代码即可。
8、存储指令
st store存储
str
往内存中写数据(偏移值为正),用于从源寄存器中将一个32位的字数据传送到存储器中。
STR R0,[R1],#8
将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。相当于*R1=R0;R1+=8;
STR R0,[R1,#8]
将R0中的字数据写入以R1+8为地址的存储器中。相当于*(R1+8)=R0;
stur
往内存中写数据(偏移值为负)
stp
该指令一般用在程序开头,作为入栈指令
stp x29, x30, [sp, #0x10] // 将 x29, x30 的值存入 sp 偏移 16 个字节的位置
同上,不需要太过深究
9、地址生成指令
adr与adrp
粗略地看
相当于一个赋值语句
x2=0x40200
10、移位运算
LSL
逻辑左移
按操作数所指定的数量向左移位,低位用零来填充。
MOV R0, R1, LSL#2 //将 R1中的内容左移两位后传送到 R0中。
ASL
算术左移
同LSL
LSR
逻辑右移
按操作数所指定的数量向右移位,左端用零来填充。
MOV R0, R1, LSR#2 //将 R1中的内容右移两位后传送到 R0中,左端用零来填充。
ASR
算术右移
按操作数所指定的数量向右移位,左端用第31位的值来填充。
MOV R0, R1, ASR#2 //将 R1中的内容右移两位后传送到 R0中,左端用第 31位的值来填充。
ROR
循环右移
按操作数所指定的数量向右循环移位,左端用右端移出的位来填充。
MOV R0, R1, ROR#2 //将 R1中的内容循环右移两位后传送到 R0中。
RRX
带扩展的循环右移
进行带扩展的循环右移的操作,按操作数所指定的数量向右循环移位,左端用进位标志位 C来填充。
MOV R0, R1, RRX#2 //将 R1中的内容进行带扩展的循环右移两位后传送到 R0中。
其他
UXTW/UXTH/UXTB:零扩展single-word / half-word / byte
SXTW/SXTH/SXTB:符号扩展 single-word / half-word / byte
总结
以上就是今天要讲的内容,本文仅仅简单介绍了看懂汇编代码(由高级语言的编译器产生,且优化等级较低的,代码变形较小的机器指令反汇编形成的)需要了解的指令,而关于汇编代码还有非常多的内容,如需要更多的了解,请参阅更多的文献。
参考
十九、 ARM64汇编(一)
二十、ARM64汇编(二)
ARM的六大类指令集—LDR、LDRB、LDRH、LDM、STR、STRB、STRH、STM
ARM指令集–移位指令
ARM汇编指令 UXTW/UXTH/UXTB, SXTW/SXTH/SXTB