*printf()格式化串安全漏洞分析(上)

本文介绍了*printf()函数的安全漏洞,特别是格式化串可能导致的缓冲区溢出问题。通过一个具体的QPOP 2.53版本中的例子,详细解析了如何利用用户提供的格式串来覆盖函数返回地址,从而实现远程或本地的安全攻击。文章通过源代码分析和调试演示了漏洞的原理,并提出攻击方法。
摘要由CSDN通过智能技术生成

*printf()格式化串安全漏洞分析(上)


     测试平台:RedHat 6.1, RedHat 6.2 (Intel i386)

前言:
=====
最近一段时间,一种新的安全漏洞正开始引起人们注意,就是诸多的*printf()函

数的格式
化串问题。其实这个问题应该说并不鲜见,只是一直没有人注意它,直到最近才

开始进行
一些深入的讨论。格式化串的问题实际上是由于程序员编程时的疏漏所导致的,

下面我们
就来看看具体是怎么回事。


关于格式化串
============
*printf()函数包括printf,  fprintf,  sprintf,  snprintf,  vprintf,

vfprintf,
vsprintf, vsnprintf等函数,它们可以将数据格式化后输出。以最简单的

printf()为例:
int printf(const char *format, arg1,arg2,...);

通过定制format的内容(%s,%d,%p,%x...),用户可以将数据按照某种格式输出。问

题是,
*printf()函数并不能确定数据参数arg1,arg2...究竟在什么地方结束,也就是说

,它不知
道参数的个数。它只会根据format中的打印格式的数目依次打印堆栈中参数

format后面地址
的内容。先来看一个简单的例子:

<- begin ->  fmt_test.c

#include <stdio.h>

int main(void)
{
   char string[]="Hello World!";
  
   printf("String: %s  , arg2: %#p , arg3: %#p/n", string);
   return 0;
}

<- end -> 

上面的例子中我们其实只提供了一个数据参数"string",但在格式串中有三个打印

格式,
我们看一下运行的结果:

[warning3@redhat-6 format]$ gcc -o fmt_test fmt_test.c
[warning3@redhat-6 format]$ ./fmt_test
String: Hello World!  , arg2: 0x6c6c6548 , arg3: 0x6f57206f

我们来看一下arg2,arg3显示的是哪里的内容:
[warning3@redhat-6 format]$ gdb ./fmt_test
<...>
(gdb) b printf
Breakpoint 1 at 0x8048308
(gdb) r
Starting program: /home/warning3/format/./fmt_test
Breakpoint 1 at 0x40064f5c: file printf.c, line 30.

Breakpoint 1, printf (
    format=0x80484c0 "String: %s  , arg2: %#p , arg3: %#p/n") at

printf.c:30
30      printf.c: No such file or directory.
(gdb) x/10x $ebp
0xbffffc88:     0xbffffca8      0x08048403      0x080484c0     

0xbffffc98
0xbffffc98:     0x6c6c6548      0x6f57206f      0x21646c72     

0x08049500
0xbffffca8:     0xbffffcc8      0x400301eb

我们看到printf()的第一个参数地址是$ebp+8,里面的内容是0x080484c0,
(gdb) x/s 0x080484c0
0x80484c0 <_IO_stdin_used+60>:   "String: %s  , arg2: %#p , arg3: %

#p/n"
这是我们的格式化串的地址

再来看我们要格式化输出的数据($ebp+12):
(gdb) x/s 0xbffffc98
0xbffffc98:      "Hello World!"

我们看到,紧接着下来的两个字的内容就是刚才的程序中显示的结果:
$ebp+16: 0x6c6c6548  "Hell"
$ebp+20: 0x6f57206f  "o Wo"

从下面的示意图上可以看得更清楚一些:

              栈顶
       +------------+
      |   ......   |   
      +------------+
0xbffffc88| 0xbffffca8 | --------> 保存的EBP  -- printf()
      +------------+
      | 0x08048403 | --------> 保存的EIP  -- printf()
      +------------+  format
format->  | 0x080484c0 | --------> "String: %s  , arg2: %#p , arg3: %

#p/n"的地址
      +------------+  arg1
      | 0xbffffc98 | --------> "Hello World!"的地址                    

    
      +------------+
      | 0x6c6c6548 | --------> string[] = "Hell   
      +------------+
      | 0x6f57206f | -------->             o Wo
      +------------+
      | 0x21646c72 | -------->             rld!"
      +------------+
      | 0x08049500 | -------->   '/0'xxx
      +------------+
0xbffffca8| 0xbffffcc8 | --------> 保存的EBP  -- main()
      +------------+
      | 0x400301eb | --------> 保存的EIP  -- main()
      +------------+
          |   ......   |   
      +------------+
              栈底

我们可以看到,arg2,arg3所显示的其实是main()中数组strings中前两个字的内

容。
从上面这个简单的例子我们可以看到, *printf()只根据format中打印格式(%)的

数目来依次
显示堆栈中format参数后面地址的内容,每次移动一个字(4个字节).
由于我们上面的例子中出现了三个(%)号,所以它会依次打印三个地址的内容:
format+4, format + 8, format + 12.

(注意:并不是所有的%格式都是移动4个字节,例如%f就每次移动8个字节。如果

要覆盖的地址
距离比较远(比如2048字节),而%的个数又有所限制的话,使用%f可以较快的到达

"目的地",
只需要256个%f就可以了,%E也是如此)

正常情况下,由于format串通常是程序员自己来定制,很少出现上面那种情况,

而且即使
出现了,也并不会有什么大的安全问题。然而,如果format串是由用户提供的话

,那么就
非常危险了!这种情况往往是由于程序员的疏忽导致的。最常见的情况是当需要

利用
vsprintf()等来构造自己的类printf()函数时,例如

mylog(LEVEL, "username = %s", username);

如果引用mylog时错误的使用了mylog(LEVEL,user_buf),而user_buf的内容又是用

户可以控
制的话,那么真正的危险就来了。

1. 问题一:格式化串导致的传统缓冲区溢出
==========================================
我们以不久前发现的QPOP 2.53的例子来做一下详细的说明。


QPOP 2.53中pop_uidl.c中有个函数pop_euidl (p),用来完成EUIDL命令的功能,

它错误的
使用了pop_msg()函数:

.......
pop_euidl (p)
POP     *   p;
{
    char                    buffer[MAXLINELEN];     /*  Read buffer */
    char            *nl, *bp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值