前置知识
在x86的结构下,格式化字符串的参数是通过栈传递的。换言之,printf()
函数打印出来的数据都直接来源于栈。
%n
转换指示符将当前已成功写入流或者缓冲区的字符个数存储到由参数指定的地址中去,意味着printf
也有写入内存能力。
%n$
则可以指定第n个参数
指示符 | 类型 | 输出 |
---|---|---|
%d | 4-byte | Integer |
%u | 4-byte | Unsigned Integer |
%x | 4-byte | Hex |
%s | 4-byte ptr | String |
%c | 1-byte | Character |
%p | 4-byte | Hex addr |
长度 | 类型 | 输出 |
hh | 1-byte | char |
h | 2-byte | short int |
l | 4-byte | long int |
ll | 8-byte | long long int |
漏洞原理
当格式字符串可以被外部更改时,则可构造特定格式字符串达到泄露、修改任意地址内存数据。
例如:
printf("%4$s")
会输出栈上对应偏移为4处地址 所指的内存数据printf("\x78\x56\x34\x12")
会将值0x12345678写入栈对应偏移处- 上面两点结合起来就可泄露任意地址内存,如(假设偏移为4):
printf("\x78\x56\x34\x12:%4$s")
则会输出地址0x12345678处的内容
pwnlib.fmtstr
pwntools里提供了利用格式化字符串漏洞的一些工具
class pwnlib.fmtstr.FmtStr(execute_fmt, offset=None, padlen=0, numbwritten=0)
- execute_fmt ( function ) – 调用与易受攻击的进程进行通信的函数
- offset ( int ) – 您控制的第一个格式化程序的偏移量
- padlen ( int ) – 要在有效负载之前添加的 pad 的大小
- numbwritten ( int ) – 已经写入的字节数
当不给出参数offset时,则尝试通过泄漏堆栈数据来找到正确的偏移量,事实上可以总是这么做
主要参数是execute_fmt,一般要视程序来编写,基本模板如下:
1 | def exec_fmt(payload): |
class pwnlib.fmtstr.fmtstr_playload(offset=None, writes, numbwritten=0, write_size='byte')
- offset ( int ) – 您控制的第一个格式化程序的偏移量(可由上面构造方法自动获取)
- writes ( dict ) – 带有 addr, value 的 dict
{addr: value, addr2: value2}
- numbwritten ( int ) – printf 函数已经写入的字节数
- write_size ( str ) – 必须是
byte
,short
或int
。告诉你是要一个字节一个字节写,一个短写还是一个 int 写(hhn、hn 或 n)
实例
HITCON CMT 2017: pwn200
1 |
|
漏洞利用思路
开了Canary,没法直接栈溢出劫持返回地址,所以可以先调试找到Canary存放的地址,再利用格式化字符串泄露Canary的值,在栈溢出时覆盖上去即可
完整exp如下:
1 | from pwn import * |
Comments