图灵社区按

TEAP是什么?TEAP是Turingbook Early Access Program的简称,即早期试读,它公布的是图灵在途新书未经编辑的内容。一本书的翻译周期约为3到6个月,如果在翻译过程中,译者就能与读者进行沟通和交流,对整本书的翻译品质是有帮助的。通过TEAP,读者可以提前阅读将来才能出版的内容,译者也能收获宝贵的反馈意见,改进翻译,提高质量。

本书原名为《A Bug Hunter's Diary》,中文暂定名为《捉虫日记》。
本篇选自第2章。
任何意见、建议,都非常感谢能联系我,我的微博@loveisbug

回到90年代

2008年10月12日,星期日
亲爱的日记,

今天我看了一下VideoLAN广受欢迎的VLC媒体播放器源代码。我喜欢VLC,它可以运行在所有我喜欢的操作系统平台上,支持多种媒体文件格式。但是支持所有这些不同格式的媒体文件也有副作用,VLC做了大量的文件解析工作,而这往往意味着有很多尚未发现的bug。

注意按照Dick Grune和Ceriel J.H. Jacobs的著作Parsing Techniques: A Practical Guide所说,“解析是按照给定的语法结构化一个线性表示的过程”。解析器是一个把字节组成的原始字符串分解成一个个单独词、句的软件。取决于数据的格式,解析会是一个非常复杂并且容易出错的任务。

熟悉了VLC的内部工作机制之后,我只用半天时间就找到了第一个漏洞。那是一个经典的栈缓冲区溢出(stack buffer overflow)(见附录A.1)。VLC在解析一种名为TiVo的媒体文件格式(TiVo数字录制设备专用的文件格式)时,会出现这个漏洞。找出这个bug之前,我从没听说过这种文件格式,但这并不妨碍我利用这个漏洞。

1. 发现漏洞

我是这样发现这个漏洞的:

  在Microsoft Windows Vista SP1(32位)平台上运行VLC 0.9.4完成下列步骤。

  • 第一步:生成VLC中解复用器(Demuxer译注1)的清单。
  • 第二步:识别输入数据。
  • 第三步:跟踪输入数据。

下面的章节将详细解释这个过程。

第一步:生成VLC中解复用器的清单

下载并解压缩VLC的源代码之后,我先生成一个VLC媒体播放器可用的解复用器清单。

注意数字视频中,解复用是指为播放媒体文件,从视频码流或容器中分离音频、视频和其他数据的过程。解复用器是从码流或容器中提取这些组成部分的软件。

生成解复用器的清单并不难,因为VLC已经把大部分解复用器分成不同的C程序文件存放在目录vlc-0.9.4\modules\demux\下了(见图1)。

enter image description here 图1:VLC解复用器清单

第二步:识别输入数据

接下来,我尝试标识出解复用器处理的输入数据。读了一些C代码后,我偶然发现下面这个数据结构,它声明在每个解复用器代码文件包含的一个头文件中。

源代码文件 vlc-0.9.4\include\vlc_demux.h

[..]
41 struct demux_t
42 {
43        VLC_COMMON_MEMBERS
44
45        /* Module properties */
46        module_t *p_module;
47
48        /* eg informative but needed (we can have access+demux) */
49        char *psz_access;
50        char *psz_demux;
51        char *psz_path;
52
53        /* input stream */
54        stream_t *s; /* NULL in case of a access+demux in one */
[..]

第54行声明结构体成员s,描述输入码流 (input stream)。这正是我要找的:解复用器所处理的输入数据的引用。

第三步:跟踪输入数据

找到demux_t数据结构和它表示输入码流的成员后,我在所有解复用器文件中搜索它的引用。输入数据一般通过p_demux->s来引用,如下面代码中第1623行和1641行所示。查找编程错误时,每发现一处这样的引用,我都会追踪输入数据。利用这个方法,我找到了下面这个漏洞。

源代码文件 vlc-0.9.4\modules\demux\Ty.c
函数 parse_master()

[..]
1623 static void parse_master(demux_t *p_demux)
1624 {
1625     demux_sys_t *p_sys = p_demux->p_sys;
1626     uint8_t mst_buf[32];
1627     int i, i_map_size;
1628     int64_t i_save_pos = stream_Tell(p_demux->s);
1629     int64_t i_pts_secs;
1630
1631     /* Note that the entries in the SEQ table in the stream may have
1632        different sizes depending on the bits per entry. We store them
1633        all in the same size structure, so we have to parse them out one
1634        by one. If we had a dynamic structure, we could simply read the
1635        entire table directly from the stream into memory in place. */
1636
1637     /* clear the SEQ table */
1638     free(p_sys->seq_table);
1639
1640     /* parse header info */
1641     stream_Read(p_demux->s, mst_buf, 32);
1642     i_map_size = U32_AT(&mst_buf[20]); /* size of bitmask, in bytes */
1643     p_sys->i_bits_per_seq_entry = i_map_size * 8;
1644     i = U32_AT(&mst_buf[28]); /* size of SEQ table, in bytes */
1645     p_sys->i_seq_table_size = i / (8 + i_map_size);
1646
1647     /* parse all the entries */
1648     p_sys->seq_table = malloc(p_sys->i_seq_table_size * sizeof(ty_seq_table_t));
1649     for (i=0; i<p_sys->i_seq_table_size; i++) {
1650         stream_Read(p_demux->s, mst_buf, 8 + i_map_size);
[..]

第1641行,函数stream_Read()读取一个TiVo媒体文件的32字节用户控制数据(通过p_demux->s引用),保存到栈缓冲区mst_buf中,mst_buf在第1626行声明。第1642行,宏U32_AT从mst_buf中取出用户控制值,保存到有符号整型变量i_map_size中。第1650行,函数stream_Read()再次把媒体文件中的用户控制数据保存到栈缓冲区mst_buf中。但是这一次,stream_Read()使用用户控制值i_map_size来计算拷贝到mst_buf中数据的大小。这将导致一个容易被利用的典型的栈缓冲区溢出(见附录A.1)。

下面是对这个bug的剖析,如图2所示:

  1. TiVo媒体文件的32字节用户控制数据拷贝到栈缓冲区mst_buf中。目标缓冲区的大小是32字节。
  2. 从缓冲区中取出4字节的用户控制值保存到i_map_size中。
  3. 再次拷贝TiVo媒体文件中的用户控制数据到mst_buf中。这一次,拷贝的数据大小由变量i_map_size计算得出。如果i_map_size的值超过24字节,就会发生栈缓冲区溢出(见附录A.1)。

enter image description here 图2:该漏洞从输入数据到栈缓冲区溢出的概览

2. 漏洞利用

为了利用这个漏洞,我会执行下面的步骤:

  • 第一步:找一个TiVo格式的样例电影文件。
  • 第二步:找一条代码路径执行到漏洞代码。
  • 第三步:修改这个TiVo电影文件以使VLC崩溃。
  • 第四步:修改这个TiVo电影文件以控制EIP。

要利用一个文件格式的bug,方法不止一个。你可以重新做一个格式正确的文件,也可以修改一个已存在的正确文件。这个例子中我采用后一种方法。

第一步:找一个TiVo格式的样例电影文件

首先,我从http://samples.mplayerhq.hu/下载了下面这个TiVo样例文件:

  网站http://samples.mplayerhq.hu/是个不错的站点,从这里可以找到各种格式多媒体样例文件。

$ wget http://samples.mplayerhq.hu/TiVo/test-dtivo-junkskip.ty%2b
--2008-10-12 21:12:25-- http://samples.mplayerhq.hu/TiVo/test-dtivo-junkskip.ty%2b
Resolving samples.mplayerhq.hu... 213.144.138.186
Connecting to samples.mplayerhq.hu|213.144.138.186|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 5242880 (5.0M) [text/plain]
Saving to: `test-dtivo-junkskip.ty+´

100%[=========================>] 5,242,880 240K/s in 22s

2008-10-12 21:12:48 (232 KB/s) - `test-dtivo-junkskip.ty+´ saved [5242880/5242880]

第二步:找一条代码路径执行到漏洞代码

找不到TiVo文件格式的规范文档,所以我直接读源代码来寻找能到达函数parse_master()中漏洞代码的执行路径。

VLC加载一个TiVo文件时,将执行以下流程(引用的所有源代码都来自VLC的vlc-0.9.4\modules\demux\Ty.c)。相关函数中第一个被调用的是Demux():

[..]
386 static int Demux( demux_t *p_demux )
387 {
388         demux_sys_t *p_sys = p_demux->p_sys;
389         ty_rec_hdr_t *p_rec;
390         block_t *p_block_in = NULL;
391
392         /*msg_Dbg(p_demux, "ty demux processing" );*/
393
394         /* did we hit EOF earlier? */
395         if( p_sys->eof )
396             return 0;
397
398         /*
399         * what we do (1 record now.. maybe more later):
400         * - use stream_Read() to read the chunk header & record headers
401         * - discard entire chunk if it is a PART header chunk
402         * - parse all the headers into record header array
403         * - keep a pointer of which record we're on
404         * - use stream_Block() to fetch each record
405         * - parse out PTS from PES headers
406         * - set PTS for data packets
407         * - pass the data on to the proper codec via es_out_Send()
408
409         * if this is the first time or
410         * if we're at the end of this chunk, start a new one
411         */
412         /* parse the next chunk's record headers */
413         if( p_sys->b_first_chunk || p_sys->i_cur_rec >= p_sys->i_num_recs )
414         {
415             if( get_chunk_header(p_demux) == 0 )
[..]

在第395行和413行的一些健全测试(sanity check译注2)之后,第415行调用了函数get_chunk_header()。

[..]
112 #define TIVO_PES_FILEID ( 0xf5467abd )
[..]
1839 static int get_chunk_header(demux_t *p_demux)
1840 {
1841     int i_readSize, i_num_recs;
1842     uint8_t *p_hdr_buf;
1843     const uint8_t *p_peek;
1844     demux_sys_t *p_sys = p_demux->p_sys;
1845     int i_payload_size; /* sum of all records' sizes */
1846
1847     msg_Dbg(p_demux, "parsing ty chunk #%d", p_sys->i_cur_chunk );
1848
1849     /* if we have left-over filler space from the last chunk, get that */
1850     if (p_sys->i_stuff_cnt > 0) {
1851         stream_Read( p_demux->s, NULL, p_sys->i_stuff_cnt);
1852         p_sys->i_stuff_cnt = 0;
1853     }
1854
1855     /* read the TY packet header */
1856     i_readSize = stream_Peek( p_demux->s, &p_peek, 4 );
1857     p_sys->i_cur_chunk++;
1858
1859     if ( (i_readSize < 4) || ( U32_AT(&p_peek[ 0 ] ) == 0 ))
1860     {
1861         /* EOF */
1862         p_sys->eof = 1;
1863         return 0;
1864     }
1865
1866     /* check if it's a PART Header */
1867     if( U32_AT( &p_peek[ 0 ] ) == TIVO_PES_FILEID )
1868     {
1869         /* parse master chunk */
1870         parse_master(p_demux);
1871         return get_chunk_header(p_demux);
1872     }
[..]

第1856行,在函数get_chunk_header()中,TiVo文件中的用户控制数据赋给了指针p_peek。之后,第1867行,程序检查p_peek指向的文件数据是否等于TIVO_PES_FILEID(在第112行被定义为0xf5467abd),相等就调用存在漏洞的函数parse_master()(第1870行)。

要想通过这条代码执行路径到达存在漏洞的函数,这个TiVo样例文件需要包含TIVO_PES_FILEID的值。我在整个文件中搜索这个值的模式,在文件的0x00300000偏移处找到了(见图3)。

enter image description here 图3:TiVo样例文件中的TIVO_PES_FILEID值模式

从parse_master()函数可以知道(见下面的源代码片段),i_map_size的值应在文件中TIVO_PES_FILEID值模式位置(文件偏移0x00300000处)再偏移20(0x14)的地方。

[..]
1641 stream_Read(p_demux->s, mst_buf, 32);
1642 i_map_size = U32_AT(&mst_buf[20]); /* size of bitmask, in bytes */
[..]

这时,我发现下载的这个TiVo样例文件已经能够触发存在漏洞的函数parse_master()了,因此不必进行任何调整。太棒了!

第三步:修改这个TiVo电影文件以使VLC崩溃

  从http://download.videolan.org/pub/videolan/vlc/0.9.4/win32/得到VLC的Windows版本漏洞程序。

接下来,我尝试修改这个TiVo样例文件以使VLC崩溃。为此,我只需要修改这个样例文件中i_map_size所在偏移位置的4字节值(在这里是偏移0x00300014处)。

如图4所示,我把文件偏移0x00300014处的32位值从0x00000002改成0x000000ff。新的值255(0xff)字节足够使32字节大小的栈缓冲区溢出,并且覆盖栈空间中保存在这块缓冲区之后的返回地址(见附录A.1)。然后,用Immunity Debugger调试时我用VLC打开这个改动过的样例文件。电影文件像之前一样开始播放,几秒钟之后——一旦执行到改动过的数据——VLC播放器就崩溃了,结果如图5所示。

enter image description here
图4:TiVo样例文件中i_map_size的新值

enter image description here
图5:Immunity Debugger中的VLC非法访问

不出所料,VLC在解析这个格式错误的TiVo文件时崩溃了。而这个崩溃将是大有可为的,因为指令指针寄存器(EIP register)指向了一个无效的内存位置(调试器状态栏提示信息Access violation when executing [20030000]可以说明)。这意味着我可以轻易控制指令指针。

第四步:修改这个TiVo电影文件以控制EIP

下一步我需要确定样例文件的哪些字节改写了当前栈帧(stack frame译注3)中的返回地址,这样我就能控制EIP了。调试器显示,程序崩溃时EIP的值为0x20030000。为确定这个值所在位置的偏移,我可以尝试计算准确的文件偏移,也可以直接在整个文件中搜索这个值的字节模式。我选择后面这个方法,从文件偏移0x00300000处开始搜索。结果在文件偏移0x0030005c处找到了想要的字节序列,用的是小端表示法(little-endian),我把这四个字节的值改成0x41414141(如图6所示)。

enter image description here

图6:TiVo样例文件中EIP的新值

然后我在调试器中重新运行VLC,打开这个新文件(如图7)。

enter image description here

图7:VLC媒体播放器的EIP控制

EIP = 41414141……EIP控制的任务完成!我可以构建一个有效的漏洞利用程序,使用David Litchfield在“Linux和Windows下漏洞利用技术的差异(Variations in Exploit Methods Between Linux and Windows)”一文中提到的著名的jmp reg技术,来实现任意代码执行(arbitrary code execution)攻击。

因为德国有严格的法律禁止这样做,所以我不会给你这个完整的、可工作的漏洞利用程序,有兴趣的话,你可以看看我录制的一个演示实际漏洞利用程序的视频片段。

3. 漏洞修正

2008年10月18日,星期六

既然我发现了这个安全漏洞,可以有几个方式公开它。我可以联系软件的开发者,“负责任”地告诉他我的发现,并帮助他打好补丁。这个过程称为负责任的漏洞披露(responsible disclosure)。由于这个词暗示其他公开方式是不负责任的,而实际并非如此,所以这个词逐渐地被替换成协作的漏洞披露(coordinated disclosure)。

另一方面,我也可以把自己的发现出售给漏洞经纪人(vulnerability broker),由他来告诉软件的开发者。现今,商业漏洞市场上两个主要的机构是Verisign的iDefense Labs提供的“VCP计划”(Vulnerability Contribution Program译注4)和Tipping Point公司的“零时差计划”(Zero Day Initiative译注5)。VCP和ZDI都遵循协作的漏洞披露实践,与受到影响的开发商合作。

另一个选择是完全披露(full disclosure)。如果选择完全披露,我将会向公众发布漏洞信息而不是报告给开发商。还有其他可选的公布方式,但它们背后的动机通常不包括修复这个bug(例如在地下市场出售)。

就本章介绍的VLC漏洞来说,我选择协作披露。也就是说,我通知了VLC的维护者,提供给他们必要的信息,与他们协作选择公开披露的时机。

我将bug通知了VLC的维护者之后,他们开发了如下补丁来处理这个漏洞:

--- a/modules/demux/ty.c
+++ b/modules/demux/ty.c
@@ -1639,12 +1639,14 @@ static void parse_master(demux_t *p_demux)
/* parse all the entries */
    p_sys->seq_table = malloc(p_sys->i_seq_table_size * sizeof(ty_seq_table_t));
    for (i=0; i<p_sys->i_seq_table_size; i++) {
-         stream_Read(p_demux->s, mst_buf, 8 + i_map_size);
+         stream_Read(p_demux->s, mst_buf, 8);
        p_sys->seq_table[i].l_timestamp = U64_AT(&mst_buf[0]);
        if (i_map_size > 8) {
            msg_Err(p_demux, "Unsupported SEQ bitmap size in master chunk");
+         stream_Read(p_demux->s, NULL, i_map_size);
            memset(p_sys->seq_table[i].chunk_bitmask, i_map_size, 0);
        } else {
+         stream_Read(p_demux->s, mst_buf + 8, i_map_size);
            memcpy(p_sys->seq_table[i].chunk_bitmask, &mst_buf[8], i_map_size);
        }
    }

所做的改动简单易懂。之前调用函数stream_Read()的漏洞现在改成了使用固定大小值,并且仅当用户控制的i_map_size值小于等于8时,才作为stream_Read()使用的大小值。一个明显bug的简单修正方法。

但是请等一等,这个漏洞真的解除了吗?变量i_map_size的类型仍然是有符号整型,如果一个大于等于0x80000000的值赋给i_map_size,它会被解释成一个负数,于是仍然会在stream_Read()函数及其补丁else分支中的memcpy()函数里发生溢出(无符号整型和有符号整型的取值范围见附录A.3)。我把这个问题也报告给了VLC的维护者,结果是另一个补丁。

[..]
@@ -1616,7 +1618,7 @@ static void parse_master(demux_t *p_demux)

{
    demux_sys_t *p_sys = p_demux->p_sys;
    uint8_t mst_buf[32];
-     int i, i_map_size;
+     uint32_t i, i_map_size;
    int64_t i_save_pos = stream_Tell(p_demux->s);
    int64_t i_pts_secs;
[..]

现在变量i_map_size是无符号整型了,这个bug被修复。也许你早就注意到函数parse_master()里有另一个缓冲区溢出漏洞。我也把这个bug报告给了VLC的维护者。如果你找不出它,仔细看看VLC维护者提供的第二个补丁,正好把这个bug也修复了。

让我惊讶的是,Windows Vista没有一种值得称道的漏洞利用缓解技术(exploit mitigation techniques)能阻止我控制EIP以及使用jmp reg技术执行栈空间中的任意代码。安全cookie或/GS特性译注6应该能防止返回地址被篡改。此外,ASLR译注7或NX/DEP应该可以防止任意代码执行(所有这些缓解技术的详细描述见附录C.1)。

为了解决这个疑惑,我下载了Process Explorer,配置它以显示进程的DEP和ASLR状态。

注意为了配置Process Explorer以显示进程的DEP和ASLR状态,我在视图区(View)增加了以下列:View>Select Columns>DEP Status和View>Select Columns>ASLR Enabled。此外,我设置了底部窗口来查看进程使用的DLL,并且增加了“ASLR Enabled”列。

Process Explorer的输出如图8所示,显示了VLC及其模块没有使用DEP和ASLR(由DEP和ASLR列的空值看出)。我深入研究确定了为什么VLC进程没有使用这些缓解技术。

enter image description here

图8:Process Explorer中的VLC

DEP可被系统策略(system policy)通过特殊的API和编译时选项控制(关于DEP的更多信息见Microsoft的安全研究及防护博客)。像Windows Vista这样的客户端操作系统中默认的系统级(system-wide)DEP策略称为OptIn。在这种操作系统模式中,DEP仅对明确加入DEP的进程起作用。因为我用的是Windows Vista 32位操作系统的默认安装,系统级DEP策略应该是设置成OptIn的。为了验证这一点,我在提升的命令提示符(elevated command prompt)中使用bcdedit.exe命令行应用程序:

C:\Windows\system32>bcdedit /enum | findstr nx
nx                         OptIn

该命令的输出显示了系统确实使用了DEP的OptIn操作模式配置,这就解释了为什么VLC没有使用缓解技术:只是因为进程没有加入到DEP中。

把一个进程加入DEP中可以有不同的方式。举例来说,你可以在编译时使用适当的链接开关(/NXCOMPAT),或者可以使用应用程序接口SetProcessDEPPolicy以编程方式将应用程序加入到DEP中。

为了全面概括VLC使用的安全相关编译时选项,我用LookingGlass扫描了媒体播放器的可执行文件(见图9)。

注意2009年,Microsoft发布了一款名叫BinScope的二进制文件分析工具,界面简单易用,可以分析二进制文件中各种各样的安全保护。

LookingGlass显示编译VLC时既没有使用ASLR也没有使用DEP链接开关,VLC媒体播放器的Windows发布版本是在Cygwin环境下编译的,这是一套在Windows操作系统下运行Linux模拟环境的工具集。因为我之前提到的链接开关仅在Microsoft的Visual C++2005 SP1及之后版本下支持(因此Cygwin不支持),所以VLC不支持并不奇怪。

enter image description here

图9:LookingGlass扫描VLC的结果

  Microsoft的Visual C++2005 SP1及之后版本的漏洞利用缓解技术:
   - 支持栈安全cookie(canary探测)的/GS选项
   - 支持ASLR的/DYNAMICBASE选项
   - 支持DEP/NX的/NXCOMPAT选项
   - 支持异常处理保护的/SAFESEH选项

请看以下VLC构建命令的部分摘录:

[..]
Building VLC from the source code
=================================
[..]
- natively on Windows, using cygwin (www.cygwin.com) with or without the POSIX
emulation layer. This is the preferred way to compile vlc if you want to do it on
Windows.
[..]
UNSUPPORTED METHODS
-------------------
[..]
- natively on Windows, using Microsoft Visual Studio. This will not work.
[..]

写这些内容时,VLC并没有使用Windows Vista及其之后发布版提供的任何漏洞利用缓解技术。因此,现在Windows下VLC的每一个bug都是容易利用的,就像20年前这些安全特性没有被广泛部署和支持时一样。

4. 经验和教训

作为一名程序员:
- 永远不要相信用户的输入(这包括文件数据,网络数据,等等)。
- 永远不要使用未经验证的数值长度或大小。
- 只要可能,务必使用现代操作系统提供的漏洞利用缓解技术。在Windows中,软件要用Microsoft的Visual C++2005 SP1或更新的版本编译,并且使用合适的编译、链接选项。另外,Microsoft发布了Enhanced Mitigation Experience Toolkit,允许一些特定的缓解技术无需重新编译便可直接应用。

作为一名媒体播放器的使用者:
- 永远不要相信媒体文件的扩展名(见下节)。

5. 补充

2008年10月20日,星期一

因为这个漏洞已修复,VLC发布了新的版本,我在自己的网站上发布了一个详细的安全报告(图2-10显示了时间线)。这个bug的编号是CVE-2008-4654。

注意根据MITRE提供的文档,公共的漏洞披露标识符(Common Vulnerabilities and Exposures Identifiers)(也称作CVE名字, CVE编号, CVE-IDs, 或CVEs)是“为公众所知安全漏洞信息的唯一、公共的标识符”。

enter image description here

图10:该漏洞的时间线

2009年1月5日,星期一

发现这个bug以及公开详细报告的副作用是,我收到很多来自VLC用户的邮件,提出了各种各样的问题。下面这两个问题我看了一遍又一遍:

  我之前从来没听说过TiVo的媒体文件格式,为什么?

  如果我不再用VLC打开TiVo媒体文件,是否就安全了?

这些问题都是合理的,所以我问自己,对于那些从互联网上下载的除了文件扩展名没有其他信息的媒体文件,我通常会怎样了解它的格式信息呢?我会用十六进制编辑器看一看这个文件的文件头,但老实说,我不认为芸芸众生都会费这个劲。可是文件扩展名可信吗?不,它们不可信。TiVo文件规范的扩展名是.ty,但怎么可能阻止攻击者把文件名从fun.ty改成fun.avi、fun.mov、fun.mkv或其他任何她想要的?文件仍然会被VLC媒体播放器当作一个TiVo文件打开并处理,因为VLC像几乎其他所有的媒体播放器一样,不是靠文件扩展名来识别媒体文件格式的。

译注1:关于demuxer,可参考:http://en.wikipedia.org/wiki/Demuxer,短址:http://bit.ly/yKVMJ9
译注2:关于sanity test或者sanity check,可参考:http://en.wikipedia.org/wiki/Sanity_check,短址:http://bit.ly/zThbOh
译注3:附录A.1详细介绍了栈帧,更多信息可参考:http://en.wikipedia.org/wiki/Stack_frame#Structure,短址:http://bit.ly/xdCyW1
译注4:相关信息可从Verisign官方网站上查询,vulnerability intelligence页面的短址:http://vrsn.cc/xyFawL
译注5:ZDI计划或称零日计划,Zero Day官方网站:http://www.zerodayinitiative.com/,短址:http://bit.ly/zLbzEl
译注6:关于security cookie可参考:http://msdn.microsoft.com/zh-cn/library/8dbf701c.aspx,短址:http://bit.ly/seXUaM
译注7:关于ASLR请参考:http://en.wikipedia.org/wiki/Address_space_layout_randomization,短址:http://bit.ly/tSz1p1