汇编语言

pwn.college 网络安全学院 --- pwn.college

寄存器

正经理解

寄存器 主要用途 编号 存储数据的范围
EAX 累加器 0 0 - 0xFFFFFFFF
ECX 计数 1 0 - 0xFFFFFFFF
EDX I/O指针 2 0 - 0xFFFFFFFF
EBX DS段的数据指针 3 0 - 0xFFFFFFFF
ESP 堆栈指针 4 0 - 0xFFFFFFFF
EBP SS段的数据指针 5 0 - 0xFFFFFFFF
ESI 字符串操作的源指针;SS段的数据指针 6 0 - 0xFFFFFFFF
EDI 字符串操作的目标指针;ES段的数据指针 7 0 - 0xFFFFFFFF
寄存器 编号(二进制) 编号(十进制)
32位 16位 8位
EAX AX AL 000 0
ECX CX CL 001 1
EDX DX DL 010 2
EBX BX BL 011 3
ESP SP AH 100 4
EBP BP CH 101 5
ESI SI DH 110 6
EDI DI BH 111 7
寄存器 32 位 (IA-32) 64 位 (x86-64)
通用寄存器 EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP RAX, RBX, RCX, RDX, RSI, RDI, RSP, RBP, R8-R15
段寄存器 CS, DS, SS, ES, FS, GS CS, DS, SS, ES, FS, GS
控制寄存器 EFLAGS, EIP RFLAGS, RIP
指针寄存器 ESP, EBP RSP, RBP

个人理解

RIP

存放下一条指令的偏移地址

RSP

存放当前栈帧的栈顶偏移地址

RBP

存放当前栈帧的栈底偏移地址

RAX

通用寄存器。存放函数的返回值

寄存器的关系

MSB                                    LSB
+----------------------------------------+
|                   rax                  |
+--------------------+-------------------+
                     |        eax        |
                     +---------+---------+
                               |   ax    |
                               +----+----+
                               | ah | al |
                               +----+----+

汇编

实际中执行指令时,EIP已经移动到下一行了

标志位

更新标志位的操作:

  • 大多数算术指令。
  • 比较指令cmp(相减,但丢弃结果)。
  • 比较指令test(想与,但丢弃结果)。

mov

mov 指令要求目标寄存器必须是 raxrbxrcxrdxr8r9r10r11r12r13r14r15 中的一个。

在 x86-64 汇编中,不能直接将大于 1 字节的数据(如 qworddword)直接存储到内存地址。必须先将数据加载到寄存器中,然后再通过 qword ptrdword ptr 等操作符将寄存器中的值存储到指定的内存地址。

所以想要执行 [rdi] = 0xdeadbeef00001337 [rsi] = 0xc0ffee0000操作就要先赋值给额外的寄存器,再把额外的寄存器给目标寄存器的地址。
而不是赋值给地址再把地址给目标寄存器的地址

mov r8,0xdeadbeef00001337
mov r9,0xc0ffee0000
mov [rdi],r8
mov [rsi],r9

add

add 指令的两个操作数必须满足以下条件之一:

  1. 两个操作数都是寄存器。
  2. 一个操作数是寄存器,另一个操作数是立即数。
  3. 一个操作数是寄存器,另一个操作数是内存地址。

但不能是:

  • 一个操作数是寄存器,另一个操作数是内存地址的直接内容

也就是如果要实现add rsi, [rdi+8]
就要先将内存中的值加载到一个寄存器中,然后再进行加法操作。

    ; 将 rdi + 8 处的四字数据加载到 rbx
    mov rbx, [rdi + 8]
    ; 将 rbx 的值加到 rsi 中
    add rsi, rbx

mul/imul

表示乘法

imul rax rdx rax*=rdx

div

表示除法

div 是一种特殊指令,可以在一个寄存器操作数的情况下,将 128 位被除数除以 64 位除数,同时存储商和余数。

对于div rag等效于

  • rax = rdx:rax / reg
  • rdx = remainder

rdx:rax 表示 rdx 将是 128 位被除数的最高 64 位,而 rax 将是 128 位被除数的最低 64 位。

也就是计算完成后rax存放商,rdx存放余数

所以在调用 div 之前必须小心查看 rdxrax 中的内容。

rep

rep 前缀会重复执行后续的字符串操作指令,重复次数由 ECX 寄存器指定。每次执行后,ECX 减 1,直到 ECX 为 0 时停止。

rep <字符串操作指令>

以下是 rep 常见的组合指令及其作用:

指令 功能描述
rep movsb 按字节从源地址 (DS:ESI) 复制到目标地址 (ES:EDI),重复 ECX
rep movsw 按字(2字节)复制,地址每次增减 2
rep movsd 按双字(4字节)复制,地址每次增减 4
rep stosb AL 的值填充到 ES:EDI 指向的内存,重复 ECX
rep stosw AX 的值填充到内存,地址每次增减 2
rep stosd EAX 的值填充到内存,地址每次增减 4
rep cmpsb 逐字节比较 DS:ESIES:EDI 的内容,直到不匹配或 ECX 归零
rep scasb ES:EDI 指向的内存中搜索 AL 的值,直到找到或 ECX 归零

字符串操作指令的地址增减方向由 方向标志位(DF) 决定:

  • cld 指令:清除方向标志(DF=0),使地址递增(ESI/EDI 增加)。
  • std 指令:设置方向标志(DF=1),使地址递减(ESI/EDI 减少)。

cmp

语句 cmp a1,0x61 sub a1,0x61
本质 a1-0x61 a1=a1-0x61
不同 a1不会赋值 a1会赋值

cmp只会判断减去后值是不是为0

lea

把寄存器存储的地址进行加减操作后赋值给另外一个寄存器

lea rax,[rbp-0x18] :rax=rbp-0x18

为什么不用sub

  1. 如果用sub,需要sub rbp,0x18;mov rax,rbp;add rbp,0x18,指令太长了。
  2. mov rax,[rbp-0x18]会直接把[rbp-0x18]存储的信息赋值给rax,而不是地址

xor

xor eax,eax:让eax等于0

不用mov ebx 0

xor eax,eax短

test

test eax,eax => cmp eax,0 exa=0 -> 0 eax!=0 -> !0

相当于and eax,eax,但是不赋值给eax

test rax, rax; jnz STAY_LEET # rax != 0

push

push ebp : esp-4,把ebp放入esp所指的位置

pop

pop ebp :把esp指向的地方赋给ebp,esp+4。esp往下挪

push和pop其他相关指令

pusha:将所有的16位通用寄存器压入堆栈

popa:将所有的16位通用寄存器取出堆栈

pushf::将的16位标志寄存器EFLAGS压入堆栈

popf:将16位标志寄存器EFLAGS取出堆栈

pushad:将所有的32位通用寄存器压入堆栈,方面后面随意使用寄存器,用于保护现场

jump

junp 0x11111 -> mov eip 0x11111

绝对跳跃

方式 1:通过寄存器实现绝对跳转

这种方式是将目标地址先放入一个寄存器,然后使用 jmp 指令跳转到该寄存器中的地址。这种方式在某些情况下(比如目标地址是动态计算的)非常有用。

方式 2:直接在 jmp 指令中使用绝对地址

这种方式是直接在 jmp 指令中指定目标地址。这种方式更简洁,适用于目标地址是已知的常量的情况。

相对跳跃

相对跳转到0x51字节字后的位置

.intel_syntax noprefix
.global _start
_start:

jmp .target

.rept 0x51 ;.rept指令,重复以下指令0x51次
nop
.endr

.target:
mov rax,0x1

call

call : push call的下一条指令(返回地址); jump func;

leave

leave : mov esp,ebp;pop ebp;

把esp移到ebp的位置,ebp再到此时栈顶(esp)的地址上,然后esp再加4。也就是往下挪

ret

pop eip ; 从栈中弹出返回地址并存入 EIP

到ret命令时,esp在哪,就把哪里的地址存储的参数给eip

把esp给eip,同时esp加4

对应关系

push ebp; --- pop ebp;

push ebp;pop ebp; --- leave

call; --- ret

cdq/cdqe

cdq是32位,cdqe是64位

作用都是将一个32位有符合数扩展为64位有符合数,数据能表示的数不变

具体是这样实现的,比如eax=fffffffb(值为-5),然后cdq把eax的最高位bit,也就是二进制1,全部复制到edx的每一个bit位,EDX 变成 FFFFFFFF,这时eax与edx连起来就是一个64位数,FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。

传参

64函数调用传参

rdi->rsi->rdx->rcx->r8->r9

系统调用参数

rax:系统调用号,返回值

rdi->rsi->rdx->r10->r8->r9

字节大小

  • Quad Word = 8 Bytes = 64 bits
  • Double Word = 4 bytes = 32 bits
  • Word = 2 bytes = 16 bits
  • Byte = 1 byte = 8 bits

地址表示

段寄存器:在 x86 架构中,内存地址是由 段寄存器偏移量 共同组成的。

默认段寄存器的规则

在 x86 汇编中,内存操作数的默认段寄存器取决于基址寄存器:

  • 如果使用 ebpesp 作为基址寄存器,默认段寄存器是 ss(栈段)。
  • 如果使用其他寄存器(如 eaxebx 等),默认段寄存器是 ds(数据段)。

比如,对于 [ebp-4],默认段寄存器是 ss,而不是 ds。如果你明确想访问 ds 段中的数据,则需要加上 ds: 前缀。

对于dword ptr ds:[ebp-4]

ds是段寄存器

是否可以去掉 ds:

  • 大多数情况下可以去掉:
    • 现代操作系统(如 Windows、Linux)的 保护模式 下,ds(数据段寄存器)和 ss(栈段寄存器)的值 通常是相同的。这是因为现代操作系统使用 平坦内存模型(Flat Memory Model),所有段寄存器都被设置为指向同一个线性地址空间。
    • 因此,ds: 通常是默认的,可以省略不写。
  • 某些情况下不能去掉:
    • 如果程序中使用了多个段(例如在实模式或某些特殊场景下),则需要明确指定段寄存器(如 ds:cs:es: 等)。
    • 如果汇编器无法确定默认的段寄存器,则需要显式加上 ds:

位置无关代码(PIC)

拿这个创建socket然后bind的汇编语言的lea rsi, [rip+sockaddr]代码举例子

.intel_syntax noprefix
.globl _start

.section .text

_start:
    # Socket syscall
    mov rdi, 2
    mov rsi, 1
    mov rdx, 0
    mov rax, 0x29
    syscall

    # Bind syscall
    mov rdi, 3
    lea rsi, [rip+sockaddr]
    mov rdx, 16
    mov rax, 0x31
    syscall

    # Exit syscall
    mov rdi, 0
    mov rax, 0x3c
    syscall

.section .data
sockaddr:
    .2byte 2
    .2byte 0x5000
    .4byte 0
    .8byte 0

为什么要使用

  • 位置无关代码(PIC)的需求

    现代操作系统和程序(尤其是共享库、动态链接库)通常需要支持 位置无关代码。这意味着代码可以被加载到内存中的任意位置运行,而不需要修改代码中的地址。

    • 如果直接使用绝对地址(例如 mov rsi, sockaddr),代码只能在固定的内存地址运行。
    • 使用 rip + sockaddr 的相对寻址方式,代码可以在内存中的任何位置运行,因为地址是动态计算的。
  • 动态加载和共享库

    在动态链接库(.so 文件)或共享库中,代码和数据的内存地址在编译时是未知的,只有在运行时才能确定。使用 rip + sockaddr 的方式,可以避免硬编码绝对地址,从而支持动态加载。

  • 安全性

    现代操作系统通常使用 地址空间布局随机化(ASLR) 技术,将程序的代码和数据加载到随机的内存地址,以增加安全性。如果代码中使用了绝对地址,ASLR 将无法正常工作。而使用 rip + sockaddr 的相对寻址方式,可以兼容 ASLR。

相对寻址

原理

在 x86-64 汇编中,rip + offset 是一种相对寻址方式。它的计算方式是:

实际地址 = 下一条指令的地址 + offset

这里的 offset 是一个固定的偏移量,表示从下一条指令的地址到目标地址的距离。

在这个例子中,汇编器和链接器会根据 sockaddr 的地址和 rip 的值,计算出正确的偏移量,并将其编码到指令中。

  • rip 是指令指针寄存器,指向下一条指令的地址。
  • sockaddr 是一个标签,表示 sockaddr 结构体的地址。
  • rip + sockaddr 中的 sockaddr 实际上是一个偏移量,表示从下一条指令的地址到 sockaddr 地址的距离。

当 CPU 执行指令时,会根据 rip 的值和偏移量动态计算出 sockaddr 的地址。

因此,即使 rip 的值在每个地方都不一样,rip + sockaddr 仍然能够正确计算出 sockaddr 的地址。

总结

开启 ASLR 时,需要使用 rip + offset 动态计算地址。

未开启 ASLR 时,可以直接使用绝对地址。

posted @ 2024-10-23 22:47  r_0xy  阅读(73)  评论(0)    收藏  举报