格式化字符串漏洞

1.什么是格式化字符串漏洞
当printf这一系列函数的格式化字符串中包含用户提交的数据时,就可能出现格式化字符串漏洞。简单说就是它将会允许用户修改程序内部变量的值,从而改变这些程序的行为。我们来看一个栗子程序:
编译,并用如下的方式运行:
41644747.png
 没有报错,反而似乎从栈上找了4个参数来充数了。这就意味着攻击者可以利用这个漏洞来获取栈上的一些数据了。
而当我们多提交一些%x格式符的时候:
43044872.png
 出现了A的十六进制表示41414141.这就说明输入的A被保存在了栈上,并且被当做了“数字”传递给了printf函数,用来替代格式化字符串对应的变量,因此我们可以从栈上获得用十六进制表示的数据。
(当然不只是可以用%x还有其他的%d,%c,%s之类的格式符都能用,可以自己去尝试一下)


再来看一个比较有趣的格式符:%n,它可以将之前输出的字符的数量保存在这个参数指向的地址里
44116801.png
31177314.png
 这样会出现段访问错误,并且生成了一个elf文件(记得关掉栈保护)。这个文件里就包含了一些输出字符串和系统目录信息,甚至还有系统版本信息。
这一点很重要,因为在实际利用某个漏洞时,并不是直接把跳转地址写入函数的返回地址单元,而是写入一个存放着函数的返回地址的地址当中,即经常说的retloc,
这个存放函数的返回地址的地址通常在我们提供的字符串的前面。简单点讲就是:我们可以利用%n,不直接覆盖返回地址,而是通过地址来间接地改写返回地址。


当满足下面条件的时候,我们就可以利用格式化字符串漏洞来运行任意代码:
  • 我们能控制参数,并可以把输出的字符的数量写入内存的任意区域。
  • 宽度格式符允许我们用任意长度填充输出,这样我们就可以用我们选择的值来改写单个字节
  • 通常来说,我们可以猜测函数指针地址,因此,我们可以促成系统把我们提交的字符串当做代码来执行。

总而言之,printf函数在处理包含用户提交的数据的格式化字符串时,通常会发生格式化字符串错误。当攻击者提交大量的格式符但栈上没有对应的参数时,系统通常会用栈上的数据来替代缺少的参数,而这样就有可能导致信息泄露或被攻击者利用来执行任意代码。

2.自定义输出字符宽度
我们知道,在格式符中间加上一个十进制整数来表示输出的最少位数,若实际位数多于定义的宽度,则按实际位数输出,若实际位数少于定义的宽度则补以空格或0。
接下来用下面这个栗子程序尝试这个方法:
21860638.png
 
21881386.png
 可以看到n的值被改成了100.
这样很容易就能想到该怎样用这个方法来覆盖一个地址了。比如我们要把0x这个地址写入内存,只要把对应的十进制值作为格式符控制宽度就行了。

3.简单格式化字符串漏洞利用
接下来我们分析一个有该漏洞的程序:
初步思路就是利用格式化字符串漏洞覆盖掉p指向的内存地址的内容为2000就可以了。

先输入%x看看:
36105214.png
 输出了一串地址,先不急想这个是什么,来分析下汇编代码:
35147118.png
 这三句就是将p指向flag,然后把flag,p压栈。

我们把程序执行到这里:35364475.png
 可以看出0xbffff0c0就是p指向的内存地址。

继续运行到scanf(),随意输入AAAAAAA,再运行到printf,查看栈区:
35774866.png
 看到这个就想起来了之前输出的数据,我们先大胆猜测它是esp+4上的数据,而我们要修改的是0xbffff0c0,它们的偏移为0x8.
那么我们要修改的地址就是:
0xbff6f2b8+0x8=0xbff6f2c0

最后要在这个地址上写入2000:
36732915.png
 36747626.png
 至此,我想你已经明白了格式化字符串漏洞是个啥玩意了:D







```