[ pwn 学习 ] CTFshow-pwn5
2026-08-22 20:36:38

前置基础 5-12

地址类十六进制字母尽量按照题目要求大写。

1.题干

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
5)运行此文件,将得到的字符串以ctfshow{xxxxx}提交。 
如:运行文件后 输出的内容为 Hello_World
提交的flag值为:ctfshow{Hello_World}
注:计组原理题型后续的flag中地址字母大写

6)立即寻址方式结束后eax寄存器的值为?

7)寄存器寻址方式结束后edx寄存器的值为?

8)直接寻址方式结束后ecx寄存器的值为?

9)寄存器间接寻址方式结束后eax寄存器的值为?

10)寄存器相对寻址方式结束后eax寄存器的值为?

11)基址变址寻址方式结束后的eax寄存器的值为?

12)相对基址变址寻址方式结束后eax寄存器的值为?

表面上考了很多知识点:

  • 立即寻址
  • 寄存器寻址
  • 直接寻址
  • 寄存器间接寻址
  • 寄存器相对寻址
  • 基址变址寻址
  • 相对基址变址寻址

但本质上反复做的事只有:先算出一个地址,再从这个地址读几个字节。

2.流水线

ELF 文件 ->操作系统加载到内存 ->CPU 执行指令 -> 寄存器里暂存数据 -> 按地址去内存读字符串

实际上反复发生的操作只有“先算出一个地址,再从这个地址读几个字节”

3.最小概念表

2.1 CPU

CPU 就是执行指令的硬件。
你可以先把它想成“真正动手干活的人”

2.2 寄存器

寄存器是 CPU 里面很小、很快的临时小格子

它们容量很小,但速度很快,专门放“马上要用”的值。

  • eax``ebx``ecx``edx32 位系统最常用的通用寄存器
  • esi``edi通常用于存储变址或源/目的地址
  • esp``ebp与堆栈相关,后续“栈溢出”的核心

32 位系统 每个格子能装 4 字节(32bit)的数据

寄存器名称 英文全称 外号 职责
EAX Extended **Accumulator **Register 累加器 核心计算位,各种加减乘除的结果通常都存在这。
EBX Extended **Base **Register 基址寄存器 常用来存放“起始地址”,像个定位导航。
ECX Extended **Counter **Register 计数器 循环里的 i++
往往就在这里。
EDX Extended Data Register 数据寄存器 配合 EAX 进行大数据运算。
ESI Extended Source Index 传送员 复制数据(如 strcpy
)时的源地址。
EDI Extended Destination Index 传送员 复制数据(如 strcpy
)时的目的地址。
ESP Extended Stack Pointer 栈管家 指向栈顶,管理程序运行时的局部变量。
EBP Extended Base Pointer 栈管家 指向栈底,管理程序运行时的局部变量。

2.3 内存

内存比寄存器大得多,用来放:

  • 程序代码
  • 字符串
  • 变量
  • 栈上的数据

2.4 地址

地址就是内存里某个位置的门牌号,比如 0x080490E8

基址是一个参考点,是程序或数据块在内存中起始位置的地址

2.5 读内存

“读内存”就是:

  • 先拿到一个地址
  • 再去那个地址取内容

2.6 ELF

ELF 是 Linux 可执行文件格式。
可以把它理解成“Linux 下程序文件的标准包装盒”

它会告诉系统:

  • 这是个什么文件
  • 代码放在哪
  • 数据放在哪
  • 每一段加载到内存后应该放到什么地址

2.7 section

ELF 会分很多 section
这题你最关心两个:

  • .text:放机器指令
  • .data:放已经初始化好的数据

2.8 偏移

偏移不是绝对地址,而是“从某个起点往后走了多远”

比如:

  • 文件偏移:从文件开头往后数多少字节
  • msg + 4:从 msg 往后走 4 个字节

相对偏移

实际物理地址=基址+偏移量

2.9 小端序

x86 是小端序。
最容易看到的现象是:

  • 内存里四个字节是 57 65 6C 63
  • 读进 32 位寄存器后常写成 0x636C6557

也就是:字节在内存里的排布顺序,和把它整体看成 32 位数时的显示顺序,不是同一个视角

4.为什么总要读内存

因为寄存器太小了

比如字符串 Welcome_to_CTFshow_PWN 很长,不可能整串都塞进 eax 这种小寄存器里。
所以字符串会放在内存里,而寄存器里通常只放:

  • 一个数字
  • 或一个地址

所以

  • mov ecx, msg ≠把整串字符串msg塞进 ecx
  • mov ecx, msg =把 msg 的地址放进 ecx

mov eax, [esi]

才是

  • esi 里存的那个地址
  • 从那个地址开始读内容
  • 把读出来的值放进 eax

[] 是这题最关键的符号

  • 没有 []:通常拿的是数字 / 地址本身
  • []:通常拿的是“那个地址里的内容”

5. msg 是什么

Welcome_to_CTFshow.asm 里:

1
2
section .data
msg db "Welcome_to_CTFshow_PWN", 0

它的意思是:

  • db 一字节一字节地放数据
  • 放进去的数据是字符串 "Welcome_to_CTFshow_PWN"
  • 最后再补一个结尾的 0
  • msg** 只是这串字节起始位置的标签**

你可以把 msg 想成“这排柜子的第一个格子叫 msg

6. 把字符串拆成字节 看上去就好点了^^

字符串是:

1
Welcome_to_CTFshow_PWN

前面的字节表如下:

偏移 字符 十六进制
msg+0 W 57
msg+1 e 65
msg+2 l 6C
msg+3 c 63
msg+4 o 6F
msg+5 m 6D
msg+6 e 65
msg+7 _ 5F
msg+8 t 74
msg+9 o 6F
msg+10 _ 5F
msg+11 C 43
msg+12 T 54
msg+13 F 46
msg+14 s 73
msg+15 h 68
msg+16 o 6F
msg+17 w 77
msg+18 _ 5F
msg+19 P 50
msg+20 W 57
msg+21 N 4E
msg+22 \0 00

这张表很重要,因为第 8-12 题本质上都是在这张表上找起点,再连续取 4 个字节。

7. 这题里的 ELF 结构怎么帮你找到 msg

这题不只是能从源码看出字符串,还能从 ELF 文件里机械地推出地址。

7.1 先取 ELF 头里的 4 个字段

在这个 ELF32 文件里,固定偏移位置有这几个值:

  • 文件偏移 0x20e_shoff
  • 文件偏移 0x2Ee_shentsize
  • 文件偏移 0x30e_shnum
  • 文件偏移 0x32e_shstrndx

本题实际字节是:

  • 0x2018 01 00 00 -> 小端 -> 0x118
  • 0x2E28 00 -> 0x28
  • 0x3004 00 -> 4
  • 0x3203 00 -> 3

意思是:

  • section header 表从文件偏移 0x118 开始
  • 每项大小 0x28
  • 一共 4
  • 3 项是 section 名字字符串表

7.2 算出 section header 每项的起点

1
第 n 项起始偏移 = e_shoff + n * e_shentsize

已经算出:

  • e_shoff = 0x118
  • e_shentsize = 0x28

直接代入

∴第 0 项

0x118 + 0 * 0x28 = 0x118

第 1 项

0x118 + 1 * 0x28 = 0x118 + 0x28 = 0x140

第 2 项

0x118 + 2 * 0x28 = 0x118 + 0x50 = 0x168

因为:

2 * 0x28 = 0x50

第 3 项

0x118 + 3 * 0x28 = 0x118 + 0x78 = 0x190

因为:

3 * 0x28 = 0x78

为什么可以这么算
因为:

  • e_shoff 是整个 section header 表的开头
  • e_shentsize 是“每一项占多少字节”
  • 所以第 n 项,就是从表开头往后跳 n 个“项宽度”

7.3 找 .text.data

ELF32 的 section header 里:

  • sh_name 在该项起始 +0x00
  • sh_addr 在该项起始 +0x0C
  • sh_offset 在该项起始 +0x10

第 1 项:

  • sh_name = 0x0B
  • sh_addr = 0x08048080
  • sh_offset = 0x80

第 2 项:

  • sh_name = 0x11
  • sh_addr = 0x080490E8
  • sh_offset = 0xE8

第 3 项是字符串表,它的 sh_offset = 0xFF
跳到文件偏移 0xFF,可以看到:

1
00 .shstrtab 00 .text 00 .data 00

于是:

  • 偏移 0x0B 对应 .text
  • 偏移 0x11 对应 .data

所以:

  • .text 加载地址是 0x08048080
  • .data 加载地址是 0x080490E8

7.4 为什么 msg = 0x080490E8

因为 msg 就定义在 .data 开头,而 .data 的加载地址正好是 0x080490E8
所以:

1
msg = 0x080490E8

7.5 为什么文件偏移 0xE8 对应内存地址 0x080490E8

对于同一个 section 里的字节,有一个很好用的换算关系:

text

目标文件偏移 = sh_offset + (目标内存地址 - sh_addr)

在本题里:

  • .datash_offset = 0xE8
  • .datash_addr = 0x080490E8

如果目标地址正好就是 .data 开头:

1
目标文件偏移 = 0xE8 + (0x080490E8 - 0x080490E8) = 0xE8

所以内存里的 msg 开头,正好对应文件偏移 0xE8

什么是偏移

偏移就是“从某个起点往后数几格”。

例如:

  • msg + 4:从 msg 的起始地址往后走 4 个字节
  • 也就是从第 5 个字符开始

因为字节编号通常从 0 开始:

  • 第 0 个字节:W
  • 第 1 个字节:e
  • 第 2 个字节:l
  • 第 3 个字节:c
  • 第 4 个字节:o

所以 msg + 4 会指到 o

什么是 ELF

ELF 就是程序文件的组织方式。会告诉系统:

  • 这文件是不是可执行程序
  • 代码在哪一段
  • 数据在哪一段
  • 各段加载到内存后应该放到什么地址

所以去找 .data、找 sh_addr、找 sh_offset,本质上是在做这件事:

“先在文件里找到这段数据,再弄清它加载到内存后会出现在什么地址。”

最容易混淆的 3 组东西

地址 和 地址里的值 不是一回事。

msg 是地址,[msg] 才是那个地址里的内容。

文件偏移 和 内存地址 不是一回事。

一个是在硬盘文件里数位置,一个是在内存里数位置。

字节顺序 和 整数显示 不是一回事。

内存里是 57 65 6C 63,显示成 32 位整数会变成 0x636C6557

做题

附件是一个Welcome_to_CTFshow``(ELF)Welcome_to_CTFshow.asm

asm 是汇编源代码文件,纯文本格式

它不是可执行文件,而是给汇编器(如 nasm)编译成机器码用的

这段代码主要是演示不同寻址方式,最后输出字符串

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
section .data
msg db "Welcome_to_CTFshow_PWN", 0

section .text
global _start

_start:

; 立即寻址方式
mov eax, 11 ; 将11赋值给eax 相当于C语言中的赋值 逗号起到=的作用
add eax, 114504 ; eax加上114504 相当于eax=eax+114504
sub eax, 1 ; eax减去1 相当于eax=eax-1
//mov:move
//add:add
//sub:substract

; 寄存器寻址方式
mov ebx, 0x36d ; 将0x36d(十六进制数)赋值给ebx
mov edx, ebx ; 将ebx的值赋值给edx

; 直接寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx

; 寄存器间接寻址方式
mov esi, msg ; 将msg的地址赋值给esi
mov eax, [esi] ; 将esi所指向的地址的值赋值给eax

; 寄存器相对寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
add ecx, 4 ; 将ecx加上4
mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax

; 基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 2 ; 将2赋值给edx
mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax

; 相对基址变址寻址方式
mov ecx, msg ; 将msg的地址赋值给ecx
mov edx, 1 ; 将1赋值给edx
add ecx, 8 ; 将ecx加上8
mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax

; 输出字符串
mov eax, 4 ; 系统调用号4代表输出字符串
mov ebx, 1 ; 文件描述符1代表标准输出
mov ecx, msg ; 要输出的字符串的地址
mov edx, 22 ; 要输出的字符串的长度
int 0x80 ; 调用系统调用

; 退出程序
mov eax, 1 ; 系统调用号1代表退出程序
xor ebx, ebx ; 返回值为0
int 0x80 ; 调用系统调用

先记住4 个字段(ELF32 固定位置)

e_shoff在文件偏移0x20,4 字节

e_shentsize0x2E,2 字节

e_shnum0x30,2 字节

e_shstrndx0x32,2 字节

pwn5

看程序最后执行了什么系统调用

1
2
3
4
5
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, 22
int 0x80
  • eax = 4:Linux 32 位下,系统调用号 4write
  • ebx = 1:文件描述符 1 代表标准输出,也就是屏幕
  • ecx = msg:从 msg 开始输出
  • edx = 22:输出 22 个字节

为什么正好是这串字符串

"Welcome_to_CTFshow_PWN" 长度正好是 22

操作方法

复制两个文件到ubuntu

1.安装工具

1
sudo apt install -y nasm binutils

2.进入桌面目录并列出当前目录文件

1
2
cd ~/Desktop
ls

3.汇编:把.asm变成.o

1
nasm -f elf32 Welcome_to_CTFshow.asm -o Welcome_to_CTFshow.o
  • nasm:汇编器
  • -f elf32:是生成32位ELF目标文件
  • Welcome_to_CTFshow.asm:是 输入内容
  • -o Welcome_to_CTFshow.o:输出的半成品机器码

结果就会生成文件Welcome_to_CTFshow.o

4.链接:把.o变成可执行文件 并运行

1
ld -m elf_i386 -s -o Welcome_to_CTFshow Welcome_to_CTFshow.o
  • ld:链接器
  • -m elf_i386:生成 32 位可执行文件
  • -s:去掉符号表(让文件更小)
  • -o Welcome_to_CTFshow:输出可执行文件名
  • Welcome_to_CTFshow.o:输入目标文件

结果就会生成可执行的Welcome_to_CTFshow

1
./Welcome_to_CTFshow

./表示当前目录下

运行后输出Welcome_to_CTFshow_PWN

因为这串字符串已经写死在数据段中
ctfshow{Welcome_to_CTFshow_PWN}


相关笔记

寻址方式系列(6-12)

  • pwn6 — 立即寻址
  • pwn7 — 寄存器寻址
  • pwn8 — 直接寻址
  • pwn9 — 寄存器间接寻址
  • pwn10 — 寄存器相对寻址
  • pwn11 — 基址变址寻址
  • pwn12 — 相对基址变址寻址

编译工具链

  • pwn13 — GCC 编译入门
  • pwn15 — nasm 汇编 + 系统调用(sys_write
  • pwn16gcc 编译 .s 汇编文件

基础概念

  • pwn0 — C 语言基础 / strcpy 函数
  • pwn1checksec 安全机制检查
上一页
2026-08-22 20:36:38
下一页