吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9738|回复: 3
收起左侧

[漏洞分析] CVE-2014-9707-漏洞形成分析

[复制链接]
Let_go 发表于 2017-4-12 17:47
本帖最后由 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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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 ; cd ..
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] 纯文本查看 复制代码
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0xb7fdebe0 in __kernel_vsyscall ()
gdb-peda$ c
Continuing.

    运行我们的Poc程序,可见GoAheadService触发了一个SIGABRT,
    触发SIGABRT的原因:
     1.Free没有初始化的地址或者错误的地址,
     2.堆越界,
     3.assert
722639_pmagtvdbd0z4ora.png
    bt 栈回溯,看调用流程,因为是堆溢出,所以栈上的数据是完整的gdb-peda$ bt
[Asm] 纯文本查看 复制代码
#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] 纯文本查看 复制代码
//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] 纯文本查看 复制代码
[/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] 纯文本查看 复制代码
[/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] 纯文本查看 复制代码
[/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] 纯文本查看 复制代码
gdb-peda$ x/100x 0xbffff104
0xbffff104:        0x00000000        0x00000000        0x00000000        0xbffff170

    此时path中的值为0x00000000,然后反编译websUrlParse函数gdb-peda$ Disas websUrlParse,查找ret指令的地址,并且在函数末尾处下一个断点
    b *0xb7fb614e   
[Asm] 纯文本查看 复制代码
 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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
static void parseFirstLine(Webs *wp)
{
    char    *op, *protoVer, *url, *host, *query, *path, *port, *ext, *buf; 
    int     testPort;
    省略一些代码............

    websNormalizeUriPath传入的参数是我们的畸形字符串,再来分析websNormalizeUriPath函数
    此函数中分配堆空间的地方有3处,strcpy函数使用有2处   
    第一处:逻辑比较简单,应该没什么问题   
[Asm] 纯文本查看 复制代码
    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] 纯文本查看 复制代码
//下面为分配堆空间,拷贝数据
    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] 纯文本查看 复制代码
gdb-peda$ b websNormalizeUriPath
Breakpoint 3 at 0xb7fb63e0: file src/http.c, line 3158

[Asm] 纯文本查看 复制代码
[------------------------------------------------------------------------------]
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
[------------------------------------------------------------------------------]
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] 纯文本查看 复制代码
gdb-peda$ print /x $eax
$1 = 0x42

    再调试运行到拷贝segments元素到dp内存的循环内,循环调试看最后到底拷贝了多少字节的数据到dp中,我们在这条指令len = (int) slen(segments)对应的汇编位置下一条处下断,看slen函数返回值b *0xb7fb6586  
[Asm] 纯文本查看 复制代码
   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] 纯文本查看 复制代码
0xb7fb65a8 <+456>:   call   0xb7fad370 <free@plt>                                //wfree()释放内存
[Asm] 纯文本查看 复制代码
gdb-peda$ b *0xb7fb65a8
Breakpoint 6 at 0xb7fb65a8: file src/http.c, line 3226

    关键断点设置完毕以后我们就开始循环执行,观察每次循环执行slen函数的返回值
[Asm] 纯文本查看 复制代码
[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xb7fd851c --> 0x303c4 
ECX: 0xf7fa4708 
EDX: 0x8

[Asm] 纯文本查看 复制代码
[----------------------------------registers-----------------------------------]
EAX: 0x7 
EBX: 0xb7fd851c --> 0x303c4 
ECX: 0xf7fa4709 
EDX: 0x0

[Asm] 纯文本查看 复制代码
[----------------------------------registers-----------------------------------]
EAX: 0x32 ('2')
EBX: 0xb7fd851c --> 0x303c4 
ECX: 0x3 
EDX: 0x5

[Asm] 纯文本查看 复制代码
[----------------------------------registers-----------------------------------]
EAX: 0x32 ('2')
EBX: 0xb7fd851c --> 0x303c4 
ECX: 0x3 
EDX: 0x5

[Asm] 纯文本查看 复制代码
[------------------------------------------------------------------------------]
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] 纯文本查看 复制代码
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGABRT
0xb7fdebe0 in __kernel_vsyscall ()
gdb-peda$

    单步前dupPath中的值
[Asm] 纯文本查看 复制代码
[/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] 纯文本查看 复制代码
//遍历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] 纯文本查看 复制代码
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;并且③号处的字符必须比④号处的字符长度要大许多,不然为④号字符串分配的缓冲区会填补上第二次拷贝③号字符串时所需的缓冲区。
         722639_sme89nt5skvw9zo.png
    对CVE-2014-9707漏洞成因的分析,记录了详细的操作过程,写的不好或理解不对的地方希望可以指出,谢谢!
    附件链接:http://pan.baidu.com/s/1pLRnLtp 密码:z2k8

免费评分

参与人数 3威望 +1 吾爱币 +12 热心值 +3 收起 理由
L4Nce + 1 + 10 + 1 谢谢精彩分析
汇成千古的守望 + 1 + 1 我很赞同!
Akesudia + 1 + 1 怎么一个评分都没有?

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

汇成千古的守望 发表于 2017-4-14 12:43
虽然一知半解 不过支持楼主的帖子 好评!
 楼主| Let_go 发表于 2017-4-14 20:21
江湖救急 发表于 2018-5-1 15:58
看了贴主let go的   CVE-2014-9707的过程分析报告,想完全跟着文章跑一边。但是在反汇编之后,下完对ret的地址断点,运行完后,x/100x *(ppath的值)时,显示cannot access memory at address
......是咋回事啊,,,echo "0" | sudo dd of=/proc/sys/kernel/randomize_va_space关闭了ASLR,但是 还是不行
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-26 01:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表