Windows格式化字符串漏洞利用
作者:mr_me net-ninja/blog/
译者:StudyRush(老钟古)wwwblogs/StudyRush/
老钟古序:
本来是打算翻译作者的另外一篇文章(net-ninja/blog/?p=71)的,但是由于在实战分析过程中遇到了一些问题自己暂时无法解决,等技术掌握更深入一些之后再去想办法解决之后再进行翻译(实在不想让它胎死腹中)。因为没有实战分析过的文章对自己产生的价值会小很多,没有实践过就等于把文章最精华的部分给浪费了,不能够为了翻译而翻译。在翻译的过程中加了很多自己的心得体会。
正文:
这经常让我感到很奇怪怎么样才能够从一个格式化字符串的bug中来执行代码。我知道在这样的一种情况下我们能够用C类型的说明符进行漏洞利用,在一个X86平台上我们不能够直接指向EIP寄存器或者结构化异常处理。当大部分格式化字符串bugs几乎不存在时,我仍然感到对于任何一个安全分析师来说这是一个值得去理解的概念。(即什么是格式化溢出漏洞)
基本步骤:
1.通过说明符%x来到我们的缓冲区攻击字符串的起始位置。
2.使用%n将这个值写入到EAX寄存器中。
3.将一个指向我们的shellcode的地址放入到ECX/EDX寄存器中(为什么呢?)
4.通过%x来重新计算EAX寄存器的偏移并用一个有效的返回值来覆写它
(译注:补充点小知识:在格式化字符串的输出中
(1)s—这个参数被视为指向字符串的指针,将以字符串的形式输出参数;(2)n—这个参数被视为指向整数的指针,在这个参数之前输出的字符的数量将被保存到这个参数指向的地址里。这个可以自己动手实践一下)
举个例子来验证一下%n的实际输出结果。
命令1:
E:\编程练习\C专家编程\第一章& AAAAAAAAAA%x%x%x%x%x%x%x
在没有使用%n说明符的输出结果看下图
AAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD00000000003e3b2f  这个里一个32个字符
用上面的输入则可以看出输出的字符数是为32的,现在我们再次输入上面的命令并把一个%x用%n进行替换,
命令2:
E:\编程练习\C专家编程\第一章& AAAAAAAAAA%x%x%x%x%x%x%n
这时候的结果就为,参看下图:
从上面的得到的结果我们就可以知道,%n格式符把它的参数作为内存地址,把前面输出的字符的数量写到那个地址去,这一操作结果也就意味着我们有机会改写某个内存地址的数据,从而控制程序的执行,是不是感到有点迷糊呢?继续看接下来的分析)。
继续前进时,请确认你有在教程中需要的工具:
Immunity debugger (用Ollydbg也可以)
pywin32 (应该是Python的解释器)
一个编译器(Dev C++或lcc32也行)  (我在实战中用CodeBlock编译器)
让我们从vuln.c开始
#include <stdio.h>
#include <string.h>
void parser(char *string)html文件格式化
{
char buff[256];
memset(buff, 0, sizeof(buff));
strncpy(buff, string, sizeof(buff) - 1);
printf(buff); //here is format string vulnerability
}
int main(int argc, char *argv[])
{
parser(argv[1]);
return 0;
}
好的,从上面的代码我们可以看到一个非常简单的格式化字符串漏洞,这个程序并没有使用格式化说明符(比如%d %s %lf这些)来指出要输出的类型,
(译注:因为printf函数的实现是可以用不定参数个数的,所以如果不具体指定要输出有多少个参数或者没有指定格式要输出的内容,这样做是很危险的。当然这个错误一般来说都是可以避免的,通过测试或者自动化来搜索相应类型的函数来进行修改,从而可以避免这样的低级错误。)
而是直接用printf()函数来解析buffer的内容。这将允许一个攻击者来解析任意的说明符(类似%x,%n之类的),读取或写入到内存。用上面两个编译器之一编译程序和继续跟随着下面的操作。
(原文作者使用lcc-32,我用的是CodeBlock这个IDE,另外如果用VS系列的编译器的话应该会有比较大的出入,我试了一下,输出来的内容有比较大的区别)
(译注:这里说明一下,在下面实际操作过程中我会展示原文中的图和自己实践过程中的截图,上面的图是原文的,下面的图是自己的)。
原文的图
自己机上实践的图
(译注:上面两个图的区别就是我用了四个%x 才输出了41414141,我用两个的时候并不能够输出,有可能这是编译器不同的原因或是不同操作系统平台)。
我们开始用程序来解析一个字符串值,它只是简单地将它打印到标准输出中。
注意到怎么样我们才能够用格式化说明符%x来进行解析和往回读取任意的数据。每一个%x说明符我们将会解析成从栈上往回读一个双字。我们必须不断用%x说明符直到我们到达字符串的起始位置。我们可以放置一个任意双字节的字符串值来代替我们的目标位置。举个例子,我们用%x直到我们到达了双字节位置。(即图中的DDDD)
原文这里需要输入28个A、4个D和9个%x,我用了11个%x,这里跟前面一样要多使用两个%x。这个应该是具体操作平台有关。
为了达到最好的效果,用一个字符串长度能够被4整除(这个将会在后面用shellcode代替)。现在让我
们在字符串中增加一个D或者\x44的数量和用%n来改变最后一个%x(即进行替换),因此我们可以写入最后一个双字节到EAX寄存器中。你的字符串看起来应该像这样:AAAAAAAAAAAAAAAAAAAAAAAAAAADDDDD%x%x%x%x%x%x%x%x% n
那么现在程序崩溃了会是怎么样呢?
(译注:我设置Immunity debugger为实时调试器之后,但当程序崩溃是却并没有弹出调试的界面,所
以我用了OllyDbg来演示。这个问题后来由论坛的Windbg 朋友给了我一个1.73版本的Immunity debugger可以解决,我的是1.82版本的。成功设置之后的测试的结果与Ollydbg是一样的。)