汇编中常见指令解释

这篇博文很有趣,竟然将hexo的content设置给冲垮了,感觉里面写了不可思议的东西,十分值得我去深挖!!!


1. pop 和 push

常见的汇编指令源码

pop机器码 含义 push机器码 含义
5f pop edi 57 push edi
5e pop esi 56 push esi
5d pop ebp 55 push ebp
5c pop esp 54 push esp
5b pop ebx 53 push ebx
5a pop edx 52 push edx
59 pop ecx 51 push ecx
58 pop eax 50 push eax

2. call


call (short) eax
1.将当前指令的下一条指令的地址压入栈中;
2.JMP到EAX这个地址。

call (long) eax
1.将CS压入栈中;
2.将当前指令的下一条指令的地址压入栈中;
2.JMP到EAX这个地址。

3. retn 和 retf


retn
1.将当前的ESP中指向的地址出栈;
2.JMP到这个地址。

retn k
1.将当前的ESP中指向的地址出栈;
2.JMP到这个地址;
3.弹出栈顶的k个字节的数据

retf
1.将当前的ESP中指向的地址出栈给EIP;
2.将当前的ESP中指向的地址出栈给CS;
2.JMP到这个地址。

4. jmp


jmp短跳
\xeb\xf6: 	jmp short $-8(其中$指代当前EIP)

在Asm2MachineCode(x86)中如果要尝试jmp short $-8,是不可能实现的,但是我们可以:
1. ImageBase(hex)设置为0012ff60 (此处为当前的EIP地址)
2. jmp short 0012FF58
  

jmp长跳
\xE9\x2B\xFF\xFF\xFF\xFF: jmp 0x0012FF30

在Asm2MachineCode(x86)中如果要尝试jmp short $-8,是不可能实现的,但是我们可以:
1. ImageBase(hex)设置为0012ff58 (此处为当前的EIP地址)
2. jmp 0012FE88

5. cmp

cmp op1, op2

ZF=1 这个简单,则说明两个数相等

当无符号时:
CF=1 则说明了有进位或借位,cmp是进行的减操作,故可以看出为借位,所以,此时oprd1<oprd2
CF=0 则说明了无借位,但此时要注意ZF是否为0,若为0,则说明结果不为0,故此时oprd1>oprd2

当有符号时:
若SF=0,OF=0 则说明了此时的值为正数,没有溢出,可以直观的看出,oprd1>oprd2
若SF=1,OF=0 则说明了此时的值为负数,没有溢出,则为oprd1<oprd2
若SF=0,OF=1 则说明了此时的值为正数,有溢出,可以看出oprd1<oprd2
若SF=1,OF=1则说明了此时的值为负数,有溢出,可以看出oprd1>oprd2

最后两个可以作出这种判断的原因是,溢出的本质问题:
两数同为正,相加,值为负,则说明溢出
两数同为负,相加,值为正,则说明溢出
故有,正正得负则溢出,负负得正则溢出

6. leave

其作用是删除函数的栈帧

leave在32位汇编下相当于

1
2
mov esp,ebp                                            
pop ebp

7. les

LES( load ES)指令的功能是:把内存中指定位置的双字操作数的低位字装入指令中指定的寄存器、高位字装入ES寄存器。

1
2
3
4
LES DSET,SRC
LES REG,MEM
DEST为destination(目的地址),SRCsource(源地址);
REG为register(CPU寄存器),MEM为memory(内存地址)。

DEST=WORD PTR[SRC]

ES=WORD PTR[SRC+2]

DEST赋值为SRC处双字的低位;

ES赋值为SRC处双字的高位;

8. 比较

A(above)大于、B(below)小于、E(equal)等于,用于比较无符号数

G(great)大于、L(less than)小于、E(equal)等于,用于比较带符号数

其实这些地方也是漏洞点,有时候比较没有对是否有符号进行确定,所以可能会出问题。

9. lods/stos

lodsb指令,将esi指向的地址处的数据取出来赋给AL寄存器, esi=esi+1;

lodsw指令则取得是一个字。

lodsd指令,取得是双字节,即mov eax,[esi],esi=esi+4;

stosb指令,将AL寄存器的值取出来赋给edi所指向的地址处。mov [edi],AL;edi=edi+1;

stosw指令去的是一个字。

stosd指令,取得是双字节,mov [edi],eax;edi=edi+4;

数学指令

add src, dest
将 src 添加到 dest 。

sub src, dest
从 dest 中减去 src 。

imul src, dest
将 dest 乘以 src 。

idiv divisor Divide rdx:rax
将 rdx:rax 除以 divisor 。将商存储在 rax 中,并将余数存储在 rdx 中。

shr reg
shl reg
将 reg 向左或向右移动 cl 中的值( rcx 的低 8 位)。

ror src, dest
将 dest 向左或向右循环 src 位。

cmp src, dest
设置与 dest 是否小于、等于或大于 src 相对应的标志

其他指令

endbr64

如果我们使用gdb查看程序的汇编代码,我们会发现第一句往往是endbr64。这个语句实际上是一个安全语句。为了对付ROP、JOP、COP攻击技术。

参考

https://mp.weixin.qq.com/s/htJ73BQK5zRTReHRaAqwxg

0x1. ARM64

mov

传送指令:将立即数传送到寄存器/内存单元,或互传内存单元和寄存器两者中的数据。

例如:mov x0, #0x1 / mov $0x1, %rdi 将立即数0x1移动到寄存器x0/rdi中

mvn

该指令与mov唯一不同的是:需要对操作数进行按位取反。

str

store register. 将寄存器中的数据存到内存单元中。

例如:str x0, [sp, #0x28] 将寄存器x0中的数据存到地址sp+0x28处

stp

store pair of registers.

例如:stp x29, x30, [sp, #0x70] 先将x29存到地址sp+0x70处,再将x30存到sp+0x78处 (Tip1)。

{ %note info % }
Tip1: 64位下,寄存器大小为8bytes
{ %endnote% }

ldr

load register. 从内存中读取数据,存到寄存器中。

例如:ldr x1, [sp, #0x30] 将内存地址sp+0x30处的数据加载到x1中。

ldp

load pair of registers.

例如:ldp x29, x30, [sp, #0x70] 将内存地址sp+0x70处的数据加载到x29中,再将sp+0x78(Tip1)处的数据加载到x30中。

b

branch. 无条件跳转。

例如:b 0x1b6b79cf8 跳转到0x1b6b79cf8处继续执行。

bl

branch with link. 将下一条指令地址copy到lr中,然后跳转。由于保存了下一条指令地址(相对于pc),所以可实现子程序的返回,而b只能单纯的实现跳转,不能实现子程序返回。

例如:

1
2
   0x102b7d8a4 <+160>: bl   0x102bda450 ; symbol stub for: objc_msgSend
-> 0x102b7d8a8 <+164>: adrp x0, 107

当程序执行完bl指令时,lr中的内容应该是是bl下一条指令的地址,即0x102b7d8a8。通过控制台查看lr中的内容如下:

1
2
3
fp = 0x000000016d2853c0
lr = 0x0000000102b7d8a8 `-[AppDelegate application:didFinishLaunchingWithOptions:] + 164 at AppDelegate.m:41:5
sp = 0x000000016d285350

cbz

compare and branch on zero. 比较操作数是否为0,若为0则跳转。

例如:cbz w24, 0x1b6b798f4 若w24 (x24寄存器低32位) 中的数据为0,则跳转到0x1b6b798f4处执行 (Note1)。

Note1: 跳转地址需要在cbz/cbnz指令之后的4~130字节内。

cbnz

compare and branch on non-zero. 操作数不为0,则跳转。

例如:cbnz w27, 0x1b73c9e2c 若w27中的数据不为0,则跳转到0x1b73c9e2c处执行 (Note1)。

lsl

logical shift left. 左移位。

例如:str x0, [x1, x2, lsl #2] 将x2中的值左移两位,再加上x1中的值,得到的结果为一个内存地址,最后将x0中的数据存到该内存中。

lsr

logical shift right. 右移位。

cmp

compare. 比较两个数是否相等,首先将两个数相减,若差为0,则ZF (Tip2) 为1,即两者相等。

cmp w8, #0x3 通过判断w8中的值与0x3的差值是否为0,即ZF (Tip2) 是否为1,来比较两者是否相等。

Tip2: ZF是零标志位寄存器。它记录相关指令执行后,其结果是否为0。若结果为0,则ZF=1;若结果为1,则ZF=0。

adrp

form pc-relative address to 4KB page. 先将操作数左移12位,再将pc的低12位清零 (即3个十六进制位清零,得到一个页对齐地址),最后两者相加,赋值给寄存器。

例如:

1
2
   0x1b703f074 <+44>:  adrp   x20, 270332
-> 0x1b703f078 <+48>: ldr x0, [x20, #0x7a0]

执行adrp时,先将操作数270332 (十六进制为0x41ffc) 左移12位得到0x41ffc000,再将pc (0x1b703f074) 低12清零得到 0x1b703f000,最后两者相加得到0x1f903b000赋值给x20。通过控制台查看x20中的内容:

1
2
3
x19 = 0x000000016ef25fb0
x20 = 0x00000001f903b000
x21 = 0x0000000281454380

adr

load a program-relative or register-relative address into a register. 是一条伪指令,遇到该指令时汇编器会生成一条add或sub指令,来计算pc和操作数的和或者差,最后将得到的结果,放在寄存器中。

例如:

1
2
3
4
5
6
7
; 生成加法指令,即x0 = 0x1022c3b08 + 0x2fc48 = 0x1022f3750
0x1022c3b08 <+108>: adr x0, #0x2fc48 ; _MergedGlobals + 16
-> 0x1022c3b0c <+112>: nop

; 生成减法指令,即x2 = 0x1022c3b10 - 0x390 = 0x1022c3780
0x1022c3b10 <+116>: adr x2, #-0x390 ; initializeAvailabilityCheck
-> 0x1022c3b14 <+120>: nop

brk

breakpoint instruction. 用于生成断点指令异常。

tbz

test branch zero. 测试位为0,则跳转。

tbz w24, #0x6, 0x19307005c ; 即w24第6位,若为0,则跳转到0x19307005c执行

w24 二级制数为:0b00000111000000000000100000000110

tbnz

test branch no zero. 测试位不为0,则跳转。

tbnz w24, #0x6, 0x19307005c ; 即w24第6位,若不为0,则跳转到0x19307005c执行

w24 二级制数为:0b00000111000000000000100000000110

0x2. AT&T

test

将指令后的两个数进行按位与操作,其结果影响ZF。通常用来判断某个数是否为0/nil。

例如:

testq %rax, %rax ; 若%rax中的值为0,则ZF=1;反之,ZF=0,q表示四字长

sete

取ZF中的值,放入目标寄存器中。

例如:

1
2
testq %rax, %rax  ; 两个寄存器中的值按位与,结果影响ZF
sete %r12 ; 若%rax和%rbx相等,即ZF=1,则将%r12设为1,反之设为0

setne

取ZF中的值,然后取反,再放入目标寄存器中。

例如:setne %rax 若ZF=1,则%rax设为0;反之设为1

je / jz

je 和 jz这两条指令虽然名字不同,但做的事情都是一样的。即ZF=1,则跳转。

例如:

1
2
3
4
5
libobjc.A.dylib`objc_retain:
0x7fff50bad350 <+0>: testq %rdi, %rdi ; 判断%rdi中的值(第一个参数)是否为nil
0x7fff50bad353 <+3>: je 0x7fff50bad371 ; <+33> 若%rdi为nil,则跳转到0x7fff50bad371执行
0x7fff50bad355 <+5>: js 0x7fff50bad373 ; <+35>
-> 0x7fff50bad357 <+7>: movq (%rdi), %rax

jne / jnz

jne 和 jnz这两条指令做的事情也都是一样的。即ZF=0,则跳转。

js

jump if sign. 若结果为负数,即SF=1,则进行跳转。

SF: 符号标志位

它记录相关指令执行后,其结果是否为负。若结果为负,则SF=1;若为非负,则SF=0。

nop

no operation. 不执行任何操作。是一条单字节指令。 Opcode 90

ud2

undefined instruction. 该指令用于生成一个无效操作码。当CPU试图执行无效或未定义的操作码时,将发生无效的操作码异常。UD2指令除了引发无效的操作码异常外,与NOP指令相同。

这里的异常是指CPU在发生“错误”时生成的。大多数情况下,有些异常并不是真正的错误,而是中断的一种类型。比如: Page Fault。异常分类如下:

Faults (错误):这些错误可以被纠正,程序可以像什么都没有发生一样继续运行。
Traps (陷阱):陷阱指令执行后会立即被报告。
Aborts (终止):一些严重的不可恢复的错误。

ret

该指令用于子程序的返回。将栈顶数据(返回地址)弹出,赋值给 ip 寄存器,此时 sp 会增加一个内存单元大小,来释放栈空间。

{ %note info % }
栈底 [ … | … , parameters, return address | previous frame pointer, %rbp ] 栈顶

pop %rbp 👇

栈底 [ … | … , parameters, return address ] 栈顶

ret 👇

栈底 [ … | … , parameters ] 栈顶

caller release parameters 👇

栈底 [ … | … ] 栈顶
{ %endnote% }

xor

常常使用这个指令来判断两个地址中的数据是否一致:

1
xor    %gs:0x14,%eax

本文章装载于此1

本文章装载于此2


汇编中常见指令解释
https://ysc2.github.io/ysc2.github.io/2023/11/09/汇编中常见指令解释/
作者
Ysc
发布于
2023年11月9日
许可协议