linux最早的漏洞防护机制nx-stack刚刚出现后就有人想出了突破方法。那就是只有栈是不可执行,而除了栈以外的其他地方还是可以执行的,只要把返回地址执行别的地方就可以。
一.格式化字符串漏洞
格式化字符串漏洞在Windows下很难以利用,但是在Linux下的pwn题目中出现的频率是很高的。
格式化字符串的“$”操作符,其允许我们从格式化字符串中选取一个作为特定的参数。例如,
printf("%3$s", 1, "b", "c", 4);
最终会显示结果“c”。这是因为格式化字符串“%3$s”,它告诉计算机“把格式化字符串后面的第三个参数告诉我,然后将参数解释为字符串”。所以,我们也可以这样做
printf("AAAA%3$n");
printf函数将值“4”(输入的A的数量)写入第三个参数指向的地址。
x64的情况要刨除5个用寄存器传递的参数
格式化字符串还有%s
参数。那么,如果在栈中保存有指向我们感兴趣数据的指针,我们就可以在打印指针的时候使用一个%s
来打印别的地方的内容。而且一般的程序都会将用户输入的数据储存在栈上。这就给了我们一个构造指针的机会,再结合格式化字符串漏洞,几乎可以得到所有内存数据。
%n
功能是将%n
之前printf
已经打印的字符个数赋值给传入的指针。通过%n
我们就可以修改内存中的值了。和%s
leak内存一样,只要栈中有我们需要修改的内存的地址就可以使用格式化字符串的漏洞修改它。
当然,如果需要修改的数据是相当大的数值时,我们可以使用%02333d这种形式。在打印数值右侧用0补齐不足位数的方式来补齐足。
可以看出,格式化字符串可以修改的内存范围更加广。只要构造出指针,就可以改写内存中的任何数值。和栈溢出的地毯轰炸不同。这种一次只能改写一个dword大小的内存的攻击方式更加精准而致命。
由此可见格式化字符串漏洞主要是:
1.泄漏任意地址的值,leak内存(比如leak出libc基地址)
2.写任意地址,可用于修改got表
主要实现方式是利用格式化串本身也处于栈中,去用直接参数访问找到这个栈中的格式化串。这个格式化串可以使用一个要写入的内存地址。也就是直接参数访问+%n格式符+长度表示=向任意地址写入值,这个写入是不能一次写入4字节的,所以可以分两次写入2字节和分四次写入1字节。其中hhn是写一个字节,hn是写两个字节。n是写四个字(注意是到目前的%n为止前面的所有)。
附送栗子一枚:http://www.cnblogs.com/Ox9A82/p/5483916.html
这部分来自icemakr的博客 32位 读 '%{}$x'.format(index) // 读4个字节 '%{}$p'.format(index) // 同上面 '${}$s'.format(index) 写 '%{}$n'.format(index) // 解引用,写入四个字节 '%{}$hn'.format(index) // 解引用,写入两个字节 '%{}$hhn'.format(index) // 解引用,写入一个字节 '%{}$lln'.format(index) // 解引用,写入八个字节 64位 读 '%{}$x'.format(index, num) // 读4个字节 '%{}$lx'.format(index, num) // 读8个字节 '%{}$p'.format(index) // 读8个字节 '${}$s'.format(index) 写 '%{}$n'.format(index) // 解引用,写入四个字节 '%{}$hn'.format(index) // 解引用,写入两个字节 '%{}$hhn'.format(index) // 解引用,写入一个字节 '%{}$lln'.format(index) // 解引用,写入八个字节 %1$lx: RSI %2$lx: RDX %3$lx: RCX %4$lx: R8 %5$lx: R9 %6$lx: 栈上的第一个QWORD
其它
这里记录一些相关的姿势
fmtstr_payload是pwntools提供的函数,用于自动生成格式化字符串。
fmtstr_payload有两个参数
第一个参数是int,用于表示取参数的偏移个数
第二个参数是字典,字典的意义是往key的地址,写入value的值
fmtstr_payload(7, {printf_got: system_add})
这个函数调用会往printf_got中写入system_add
此外调用一次fsb函数并不意味着只能进行一次写操作,实际上可以传递多个写格式串以实现一次调用对多个地址写的操作,注意写入的值是“之前”输入的字符总数。