- May 7, 2013
- 10,400
The Store Manager is an important component and is responsible for a new feature which was introduced in Windows 8: memory compression. Although, the Store Manager was initially introduced with the advent of Windows 7, whereby it was responsible for managing the caching stores (hence the name Store Manager).
These stores can be any appropriate form of non-volatile RAM such as a USB flash drive or the motherboard. Each store then contains a number of pages called store pages. These pages are the same as the pages used by the Memory Manager, however, are aptly named as store pages to differentiate who is responsible for managing them. The Store Manager keeps track of which pages are in which are store, with each page being uniquely identified by a store key.
Each store has a virtual and physical portion. The virtual portion will only contain data which is backed by a page file and this makes sense since data which is page-file-backed will only be present in at least physical memory. It is not committed to the disk at any point.
On the other hand, the physical portion is divided further into static and dynamic caches. The static cache is managed by the user-mode portion of the Store Manager and the data which populates it is determined by this service, typically through the use of access logs to determine which files are being accessed the most frequently.
With Windows 8, memory compression was introduced and is carried out by a minimal process called MemCompression. In almost all Stop 0x154 crashes, the running process will be the MemCompression executable, however, the process is transparent to Task Manager and therefore it will not appear to be running at all. Memory compression is managed by the Store Manager through the use of a compression store, which is represented by the SMKM_STORE structure and is unfortunately not publicly documented.
The main idea of memory compression is to reduce the amount disk space required for the page file, but also to decrease the time taken for a page to be paged from the disk back into memory and vice versa. Unfortunately, the Store Manager is largely undocumented and therefore can be difficult to debug, however, there does appear to be three main causes for a Stop 0x154: faulty RAM, faulty hard disk or storage stack drivers such as anti-virus software and backup programs.
With each Stop 0x154 bugcheck, there is two key parameters: the first of which is a pointer to some internal structure, and the second of which is a pointer to structure called EXCEPTION_POINTERS. As the name suggests, this structure contains two other pointers which correspond to the exception record and the context record.
If we examine the exception record, we can that the in-page I/O error was thrown while attempting to decompress the associated page. This is almost always why a Stop 0x154 is produced and I'm yet to see where this isn't the case. Remember: this error can be due by storage stack drivers as well as faulty hardware.
We take a look at the memory address being referenced by the pointer, we can see that it contains a junk value and therefore why the page was not able to be decompressed correctly. Let's examine the call stack and have a further look at what is happening.
We begin with a page fault which is to be expected, since the compressed page is no longer part of the working set of the original process, but part of the working set for the compression process: MemCompression (1). When the original process of the compressed page requires it, the page is decompressed and then returned to the working set of the original process. We can see this occurring later in the call stack with the RtlDecompressBufferEx function. You can determine the type of page which the address which is responsible for the page fault by examining its PTE.
The PageFileLow field will be set to 0x2 if the page is part of the virtual store. The PageFileLow field should correspond to the PageFileNumber field found in the _MMPAGING_FILE structure, which in turn serves as an index into an array called nt!MmPagingFile. The array contains pointers to all the possible page files present on a system. There can be up to 15 different page files at any given time.
The VirtualStorePagefile field is a boolean which indicates wherever the page file is used by one of the virtual stores.
Once the page has been identified as belonging to the Store Manager, the store key is then calculated from the PTE using the following three fields: PageFileHigh, PageFileLow and the SwizzleBit. This is known as the SM_PAGE_KEY. The calculation is as follows:
The InvalidPteMask field is part of the MI_HARDWARE_STATE structure:
Once the appropriate store key has been calculated, it is used to index into the B+ tree held by the SMKM_STORE_MGR structure which is responsible for managing all of the global virtual stores (3). As mentioned earlier, the store itself is represented by the SMKM_STORE structure and this contains a number of interesting fields such as which process the store belongs to and the regions in which the store pages belong. Each region is 128KB in size and has a number of 16 byte chunks associated to it. Each region is maintained in an region array which in turn corresponds to a region entry managed by a local B+ tree (part of ST_DATA_MGR).
The SMKM_STORE also contains a pointer to another structure called the ST_STORE. This structure has a pointer to a structure called ST_DATA_MGR and it is this manager object which used in conjunction with some chunk metadata to find the ST_PAGE_RECORD which represents a store page. Once the store page has been found, it then decompressed using the Xpress algorithm (6). However, this process fails due to an invalid memory address and therefore throws an exception, which in turn in not handled and therefore leads to the Stop 0x154 bugcheck which we see.
References:
Finding Evil in Windows 10 Compressed Memory, Part One: Volatility and Rekall Tools | Mandiant
Finding Evil in Windows 10 Compressed Memory, Part Two: Virtual Store Deep Dive
Finding Evil in Windows 10 Compressed Memory, Part Three: Automating Undocumented Structure Extraction
Windows Internals 7th Edition - Part 1
Windows Internals 6th Edition - Part 2
These stores can be any appropriate form of non-volatile RAM such as a USB flash drive or the motherboard. Each store then contains a number of pages called store pages. These pages are the same as the pages used by the Memory Manager, however, are aptly named as store pages to differentiate who is responsible for managing them. The Store Manager keeps track of which pages are in which are store, with each page being uniquely identified by a store key.
Each store has a virtual and physical portion. The virtual portion will only contain data which is backed by a page file and this makes sense since data which is page-file-backed will only be present in at least physical memory. It is not committed to the disk at any point.
On the other hand, the physical portion is divided further into static and dynamic caches. The static cache is managed by the user-mode portion of the Store Manager and the data which populates it is determined by this service, typically through the use of access logs to determine which files are being accessed the most frequently.
With Windows 8, memory compression was introduced and is carried out by a minimal process called MemCompression. In almost all Stop 0x154 crashes, the running process will be the MemCompression executable, however, the process is transparent to Task Manager and therefore it will not appear to be running at all. Memory compression is managed by the Store Manager through the use of a compression store, which is represented by the SMKM_STORE structure and is unfortunately not publicly documented.
The main idea of memory compression is to reduce the amount disk space required for the page file, but also to decrease the time taken for a page to be paged from the disk back into memory and vice versa. Unfortunately, the Store Manager is largely undocumented and therefore can be difficult to debug, however, there does appear to be three main causes for a Stop 0x154: faulty RAM, faulty hard disk or storage stack drivers such as anti-virus software and backup programs.
Rich (BB code):
UNEXPECTED_STORE_EXCEPTION (154)
The store component caught an unexpected exception.
Arguments:
Arg1: ffff888799a84000, Pointer to the store context or data manager << Some offset into SmGlobals?
Arg2: fffff98366371170, Exception information << Pointer to _EXCEPTION_POINTERS
Arg3: 0000000000000002, Reserved
Arg4: 0000000000000000, Reserved
With each Stop 0x154 bugcheck, there is two key parameters: the first of which is a pointer to some internal structure, and the second of which is a pointer to structure called EXCEPTION_POINTERS. As the name suggests, this structure contains two other pointers which correspond to the exception record and the context record.
Rich (BB code):
1: kd> dt _EXCEPTION_POINTERS fffff98366371170
nt!_EXCEPTION_POINTERS
+0x000 ExceptionRecord : 0xfffff983`663720f8 _EXCEPTION_RECORD
+0x008 ContextRecord : 0xfffff983`66371930 _CONTEXT
Rich (BB code):
1: kd> .exr 0xfffff983663720f8
ExceptionAddress: fffff800516d10d0 (nt!RtlDecompressBufferXpressLz+0x0000000000000050)
ExceptionCode: c0000006 (In-page I/O error)
ExceptionFlags: 00000000
NumberParameters: 3
Parameter[0]: 0000000000000000
Parameter[1]: 000002293d694f30
Parameter[2]: 00000000c000000e
Inpage operation failed at 000002293d694f30, due to I/O error 00000000c000000e
If we examine the exception record, we can that the in-page I/O error was thrown while attempting to decompress the associated page. This is almost always why a Stop 0x154 is produced and I'm yet to see where this isn't the case. Remember: this error can be due by storage stack drivers as well as faulty hardware.
Rich (BB code):
1: kd> .cxr 0xfffff98366371930
rax=fffff800516d1080 rbx=ffffe580dd293000 rcx=ffffe580dd293000
rdx=ffffe580dd293000 rsi=0000000000000002 rdi=000002293d694f30
rip=fffff800516d10d0 rsp=fffff98366372338 rbp=000002293d694f82
r8=000002293d694f30 r9=00000000000000a8 r10=ffffe580dd293ea0
r11=000002293d694fd8 r12=fffff983663725a8 r13=ffff88879e5f2000
r14=ffffe580dd294000 r15=0000000000000000
iopl=0 nv up ei pl zr na po nc
cs=0010 ss=0000 ds=002b es=002b fs=0053 gs=002b efl=00010246
nt!RtlDecompressBufferXpressLz+0x50:
fffff800`516d10d0 418b08 mov ecx,dword ptr [r8] ds:002b:00000229`3d694f30=????????
We take a look at the memory address being referenced by the pointer, we can see that it contains a junk value and therefore why the page was not able to be decompressed correctly. Let's examine the call stack and have a further look at what is happening.
Rich (BB code):
1: kd> knL
# Child-SP RetAddr Call Site
00 fffff983`663710b8 fffff800`51994dee nt!KeBugCheckEx
01 fffff983`663710c0 fffff800`51810023 nt!SMKM_STORE<SM_TRAITS>::SmStUnhandledExceptionFilter+0x7e
02 fffff983`66371110 fffff800`517cca4f nt!`SMKM_STORE<SM_TRAITS>::SmStDirectReadIssue'::`1'::filt$0+0x22
03 fffff983`66371140 fffff800`517ffadf nt!_C_specific_handler+0x9f
04 fffff983`663711b0 fffff800`51687547 nt!RtlpExecuteHandlerForException+0xf
05 fffff983`663711e0 fffff800`51686136 nt!RtlDispatchException+0x297
06 fffff983`66371900 fffff800`51808cac nt!KiDispatchException+0x186
07 fffff983`66371fc0 fffff800`51804e43 nt!KiExceptionDispatch+0x12c
08 fffff983`663721a0 fffff800`516d10d0 nt!KiPageFault+0x443
09 fffff983`66372338 fffff800`516a6bf0 nt!RtlDecompressBufferXpressLz+0x50
0a fffff983`66372350 fffff800`516a6938 nt!RtlDecompressBufferEx+0x60 (6)
0b fffff983`663723a0 fffff800`516a67c5 nt!ST_STORE<SM_TRAITS>::StDmSinglePageCopy+0x150
0c fffff983`66372460 fffff800`516a5ffc nt!ST_STORE<SM_TRAITS>::StDmSinglePageTransfer+0xa5
0d fffff983`663724b0 fffff800`516a5e2c nt!ST_STORE<SM_TRAITS>::StDmpSinglePageRetrieve+0x180
0e fffff983`66372550 fffff800`516a5c79 nt!ST_STORE<SM_TRAITS>::StDmPageRetrieve+0xc8 (5)
0f fffff983`66372600 fffff800`516a5b31 nt!SMKM_STORE<SM_TRAITS>::SmStDirectReadIssue+0x85
10 fffff983`66372680 fffff800`51698bc8 nt!SMKM_STORE<SM_TRAITS>::SmStDirectReadCallout+0x21
11 fffff983`663726b0 fffff800`516a368f nt!KeExpandKernelStackAndCalloutInternal+0x78
12 fffff983`66372720 fffff800`51759934 nt!SMKM_STORE<SM_TRAITS>::SmStDirectRead+0xc7
13 fffff983`663727f0 fffff800`51759368 nt!SMKM_STORE<SM_TRAITS>::SmStWorkItemQueue+0x1ac (4)
14 fffff983`66372840 fffff800`516a4117 nt!SMKM_STORE_MGR<SM_TRAITS>::SmIoCtxQueueWork+0xc0
15 fffff983`663728d0 fffff800`516fa96b nt!SMKM_STORE_MGR<SM_TRAITS>::SmPageRead+0x167 (3)
16 fffff983`66372940 fffff800`5165e0a0 nt!SmPageRead+0x33 (2)
17 fffff983`66372990 fffff800`5165bb4d nt!MiIssueHardFaultIo+0x10c
18 fffff983`663729e0 fffff800`51728278 nt!MiIssueHardFault+0x29d
19 fffff983`66372aa0 fffff800`51804d5e nt!MmAccessFault+0x468
1a fffff983`66372c40 00007ffa`3145845f nt!KiPageFault+0x35e (1)
1b 0000003b`371fdc10 00000000`00000000 0x00007ffa`3145845f
We begin with a page fault which is to be expected, since the compressed page is no longer part of the working set of the original process, but part of the working set for the compression process: MemCompression (1). When the original process of the compressed page requires it, the page is decompressed and then returned to the working set of the original process. We can see this occurring later in the call stack with the RtlDecompressBufferEx function. You can determine the type of page which the address which is responsible for the page fault by examining its PTE.
Rich (BB code):
1: kd> dt _MMPTE_SOFTWARE
nt!_MMPTE_SOFTWARE
+0x000 Valid : Pos 0, 1 Bit
+0x000 PageFileReserved : Pos 1, 1 Bit
+0x000 PageFileAllocated : Pos 2, 1 Bit
+0x000 ColdPage : Pos 3, 1 Bit
+0x000 SwizzleBit : Pos 4, 1 Bit
+0x000 Protection : Pos 5, 5 Bits
+0x000 Prototype : Pos 10, 1 Bit
+0x000 Transition : Pos 11, 1 Bit
+0x000 PageFileLow : Pos 12, 4 Bits
+0x000 UsedPageTableEntries : Pos 16, 10 Bits
+0x000 ShadowStack : Pos 26, 1 Bit
+0x000 Unused : Pos 27, 5 Bits
+0x000 PageFileHigh : Pos 32, 32 Bits
The PageFileLow field will be set to 0x2 if the page is part of the virtual store. The PageFileLow field should correspond to the PageFileNumber field found in the _MMPAGING_FILE structure, which in turn serves as an index into an array called nt!MmPagingFile. The array contains pointers to all the possible page files present on a system. There can be up to 15 different page files at any given time.
Rich (BB code):
1: kd> dt _MMPAGING_FILE -y PageFileNumber
nt!_MMPAGING_FILE
+0x0cc PageFileNumber : Pos 0, 4 Bits
Rich (BB code):
1: kd> dt _MMPAGING_FILE -y VirtualStorePagefile
nt!_MMPAGING_FILE
+0x0cc VirtualStorePagefile : Pos 6, 1 Bit
The VirtualStorePagefile field is a boolean which indicates wherever the page file is used by one of the virtual stores.
Once the page has been identified as belonging to the Store Manager, the store key is then calculated from the PTE using the following three fields: PageFileHigh, PageFileLow and the SwizzleBit. This is known as the SM_PAGE_KEY. The calculation is as follows:
Rich (BB code):
(PageFileLow << 0x1c) | (PageFileHigh & ~InvalidPteMask)
The InvalidPteMask field is part of the MI_HARDWARE_STATE structure:
Rich (BB code):
1: kd> dt _MI_HARDWARE_STATE -y InvalidPteMask
nt!_MI_HARDWARE_STATE
+0x0c0 InvalidPteMask : Uint8B
Once the appropriate store key has been calculated, it is used to index into the B+ tree held by the SMKM_STORE_MGR structure which is responsible for managing all of the global virtual stores (3). As mentioned earlier, the store itself is represented by the SMKM_STORE structure and this contains a number of interesting fields such as which process the store belongs to and the regions in which the store pages belong. Each region is 128KB in size and has a number of 16 byte chunks associated to it. Each region is maintained in an region array which in turn corresponds to a region entry managed by a local B+ tree (part of ST_DATA_MGR).
The SMKM_STORE also contains a pointer to another structure called the ST_STORE. This structure has a pointer to a structure called ST_DATA_MGR and it is this manager object which used in conjunction with some chunk metadata to find the ST_PAGE_RECORD which represents a store page. Once the store page has been found, it then decompressed using the Xpress algorithm (6). However, this process fails due to an invalid memory address and therefore throws an exception, which in turn in not handled and therefore leads to the Stop 0x154 bugcheck which we see.
References:
Finding Evil in Windows 10 Compressed Memory, Part One: Volatility and Rekall Tools | Mandiant
Finding Evil in Windows 10 Compressed Memory, Part Two: Virtual Store Deep Dive
Finding Evil in Windows 10 Compressed Memory, Part Three: Automating Undocumented Structure Extraction
Windows Internals 7th Edition - Part 1
Windows Internals 6th Edition - Part 2