pwn学习小记(1)

PWN学习小记(1)

汇编基础

CPU结构和指令集

CPU是名称为中央处理单元,简称处理器,主要的作用是从内存中读取指令,然后解码和执行。
CPU架构就是CPU内部设计的结构,是一堆硬件组成,用于实现指令集所规定的操作,指令集包含了一系列的操作码以及特定的CPU执行的基本命令。根据现在指令集的特征,可以分为两类:CSSC(复杂指令集计算机,代表的是x86处理器)和RISC(精简指令集计算机,典型代表是ARM处理器)。RISC可以完全使用寄存器来传递参数,而CISC只能使用栈,或者是栈和寄存器结合起来使用

语法风格

x86汇编主要的语法有两种风格: AT&TIntel风格

通常在Linux中见到的AT&T的风格比较多,常见的GCC、GDB、objdump都是使用的AT&T风格,他们两个之间语法有一些简单的区别,我们主要了解Intel风格

有很多细节的不同,AT&T的特征是寄存器和数字的前面通常会加入% $的符号,Intel没有,十六进制使用的是0xIntel使用的是后方计入h

寄存器

寄存器是 CPU 内部用于存放数据、地址和状态信息的高速存储单元,是 CPU 进行计算和控制的核心组成部分。

下面是8到64位寄存器的实例和说明

位数 通用寄存器示例 说明
8 位 AL, AH, BL, BH, CL, CH, DL, DH 8086 中 AX、BX、CX、DX 的高 8 位/低 8 位拆分
16 位 AX, BX, CX, DX, SP, BP, SI, DI 8086 基本寄存器:累加、基址、计数、数据、堆栈指针、基址指针、变址寄存器
32 位 EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI IA-32 架构:E 前缀表示扩展为 32 位
64 位 RAX, RBX, RCX, RDX, RSP, RBP, RSI, RDI,及 R8–R15 x86-64 架构:R 前缀表示 64 位,新增 R8–R15 共 16 个通用寄存器

补充:

  • 在 64 位模式下,默认操作数大小是 32 位,如果要使用 64 位操作数,需要在指令中加上 REX 前缀(由汇编器自动生成)。
  • R8–R15 的低 32 位、16 位和 8 位也能单独访问(如 R8D, R8W, R8B)。

常量表示方法

(1)整数常量
  • 十进制(默认):直接写,例如 1234
  • 十六进制:x86 汇编常用写法是 后缀 h,如 1Ah,如果首位是字母要在前加 0,如 0ABCh
    • C 语言风格:0x1A
  • 八进制:前缀 0,如 0123(等于十进制 83)。
  • 二进制:某些汇编器支持后缀 b,如 1010b

(2)浮点数常量(实数常量)
  • 必须有整数部分和小数部分至少一部分,比如:
    • 1.0
    • 0.5
    • 3.(整数部分+小数点)
    • .25(小数点+小数部分)
  • 可以带正负号:+2.3-3.14159
  • 科学计数法:26.E3(即 26 × 10³)。

(3)字符串常量
  • 单引号 '双引号 " 包裹:

    • 'abc'"abc"
  • 在汇编中字符串常量通常是 一串 ASCII 字符,比如:

    1
    msg db 'Hello, world!', 0

    这里 db 定义字节数组,最后的 0 代表字符串结束符(C 风格)。

常见寄存器

32位CPU

1. 通用寄存器

4 个最基本的通用寄存器,每个 32 位,可以拆分访问:

  • EAX:累加器(Accumulator),常用于算术运算、函数返回值。
    • 高 16 位:AX → AH(高 8 位)、AL(低 8 位)
  • EBX:基址寄存器(Base Register),常用作内存访问基址。
  • ECX:计数寄存器(Counter Register),常用于循环、移位/旋转操作。
  • EDX:数据寄存器(Data Register),常配合 EAX 参与乘除法运算,高位存放结果。

特点:虽然称为“通用”,但在指令集里常有特定用途。


2. 段寄存器

8086 的分段机制在 32 位中仍保留,但在保护模式下意义有所变化。共有 6 个段寄存器,每个 16 位:

  • CS:代码段寄存器(Code Segment)
  • DS:数据段寄存器(Data Segment)
  • SS:堆栈段寄存器(Stack Segment)
  • ES:附加数据段寄存器(Extra Segment)
  • FS:附加段寄存器(常用于线程本地存储 TLS 等)
  • GS:附加段寄存器(在操作系统和多任务环境中常有特殊用途)

在 32 位保护模式中,这些寄存器通常保存的是“段选择子”,对应 GDT/LDT 表项


3. 变址寄存器
  • ESI(Extended Source Index):扩展源变址寄存器
  • EDI(Extended Destination Index):扩展目的变址寄存器

主要用于数组、字符串、内存块操作,常与 MOVSxCMPSxSCASx 等字符串指令配合。
其低 16 位与 8086 的 SI、DI 兼容。


4. 指针寄存器
  • ESP(Extended Stack Pointer):堆栈指针寄存器,指向当前栈顶。
  • EBP(Extended Base Pointer):基址指针寄存器,常用于访问栈帧中的参数和局部变量。

5. 指令指针寄存器
  • EIP(Extended Instruction Pointer):保存下一条即将执行指令的偏移地址。

    CPU 自动更新 EIP,程序员一般不能直接修改(必须用跳转、调用、返回等指令间接修改)。


6. 标志寄存器

32 位的标志寄存器,存放算术/逻辑运算的状态信息和控制标志。
常见标志位:

  • ZF:零标志(Zero Flag)
  • CF:进位标志(Carry Flag)
  • SF:符号标志(Sign Flag)
  • OF:溢出标志(Overflow Flag)
  • IF:中断允许标志(Interrupt Flag)
  • DF:方向标志(Direction Flag)
类别 寄存器 说明
通用寄存器 EAX, EBX, ECX, EDX 算术、逻辑、计数、基址等
段寄存器 CS, DS, SS, ES, FS, GS 分段寻址(保护模式下为选择子)
变址寄存器 ESI, EDI 数组、字符串、块操作
指针寄存器 ESP, EBP 栈顶指针、栈帧基址
指令指针寄存器 EIP 指向下一条指令
标志寄存器 EFLAGS 记录结果状态、控制流程

16位CPU

8086 是经典的 16 位 CPU,内部所有寄存器宽度为 16 位。它的寄存器主要分为以下几类:

(1)通用寄存器

共有 4 个,每个 16 位,可以再分为高 8 位和低 8 位:

  • AX(累加器,Accumulator) → AH(高 8 位)、AL(低 8 位)
  • BX(基址寄存器,Base) → BH、BL
  • CX(计数寄存器,Count) → CH、CL
  • DX(数据寄存器,Data) → DH、DL

作用:

  • AX 常用于算术逻辑运算,很多指令默认使用它。
  • BX 常用作内存寻址的基址。
  • CX 常用作循环/移位的计数器(比如 LOOP 指令)。
  • DX 常用于乘法、除法的扩展寄存器。

(2)段寄存器

8086 采用 分段存储管理,物理地址 = 段基址(段寄存器 ×16) + 偏移量。

  • CS(代码段寄存器)
  • DS(数据段寄存器)
  • SS(堆栈段寄存器)
  • ES(附加段寄存器)

用途:

  • CS:IP 指定当前正在执行的指令地址。
  • SS:SP 指定当前栈顶位置。
  • DS、ES 常用于数据访问,ES 常配合字符串操作。

(3)指针与变址寄存器
  • SP(栈指针,Stack Pointer):指向栈顶,配合 SS。
  • BP(基址指针,Base Pointer):主要用于访问栈中的参数和局部变量。
  • SI(源变址寄存器,Source Index):常用于数组、字符串操作(数据源)。
  • DI(目的变址寄存器,Destination Index):常用于数组、字符串操作(数据目的地)。

(4)指令指针寄存器
  • IP(Instruction Pointer):保存下一条要执行指令在代码段内的偏移量。
    CS 配合:CS:IP 就是当前指令的物理地址。

(5)标志寄存器

8086 的 FLAGS 寄存器(16 位) 保存运算状态和控制信息。
常见标志位:

  • ZF(Zero Flag,零标志)
  • CF(Carry Flag,进位标志)
  • SF(Sign Flag,符号标志)
  • OF(Overflow Flag,溢出标志)
  • IF(Interrupt Enable,中断允许)
  • DF(Direction Flag,字符串操作方向)
类别 寄存器 说明
通用寄存器 AX, BX, CX, DX(可拆 AH/AL 等) 存放操作数、结果,部分有专用用途
段寄存器 CS, DS, SS, ES 用于分段寻址,提供段基址
指针寄存器 SP, BP 指向栈顶、基址(栈帧)
变址寄存器 SI, DI 用于字符串、数组操作
指令指针寄存器 IP 存放下一条指令偏移地址
标志寄存器 FLAGS 存放状态和控制标志

AX、BX、CX、DX → CPU 的“算术草稿本”。

CS、DS、SS、ES → 指定不同“书本章节”(代码段、数据段、栈段)。

SP、BP、SI、DI → 用来在“书中”翻页找具体数据。

IP → 当前读到的“下一句”。

FLAGS → CPU 计算结果的“批注标记”。

64位CPU

64 位寄存器架构在 32 位的基础上扩展了通用寄存器数量与宽度,弱化了段寄存器功能,同时引入了更强大的 SIMD 寄存器体系。

1. 通用寄存器

x86-64 架构中,共有 16 个 64 位通用寄存器

  • 原有的 8 个寄存器(由 32 位扩展而来):
    • RAX, RBX, RCX, RDX, RSP, RBP, RSI, RDI
  • 新增的 8 个寄存器
    • R8, R9, R10, R11, R12, R13, R14, R15

特点:

  1. 每个寄存器都是 64 位,并且可以分段访问:
    • RAX → EAX (32位) → AX (16位) → AH/AL (8位)
    • R8 → R8D (32位) → R8W (16位) → R8B (8位)
  2. 使用方式完全兼容旧的 32 位和 16 位寄存器。

2. 指针与栈相关寄存器
  • RSP(Stack Pointer):栈顶指针
  • RBP(Base Pointer):栈基址指针,常用于函数调用栈帧
  • RIP(Instruction Pointer):指向下一条即将执行的指令地址(在 64 位模式下是 64 位宽度)。

3. 段寄存器

虽然 x86-64 仍然保留了段寄存器(CS、DS、SS、ES、FS、GS)
但在 64 位长模式下,大部分段机制被弱化:

  • CS、DS、SS、ES 基本被固定使用,几乎不起作用。
  • FS、GS 仍然有用,常用于存放 线程本地存储(TLS) 或操作系统内核结构的基地址。

4. 标志寄存器
  • 64 位扩展的 RFLAGS(以前是 EFLAGS/FLAGS),保存运算状态与控制标志。
  • 常见标志位:
    • CF(进位标志)、ZF(零标志)、SF(符号标志)、OF(溢出标志)、IF(中断允许)、DF(方向标志)等。

5. 控制寄存器
  • CR0, CR2, CR3, CR4, CR8 等,用于存储分页机制、虚拟内存、保护模式和特权级信息。
    • CR3:页目录基地址寄存器(内存分页关键)。
    • CR8:任务优先级控制(TLP)。

6. SIMD/浮点寄存器

除了通用寄存器外,64 位 CPU 还包含丰富的 SIMD 和浮点扩展寄存器:

  • XMM0–XMM15(128 位,SSE 指令集)
  • YMM0–YMM15(256 位,AVX 指令集)
  • ZMM0–ZMM31(512 位,AVX-512 指令集,部分 CPU 支持)
  • x87 FPU 寄存器栈(ST0–ST7,80 位浮点寄存器,兼容旧代码)
类别 64 位寄存器 说明
通用寄存器 RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8–R15 16 个通用寄存器,64 位宽
指令指针 RIP 下一条执行指令地址
段寄存器 CS, DS, SS, ES, FS, GS 长模式下大多弱化,FS/GS 仍常用
标志寄存器 RFLAGS 状态/控制标志
控制寄存器 CR0, CR2, CR3, CR4, CR8… 内存管理、分页、特权控制
SIMD/浮点寄存器 XMM0–XMM15, YMM0–YMM15, ZMM0–ZMM31, ST0–ST7 向量计算、浮点运算
  • 通用寄存器:CPU 的“草稿纸”。
  • RIP:指令“书签”,告诉 CPU 读下一条指令。
  • RSP/RBP:函数调用的“栈工具”。
  • FS/GS:线程局部数据“起点”。
  • SIMD 寄存器:并行计算的“高速计算器”。

基本指令

不同 CPU 有不同的指令集架构(Instruction Set Architecture, ISA)。汇编语言就是 ISA 的最底层表现形式,因此 CPU 架构不同,汇编指令也不同。所以汇编语言不是通用的,它紧密依赖 CPU 的指令集架构。换 CPU,汇编指令就会变化。

16位CPU的基本命令

16 位汇编指令(8086 指令集)是 x86 架构的最早版本,主要包括数据传送、算术逻辑、控制转移、字符串处理和处理机控制五大类。

1. 数据传送指令
指令 解释
MOV dst, src 数据传送,把 src 的值送到 dst
XCHG a, b 交换两个寄存器/内存操作数的值
PUSH reg/mem/imm 将操作数压入栈,SP 自动减 2
POP reg/mem 将栈顶数据弹出到目的操作数,SP 自动加 2
IN AL/AX, port 从端口读入数据到 AL/AX
OUT port, AL/AX 将 AL/AX 的内容输出到端口
LEA reg, mem 装入有效地址(即偏移量),常用于指针运算
LDS reg, mem 从内存中取一个 32 位的指针(段:偏移)到寄存器和 DS
LES reg, mem 同上,但段寄存器为 ES

2. 算术运算指令
指令 解释
ADD dst, src 加法,dst = dst + src
ADC dst, src 带进位加法,dst = dst + src + CF
SUB dst, src 减法,dst = dst - src
SBB dst, src 带借位减法,dst = dst - src - CF
INC reg/mem 自增 1
DEC reg/mem 自减 1
MUL reg/mem 无符号乘法,隐含操作数 AX;结果放在 AX 或 DX:AX
IMUL reg/mem 有符号乘法
DIV reg/mem 无符号除法,结果:商在 AL/AX,余数在 AH/DX
IDIV reg/mem 有符号除法
NEG reg/mem 取负,等于 0 - 操作数
CMP a, b 比较(执行 a - b,结果不保存,只修改标志位)

3. 逻辑与位操作指令
指令 解释
AND a, b 按位与
OR a, b 按位或
XOR a, b 按位异或
NOT a 按位取反
TEST a, b 按位与,但结果不保存,仅修改标志位
SHL/SAL a, n 左移(逻辑/算术左移相同),低位补 0
SHR a, n 逻辑右移,高位补 0
SAR a, n 算术右移,高位补符号位
ROL a, n 循环左移
ROR a, n 循环右移
RCL a, n 带进位循环左移
RCR a, n 带进位循环右移

4. 控制转移指令
指令 解释
JMP label 无条件跳转到指定地址
CALL label 调用子程序(压栈返回地址,然后跳转)
RET 从子程序返回(弹出返回地址并跳转)
INT n 触发软件中断 n(调用中断向量表中的服务程序)
IRET 从中断返回(恢复标志、CS、IP)

条件跳转(根据标志位决定是否跳转):

指令 条件(FLAGS)
JE/JZ 等于 / 零标志 ZF=1
JNE/JNZ 不等 / 非零 ZF=0
JC 有进位 CF=1
JNC 无进位 CF=0
JS 负数 SF=1
JNS 非负数 SF=0
JO 溢出 OF=1
JNO 无溢出 OF=0
JG/JNLE 大于(有符号)ZF=0 且 SF=OF
JL/JNGE 小于(有符号)SF≠OF
JGE/JNL 大于等于(有符号)SF=OF
JLE/JNG 小于等于(有符号)ZF=1 或 SF≠OF
JA 大于(无符号)CF=0 且 ZF=0
JB 小于(无符号)CF=1
JAE 大于等于(无符号)CF=0
JBE 小于等于(无符号)CF=1 或 ZF=1

5. 字符串操作指令

SI(源)、DI(目的)、CX(计数器)、DF(方向标志) 联用,常配合 REP/REPE/REPNE

指令 解释
MOVS/MOVSB/MOVSW 从 DS:SI 指向的内存传送到 ES:DI,自动更新 SI/DI
CMPS/CMPSB/CMPSW 比较 DS:SI 和 ES:DI 的数据
SCAS/SCASB/SCASW 用 AL/AX 与 ES:DI 的数据比较
LODS/LODSB/LODSW 取 DS:SI 的数据放到 AL/AX
STOS/STOSB/STOSW 把 AL/AX 的数据存到 ES:DI

前缀:

  • REP:重复执行,直到 CX=0
  • REPE/REPZ:当相等且 CX≠0 时继续
  • REPNE/REPNZ:当不相等且 CX≠0 时继续

6. 处理机控制指令
指令 解释
CLC 清除进位标志 CF=0
STC 置位进位标志 CF=1
CMC 进位标志取反 CF=¬CF
CLD 清除方向标志 DF=0(字符串操作递增)
STD 设置方向标志 DF=1(字符串操作递减)
CLI 禁止中断 IF=0
STI 允许中断 IF=1
HLT 处理器停止,等待中断/复位唤醒
NOP 空操作(占位用,不做任何事)

32位CPU的基本命令

1. 数据传送指令

和 16 位类似,只是寄存器换成 32 位(EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP):

指令 解释
MOV dst, src 传送数据
XCHG a, b 交换两个操作数
PUSH/POP reg/mem/imm32 压栈/出栈,ESP 自动 ±4
PUSHAD / POPAD 压入/弹出所有 32 位通用寄存器
IN/OUT I/O 端口读写
LEA reg, [mem] 装入有效地址(偏移量)
MOVZX/MOVSX 零扩展/符号扩展传送(从小操作数 → 大操作数)

2. 算术运算指令

基本和 16 位一致,只是操作数可以是 32 位。

指令 解释
ADD / ADC 加 / 带进位加
SUB / SBB 减 / 带借位减
INC / DEC 自增 / 自减
MUL / IMUL 乘法(无符号 / 有符号)
DIV / IDIV 除法(无符号 / 有符号)
NEG 取负
CMP 比较(结果不保存,仅改标志位)

新增:

  • BSWAP reg32:字节反转(如 12345678h → 78563412h
  • IMUL reg, imm32:支持立即数乘法,简化了算式编译

3. 逻辑与位操作指令

和 16 位相同,但支持 32 位操作数,另外增加了一些 位扫描/测试类

指令 解释
AND / OR / XOR / NOT 与 / 或 / 异或 / 取反
TEST 按位与,只改标志
SHL/SAL / SHR / SAR 移位
ROL / ROR / RCL / RCR 循环移位(可带进位)
BT / BTS / BTR / BTC 位测试、置位、复位、取反(Bit Test & Set/Reset/Complement)
BSF / BSR 找到第一个/最后一个置位的比特位

4. 控制转移指令
指令 解释
JMP label 无条件跳转
CALL / RET 子程序调用与返回
INT n / IRET 中断调用与返回
LOOP/LOOPZ/LOOPNZ 用 ECX 循环计数器控制的循环跳转
条件跳转 Jcc 和 16 位一样,支持 JE/JNE/JG/JL/JA/JB 等

特点:

  • 偏移量可以是 8 位或 32 位(16 位时代是 8 位或 16 位)。
  • CALL / JMP 支持远指针(段:偏移),但保护模式下主要用选择子(Selector)。

5. 字符串操作指令(增强)

继承 16 位的串操作,但寄存器扩展为 32 位:

指令 解释
MOVSD 传送双字(4 字节)串
STOSD 存储双字
LODSD 装入双字
SCASD 扫描双字
CMPSD 比较双字

可配合 REP/REPE/REPNE,和 16 位类似。


6. 处理机控制指令

在 80386 引入了保护模式,因此多了一些控制类指令:

指令 解释
CLC/STC/CMC 清除/设置/取反进位标志
CLD/STD 清除/设置方向标志
CLI/STI 禁止/允许中断
HLT 停机,等待中断
NOP 空操作
LGDT / SGDT 加载/保存全局描述符表寄存器 GDTR
LIDT / SIDT 加载/保存中断描述符表寄存器 IDTR
LLDT / SLDT 加载/保存局部描述符表寄存器 LDTR
LTR / STR 加载/保存任务寄存器 TR
MOV CRx, reg / MOV reg, CRx 控制寄存器 CR0–CR4 的访问(分页、保护模式相关)

64位CPU的基本指令

1. 数据传送指令
指令 说明
MOV r64, r/m64 / imm64 数据传送,支持 64 位立即数
XCHG r64, r/m64 交换两个操作数
PUSH r64 / POP r64 压栈/出栈(栈指针 RSP ± 8)
PUSHFQ / POPFQ 压栈/出栈 RFLAGS(64 位)
LEA r64, [mem] 取内存有效地址(常用于指针/数组计算)
MOVZX / MOVSX / MOVSXD 零扩展/符号扩展(特别是 MOVSXD 从 32 位 → 64 位)

2. 算术运算指令
指令 说明
ADD / ADC 加 / 带进位加
SUB / SBB 减 / 带借位减
INC / DEC 自增 / 自减
NEG 取负
CMP 比较(只修改标志位)
MUL / IMUL 乘法(无符号/有符号)
DIV / IDIV 除法(无符号/有符号)
BSWAP r64 字节反转(大端/小端转换)
POPCNT 统计 1 的个数(SSE4.2+)
LZCNT / TZCNT 前导零/尾随零计数(BMI 指令集)

3. 逻辑与位操作指令
指令 说明
AND / OR / XOR / NOT 按位逻辑运算
TEST 按位与,不保存结果,仅改标志
SHL / SHR / SAR 移位(逻辑/算术)
ROL / ROR / RCL / RCR 循环移位(可带进位)
BT / BTS / BTR / BTC 位测试/置位/复位/取反
BSF / BSR 位扫描(找第一个/最后一个 1 位)

4. 控制转移指令
指令 说明
JMP label 无条件跳转(支持 64 位地址)
CALL / RET 调用子程序 / 返回
INT n / IRETQ 软件中断 / 中断返回
Jcc 条件跳转(JE/JNE/JG/JL/JA/JB 等)
LOOP / LOOPE / LOOPNE 用 RCX 控制循环
RIP 相对寻址 64 位特有,用于位置无关代码(PIC)

5. 字符串操作指令(64 位版本)
指令 说明
MOVSQ 传送 8 字节(Quadword)
STOSQ 存储 8 字节
LODSQ 装入 8 字节
SCASQ 扫描 8 字节
CMPSQ 比较 8 字节
REP/REPE/REPNE 前缀 串操作重复执行(由 RCX 控制次数)

6. 处理机控制指令
指令 说明
CLC / STC / CMC 清除/设置/取反进位标志
CLD / STD 清除/设置方向标志
CLI / STI 禁止/允许中断
HLT 停机,等待中断
NOP 空操作
SYSCALL / SYSRET 系统调用/返回(64 位新增)
SYSENTER / SYSEXIT 快速系统调用
SWAPGS 用户态/内核态切换时交换 GS 基址
CPUID 查询 CPU 信息
RDMSR / WRMSR 读/写 MSR(模型特定寄存器)
RDTSC / RDTSCP 读取时间戳计数器

C与汇编

下面是一个很简单的一个加法功能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int sum(int a,int b){
return a+b;
};
int main(){
int a,b;
if(scanf("%d",&a) != 1){
printf("no");
return 1;
}
if(scanf("%d",&b) != 1){
printf("no");
return 1;
}
sum(a,b);
return 0;
}

这是所对应的汇编命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
$ objdump -d t

t: 文件格式 elf64-x86-64


Disassembly of section .init:

0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__>
100f: 48 85 c0 test %rax,%rax
1012: 74 02 je 1016 <_init+0x16>
1014: ff d0 callq *%rax
1016: 48 83 c4 08 add $0x8,%rsp
101a: c3 retq

Disassembly of section .plt:

0000000000001020 <.plt>:
1020: ff 35 8a 2f 00 00 pushq 0x2f8a(%rip) # 3fb0 <_GLOBAL_OFFSET_TABLE_+0x8>
1026: f2 ff 25 8b 2f 00 00 bnd jmpq *0x2f8b(%rip) # 3fb8 <_GLOBAL_OFFSET_TABLE_+0x10>
102d: 0f 1f 00 nopl (%rax)
1030: f3 0f 1e fa endbr64
1034: 68 00 00 00 00 pushq $0x0
1039: f2 e9 e1 ff ff ff bnd jmpq 1020 <.plt>
103f: 90 nop
1040: f3 0f 1e fa endbr64
1044: 68 01 00 00 00 pushq $0x1
1049: f2 e9 d1 ff ff ff bnd jmpq 1020 <.plt>
104f: 90 nop
1050: f3 0f 1e fa endbr64
1054: 68 02 00 00 00 pushq $0x2
1059: f2 e9 c1 ff ff ff bnd jmpq 1020 <.plt>
105f: 90 nop

Disassembly of section .plt.got:

0000000000001060 <__cxa_finalize@plt>:
1060: f3 0f 1e fa endbr64
1064: f2 ff 25 8d 2f 00 00 bnd jmpq *0x2f8d(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
106b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

Disassembly of section .plt.sec:

0000000000001070 <__stack_chk_fail@plt>:
1070: f3 0f 1e fa endbr64
1074: f2 ff 25 45 2f 00 00 bnd jmpq *0x2f45(%rip) # 3fc0 <__stack_chk_fail@GLIBC_2.4>
107b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

0000000000001080 <__printf_chk@plt>:
1080: f3 0f 1e fa endbr64
1084: f2 ff 25 3d 2f 00 00 bnd jmpq *0x2f3d(%rip) # 3fc8 <__printf_chk@GLIBC_2.3.4>
108b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

0000000000001090 <__isoc99_scanf@plt>:
1090: f3 0f 1e fa endbr64
1094: f2 ff 25 35 2f 00 00 bnd jmpq *0x2f35(%rip) # 3fd0 <__isoc99_scanf@GLIBC_2.7>
109b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)

Disassembly of section .text:

00000000000010a0 <_start>:
10a0: f3 0f 1e fa endbr64
10a4: 31 ed xor %ebp,%ebp
10a6: 49 89 d1 mov %rdx,%r9
10a9: 5e pop %rsi
10aa: 48 89 e2 mov %rsp,%rdx
10ad: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
10b1: 50 push %rax
10b2: 54 push %rsp
10b3: 4c 8d 05 f6 01 00 00 lea 0x1f6(%rip),%r8 # 12b0 <__libc_csu_fini>
10ba: 48 8d 0d 7f 01 00 00 lea 0x17f(%rip),%rcx # 1240 <__libc_csu_init>
10c1: 48 8d 3d c9 00 00 00 lea 0xc9(%rip),%rdi # 1191 <main>
10c8: ff 15 12 2f 00 00 callq *0x2f12(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5>
10ce: f4 hlt
10cf: 90 nop

00000000000010d0 <deregister_tm_clones>:
10d0: 48 8d 3d 39 2f 00 00 lea 0x2f39(%rip),%rdi # 4010 <__TMC_END__>
10d7: 48 8d 05 32 2f 00 00 lea 0x2f32(%rip),%rax # 4010 <__TMC_END__>
10de: 48 39 f8 cmp %rdi,%rax
10e1: 74 15 je 10f8 <deregister_tm_clones+0x28>
10e3: 48 8b 05 ee 2e 00 00 mov 0x2eee(%rip),%rax # 3fd8 <_ITM_deregisterTMCloneTable>
10ea: 48 85 c0 test %rax,%rax
10ed: 74 09 je 10f8 <deregister_tm_clones+0x28>
10ef: ff e0 jmpq *%rax
10f1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
10f8: c3 retq
10f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

0000000000001100 <register_tm_clones>:
1100: 48 8d 3d 09 2f 00 00 lea 0x2f09(%rip),%rdi # 4010 <__TMC_END__>
1107: 48 8d 35 02 2f 00 00 lea 0x2f02(%rip),%rsi # 4010 <__TMC_END__>
110e: 48 29 fe sub %rdi,%rsi
1111: 48 89 f0 mov %rsi,%rax
1114: 48 c1 ee 3f shr $0x3f,%rsi
1118: 48 c1 f8 03 sar $0x3,%rax
111c: 48 01 c6 add %rax,%rsi
111f: 48 d1 fe sar %rsi
1122: 74 14 je 1138 <register_tm_clones+0x38>
1124: 48 8b 05 c5 2e 00 00 mov 0x2ec5(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable>
112b: 48 85 c0 test %rax,%rax
112e: 74 08 je 1138 <register_tm_clones+0x38>
1130: ff e0 jmpq *%rax
1132: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
1138: c3 retq
1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

0000000000001140 <__do_global_dtors_aux>:
1140: f3 0f 1e fa endbr64
1144: 80 3d c5 2e 00 00 00 cmpb $0x0,0x2ec5(%rip) # 4010 <__TMC_END__>
114b: 75 2b jne 1178 <__do_global_dtors_aux+0x38>
114d: 55 push %rbp
114e: 48 83 3d a2 2e 00 00 cmpq $0x0,0x2ea2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
1155: 00
1156: 48 89 e5 mov %rsp,%rbp
1159: 74 0c je 1167 <__do_global_dtors_aux+0x27>
115b: 48 8b 3d a6 2e 00 00 mov 0x2ea6(%rip),%rdi # 4008 <__dso_handle>
1162: e8 f9 fe ff ff callq 1060 <__cxa_finalize@plt>
1167: e8 64 ff ff ff callq 10d0 <deregister_tm_clones>
116c: c6 05 9d 2e 00 00 01 movb $0x1,0x2e9d(%rip) # 4010 <__TMC_END__>
1173: 5d pop %rbp
1174: c3 retq
1175: 0f 1f 00 nopl (%rax)
1178: c3 retq
1179: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

0000000000001180 <frame_dummy>:
1180: f3 0f 1e fa endbr64
1184: e9 77 ff ff ff jmpq 1100 <register_tm_clones>

0000000000001189 <sum>:
1189: f3 0f 1e fa endbr64
118d: 8d 04 37 lea (%rdi,%rsi,1),%eax
1190: c3 retq

0000000000001191 <main>:
1191: f3 0f 1e fa endbr64
1195: 53 push %rbx
1196: 48 83 ec 10 sub $0x10,%rsp
119a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
11a1: 00 00
11a3: 48 89 44 24 08 mov %rax,0x8(%rsp)
11a8: 31 c0 xor %eax,%eax
11aa: 48 89 e6 mov %rsp,%rsi
11ad: 48 8d 3d 50 0e 00 00 lea 0xe50(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
11b4: e8 d7 fe ff ff callq 1090 <__isoc99_scanf@plt>
11b9: 83 f8 01 cmp $0x1,%eax
11bc: 74 33 je 11f1 <main+0x60>
11be: 48 8d 35 42 0e 00 00 lea 0xe42(%rip),%rsi # 2007 <_IO_stdin_used+0x7>
11c5: bf 01 00 00 00 mov $0x1,%edi
11ca: b8 00 00 00 00 mov $0x0,%eax
11cf: e8 ac fe ff ff callq 1080 <__printf_chk@plt>
11d4: bb 01 00 00 00 mov $0x1,%ebx
11d9: 48 8b 44 24 08 mov 0x8(%rsp),%rax
11de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
11e5: 00 00
11e7: 75 44 jne 122d <main+0x9c>
11e9: 89 d8 mov %ebx,%eax
11eb: 48 83 c4 10 add $0x10,%rsp
11ef: 5b pop %rbx
11f0: c3 retq
11f1: 89 c3 mov %eax,%ebx
11f3: 48 8d 74 24 04 lea 0x4(%rsp),%rsi
11f8: 48 8d 3d 05 0e 00 00 lea 0xe05(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
11ff: b8 00 00 00 00 mov $0x0,%eax
1204: e8 87 fe ff ff callq 1090 <__isoc99_scanf@plt>
1209: 83 f8 01 cmp $0x1,%eax
120c: 75 07 jne 1215 <main+0x84>
120e: bb 00 00 00 00 mov $0x0,%ebx
1213: eb c4 jmp 11d9 <main+0x48>
1215: 48 8d 35 eb 0d 00 00 lea 0xdeb(%rip),%rsi # 2007 <_IO_stdin_used+0x7>
121c: bf 01 00 00 00 mov $0x1,%edi
1221: b8 00 00 00 00 mov $0x0,%eax
1226: e8 55 fe ff ff callq 1080 <__printf_chk@plt>
122b: eb ac jmp 11d9 <main+0x48>
122d: e8 3e fe ff ff callq 1070 <__stack_chk_fail@plt>
1232: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
1239: 00 00 00
123c: 0f 1f 40 00 nopl 0x0(%rax)

0000000000001240 <__libc_csu_init>:
1240: f3 0f 1e fa endbr64
1244: 41 57 push %r15
1246: 4c 8d 3d 5b 2b 00 00 lea 0x2b5b(%rip),%r15 # 3da8 <__frame_dummy_init_array_entry>
124d: 41 56 push %r14
124f: 49 89 d6 mov %rdx,%r14
1252: 41 55 push %r13
1254: 49 89 f5 mov %rsi,%r13
1257: 41 54 push %r12
1259: 41 89 fc mov %edi,%r12d
125c: 55 push %rbp
125d: 48 8d 2d 4c 2b 00 00 lea 0x2b4c(%rip),%rbp # 3db0 <__do_global_dtors_aux_fini_array_entry>
1264: 53 push %rbx
1265: 4c 29 fd sub %r15,%rbp
1268: 48 83 ec 08 sub $0x8,%rsp
126c: e8 8f fd ff ff callq 1000 <_init>
1271: 48 c1 fd 03 sar $0x3,%rbp
1275: 74 1f je 1296 <__libc_csu_init+0x56>
1277: 31 db xor %ebx,%ebx
1279: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1280: 4c 89 f2 mov %r14,%rdx
1283: 4c 89 ee mov %r13,%rsi
1286: 44 89 e7 mov %r12d,%edi
1289: 41 ff 14 df callq *(%r15,%rbx,8)
128d: 48 83 c3 01 add $0x1,%rbx
1291: 48 39 dd cmp %rbx,%rbp
1294: 75 ea jne 1280 <__libc_csu_init+0x40>
1296: 48 83 c4 08 add $0x8,%rsp
129a: 5b pop %rbx
129b: 5d pop %rbp
129c: 41 5c pop %r12
129e: 41 5d pop %r13
12a0: 41 5e pop %r14
12a2: 41 5f pop %r15
12a4: c3 retq
12a5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
12ac: 00 00 00 00

00000000000012b0 <__libc_csu_fini>:
12b0: f3 0f 1e fa endbr64
12b4: c3 retq

Disassembly of section .fini:

00000000000012b8 <_fini>:
12b8: f3 0f 1e fa endbr64
12bc: 48 83 ec 08 sub $0x8,%rsp
12c0: 48 83 c4 08 add $0x8,%rsp
12c4: c3 retq

C 代码:

1
2
3
int sum(int a, int b){
return a+b;
}

汇编(反汇编片段):

1
2
3
4
0000000000001189 <sum>:
1189: f3 0f 1e fa endbr64
118d: 8d 04 37 lea (%rdi,%rsi,1),%eax
1190: c3 retq

解释:

  • rdirsi 在 x86-64 System V ABI 中是 前两个整数参数ab)。
  • lea (%rdi,%rsi,1),%eax 计算 rdi + rsi,结果放进 eax(函数返回值寄存器)。
  • retq 返回。

C 代码:

1
2
3
4
5
6
7
int main(){
int a,b;
scanf("%d",&a);
scanf("%d",&b);
sum(a,b);
return 0;
}

汇编关键部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0000000000001191 <main>:
1191: f3 0f 1e fa endbr64
1195: 53 push %rbx
1196: 48 83 ec 10 sub $0x10,%rsp ; 栈上分配16字节,给局部变量用

; --- 第一次 scanf ---
11aa: 48 89 e6 mov %rsp,%rsi ; 把 &a 放到 rsi(第二个参数)
11ad: 48 8d 3d 50 0e 00 00 lea 0xe50(%rip),%rdi ; rdi = "%d" 的地址
11b4: e8 d7 fe ff ff callq 1090 <__isoc99_scanf@plt>

; --- 检查返回值(如果 scanf 没读到数,会走 printf)---
11b9: 83 f8 01 cmp $0x1,%eax ; scanf 返回值和 1 比较
11bc: 74 33 je 11f1 <main+0x60>

; --- 错误时打印 ---
11c5: bf 01 00 00 00 mov $0x1,%edi
11cf: e8 ac fe ff ff callq 1080 <__printf_chk@plt>
...

; --- 第二次 scanf ---
11f3: 48 8d 74 24 04 lea 0x4(%rsp),%rsi ; &b
11f8: 48 8d 3d 05 0e 00 00 lea 0xe05(%rip),%rdi ; "%d"
1204: e8 87 fe ff ff callq 1090 <__isoc99_scanf@plt>
...
; --- 调 sum ---
(这里调用 sum 结果其实被优化掉了,因为你没用 sum(a,b) 的返回值)

结合 C 和汇编的理解

  1. C 里的局部变量 a, b → 栈上分配
    • sub $0x10,%rspab 留了空间
    • &arsp
    • &brsp+4
  2. scanf(“%d”, &a) → 调用外部函数
    • %d 的格式字符串地址放到 rdi(第1参数)
    • &a 放到 rsi(第2参数)
    • 然后 call __isoc99_scanf@plt
  3. 函数调用规范(System V AMD64 ABI)
    • 前 6 个整数参数:rdi, rsi, rdx, rcx, r8, r9
    • 返回值:放在 rax
  4. sum(a, b) → 内联优化
    • sum 的实现就是 a+b
    • 编译器可能直接用 lea 计算了,不会真的存储调用结果(因为没用它)。

pwn学习小记(1)
https://eznp.github.io/2025/10/04/pwn学习小记(1)/
作者
Zer0
发布于
2025年10月4日
许可协议