PWN入门之三:寄存器与汇编
PWN入门之三:寄存器与汇编
寄存器
0x00 前言
本篇文章实际上为PWN入门之四:C语言调用栈的前置,旨在介绍x86架构下的击中常见的寄存器,和一些简单的汇编代码。
0x01 什么是寄存器?
简单来说,寄存器就是CPU内部的一些小型储存区域,用来暂时存放一些需要计算的数据以及运算出的结果和一些CPU运行需要的信息,比如下面这些:
- 可将其内的数据执行算数和逻辑运算。
- 储存在其内的地址可以用来指向内存的某个地址,即寻址。
- 可以用来读写数据到电脑周边的数据
0x02 通用寄存器
通用寄存器,看名字也知道,这也是我们最常见的寄存器,电脑运行程序是,大部分的操作都是在这些寄存器中完成的,但是,虽然名字里面有“通用”,但是并不是真正的通用,这几个寄存器在实际使用中还有一些自己的潜规则。
寄存器(64位/32位) | 作用 |
---|---|
RAX/EAX | 通常用来执行加法,函数调用的返回值一般也放在这里面 |
RBX/EBX | 数据存取 |
RCX/ECX | 通常用来作为计数器,比如for循环 |
RDX/EDX | 读写I/O端口时,edx用来存放端口号 |
RSP/ESP | 栈顶指针,指向栈的顶部 |
RBP/EBP | 栈底指针,指向栈的底部,通常用ebp+偏移量 的形式来定位函数存放在栈中的局部变量 |
RSI/ESI | 字符串操作时,用于存放数据源的地址 |
RDI/EDI | 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作 |
R8~R15(64位独有) |
0x03 指令寄存器
RIP/EIP
指令寄存器可以说是CPU中最最重要的寄存器了,它指向了下一条要执行的指令所存放的地址,CPU的工作其实就是不断取出它指向的指令,然后执行这条指令,同时指令寄存器继续指向下面一条指令,如此不断重复,这就是CPU工作的基本日常。而在漏洞攻击中,黑客想尽办法费尽心机都想要修改指令寄存器的地址,从而能够执行恶意代码。
汇编
参考资料
pwn.college《汇编语言第3版》王爽著
从0到1:CTFer成长之路Nu1L战队%20编著
0x00 前言
“汇编”部分,旨在能让读者能在较短时间内掌握几个比较基础的汇编命令,进而可以看的懂大部分的反汇编代码,并不真正意义上的汇编入门教程。如果你想更全面的了解pwn中的汇编,可以去参考资料的pwncollege中完成Computing 101这个道馆。
0x01 MOV
MOV
是一种数据传送型指令, 其指令的基本格式是:
1 |
|
- 目标操作数:数据要去的地方,可以是寄存器或者内存地址
- 源操作数:数据来的地方,可以是寄存器或者内存地址或一个明确的数值
具体细分,有以下五种用法
立即数到寄存器
1 |
|
将一个明确的数值放入寄存器。这是初始化寄存器最常用的方法。
寄存器到寄存器
1 |
|
将数据从一个寄存器复制到另一个寄存器。
立即数到内存
1 |
|
将42这个立即数放入10000这个地址中,这里的[]
是解引用的意思。
内存到寄存器
1 |
|
将10000这个地址中储存的数放入eax
这个寄存器中
寄存器到内存
1 |
|
将eax
这个寄存器中的值,放入ebx
储存的地址值下
0x02 LEA
LEA
是一条取地址指令,其指令的基本格式是:
1 |
|
- 目标寄存器:必须是一个通用寄存器
- 内存源操作数:必须是以一个内存寻址模式,比如
[eax]
之类的
举个例子:
1 |
|
这条代码的与下面这条C语言伪代码的作用是一样的
1 |
|
拆开来说就是,先获取储存在esi
寄存器中的地址值,比如说10000
,然后再使用[]
解引用,找到地址10000
下的储存的值。比如说42
,然后再计算42
这个值所在的地址,即10000
。
0x03 ADD
ADD
是一条算术运算指令,其的使用方式和MOV
非常相似
1 |
|
翻译成伪代码就是
1 |
|
它的用法细分和MOV
一样,这里就不展开赘述了。
0x04 SUB
SUB
是一条算术运算指令,其的使用方式与ADD
一样,和MOV
非常相似
1 |
|
翻译成伪代码就是
1 |
|
它的用法细分和MOV
一样,这里就不展开赘述了。
0x05 AND
AND
是一条逻辑运算指令,意思是“与”,其基本格式是:
1 |
|
不理解的可以去了解一下逻辑运算,“与”就是“逻辑乘”,遇0则0,只有两个对应的位都为 1 时,结果的该位才为 1。
它有如下四种用法:
- 寄存器 AND 寄存器 -> 寄存器
- 寄存器 AND 立即数 -> 寄存器
- 内存 AND 立即数 -> 内存
- 寄存器 AND 内存 -> 寄存器
0x06 XOR
XOR
是一条逻辑运算指令,意思是异或
,它的核心功能是:对两个操作数进行按位“异或”运算,并将结果存放到目标操作数中。基本格式是:
1 |
|
逻辑“异或”的规则是:两个对应的位不同时,结果的该位为 1;相同时,结果的该位为 0。
它有如下四种用法:
- 寄存器 XOR 寄存器 -> 寄存器
- 寄存器 XOR 立即数 -> 寄存器
- 内存 XOR 立即数 -> 内存
- 寄存器 XOR 内存 -> 寄存器
0x07 CALL
CALL
是一条函数调用指令,具体在执行CALL
的时候,CPU都干了什么,这部分会在下一部分PWN入门之四:C语言调用栈中详细讲解,
CALL
指令的目标地址可以通过多种方式指定:
寻址方式 | 示例 | 解释 |
---|---|---|
直接调用 (Direct Call) | CALL myFunction |
目标地址是明确的符号(标签)或固定的内存地址。汇编器/链接器会计算其实际地址。 |
寄存器间接调用 (Register Indirect Call) | CALL EAX |
目标地址存储在寄存器 EAX 中。程序运行时动态决定跳转到哪里。常用于函数指针调用、虚函数表跳转等。 |
内存间接调用 (Memory Indirect Call) | CALL DWORD PTR [EBX] |
目标地址存储在内存中 EBX 指向的位置(双字)。同样用于动态调用。 |
远调用 (Far Call) (16位/特殊场景) | CALL 1000h:2000h |
同时改变代码段寄存器 CS 和指令指针 IP / EIP 。用于跨段调用(如不同特权级、不同代码段)。现代操作系统(保护模式)较少直接使用。 |
0x08 RET
RET
是一条函数返回指令,它的核心功能是:从子程序(函数或过程)返回到调用者。它利用栈上保存的信息,恢复程序的执行流程。
它的基本格式可以是:
- RET
(近返回)
- RET n
(带立即数操作数的返回,
n`通常是偶数,表示字节数)
具体RET
都干了什么,这部分会在下一部分PWN入门之四:C语言调用栈中详细讲解
0x09 CMP
CMP
是一条比较指令,它的核心功能是:比较两个操作数,并根据比较结果设置处理器的状态标志位 (FLAGS)。它是实现条件分支(如 if
语句、循环)的基石。
它的基本格式是:
1 |
|
其执行的操作可以理解为:
1 |
|
这个指令会影响的标志位很多,但是我们只需要记忆一个,ZF
零标志位:如果结果为 0,则置 1;否则清 0。操作数1 == 操作数2
时 ZF=1。
0x10 JMP
JMP
是一条无条件跳转指令,它的功能简单而强大:立即、无条件地改变程序的执行流程,跳转到指定的目标地址继续执行。它是实现循环、分支、函数调用等所有非顺序执行逻辑的基础。
它的基本格式是:
1 |
|
执行 JMP
指令时,CPU 会做一件事:
- 将程序计数器
EIP
/RIP
(Instruction Pointer) 的值设置为<目标地址>
。 - 程序的下一条指令将从
<目标地址>
处开始执行。
0x11 PUSH & POP
PUSH
和POP
都是栈操作指令,PUSH
是压栈,POP
是弹栈,这两个指令在这篇文章中不做重点介绍,我将会在下一篇文章中详细介绍这两个汇编指令