好友
阅读权限10
听众
最后登录1970-1-1
|
本帖最后由 Let_go 于 2017-4-12 19:31 编辑
CVE-2014-9707-漏洞形成分析
前言
实验主机: Linux 4.0.0-kali1-686-pae #1 SMP Debian 4.0.4-1+kali2 (2015-06-03) i686 GNU/Linux(关闭ASLR、NX)
工具: GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1(带peda插件)
漏洞程序版本: Version: 3.1.3-0
Embedthis Software GoAhead 3.0.0版本至3.4.1版本中存在安全漏洞,该漏洞源于程序没有正确处理以”.”字符开始的路径部分。远程攻击者可借助构造URI利用该漏洞实施目录遍历攻击,造成拒绝服务(基于堆的缓冲区溢出和崩溃),也可能执行任意代码。
漏洞分析
先把我们的漏洞程序GoAhead运行起来,然后打开另一个命令行终端,输入ps -ef查看当前GoAhead的PID,便于我们gdb附加
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 | root@Hacker-Deceive:/Overflow/goahead-3.1.3-0# make run[/ size ][/font][font=宋体][ size =16px]
goahead: 2: Configuration for Embedthis GoAhead
goahead: 2: ---------------------------------------------
goahead: 2: Version: 3.1.3-0
goahead: 2: BuildType: Debug
goahead: 2: CPU : x86
goahead: 2: OS: linux
goahead: 2: Host: 127.0.1.1
goahead: 2: Directory: /Overflow/goahead-3.1.3-0/src
goahead: 2: Documents: web
goahead: 2: Configure: bit -d -q -platform linux-x86-default -configure . -gen make
goahead: 2: ---------------------------------------------
goahead: 2: Started http://*:80
goahead: 2: Started https://*:443
|
可以看到GoAhead程序的PID为8742,然后我们使用Gdb附加GoAhead程序gdb attech 8742
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | root 8724 3875 0 17:01 pts/1 00:00:00 make run
root 8734 8724 0 17:01 pts/1 00:00:00 make --no-print-directory -f projects/g
root 8741 8734 0 17:01 pts/1 00:00:00 /bin/sh - c cd src; goahead -v
root 8742 8741 0 17:01 pts/1 00:00:00 goahead -v
root 8746 4688 0 17:02 pts/2 00:00:00 ps -ef
|
附加成功,程序断了下来,然后我们输入命令C 让程序继续运行.
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | [------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xb7fdebe0 in __kernel_vsyscall ()
gdb-peda$ c
Continuing.
|
运行我们的Poc程序,可见GoAheadService触发了一个SIGABRT,
触发SIGABRT的原因:
1.Free没有初始化的地址或者错误的地址,
2.堆越界,
3.assert
bt 栈回溯,看调用流程,因为是堆溢出,所以栈上的数据是完整的gdb-peda$ bt
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #0 0xb7fdebe0 in __kernel_vsyscall ()
#1 0xb7d73307 in __GI_raise (sig=sig@entry=0x6)
at ../nptl/sysdeps/unix/sysv/linux/raise. c :56
#2 0xb7d749c3 in __GI_abort () at abort. c :89
#3 0xb7db16f8 in __libc_message (do_abort=do_abort@entry=0x1,
fmt=fmt@entry=0xb7ea765c "*** Error in `%s': %s: 0x%s ***\n" )
at ../sysdeps/posix/libc_fatal. c :175
#4 0xb7db776a in malloc_printerr (action=<optimized out >,
str =0xb7ea77b4 "free(): corrupted unsorted chunks" , ptr =0x805b938) at malloc. c :4996
#5 0xb7db83bd in _int_free (av=0xb7eeb420 <main_arena>, p=<optimized out >, have_lock=0x0)
at malloc. c :3840
#6 0xb7dbb0c3 in __GI___libc_free (mem=<optimized out >) at malloc. c :2946
#7 0xb7fb65ad in websNormalizeUriPath (
pathArg=0x805b8a0 "/1234567/./89abcdef" , 'x' <repeats 42 times >, "/.gh" )
at src/http. c :3226
#8 0xb7fb8a74 in parseFirstLine (wp=<optimized out >) at src/http. c :954
#9 parseIncoming (wp=<optimized out >) at src/http. c :870
#10 websPump (wp=0x804f550) at src/http. c :824
#11 0xb7fb900d in readEvent (wp=0x804f550) at src/http. c :797
#12 socketEvent (sid=0x2, mask =0x2, wptr=0x804f550) at src/http. c :735
#13 0xb7fc6312 in socketDoEvent ( sp =0x804f4a0) at src/socket. c :649
#14 socketProcess () at src/socket. c :623
#15 0xb7fb3585 in websServiceEvents (finished=0x804ab44 <finished>) at src/http. c :1290
#16 0x08048c31 in main (argc=0x2, argv=0xbffff304, envp=0xbffff310) at src/goahead. c :146
#17 0xb7d5ea63 in __libc_start_main (main=0x8048a70 <main>, argc=0x2, argv=0xbffff304,
init=0x80491e0 <__libc_csu_init>, fini=0x8049250 <__libc_csu_fini>,
rtld_fini=0xb7fedc90 <_dl_fini>, stack_end=0xbffff2fc) at libc-start. c :287
#18 0x08048f67 in _start ()
gdb-peda$
|
src/目录下的是GoAhead程序源代码中的函数,可以看见程序在WebNormalizeUriPath函数中调用系统函数Free时发生了异常,并且调用WebNormalizeUriPath函数时传递的参数中有我们的畸形字符串函数
调用链是这样的:MAIN(goahead, int argc, char **argv, char **envp)---> websServiceEvents(&finished) ---> socketProcess() ---> socketDoEvent(WebsSocket *sp) ---> socketEvent(int sid, int mask, void *wptr) ---> readEvent(Webs *wp) ---> websPump(Webs *wp) ---> parseIncoming(Webs *wp) ---> parseFirstLine(Webs *wp) ---> websNormalizeUriPath(char *pathArg) ---> wfree(dupPath)[释放dupPath堆块的时候发生异常,说明dupPath指向的堆块被破坏]
因为这是开源软件所以我们可以对照着源代码追溯漏洞发生的原因.根据分析调用链发现#14处的函数调用并没有参数传递所以从这个函数往上的调用都可以直接排除,并且可以看出从readEvent函数开始连续传递wp参数,说明wp参数蛮可疑的,在源代码中看看Webs到底是什么类型.
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 | //src/goahead.h源文件代码
/**
GoAhead request structure. This is a per-socket connection structure.
@defgroup Webs Webs
*/
typedef struct Webs {
WebsBuf rxbuf; /**< Raw receive buffer */
WebsBuf input; /**< Receive buffer after de-chunking */
WebsBuf output; /**< Transmit buffer after chunking */
WebsBuf chunkbuf; /**< Pre-chunking data buffer */
WebsBuf *txbuf;
WebsTime since; /**< Parsed if -modified-since time */
WebsHash vars; /**< CGI standard variables */
WebsTime timestamp; /**< Last transaction with browser */
int timeout; /**< Timeout handle */
char ipaddr[64]; /**< Connecting ipaddress */
char ifaddr[64]; /**< Local interface ipaddress */
int rxChunkState; /**< Rx chunk encoding state */
ssize rxChunkSize; /**< Rx chunk size */
char *rxEndp; /**< Pointer to end of raw data in input beyond endp */
省略一些代码............
|
Webs是一个结构体,存放着我们的畸形Get数据包。我们看到WebNormalizeUriPath函数参数是我们的畸形字串,所以可以到它的上层函数parseFirstLine函数中看一下调用WebNormalizeUriPath函数时的具体流程
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | [/ size ][/font]
[font=宋体][ size =16px] //src/http. c 源文件代码
省略一些代码............
[/ size ][/font]
[font=宋体][ size =16px] /*
Parse the URL and store all the various URL components. websUrlParse returns an allocated buffer in buf which we
must free. We support both proxied and non-proxied requests. Proxied requests will have http://host/ at the
start of the URL. Non-proxied will just be local path names.
*/
host = path = port = query = ext = NULL;
if (websUrlParse(url, &buf, NULL, &host, &port, &path, &ext, NULL, &query) < 0) {
error ( "Cannot parse URL: %s" , url);
websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE | WEBS_NOLOG, "Bad URL" );
return;
}
if ((wp->path = websNormalizeUriPath(path)) == 0) {
error ( "Cannot normalize URL: %s" , url);
websError(wp, HTTP_CODE_BAD_REQUEST | WEBS_CLOSE | WEBS_NOLOG, "Bad URL" );
wfree(buf);
return;
}
省略一些代码............
|
在此函数中调用websNormalizeUriPath函数时给函数传递了一个变量path,此变量中存放的也就是我们的畸形字符串,那么变量path中的值又是如何来的?分析函数我们找到path是一个局部变量,被初始化为Null,然后调用websUrlParse函数把path的地址当做参数传递进去,调用完此函数后调用的就是websNormalizeUriPath函数,所以我们推测path中的畸形字符串应该是被websUrlParse函数写入进去的,为了准确我们可以动态调试一下
重启GoAhead,ps -ef查看PID,gdb attech PID附加程序,因为我们有源代码,所以在gdb中直接用websUrlParse函数名进行下断,若我们没有源代码的话可以用IDA反汇编,在函数地址处进行下断,这里直接用函数名下断点b websUrlParse
[Asm] 纯文本查看 复制代码 1 2 3 4 | [/ size ][/font]
[font=宋体][ size =16px]gdb-peda$ b websUrlParse[/ size ][/font]
[font=宋体][ size =16px]reakpoint 1 at 0xb7fb5f60: file src/http. c , line 3018.
gdb-peda$
|
断点设置成功,输入C命令让程序继续运行,运行POC给GoAheadWebServer发送畸形Get数据包,gdb马上就断了下来
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 7 | [/ size ][/font][font=宋体][ size =16px][------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, websUrlParse (url=0x805d89c "/1234567/./89abcdef" , 'x' <repeats 42 times >, "/.gh" ,
pbuf=0xbffff110, pprotocol=0x0, phost=0xbffff0fc, pport=0xbffff108, ppath=0xbffff104,
pext=0xbffff10c, preference=0x0, pquery=0xbffff100) at src/http. c :3018
3018 {
gdb-peda$
|
此时websUrlParse函数的参数如上图所示,path变量对应着ppath,因为此函数传入的是path变量的地址是一个指针,所以我们看到的是ppath,我们看一下此时path变量中的值是什么,
[Asm] 纯文本查看 复制代码 1 2 | gdb-peda$ x/100x 0xbffff104
0xbffff104: 0x00000000 0x00000000 0x00000000 0xbffff170
|
此时path中的值为0x00000000,然后反编译websUrlParse函数gdb-peda$ Disas websUrlParse,查找ret指令的地址,并且在函数末尾处下一个断点
b *0xb7fb614e
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 7 8 | 0xb7fb6408 <+40>: pop ebx
0xb7fb6409 <+41>: pop esi
0xb7fb640a <+42>: pop edi
0xb7fb640b <+43>: pop ebp
0xb7fb640c <+44>: ret
0xb7fb640d <+45>: lea esi ,[ esi +0x0]
gdb-peda$ b *0xb7fb614e
Breakpoint 2 at 0xb7fb614e: file src/http. c , line 3150.
|
然后执行C命令直接运行到函数末尾处
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | Breakpoint 2, 0xb7fb614e in websUrlParse (
url=0x805d89c "/1234567/./89abcdef" , 'x' <repeats 42 times >, "/.gh" , pbuf=0xbffff110,
pprotocol=0x0, phost=0xbffff0fc, pport=0xbffff108, ppath=0xbffff104, pext=0xbffff10c,
preference=0x0, pquery=0xbffff100) at src/http. c :3150
3150 }
|
此时我们再看看path中的值,可以看到path确实是被websUrlParse函数设置为了指向我们的畸形字符串.gdb-peda$ x/100x *0xbffff104
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | 0x805b8a0: 0x3332312f 0x37363534 0x382f2e2f 0x63626139
0x805b8b0: 0x78666564 0x78787878 0x78787878 0x78787878
0x805b8c0: 0x78787878 0x78787878 0x78787878 0x78787878
0x805b8d0: 0x78787878 0x78787878 0x78787878 0x672e2f78
0x805b8e0: 0x00000068 0x00000000 0x00000000 0x00000000
|
注意:这里为什么需要解引用?答:因为0xbffff104中存放的是path的值,而path本身就是一个指针,我们需要取这个指针中的内容所以需要解引用.//src/http.c源文件代码
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | static void parseFirstLine(Webs *wp)
{
char *op, *protoVer, *url, *host, *query, *path, *port, *ext, *buf;
int testPort;
省略一些代码............
|
websNormalizeUriPath传入的参数是我们的畸形字符串,再来分析websNormalizeUriPath函数
此函数中分配堆空间的地方有3处,strcpy函数使用有2处
第一处:逻辑比较简单,应该没什么问题
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 7 8 | len = ( int ) slen(pathArg); //计算传入的payload长度,赋值给len
if ((dupPath = walloc(len + 2)) == 0) { //dupPath指向 申请len+2大小的堆空间
return NULL;
}
strcpy(dupPath, pathArg); //把传入的payload拷贝到新开辟的堆空间中
//申请len+1大小的堆空间
if ((segments = walloc( sizeof (char*) * (len + 1))) == 0) {
return NULL;
|
第二处:这一块的逻辑比第一处的逻辑稍微复杂一点,问题可能会出在这里
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | //下面为分配堆空间,拷贝数据
nseg = j; //nseg == j
assert(nseg >= 0); //断言nseg大于等于零
//分配了(len + nseg + 1)大小的堆空间,path指向
if ((path = walloc(len + nseg + 1)) != 0) {
//dp也指向新申请的空间,循环次数nseg
for (i = 0, dp = path; i < nseg; ) {
//这里有一个拷贝函数,循环运行记录每次拷贝的大小,总和是否会大于(len + nseg + 1)
strcpy(dp, segments);
len = ( int ) slen(segments);
dp += len;
if (++i < nseg || (nseg == 1 && *segments[0] == '\0' && firstc == '/' )) {
*dp++ = '/' ;
}
}
*dp = '\0' ;
}
wfree(dupPath); //释放dupPath(再次发生异常)
wfree(segments);
|
先调试获取分配给dp的内存到底有多大(len + nseg + 1),在websNormalizeUriPath处下断点,C继续运行,断点断在websNormalizeUriPath函数处.
[Asm] 纯文本查看 复制代码 1 2 | gdb-peda$ b websNormalizeUriPath
Breakpoint 3 at 0xb7fb63e0: file src/http. c , line 3158
|
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 | [------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 3, websNormalizeUriPath (
pathArg=0x805b8a0 "/1234567/./89abcdef" , 'x' <repeats 42 times >, "/.gh" ) at src/http. c :3158
3158 {
gdb-peda$
|
gdb-peda$ disas websNormalizeUriPath //反汇编websNormalizeUriPath函数
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 0xb7fb6524 <+324>: jg 0xb7fb6500 <websNormalizeUriPath+288>
0xb7fb6526 <+326>: lea esi ,[ esi +0x0]
0xb7fb6529 <+329>: lea edi ,[ edi +eiz*1+0x0]
0xb7fb6530 <+336>: sub esp ,0xc
0xb7fb6533 <+339>: mov eax , DWORD PTR [ esp +0x18]
0xb7fb6537 <+343>: lea eax ,[ esi + eax *1+0x1] // eax = (len+nseg+1)
0xb7fb653b <+347>: push eax
0xb7fb653c <+348>: call 0xb7fad990 <malloc@ plt > //walloc()
0xb7fb6541 <+353>: mov DWORD PTR [ esp +0x28], eax //path = walloc()函数的返回值
0xb7fb6545 <+357>: add esp ,0x10
0xb7fb6548 <+360>: test eax , eax //检测堆空间是否申请超过那个
0xb7fb654a <+362>: je 0xb7fb65a1 <websNormalizeUriPath+449> //若返回值等于0则跳转
0xb7fb654c <+364>: test esi , esi
0xb7fb654e <+366>: je 0xb7fb6642 <websNormalizeUriPath+610>
0xb7fb6554 <+372>: mov DWORD PTR [ esp +0x10], esi
0xb7fb6558 <+376>: xor edi , edi
0xb7fb655a <+378>: mov esi , ebp
0xb7fb655c <+380>: mov ebp , eax
0xb7fb655e <+382>: jmp 0xb7fb6566 <websNormalizeUriPath+390>
0xb7fb6560 <+384>: lea ebp ,[ eax +0x1]
0xb7fb6563 <+387>: mov BYTE PTR [ eax ],0x2f
0xb7fb6566 <+390>: mov ecx , DWORD PTR [ esi + edi *4]
0xb7fb6569 <+393>: sub esp ,0x8
0xb7fb656c <+396>: add edi ,0x1
0xb7fb656f <+399>: push ecx
0xb7fb6570 <+400>: mov DWORD PTR [ esp +0x18], ecx
0xb7fb6574 <+404>: push ebp
0xb7fb6575 <+405>: call 0xb7fad8a0 <strcpy@ plt > //strcpy(dp, segments);
0xb7fb657a <+410>: mov ecx , DWORD PTR [ esp +0x1c]
0xb7fb657e <+414>: mov DWORD PTR [ esp ], ecx
0xb7fb6581 <+417>: call 0xb7fad690 <slen@ plt > //len = ( int ) slen(segments);
0xb7fb6586 <+422>: add esp ,0x10
0xb7fb6589 <+425>: add eax , ebp
0xb7fb658b <+427>: cmp edi , DWORD PTR [ esp +0x10]
0xb7fb658f <+431>: jl 0xb7fb6560 <websNormalizeUriPath+384>
0xb7fb6591 <+433>: cmp DWORD PTR [ esp +0x10],0x1
0xb7fb6596 <+438>: je 0xb7fb664b <websNormalizeUriPath+619>
0xb7fb659c <+444>: mov ebp , esi
0xb7fb659e <+446>: mov BYTE PTR [ eax ],0x0
0xb7fb65a1 <+449>: sub esp ,0xc
0xb7fb65a4 <+452>: push DWORD PTR [ esp +0x20]
0xb7fb65a8 <+456>: call 0xb7fad370 <free@ plt > //wfree(dupPath);
0xb7fb65ad <+461>: mov DWORD PTR [ esp ], ebp
0xb7fb65b0 <+464>: call 0xb7fad370 <free@ plt > //wfree(segments);
|
根据查找几个关键函数可以定位到0xb7fb653c处对应的大概就是walloc(len + nseg + 1)),传入参数存放在eax处,在0xb7fb653c处下断,C命令继续执行,中断于0xb7fb653c
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 | [------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 4, 0xb7fb653c in websNormalizeUriPath (
pathArg=0x805b8a0 "/1234567/./89abcdef" , 'x' <repeats 42 times >, "/.gh" ) at src/http. c :3215
3215 if ((path = walloc(len + nseg + 1)) != 0) {
gdb-peda$
|
查看当前eax的值eax == 0x42,也就是说程序分配了0x42(66)个字节大小的空间,并让path指向其首地址
[Asm] 纯文本查看 复制代码 1 2 | gdb-peda$ print /x $ eax
$1 = 0x42
|
再调试运行到拷贝segments元素到dp内存的循环内,循环调试看最后到底拷贝了多少字节的数据到dp中,我们在这条指令len = (int) slen(segments)对应的汇编位置下一条处下断,看slen函数返回值b *0xb7fb6586
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 7 | gdb-peda$ b *0xb7fb6586
Breakpoint 5 at 0xb7fb6586: file src/http. c , line 3220.
0xb7fb6575 <+405>: call 0xb7fad8a0 <strcpy@ plt > //strcpy(dp, segments);
0xb7fb657a <+410>: mov ecx , DWORD PTR [ esp +0x1c]
0xb7fb657e <+414>: mov DWORD PTR [ esp ], ecx
0xb7fb6581 <+417>: call 0xb7fad690 <slen@ plt > //len = ( int ) slen(segments);
0xb7fb6586 <+422>: add esp ,0x10
|
我们还应该在这个for循环结束的时候下一个断点,这样我们循环结束以后程序就不至于跑飞,我们在wfree(dupPath)对应的汇编位置下断b *0xb7fb65a8
[Asm] 纯文本查看 复制代码 1 | 0xb7fb65a8 <+456>: call 0xb7fad370 <free@ plt > //wfree()释放内存
|
[Asm] 纯文本查看 复制代码 1 2 | gdb-peda$ b *0xb7fb65a8
Breakpoint 6 at 0xb7fb65a8: file src/http. c , line 3226
|
关键断点设置完毕以后我们就开始循环执行,观察每次循环执行slen函数的返回值
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | [----------------------------------registers-----------------------------------]
EAX : 0x0
EBX : 0xb7fd851c --> 0x303c4
ECX : 0xf7fa4708
EDX : 0x8
|
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | [----------------------------------registers-----------------------------------]
EAX : 0x7
EBX : 0xb7fd851c --> 0x303c4
ECX : 0xf7fa4709
EDX : 0x0
|
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | [----------------------------------registers-----------------------------------]
EAX : 0x32 ( '2' )
EBX : 0xb7fd851c --> 0x303c4
ECX : 0x3
EDX : 0x5
|
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | [----------------------------------registers-----------------------------------]
EAX : 0x32 ( '2' )
EBX : 0xb7fd851c --> 0x303c4
ECX : 0x3
EDX : 0x5
|
[Asm] 纯文本查看 复制代码 1 2 3 4 5 6 | [------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 6, 0xb7fb65a8 in websNormalizeUriPath (
pathArg=0x805b8a0 "/1234567/./89abcdef" , 'x' <repeats 42 times >, "/.gh" ) at src/http. c :3226
3226 wfree(dupPath);
gdb-peda$
|
循环结束,程序命中了我们设置在释放函数处的断点,然后我们来计算一下拷贝的总长度,看是否大于dp指向的堆空间的大小(0x42(66))
拷贝总数:0x0 + 0x7 + 0x32 + 0x32 = 0x6B因为0x6B>0x42
所以此次的拷贝存在堆溢出,我们单步运行程序,程序在释放dupPath指向的堆空间的时候出发了异常,
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | [------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGABRT
0xb7fdebe0 in __kernel_vsyscall ()
gdb-peda$
|
单步前dupPath中的值
[Asm] 纯文本查看 复制代码 1 2 3 4 5 | [/ size ][/font]
[font=宋体][ size =3]gdb-peda$ x/10x dupPath[/ size ][/font]
[font=宋体][ size =16px]0x805b938: 0x33323100 0x37363534 0x38002e00 0x63626139
0x805b948: 0x78666564 0x78787878 0x78787878 0x78787878
0x805b958: 0x78787878 0x78787878
|
异常触发原因应该就是之前的堆溢出覆盖了dupPath指向的堆空间,导致堆空间被破坏,Free堆的时候触发unlink操作,会把当前要释放的堆块进行脱链,脱链的过程取决于堆块的前向指针与后向指针,但是因为前向指针与后向指针被覆盖为了无效指针,所以发生异常.
找到了溢出现场,那是什么原因造成的拷贝数据过多呢?
分析一下程序解析url的代码
len + nseg + 1
len //畸形字符串的有效字符总数
nseg //segments数组的元素个数,此个数用来后期填充为'/'的个数
1 //用于填充结尾'\0'字符
因为程序员在写程序时考虑不全,没把.x这类字符串考虑进去,所以若畸形字符串中包含.x的时候程序不对其进行任何处理直接i++,j++,这样segments数组中所对应的指针也就没有进行处理,
第一次for循环去除'/',没毛病
segments[0] ==
segments[1] == 1234567
segments[2] == .
segments[3] == 89abcdef
segments[4] == .gh
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | //遍历dupPath指向的字符串的每一个字符,以 '/' 为分隔符并且把每个 '/' 替换为 '\0' 便于把分割出来的每段字符串首地址放到segments数组中
for (mark = sp = dupPath; * sp ; sp ++) { //初始化变量 mark, sp ,dupPath全指向畸形字符串,循环次数为畸形字符串的字符个数
if (* sp == '/' ) { //当遇到’/’则进入
* sp = '\0' ; //把当前 sp 指向的’/’赋值为’\0’
while ( sp [1] == '/' ) { //若 sp 指向的元素的下一个元素也等于’/’则进入
sp ++; // sp ++ 指向下一个元素,直到 sp 指向的元素的下一个元素不等于’/’时退出循环
}
segments[nseg++] = mark; //把mark赋值给segments数组的第nseg个元素,然后nseg++,mark指向畸形字符串首地址
len += ( int ) ( sp - mark); //计算 sp - mark的差,len最后等于所有有效字符的个数,
mark = sp + 1; //mark指向 sp + 1 ,加1是因为当前 sp =’\0’所以需要加1指向下一个有效字符
}
}
//因为 sp 要领先几步,所以循环完毕以后还需在做一次与循环同样的处理
segments[nseg++] = mark; //segmaents的第nseg个元素等于mark,nseg++,末尾添加一个 '\0' 用以结尾
len += ( int ) ( sp - mark); //len += 计算 sp - mark的差,
//这轮循环过后,len == 畸形字符串去除所有 '/' 的字符总数.
|
第二次for循环去除'.'
[======]segments[0] ==
[======]segments[1] == 1234567
[======]segments[2] == 89abcdef
[======]segments[3] == 89abcdef
[Asm] 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | for (j = i = 0; i < nseg; i++, j++) { //j,i=0,循环次数nseg-1
sp = segments; //把segments的第i个元素赋值给 sp , sp 分别指向每一个被分割出来的字符串
if ( sp [0] == '.' ) { //如果 sp [0]等于’.’,则进入
if ( sp [1] == '\0' ) { //如果 sp [1]等于’\0’,则进入 检测当前 sp 指向的字符是否为’.’
if ((i+1) == nseg) { //判断当前i的值是否已经到了最大值,也就是segments数组的最后一个元素
segments[j] = "" ; //如果已经到达最后一个元素,则把segments的第j个元素赋值为空
} else { //否则j--,与 for 循环中的条件表达式的j++相互抵消,
j--; //如果 sp 指向’.’但是又没有又没有到达数组的最后一个元素,那么j不变
}
} else if ( sp [1] == '.' && sp [2] == '\0' ) {//检测当前 sp 指向的是否为 ".."
if (i == 1 && *segments[0] == '\0' ) { //若i==1并且segments[0] == ’\0’
j = 0; //j赋值为0
} else if ((i+1) == nseg) { //判断当前i的值是否已经到了最大值,也就是segments数组的最后一个元素
if (--j >= 0) { //若j自减之后还 >= 0,则说明j小都为1
segments[j] = "" ; //segments的第j个元素赋值为空
}
} else {
j = max(j - 2, -1); //比较j-2与-1,把最大的值赋值给j
}
}
} else {
segments[j] = segments; //把第i个元素直接赋值给第j个元素
}
}
|
循环时第[0]个字符串指针和第[1]个字符串指针没什么问题都没包含'.'直接赋值,
处理第[2]个字符串指针时因为字符串指针指向的是'.'所以跳过本次的赋值,
处理第[3]个字符指针时,因为不包含'.'所以赋值到数组第[2]个位置上,处
理第[4]个字符串指针时因为指向的是'.gh',程序没有对其进行处理,所以值还是原来的值,这样就导致多出了一个字符串指针,[2][3]指向的是同一段字符串,拷贝的时候拷贝了两次,这样的话就会导致最开始申请的缓冲区存放不了多出的字符
构造出类似这样的畸形字符串/1234567/./89abcdef/.gh;并且③号处的字符必须比④号处的字符长度要大许多,不然为④号字符串分配的缓冲区会填补上第二次拷贝③号字符串时所需的缓冲区。
对CVE-2014-9707漏洞成因的分析,记录了详细的操作过程,写的不好或理解不对的地方希望可以指出 ,谢谢!
附件链接:http://pan.baidu.com/s/1pLRnLtp 密码:z2k8
|
免费评分
-
查看全部评分
|