Slide 1: Pwning the Windows 10 Kernel with NTFS and WNF - POC 2021
Pwning the Windows 10 Kernel with NTFS and WNF - POC 2021
Slide 2: Introduction
Introduction
Slide 3: About
About Currently a Security Researcher within NCC Exploit Development Group (EDG). Supported by other team members at NCC (Cedric Halbronn and Aaron Adams). Previously won some Pwn2Own's (2018 Apple Safari / 2017 Huawei Mate Pro etc) Research interests primarily platform security (OS's/Mobile/Browser
Slide 4: Background
Background A Windows local kernel priv escalation CVE-2021-31956 vulnerability affecting a large range of versions Based on a vulnerability found exploited in the wild by Boris Larin of Kaspersky Challenges around exploit development on latest Windows 10 version at the time - 20H2 (Segment Heap etc)
Slide 5: Agenda
Agenda Vulnerability Overview WNF Introduction WNF Exploit Primitives Exploitation without CVE-2021-31955 Post Exploitation Reliability and Cleanup Exploit Testing Detection 5 / 70
Slide 6: Vulnerability Overview
Vulnerability Overview NTFS Vulnerability Details
Slide 7: Vulnerability Details
Vulnerability Details To exploit a vulnerability we first need a good understanding of the issue. Kaspersky had done a lot of the initial triage in their blog. However, from an exploit developer perspective, we need to understand all the contraints and flexibility it offers. In this case "is it a go
Slide 8: Vulnerability Details
Vulnerability Details __int64 __fastcall NtfsQueryEaUserEaList(__int64 a1, __int64 eas_blocks_for_file, __int64 a3, __int64 out_buf, unsigned int out_buf_length, unsigned int *a6, char a7) { unsigned int padding; // er15 padding = 0; for ( i = a6; ; i = (unsigned int *)((char *)i + *i) ) { if ( i ==
Slide 9: Vulnerability Details
Vulnerability Details *((_BYTE *)out_buf_pos + *((unsigned __int8 *)v11 + 4) + 8) = 0; LABEL_8: v18 = ea_block_size + padding + v15; occupied_length = v18; if ( !a7 ) { if ( v23 ) *v23 = (_DWORD)out_buf_pos - (_DWORD)v23; if ( *v11 ) { v23 = out_buf_pos; out_buf_length -= ea_block_size + padding; pa
Slide 10: Vulnerability Details
Vulnerability Details Lets put some sample numbers into this. Assume two EA's so two iterations of the loop. First iteration: EaNameLength = 5 EaValueLength = 4 ea_block_size = 9 + 5 + 4 = 18 padding = 0 So assuming that 18 < out_buf_length - 0, data would be copied into the buffer. We will use 30 f
Slide 11: Vulnerability Details
Vulnerability Details Asssume second extended attribute with the same values EaNameLength = 5 EaValueLength = 4 ea_block_size = 9 + 5 + 4 = 18 At this point padding is 2, so the calculation is: 18 <= 12 - 2 // is False. Second memcpy fails as it would overflow the buffer. 11 / 70
Slide 12: Vulnerability Details
Vulnerability Details So lets consider an overflowing case (when output buffer size is 18). First EA: EaNameLength = 5 EaValueLength = 4 Second Ea: EaNameLength = 5 EaValueLength = 47 First iteration the loop: EaNameLength = 5 EaValueLength = 4 ea_block_size = 9 + 5 + 4 // 18 padding = 0 // First ti
Slide 13: Vulnerability Details
Vulnerability Details 18 <= 18 - 0 // is True and a copy of 18 occurs. out_buf_length = 18 - 18 + 0 out_buf_length = 0 // out_buf_len has been decremented (0 bytes left). padding = ((18+3) & 0xFFFFFFFC) - 18 padding = 2 Second iteration of loop: EaNameLength = 5 EaValueLength = 47 ea_block_size = 5
Slide 14: Vulnerability Details
Vulnerability Details Next Questions are: Where is the buffer allocated? Can we control the contents of the overflow? The allocation NtfsCommonQueryEa: if ( (_DWORD)out_buf_length ) { out_buf = (PVOID)NtfsMapUserBuffer(a2, 16i64); v28 = out_buf; v16 = (unsigned int)out_buf_length; if ( *(_BYTE *)(a2
Slide 15: Triggering the Corruption
Triggering the Corruption To anwser the second question we need to look at how to trigger the overflow. Looking at the callers for NtfsCommonQueryEa we can see NtQueryEaFile as NT syscall. NTSTATUS NtQueryEaFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, BOOLEAN
Slide 16: Triggering the Corruption
Triggering the Corruption NtSetEaFile is the way to set extended attributes NTSTATUS ZwSetEaFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length ); Key thing here is Buffer which needs to be crafted correctly. 16 / 70
Slide 17: Triggering the Corruption
Triggering the Corruption Buffer is a pointer to a caller-supplied, FILE_FULL_EA_INFORMATION structured input buffer that contains the extended attribute values to be set. typedef struct _FILE_FULL_EA_INFORMATION { ULONG NextEntryOffset; UCHAR Flags; UCHAR EaNameLength; USHORT EaValueLength; CHAR Ea
Slide 18: Vulnerability Summary
Vulnerability Summary 1. The attacker can control the data which is used within the overflow and the size of the overflow. Extended attribute values do not constrain the values which they can contain. 2. The overflow is linear and will corrupt any adjacent pool chunks. 3. The attacker has control ov
Slide 19: Windows 10 Kernel Pool Layout
Windows 10 Kernel Pool Layout What does the Kernel Memory look like? Aim to cover some of the basics here Recommend reading the following papers: Scoop the Windows 10 Pool by by Corentin Bayet and Paul Fariello Windows Kernel Heap by scwuaptx Windows Heap Backed Pool by Yarden Shafir 19 / 70
Slide 20: Windows 10 Kernel Pool Layout
Windows 10 Kernel Pool Layout Allocator Backends: Low Fragmentation Heap (LFH) Variable Size Heap (VS) Segment Allocation Large Alloc In this talk we are going to focus on exploitation on the LFH. 20 / 70
Slide 21: Windows 10 Kernel Pool Layout
Windows 10 Kernel Pool Layout When I started doing this research I actually imposed more constraints that needed on myself. Going to talk about exploitation this way, then and improved iteration of the exploit. Pool page ffff9a069986f3b0 region is Paged pool ffff9a069986f010 size: 30 previous size:
Slide 22: Windows 10 Kernel Pool Layout
Windows 10 Kernel Pool Layout !pool @r9 ffff8001668c4d80 size: 30 previous size: 0 (Allocated) *NtFE Pooltag NtFE : Ea.c, Binary : ntfs.sys ffff8001668c4db0 size: 30 previous size: 0 (Free) C... 1: kd> dt !_POOL_HEADER ffff8001668c4d80 nt!_POOL_HEADER +0x000 PreviousSize : 0y00000000 (0) +0x000 Pool
Slide 23: WNF Introduction
WNF Introduction Windows Notification Framework
Slide 24: WNF Introduction
WNF Introduction The original Kaspersky article mentioned the in-the-wild attackers were using WNF. This was a novel exploitation techique to enable arbitrary r/w. WNF is an undocumented subsystem of the Windows Kernel. However, there has been previous research from a how it works and logic bugs per
Slide 25: WNF Exploit Primitives
WNF Exploit Primitives Windows Notification Framework Primitives
Slide 26: Controlled Page Pool Allocations
Controlled Page Pool Allocations Key observation here, that WNF allocations are made within the Paged Pool (same as the NTFS overflowing chunk) The data used for notifications looks like this (header followed by the data itself): nt!_WNF_STATE_DATA +0x000 Header : _WNF_NODE_HEADER +0x004 AllocatedSi
Slide 27: Controlled Page Pool Allocations
Controlled Page Pool Allocations NtUpdateWnfStateData calls ExpWnfWriteStateData which has the following code: v19 = ExAllocatePoolWithQuotaTag((POOL_TYPE)9, (unsigned int)(v6 + 16), 0x20666E57u); Looking at the function prototype: extern "C" NTSTATUS NTAPI NtUpdateWnfStateData( _In_ PWNF_STATE_NAME
Slide 28: Controlled Free
Controlled Free Initial Setup ringO - paged pool Controlled Free _WNF_STATE_DATA ringO - paged pool ———— ncecgroup~
Slide 29: Relative Memory Read
Relative Memory Read Overflow into DataSize to corrupt the value and enable a larger memory read. nt!_WNF_STATE_DATA +0x000 Header : _WNF_NODE_HEADER +0x004 AllocatedSize : Uint4B +0x008 DataSize : Uint4B +0x00c ChangeStamp : Uint4B Read the data back using NtQueryWnfStateData 29 / 70
Slide 30: Relative Memory Write
Relative Memory Write Corrupt the AllocatedSize nt!_WNF_STATE_DATA +0x000 Header : _WNF_NODE_HEADER +0x004 AllocatedSize : Uint4B +0x008 DataSize : Uint4B +0x00c ChangeStamp : Uint4B extern "C" NTSTATUS NTAPI NtUpdateWnfStateData( _In_ PWNF_STATE_NAME StateName, _In_reads_bytes_opt_(Length) const VO
Slide 31: Arbitrary Read (Pipe Attributes Technique)
Arbitrary Read (Pipe Attributes Technique) Discussed within the Scoop the Windows 10 Pool paper. Relies in being able to overflow into an adjacent Pipe Attribute (also allocated on Paged Pool) Corrupt the list FLINK pointer and inject in fake "Pipe Attribute". 31 / 70
Slide 32: Arbitrary Write (StateData Pointer Corruption)
Arbitrary Write (StateData Pointer Corruption) Investigate if it is possible to corrupt the StateData pointer of a _WNF_NAME_INSTANCE to change relative write to arbitrary write. Fake sane values for DataSize and AllocatedSize Use ExpWnfWriteStateData to write controlled data to a controlled locatio
Slide 33: StateName Lookup
StateName Lookup So this works to overflow the StateData pointer. The aim was to point StateData at the leaked EPROCESS address from CVE-2021-31955. However in the process we destroy other fields within the struct: 1: kd> dt _WNF_NAME_INSTANCE ffffdd09b35c8310+0x10 nt!_WNF_NAME_INSTANCE +0x000 Heade
Slide 34: StateName Lookup
StateName Lookup How do we workaround this? StateName is used for the lookup. There is the external version of the StateName which is the internal version of the StateName XOR’d with 0x41C64E6DA3BC0074. For example, the external StateName value 0x41c64e6da36d9945 would become the following internall
Slide 35: StateName Lookup
StateName Lookup struct _WNF_SCOPE_INSTANCE { struct _WNF_NODE_HEADER Header; //0x0 struct _EX_RUNDOWN_REF RunRef; //0x8 enum _WNF_DATA_SCOPE DataScope; //0x10 ULONG InstanceIdSize; //0x14 VOID* InstanceIdData; //0x18 struct _LIST_ENTRY ResolverListEntry; //0x20 struct _WNF_LOCK NameSetLock; //0x30
Slide 36: StateName Forgery
StateName Forgery We dont know what element is going to be corrupted. However, with the control over the heap we can forge this. This is not very reliable though. 36 / 70
Slide 37: Security Descriptor
Security Descriptor The final thing we need to forge is the security descriptor Can point this to forged one within userspace. 1: kd> dx -id 0,0,ffffce86a715f300 -r1 ((ntkrnlmp!_SECURITY_DESCRIPTOR *)0xffff9e8253eca5a0) ((ntkrnlmp!_SECURITY_DESCRIPTOR *)0xffff9e8253eca5a0) : 0xffff9e8253eca5a0 [Type
Slide 38: CVE-2021-31955 Information Leak
CVE-2021-31955 Information Leak A seporate information leak vulnerability. Allows leaking the EPROCESS address from every process out. NtQuerySystemInformation with SUPERFETCH_INFORMATION discloses it. NtQuerySystemInformation only available at medium integrity. There's public POCs online for this n
Slide 39: EPROCESS Overwrite
EPROCESS Overwrite But the exploit is not very reliable.. can we improve this? 39 / 70
Slide 40: Exploitation without CVE-2021-31955
Exploitation without CVE-2021-31955
Slide 41: Exploit Version 2
Exploit Version 2 Aim was to exploit without using CVE-2021-31955 information leak. To allow exploitation from low integrity. To increase reliability to a high standard. More investigation of _WNF_NAME_INSTANCE Credits also to Yan ZiShuang who also published on this. 41 / 70
Slide 42: _WNF_NAME_INSTANCE EPROCESS
_WNF_NAME_INSTANCE EPROCESS nt!_WNF_NAME_INSTANCE +0x000 Header : _WNF_NODE_HEADER +0x008 RunRef : _EX_RUNDOWN_REF +0x010 TreeLinks : _RTL_BALANCED_NODE +0x028 StateName : _WNF_STATE_NAME_STRUCT +0x030 ScopeInstance : Ptr64 _WNF_SCOPE_INSTANCE +0x038 StateNameInfo : _WNF_STATE_NAME_REGISTRATION +0x0
Slide 43: Goal Layout
Goal Layout Ideal Layout WNF Chunk WNF Chunk NTFS Chunk = WNF_NAME_INSTANCE CreatorProcess’ LS Relative OOB Read StateData~ ringO - paged pool ncecgroup~
Slide 44: LFH Randomisation
LFH Randomisation LFH Allocation LFH Segment nccgroup”
Slide 45: Spray and Overflow (Take 2)
Spray and Overflow (Take 2) _WNF_NAME_INSTANCE is 0xA8 + the POOL_HEADER (0x10), so 0xB8 (Chunk size 0xC0) _WNF_STATE_DATA objects of size 0xA0 (which when added with the header 0x10 + the POOL_HEADER (0x10) we also end up with a chunk allocated of 0xC0. Possible corrupted data nt!_WNF_NAME_INSTANCE
Slide 46: Problems and Solutions
Problems and Solutions Only want to corrupt _WNF_STATE_DATA objects first but pool segment also contains _WNF_NAME_INSTANCE due to being the same size. Use only a 0x10 data size overflow and clean up afterwards. Unbounded _WNF_STATA_DATA could be positioned at end of chunk. NtQueryWnfStateData read
Slide 47: Locating a _WNF_NAME_INSTANCE and overwriting the State
Locating a _WNF_NAME_INSTANCE and overwriting the State At this point _WNF_STATE_DATA has been overflowed and unbounded the DataSize and AllocatedSize But how to we locate a _WNF_NAME_INSTANCE? Each has a byte patern "\x03\x09\xa8" in its header. Therefore from this we know the start and can work ou
Slide 48: PreviousMode
PreviousMode "When a user-mode application calls the Nt or Zw version of a native system services routine, the system call mechanism traps the calling thread to kernel mode. To indicate that the parameter values originated in user mode, the trap handler for the system call sets the PreviousMode fiel
Slide 49: Locating PreviousMode from EPROCESS
Locating PreviousMode from EPROCESS 49 / 70
Slide 50: Stage 2 Diagram
Stage 2 Diagram ringO - paged pool Accgroup* ..
Slide 51: Abusing PreviousMode
Abusing PreviousMode PreviousMode Overwrite
Slide 52: Abusing PreviousMode
Abusing PreviousMode Once we have set the StateData pointer of the _WNF_NAME_INSTANCE prior to the _KPROCESS ThreadListHead Flink we can leak out the value by confusing it with the DataSize and the ChangeTimestamp, we can then calculate the FLINK as FLINK = (uintptr_t)ChangeTimestamp << 32 | DataSiz
Slide 53: Stage 3
Stage 3 PreviousMode StateData* x ring - paged pool Relatve OOE Read Wie nccgroup® ..
Slide 54: Game Over
Game Over After setting PreviousMode to 0, arbitrary read/write across whole memory space using NtWriteVirtualMemory and NtReadVirtualMemory. Trivial to either: Walk the ActiveProcessLinks within the EPROCESS, obtain a pointer to a SYSTEM token and replace current token. Overwrite _SEP_TOKEN_PRIVILE
Slide 55: Reliability and Testing
Reliability and Testing Reliability and Testing
Slide 56: Reliability
Reliability At this point exploit is succesful! However, kernel memory can be in a bad state.. Can lead to a BSOD quicky after. Need to clean up kernel memory to maintain stability. There's a limit to what we can actually do though. 56 / 70
Slide 57: PreviousMode Restoration
PreviousMode Restoration Simply set PreviousMode back to 1 using NtWriteVirtualMemory If we don't do this we get a crash as follows: Access violation - code c0000005 (!!! second chance !!!) nt!PspLocateInPEManifest+0xa9: fffff804`502f1bb5 0fba68080d bts dword ptr [rax+8],0Dh 0: kd> kv # Child-SP Ret
Slide 58: StateData Pointer Restoration
StateData Pointer Restoration This one is more tricky. StateData pointer is free'd on process termination (i.e. Neds to be valid allocated address) Walk the Name Instance Tree and fix up 58 / 70
Slide 59: StateData Pointer Restoration
StateData Pointer Restoration QWORD* FindStateName(unsigned __int64 StateName) { QWORD* i; // _WNF_SCOPE_INSTANCE+0x38 (NameSet) for (i = (QWORD*)read64((char*)BackupScopeInstance+0x38); ; i = (QWORD*)read64((char*)i + 0x8)) { while (1) { if (!i) return 0; // StateName is 0x18 after the TreeLinks FL
Slide 60: RunRef Restoration
RunRef Restoration RunRef from _WNF_NAME_INSTANCE‘s in the process of obtaining our unbounded _WNF_STATE_DATA ExReleaseRundownProtection causes a crash because its been corrupted. Need to obtain a full list of _WNF_NAME_INSTANCES _EPROCESS WnfContext nt!_WNF_PROCESS_CONTEXT +0x000 Header : _WNF_NODE
Slide 61: RunRef Restoration
RunRef Restoration void FindCorruptedRunRefs(LPVOID wnf_process_context_ptr) { // +0x040 TemporaryNamesListHead : _LIST_ENTRY LPVOID first = read64((char*)wnf_process_context_ptr + 0x40); LPVOID ptr; for (ptr = read64(read64((char*)wnf_process_context_ptr + 0x40)); ; ptr = read64(ptr)) { if (ptr ==
Slide 62: Is it reliable enough?
Is it reliable enough?
Slide 63: Statistics
Statistics SYSTEM shells – Number of times a SYSTEM shell was launched. Total LFH Writes – For all 100 runs of the exploit, how many corruptions were triggered. Avg LFH Writes – Average number of LFH overflows needed to obtain a SYSTEM shell. Failed after 32 – How many times the exploit failed to ov
Slide 64: Spray Size Variation
Spray Size Variation Result 3000 6000 10000 20000 SYSTEM shells 78 81 85 91 Total LFH writes 688 696 732 681 Avg LFH writes 8 8 8 7 Failed after 32 2 3 3 2 BSODs on exec 20 16 11 7 Unmapped Read 7 4 1 0 Increasing spray size leads to much decreased change of hitting unmapped reads. Average number of
Slide 65: Exploit Demo!
Exploit Demo! Command Prompt crosoft Windows [Version 10.@.19042.5@8] ) 2020 Microsoft Corporation. All rights reserved. C:\Users\test>cd Desktop C:\Users\test \Desktop>whoami desktop-o15vh7b\test C:\Users\test \Desktop>am
Slide 66: How can this be found?
How can this be found?
Slide 67: Detection
Detection Possible artefacts? NTFS Extended Attributes being created and queried. WNF objects being created (as part of the spray) Failed exploit attempts leading to BSODs 67 / 70
Slide 68: NTFS Extended Attributes
NTFS Extended Attributes 68 / 70
Slide 69: Conclusion
Conclusion Affects a wide range of Windows versions, however, prior to segment heap needs different exploitation techniques. Managed to get a 90% reliable exploit on the most recent Windows version with all mitigations on. However, from a practical purpose, there are better bugs which enable more re
Slide 70: Credits
Credits Boris Larin Cedric Halbronn and Aaron Adams Yan ZiShuang Alex Ionescu and Gabrielle Viala Corentin Bayet and Paul Fariello 70 / 70