【CTFshow-pwn系列】06_前置基础【pwn 035】详解:利用 SIGSEGV 信号处理机制

本文仅用于技术研究,禁止用于非法用途。

Author:枷锁

在经历了 FORTIFY_SOURCE 系列(pwn 032-034)的编译时检查防御后,PWN 035 带我们回归到了经典的 栈溢出 (Stack Overflow) 领域。

但这道题与常规的"溢出 -> 覆盖返回地址 -> ROP/Shellcode"完全不同。在这里,我们不需要精心构造跳转地址,甚至不需要控制程序流向哪里。我们的目标只有一个:让程序崩溃

因为有些时候,程序的"临终遗言"比它活着的时候更有价值。这道题完美诠释了什么是"置之死地而后生"。

pwn 035 此时无声胜有声:利用崩溃获取 Flag

题目信息与环境侦察

题目描述

复制代码
pwn35:
正式开始栈溢出了,先来一个最最最最简单的吧

用户名为 ctfshow 密码 为 123456 请使用 ssh软件连接

ssh ctfshow@题目地址 -p题目端口号
不是nc连接

解题过程: 首先使用 checksec 检查程序保护情况。

  • Arch: i386-32-little (32位)
  • RELRO : Partial RELRO (GOT表前段只读,PLT可写)
  • Stack : No canary found (无栈哨兵,这是栈溢出的关键前提)
  • NX : Enabled (数据段不可执行,这意味着我们不能直接在栈上运行 Shellcode)
  • PIE : No PIE (地址固定,方便定位,虽然本题不需要定位)

侦察分析 : 虽然开启了 NX 保护,无法直接执行 Shellcode,但 No canary 意味着我们可以随意覆盖栈上的数据,包括 EBP 和返回地址(Return Address)。

第一部分:机制详解 ------ 信号处理 (Signal Handler)

在 Linux 系统中,当程序发生严重异常(如除以零、非法内存访问)时,内核会向进程发送一个信号 (Signal)

什么是 SIGSEGV?

  • 信号名称:SIGSEGV (Signal 11)
  • 中文含义:段错误 (Segmentation Fault)
  • 触发条件 :当程序试图访问未分配的内存,或者试图向只读内存(如 .rodata)写入数据时触发。
  • 常见场景 :在栈溢出攻击中,如果我们覆盖了返回地址为一个无效地址(如 0x41414141),当函数尝试 ret 跳转时,CPU 就会因为无法访问该地址而抛出异常,进而触发 SIGSEGV。

信号处理函数 (Signal Handler)

程序员可以通过 signal() 系统调用,自定义当某个信号发生时要执行的代码。

  • 默认行为:如果你不设置,发生 SIGSEGV 时程序会直接终止并 core dump。
  • 自定义行为 :如果程序注册了 signal(11, handler_func),那么当程序崩溃时,它不会立即死亡,而是会先去执行 handler_func 函数。这给了攻击者可乘之机。

第二部分:代码审计与漏洞挖掘

1. 静态分析 (IDA Pro)

将程序拖入 IDA 32位,我们重点关注 main 函数的初始化流程。

复制代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *stream; // [esp+0h] [ebp-1Ch]

  // [步骤 1]:尝试打开 Flag 文件
  // "r" 表示以只读模式打开 /ctfshow_flag
  stream = fopen("/ctfshow_flag", "r");
  if ( !stream )
  {
    // 如果打开失败(比如本地没有这个文件),程序会提示并退出
    puts("/ctfshow_flag: No such file or directory.");
    exit(0);
  }

  // [步骤 2]:将 Flag 读入全局内存
  // fgets 从文件中读取 64 字节,存放到全局变量 flag 中
  // 注意:这个 flag 变量位于 .bss 段或 .data 段,在整个程序生命周期内都存在
  fgets(flag, 64, stream);

  // [步骤 3]:设置"崩溃触发器" (关键点!)
  // signal(11, ...) 注册了 SIGSEGV 信号的处理函数
  // 意思是:如果程序之后发生了段错误(崩溃),先别急着死,去执行 sigsegv_handler 函数
  signal(11, (__sighandler_t)sigsegv_handler);

  // ... 打印一堆 Logo 信息,无关紧要 ...
  puts(asc_8048910);
  // ...
  puts("Where is flag?\n");

  // [步骤 4]:参数检查
  if ( argc <= 1 )
  {
    puts("Try again!");
  }
  else
  {
    // [步骤 5]:漏洞触发点
    // 将用户输入的第一个参数 argv[1] 传入 ctfshow 函数
    // 这里就是我们注入 Payload 的地方
    ctfshow((char *)argv[1]);
    
    // 如果 ctfshow 正常返回(没崩溃),会执行这行
    printf("QaQ!FLAG IS NOT HERE! Here is your input : %s", argv[1]);
  }
  return 0;
}

详细分析:

  1. Flag 在哪里? 程序一启动,fgets 就把 flag 的内容读到了内存里(全局变量 flag)。这就像是把宝藏从保险箱(文件系统)拿出来,放到了桌子上(内存)。
  2. 机关是什么? signal(11, sigsegv_handler) 设置了一个机关。它告诉操作系统:"如果有人把桌子掀了(触发崩溃),请自动执行后续的 sigsegv_handler 操作。"
  3. 如何触发? ctfshow 函数接收了我们的输入。如果我们在那里制造溢出,程序就会崩溃,从而触发上面的机关。

2. 深入信号处理函数

跟进 sigsegv_handler 函数(在 IDA 中双击 handler 地址):

复制代码
void __noreturn sigsegv_handler()
{
  // 当程序崩溃时,这里会执行
  // 它将之前读取的全局变量 flag 输出到标准错误流 (stderr)
  fprintf(stderr, "%s\n", flag);
  fflush(stderr);
  exit(1);
}

分析 :这个函数就是我们梦寐以求的终点。只要程序崩溃,它就会通过 fprintf 把 Flag 打印到标准错误输出,并刷新缓冲区确保我们能看到。

3. 寻找溢出点

跟进 ctfshow 函数:

复制代码
char *__cdecl ctfshow(char *src)
{
  // [esp+Ch] [ebp-6Ch] BYREF
  // 虽然数组定义为 104 字节,但栈偏移显示它距离 EBP 有 0x6C (108字节)
  char dest[104]; 

  // [致命漏洞]:strcpy 无长度限制
  // strcpy 会将 src 的内容复制到 dest,直到遇到 \0
  // 我们可以通过 argv[1] 传入超长字符串,覆盖 dest,进而覆盖 EBP 和 返回地址
  return strcpy(dest, src);
}

精确栈布局分析: IDA 注释 [ebp-6Ch] 揭示了关键的内存布局信息:

  • dest 缓冲区起始地址ebp - 0x6C (即 ebp - 108 字节)。
  • Saved EBP :位于 ebp (占用 4 字节)。
  • Return Address :位于 ebp + 4

计算崩溃阈值 : 要覆盖到返回地址,我们需要填充的数据长度 = 108 (填充缓冲区及对齐填充) + 4 (覆盖 Old EBP) = 112 字节 。 只要我们输入的字符串长度超过 112 字节,第 113-116 字节就会覆盖返回地址。一旦返回地址被修改为乱码(如 0x61616161),函数返回时就会触发段错误。

第三部分:解题思路

这道题的逻辑闭环非常完美,它是一个典型的"诱捕"结构,但也留下了后门:

  1. Flag 已在内存中:程序启动时,Flag 就已经被读入到全局变量里了。
  2. 崩溃即胜利:程序告诉操作系统:"如果我崩了(SIGSEGV),请把 Flag 打印出来再死"。
  3. 制造崩溃ctfshow 函数中存在 strcpy 栈溢出漏洞。我们只需要输入足够长的字符串,覆盖掉函数的返回地址(Return Address)。
  4. 触发流程
    • 我们输入 200 个 'A'(远超 112 字节)。
    • strcpy 将其写入栈。
    • 栈上的 Old EBP 被覆盖。
    • 栈上的 Return Address 被覆盖为 0x41414141 ('AAAA')。
    • ctfshow 函数执行完毕,执行 ret 指令。
    • CPU 尝试跳转到 0x41414141
    • 0x41414141 是非法内存地址 -> 触发 SIGSEGV
    • 内核捕获信号,跳转执行 sigsegv_handler
    • fprintf(stderr, ...) 执行,Flag 到手。

攻击策略

"只要输入得够长,程序就不知道该去哪,然后它就会把 Flag 给我。"

我们甚至不需要计算精确的偏移量,不需要构造 ROP 链,不需要知道 system 地址,只需要由着性子输入一大串 A 即可。

第四部分:实战操作

由于是 SSH 连接,我们不需要编写 Python 脚本,直接在终端操作即可。

1. 尝试正常输入

复制代码
./pwn hello
# 程序正常运行,打印输入内容,没有 Flag
QaQ! FLAG IS NOT HERE! Here is your input: hello

2. 尝试攻击 (Payload)

构造一个明显超过栈空间的字符串(大于 112 字节)。为了保险,我们直接输入一大串(比如 200 个字符)。

复制代码
# 在终端直接运行,参数输入一长串 'a'
./pwn aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

3. 运行结果

复制代码
CTFshow
...
* Type : Stack_Overflow
* Hint : See what the program does!
...
Where is flag?

ctfshow{xxxxxxxxxxxxxxxxxxx}

程序在尝试返回时崩溃,触发了信号处理函数,成功吐出了 Flag。

总结:PWN 035 的核心逻辑

步骤 动作 本质
预设 程序读取 Flag 并注册 SIGSEGV 句柄 "临终遗言"机制:崩了就打印 Flag。
漏洞 strcpy 无限制复制 制造混乱:允许破坏栈结构。
触发 输入超长字符串覆盖返回地址 实施谋杀:让程序跳向非法地址导致 Crash。
结果 信号处理函数接管控制权 获取遗产:程序崩溃前打印 Flag。

核心启示 : 在 PWN 中,Crash(崩溃) 并不总是坏事。

  1. 有时候崩溃能泄露信息(如报错信息中包含内存地址,这是 Stack Smash 保护的利用点之一)。
  2. 有时候崩溃本身就是程序逻辑的一部分(如本题的信号处理)。
  3. 在分析程序时,一定要留意 signalsigaction 等注册异常处理的函数,它们往往藏着作者留下的"后门"或"彩蛋"。

宇宙级免责声明 🚨 重要声明:本文仅供合法授权下的安全研究与教育目的!🚨 1.合法授权:本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法,可能导致法律后果(包括但不限于刑事指控、民事诉讼及巨额赔偿)。 2.道德约束:黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范,仅用于提升系统安全性,而非恶意入侵、数据窃取或服务干扰。 3.风险自担:使用本文所述工具和技术时,你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。 4.合规性:确保你的测试符合当地及国际法律法规(如《计算机欺诈与滥用法案》(CFAA)、《通用数据保护条例》(GDPR)等)。必要时,咨询法律顾问。 5.最小影响原则:测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。 6.数据保护:不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息,应立即报告相关方并删除。 7.免责范围:作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。
🔐 安全研究的正确姿势:

✅ 先授权,再测试

✅ 只针对自己拥有或有权测试的系统

✅ 发现漏洞后,及时报告并协助修复

✅ 尊重隐私,不越界

⚠️ 警告:技术无善恶,人心有黑白。请明智选择你的道路。

相关推荐
Scott9999HH3 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔3 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社4 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海4 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖5 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
九硕智慧建筑一体化厂家8 小时前
直流智能照明|一站式低碳照明解决方案,安全、节能、降本、全场景适配
安全
心平气和量大福大8 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai8 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang