├── README.md └── shimcachemem ├── README.md └── shimcachemem.py /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This project contains plugins for the Volatility Framework. 4 | 5 | * shimcachemem - parses the Windows Application Compatibility Database (aka, ShimCache) from memory 6 | -------------------------------------------------------------------------------- /shimcachemem/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This project contains a plugin for Volatility 2.5 that parses the Windows Application Compatibility Database (aka, ShimCache) from memory. Most forensic tools that parse the shim cache rely on the cache stored in the Windows registry. The cache in the registry is only updated when a system is shutdown so this approach has the disadvantage of only parsing cache entries 4 | since the last shutdown. On systems that are not rebooted regularly (e.g., production servers) an analyst must either use out-of-date shim cache data or request a system reboot. 5 | 6 | This plugin parses the shim cache directly from the module or process containing the cache, thereby providing analysts access to the most up-to-date cache. The plugin supports Windows XP SP2 through Windows 10 on both 32 and 64 bit architectures. 7 | 8 | ## Installation 9 | 10 | > All development and testing was performed using Python 2.7.6 (x64). 11 | 12 | Install the following packages: 13 | * Python 2.7.6 (x64) 14 | * Volatility 2.5 15 | * Shim Cache Memory Plugin 16 | * Copy the plugin file "shimcachemem.py" to the Volatility plugins folder located in volatility/plugins 17 | * OR, use the Volatility "--plugins" option and point to the directory containing the plugin 18 | 19 | If working with Windows 8+, the following additional Python modules are required: 20 | * pycrypto 21 | * distorm3 22 | 23 | See [this volatility page](https://github.com/volatilityfoundation/volatility/wiki/Windows-8-2012) for more information on why pycrypto and distorm3 are needed for Windows 8+ analysis. 24 | 25 | ## Using the plugin 26 | 27 | The process is as follows: 28 | 29 | 1. Run the volatility "imageinfo" plugin to determine the profile, KDBG offset, and DTB offset. 30 | 2. For Windows 8+, run the volatility "kdbgscan" plugin to determine the KdCopyDataBlock offset. 31 | 3. As a sanity check, use the results of steps 1/2 to list all modules. If this doesn't work, start over. 32 | 4. Run the "shimcachemem" plugin using the results of steps 1 and 2. 33 | 34 | The plugin supports the following options: 35 | 36 | | Option | Long Option | Description | 37 | |--------|-----------------------|-------------| 38 | | -h | --help | List all plugin options | 39 | | | --output=csv | Specify the output format. CSV is the only supported format. If the option is ommitted, the plugin outputs the results to the terminal. | 40 | | | --output-file=out.csv | The name of the CSV output file. | 41 | | -c | --clean_file_paths | Strips UNC path prefixes ("\\??\") and replaces SYSVOL with "C:". Intended an a convenience for analysts. | 42 | | -P | --print_offset | Prints the virtual and physical offset of each shim cache entry. Intended to facilitate additional forensic analysis of the memory image. | 43 | 44 | ## Sample Usage 45 | 46 | ### Pre-windows 8 (XP SP2 - Windows 7/2008 R2) 47 | 48 | #### Identify the profile and KDBG offset. 49 | 50 | ``` 51 | > python vol.py -f D:\Projects\Volatility\WinXPSP2x86.bin imageinfo 52 | 53 | Volatility Foundation Volatility Framework 2.5 54 | Determining profile based on KDBG search... 55 | 56 | Suggested Profile(s) : WinXPSP2x86, WinXPSP3x86 (Instantiated with WinXPSP2x86) 57 | AS Layer1 : IA32PagedMemory (Kernel AS) 58 | AS Layer2 : FileAddressSpace (D:\Projects\Volatility\WinXPSP2x86.bin) 59 | PAE type : No PAE 60 | DTB : 0x39000L 61 | KDBG : 0x8054cde0L 62 | Number of Processors : 1 63 | Image Type (Service Pack) : 3 64 | KPCR for CPU 0 : 0xffdff000L 65 | KUSER_SHARED_DATA : 0xffdf0000L 66 | Image date and time : 2012-11-27 01:57:28 UTC+0000 67 | Image local date and time : 2012-11-26 19:57:28 -0600 68 | ``` 69 | 70 | #### As a sanity check, run the volatility "modules" plugin. 71 | 72 | ``` 73 | > python vol.py -f D:\Projects\Volatility\WinXPSP2x86.bin --profile=WinXPSP2x86 --kdbg=0x8054cde0 --dtb=0x39000 modules 74 | 75 | Volatility Foundation Volatility Framework 2.5 76 | Offset(V) Name Base Size File 77 | ---------- -------------------- ---------- ---------- ---- 78 | 0x823fc3a0 ntoskrnl.exe 0x804d7000 0x216680 \WINDOWS\system32\ntoskrnl.exe 79 | ... 80 | 81 | (output redacted) 82 | ``` 83 | 84 | #### Run the shimcachemem plugin. 85 | 86 | ``` 87 | > python vol.py -f D:\Projects\Volatility\WinXPSP2x86.bin --profile=WinXPSP2x86 --kdbg=0x8054cde0 --dtb=0x39000 shimcachemem 88 | 89 | Volatility Foundation Volatility Framework 2.5 90 | 91 | Order Last Modified Last Update Exec File Size File Path 92 | ----- --------------------- --------------------- ----- ---------- --------- 93 | 1 2012-11-27 01:42:21 2012-11-27 01:57:28 95104 \??\C:\mdd.exe 94 | 2 2008-04-14 11:42:06 2012-11-27 01:56:17 8461312 \??\C:\WINDOWS\system32\SHELL32.dll 95 | 3 2008-04-14 11:42:40 2012-11-27 01:56:17 28672 \??\C:\WINDOWS\system32\verclsid.exe 96 | 97 | (output redacted) 98 | ``` 99 | 100 | ### Windows 8+ 101 | 102 | #### Identify the profile 103 | 104 | ``` 105 | > python vol.py -f D:\Projects\Volatility\Win2012R2x64.raw imageinfo 106 | 107 | Volatility Foundation Volatility Framework 2.5 108 | Determining profile based on KDBG search... 109 | 110 | Suggested Profile(s) : Win2012R2x64, Win8SP0x64, Win8SP1x64, Win2012x64 (Instantiated with Win8SP1x64) 111 | AS Layer1 : AMD64PagedMemory (Kernel AS) 112 | AS Layer2 : FileAddressSpace (D:\Projects\Volatility\Win2012R2x64.raw) 113 | PAE type : No PAE 114 | DTB : 0x1a7000L 115 | KDBG : 0xf801a191ca30L 116 | Number of Processors : 1 117 | Image Type (Service Pack) : 0 118 | KPCR for CPU 0 : 0xfffff801a1977000L 119 | KUSER_SHARED_DATA : 0xfffff78000000000L 120 | Image date and time : 2015-09-19 09:14:05 UTC+0000 121 | Image local date and time : 2015-09-19 02:14:05 -0700 122 | ``` 123 | 124 | #### Identify the KdCopyDataBlock offset 125 | 126 | ``` 127 | > python vol.py -f D:\Projects\Volatility\Win2012R2x64.raw --profile=Win2012R2x64 kdbgscan 128 | 129 | Volatility Foundation Volatility Framework 2.5 130 | ************************************************** 131 | Instantiating KDBG using: Unnamed AS Win2012x64 (6.2.9201 64bit) 132 | Offset (V) : 0xf801a191ca30 133 | Offset (P) : 0x231ca30 134 | KdCopyDataBlock (V) : 0xf801a185b9b0 135 | Block encoded : Yes 136 | Wait never : 0xc790fcab400a5f1f 137 | Wait always : 0xa5f1a969fdc88 138 | KDBG owner tag check : True 139 | Profile suggestion (KDBGHeader): Win2012x64 140 | Version64 : 0xf801a191cd90 (Major: 15, Minor: 9600) 141 | Service Pack (CmNtCSDVersion) : 0 142 | Build string (NtBuildLab) : 9600.16384.amd64fre.winblue_rtm. 143 | PsActiveProcessHead : 0xfffff801a1933700 (34 processes) 144 | PsLoadedModuleList : 0xfffff801a194d9b0 (150 modules) 145 | KernelBase : 0xfffff801a1686000 (Matches MZ: True) 146 | Major (OptionalHeader) : 6 147 | Minor (OptionalHeader) : 3 148 | KPCR : 0xfffff801a1977000 (CPU 0) 149 | ... 150 | 151 | (output redacted) 152 | ``` 153 | 154 | Note that the PsActiveProcessHead and PsLoadedModuleList values above list 34 processes and 150 modules, indicating that the correct KdCopyDataBlock offset has been found. The kdbgscan plugin may produce multiple results, some of which will list 0 processes and modules, indicating that the offset is probably not correct. 155 | 156 | #### As a sanity check, run the volatility "modules" plugin. 157 | 158 | ``` 159 | > python vol.py -f D:\Projects\Volatility\Win2012R2x64.raw --profile=Win2012R2x64 --kdbg=0xf801a185b9b0 --dtb=0x1a7000 modules 160 | 161 | Volatility Foundation Volatility Framework 2.5 162 | Offset(V) Name Base Size File 163 | ------------------ -------------------- ------------------ ------------------ ---- 164 | 0xffffe000000555d0 ntoskrnl.exe 0xfffff801a1686000 0x783000 \SystemRoot\system32\ntoskrnl.exe 165 | 0xffffe00000f53f30 ahcache.sys 0xfffff8000164c000 0x17000 \SystemRoot\system32\DRIVERS\ahcache.sys 166 | ... 167 | 168 | (output redacted) 169 | ``` 170 | 171 | #### Run the shimcachemem plugin 172 | 173 | ``` 174 | > python vol.py -f D:\Projects\Volatility\Win2012R2x64.raw --profile=Win2012R2x64 --kdbg=0xf801a185b9b0 --dtb=0x1a7000 shimcachemem 175 | 176 | Volatility Foundation Volatility Framework 2.5 177 | INFO : volatility.plugins.shimcachemem: Shimcache found at 0xffffc0000046e4b8 178 | INFO : volatility.plugins.shimcachemem: Shimcache found at 0xffffc00000465ce8 179 | 180 | Order Last Modified Last Update Exec File Size File Path 181 | ----- --------------------- --------------------- ----- ---------- --------- 182 | 1 2015-03-12 16:44:52 True SYSVOL\Users\Administrator\Desktop\DumpIt.exe 183 | 2 2013-08-22 11:45:14 True SYSVOL\Windows\System32\wbem\WmiPrvSE.exe 184 | 3 2013-08-22 11:44:42 True SYSVOL\Windows\System32\wbem\WMIADAP.exe 185 | 4 2013-08-22 11:03:41 True SYSVOL\Windows\System32\rundll32.exe 186 | 5 2012-05-01 20:12:56 True SYSVOL\Program Files\VMware\VMware Tools\TPAutoConnect.exe 187 | ... 188 | 189 | (output redacted) 190 | ``` 191 | -------------------------------------------------------------------------------- /shimcachemem/shimcachemem.py: -------------------------------------------------------------------------------- 1 | # Filename: shimcachemem.py 2 | # 3 | # Authors: 4 | # Volatility Plugin Development 5 | # * Fred House - Mandiant, a FireEye Company 6 | # Twitter: @0xF2EDCA5A 7 | # 8 | # Windows Shimcache Analysis 9 | # * Andrew Davis - Mandiant, a FireEye Company 10 | # * Claudiu Teodorescu - FireEye Inc. 11 | # - Twitter: @cteo13 12 | # 13 | # Purpose: This project contains a plugin for Volatility 2.4 that parses the 14 | # Windows Application Compatibility Database (aka, ShimCache) from 15 | # memory. 16 | # 17 | # This file is part of Volatility. 18 | # 19 | # Volatility is free software; you can redistribute it and/or modify 20 | # it under the terms of the GNU General Public License as published by 21 | # the Free Software Foundation; either version 2 of the License, or 22 | # (at your option) any later version. 23 | # 24 | # Volatility is distributed in the hope that it will be useful, 25 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | # GNU General Public License for more details. 28 | # 29 | # You should have received a copy of the GNU General Public License 30 | # along with Volatility. If not, see . 31 | import binascii 32 | import datetime 33 | import volatility.debug as debug 34 | import volatility.exceptions as exceptions 35 | import volatility.obj as obj 36 | import volatility.plugins.common as common 37 | import volatility.plugins.taskmods as taskmods 38 | from volatility.renderers import TreeGrid 39 | import volatility.utils as utils 40 | import volatility.win32.modules as modules 41 | import volatility.win32.tasks as tasks 42 | 43 | ############################################################################### 44 | # Data Structures 45 | ############################################################################### 46 | 47 | ####################################### 48 | # Windows XP (x86) 49 | ####################################### 50 | shimcache_xp_x86 = { 51 | 52 | 'SHIM_CACHE_HEADER' : [ 0x190, { 53 | 'Magic' : [0x0, ['unsigned int']], 54 | 'Unknown' : [0x4, ['unsigned int']], 55 | 'NumEntries' : [0x8, ['unsigned int']], 56 | 'Unknown' : [0xc, ['unsigned int']], 57 | } ], 58 | 59 | 'SHIM_CACHE_ENTRY' : [ 0x228, { 60 | 'Path' : [ 0x0, ['NullString', dict(length = 0x208, encoding = 'utf8')]], 61 | 'LastModified' : [ 0x210, ['WinTimeStamp', dict(is_utc = True)]], 62 | 'FileSize': [0x218, ['long long']], 63 | 'LastUpdate' : [ 0x220, ['WinTimeStamp', dict(is_utc = True)]], 64 | } ], 65 | } 66 | 67 | shimcache_xp_sp2_x86 = { 68 | #redefine for consistency with XP SP3 definition below 69 | '_SEGMENT' : [ 0x40, { 70 | 'ControlArea' : [ 0x0, ['pointer', ['_CONTROL_AREA']]], 71 | 'TotalNumberOfPtes' : [ 0x4, ['unsigned long']], 72 | 'NonExtendedPtes' : [ 0x8, ['unsigned long']], 73 | 'WritableUserReferences' : [ 0xc, ['unsigned long']], 74 | 'SizeOfSegment' : [ 0x10, ['unsigned long long']], 75 | 'SegmentPteTemplate' : [ 0x18, ['_MMPTE']], 76 | 'NumberOfCommittedPages' : [ 0x1c, ['unsigned long']], 77 | 'ExtendInfo' : [ 0x20, ['pointer', ['_MMEXTEND_INFO']]], 78 | 'SystemImageBase' : [ 0x24, ['pointer', ['void']]], 79 | 'BasedAddress' : [ 0x28, ['pointer', ['void']]], 80 | 'u1' : [ 0x2c, ['pointer', ['void']]], 81 | 'u2' : [ 0x30, ['pointer', ['void']]], 82 | 'PrototypePte' : [ 0x34, ['pointer', ['_MMPTE']]], 83 | 'ThePtes' : [ 0x3c, ['array', 1, ['_MMPTE']]], 84 | } ], 85 | } 86 | 87 | shimcache_xp_sp3_x86 = { 88 | #redefine as the sizes of SegmentPteTemplate and PrototypePte are incorrect 89 | # in the XP overlay (should be 8 bytes, not 4) 90 | '_SEGMENT' : [ 0x48, { 91 | 'ControlArea' : [ 0x0, ['pointer', ['_CONTROL_AREA']]], 92 | 'TotalNumberOfPtes' : [ 0x4, ['unsigned long']], 93 | 'NonExtendedPtes' : [ 0x8, ['unsigned long']], 94 | 'WritableUserReferences' : [ 0xc, ['unsigned long']], 95 | 'SizeOfSegment' : [ 0x10, ['unsigned long long']], 96 | 'SegmentPteTemplate' : [ 0x18, ['_MMPTE']], 97 | 'NumberOfCommittedPages' : [ 0x20, ['unsigned long']], 98 | 'ExtendInfo' : [ 0x24, ['pointer', ['_MMEXTEND_INFO']]], 99 | 'SystemImageBase' : [ 0x28, ['pointer', ['void']]], 100 | 'BasedAddress' : [ 0x2c, ['pointer', ['void']]], 101 | 'u1' : [ 0x30, ['pointer', ['void']]], 102 | 'u2' : [ 0x34, ['pointer', ['void']]], 103 | 'PrototypePte' : [ 0x38, ['pointer', ['_MMPTE']]], 104 | 'ThePtes' : [ 0x40, ['array', 1, ['_MMPTE']]], 105 | } ], 106 | } 107 | 108 | ####################################### 109 | # Windows Server 2003 (x86/x64) 110 | ####################################### 111 | shimcache_2003_x86 = { 112 | 'SHIM_CACHE_ENTRY' : [ None, { 113 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 114 | 'Path' : [ 0x8, ['_UNICODE_STRING']], 115 | 'LastModified' : [0x10, ['WinTimeStamp', dict(is_utc = True)]], 116 | 'FileSize': [0x18, ['unsigned long']], 117 | 'Padding': [0x20, ['unsigned long']], 118 | } ], 119 | } 120 | 121 | shimcache_2003_x64 = { 122 | 'SHIM_CACHE_ENTRY' : [ 0x30, { 123 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 124 | 'Path' : [ 0x10, ['_UNICODE_STRING']], 125 | 'LastModified' : [0x20, ['WinTimeStamp', dict(is_utc = True)]], 126 | 'FileSize' : [0x28, ['unsigned long long']], 127 | } ], 128 | } 129 | 130 | ####################################### 131 | # Windows Vista (x86/x64) 132 | # Windows Server 2008 (x86/x64) 133 | ####################################### 134 | shimcache_vista_x86 = { 135 | 'SHIM_CACHE_ENTRY' : [ 0x20, { 136 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 137 | 'Path' : [ 0x8, ['_UNICODE_STRING']], 138 | 'LastModified' : [0x10, ['WinTimeStamp', dict(is_utc = True)]], 139 | 'InsertFlags' : [0x18, ['unsigned int']], 140 | 'ShimFlags' : [0x1c, ['unsigned int']], 141 | } ], 142 | } 143 | 144 | shimcache_vista_x64 = { 145 | 'SHIM_CACHE_ENTRY' : [ 0x30, { 146 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 147 | 'Path' : [ 0x10, ['_UNICODE_STRING']], 148 | 'LastModified' : [0x20, ['WinTimeStamp', dict(is_utc = True)]], 149 | 'InsertFlags' : [0x28, ['unsigned int']], 150 | 'ShimFlags' : [0x2c, ['unsigned int']], 151 | } ], 152 | } 153 | 154 | ####################################### 155 | # Windows 7 (x86/x64) 156 | # Windows Server 2008 R2 (x86/x64) 157 | ####################################### 158 | shimcache_win7_x86 = { 159 | 'SHIM_CACHE_ENTRY' : [ None, { 160 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 161 | 'Path' : [ 0x08, ['_UNICODE_STRING']], 162 | 'LastModified' : [0x10, ['WinTimeStamp', dict(is_utc = True)]], 163 | 'InsertFlags' : [0x18, ['unsigned int']], 164 | 'ShimFlags' : [0x1c, ['unsigned int']], 165 | 'BlobSize' : [0x20, ['unsigned int']], 166 | 'BlobBuffer' : [0x24, ['unsigned long']], 167 | } ], 168 | } 169 | 170 | shimcache_win7_x64 = { 171 | 'SHIM_CACHE_ENTRY' : [ None, { 172 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 173 | 'Path' : [ 0x10, ['_UNICODE_STRING']], 174 | 'LastModified' : [0x20, ['WinTimeStamp', dict(is_utc = True)]], 175 | 'InsertFlags' : [0x28, ['unsigned int']], 176 | 'ShimFlags' : [0x2c, ['unsigned int']], 177 | 'BlobSize' : [0x30, ['unsigned long long']], 178 | 'BlobBuffer' : [0x38, ['unsigned long long']], 179 | } ], 180 | } 181 | 182 | ####################################### 183 | # Windows 8 (x86/x64) 184 | # Windows 8.1 (x86/x64) 185 | # Windows Server 2012 (x86/x64) 186 | # Windows Server 2012 R2 (x86/x64) 187 | ####################################### 188 | shimcache_win8_x86 = { 189 | 'SHIM_CACHE_ENTRY' : [ None, { 190 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 191 | 'Unknown1' : [ 0x8, ['unsigned long']], 192 | 'Unknown2' : [ 0xc, ['unsigned long']], 193 | 'Path' : [ 0x10, ['_UNICODE_STRING']], 194 | 'Unknown3' : [ 0x18, ['unsigned long long']], 195 | 'ListEntryDetail' : [ 0x20, ['pointer', ['SHIM_CACHE_ENTRY_DETAIL']]], 196 | } ], 197 | } 198 | 199 | shimcache_win8_x86_detail = { 200 | 'SHIM_CACHE_ENTRY_DETAIL' : [ None, { 201 | 'LastModified' : [0x0, ['WinTimeStamp', dict(is_utc = True)]], 202 | 'InsertFlags' : [0x08, ['unsigned int']], 203 | 'ShimFlags' : [0x0c, ['unsigned int']], 204 | 'BlobSize' : [0x10, ['unsigned long']], 205 | 'BlobBuffer' : [0x14, ['unsigned long']], 206 | } ], 207 | } 208 | 209 | shimcache_win8_x64 = { 210 | 'SHIM_CACHE_ENTRY' : [ None, { 211 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 212 | 'Unknown1' : [ 0x10, ['unsigned long long']], 213 | 'Path' : [ 0x18, ['_UNICODE_STRING']], 214 | 'Unknown2' : [ 0x28, ['unsigned long long']], 215 | 'Unknown3' : [ 0x30, ['unsigned long long']], 216 | 'ListEntryDetail' : [ 0x38, ['pointer', ['SHIM_CACHE_ENTRY_DETAIL']]], 217 | } ], 218 | } 219 | 220 | shimcache_win8_x64_detail = { 221 | 'SHIM_CACHE_ENTRY_DETAIL' : [ None, { 222 | 'LastModified' : [0x0, ['WinTimeStamp', dict(is_utc = True)]], 223 | 'InsertFlags' : [0x08, ['unsigned int']], 224 | 'ShimFlags' : [0x0c, ['unsigned int']], 225 | 'BlobSize' : [0x10, ['unsigned long long']], 226 | 'Padding' : [0x18, ['unsigned long long']], 227 | 'BlobBuffer' : [0x20, ['unsigned long long']], 228 | } ], 229 | } 230 | 231 | ####################################### 232 | # Windows 10 (x86/x64) 233 | ####################################### 234 | shimcache_win10_x86 = { 235 | 'SHIM_CACHE_ENTRY' : [ None, { 236 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 237 | 'Unknown1' : [ 0x8, ['unsigned long']], 238 | 'Path' : [ 0xc, ['_UNICODE_STRING']], 239 | 'ListEntryDetail' : [ 0x14, ['pointer', ['SHIM_CACHE_ENTRY_DETAIL']]], 240 | } ], 241 | } 242 | 243 | shimcache_win10_x86_detail = { 244 | 'SHIM_CACHE_ENTRY_DETAIL' : [ None, { 245 | 'Unknown1' : [0x0, ['unsigned long']], 246 | 'Unknown2' : [0x4, ['unsigned long']], 247 | 'LastModified' : [0x8, ['WinTimeStamp', dict(is_utc = True)]], 248 | 'BlobSize' : [0x10, ['unsigned long']], 249 | 'BlobBuffer' : [0x14, ['unsigned long']], 250 | } ], 251 | } 252 | 253 | shimcache_win10_x64 = { 254 | 'SHIM_CACHE_ENTRY' : [ None, { 255 | 'ListEntry' : [0x0, ['_LIST_ENTRY']], 256 | 'Unknown1' : [ 0x10, ['unsigned long long']], 257 | 'Path' : [ 0x18, ['_UNICODE_STRING']], 258 | 'ListEntryDetail' : [ 0x28, ['pointer', ['SHIM_CACHE_ENTRY_DETAIL']]], 259 | } ], 260 | } 261 | 262 | shimcache_win10_x64_detail = { 263 | 'SHIM_CACHE_ENTRY_DETAIL' : [ None, { 264 | 'Unknown1' : [ 0x0, ['unsigned long long']], 265 | 'LastModified' : [0x08, ['WinTimeStamp', dict(is_utc = True)]], 266 | 'BlobSize' : [0x10, ['unsigned long']], 267 | 'Unknown2' : [0x14, ['unsigned long']], 268 | 'BlobBuffer' : [0x18, ['unsigned long long']], 269 | } ], 270 | } 271 | 272 | ####################################### 273 | # Override objects 274 | ####################################### 275 | shimcache_objs_x86 = { 276 | # explicitly define _RTL_BALANCED_LINKS and _RTL_AVL_TABLE as they are not 277 | # present in all OS platform overlays (e.g., 2003 x64) 278 | '_RTL_BALANCED_LINKS' : [ 0x10, { 279 | 'Parent' : [ 0x0, ['pointer', ['_RTL_BALANCED_LINKS']]], 280 | 'LeftChild' : [ 0x4, ['pointer', ['_RTL_BALANCED_LINKS']]], 281 | 'RightChild' : [ 0x8, ['pointer', ['_RTL_BALANCED_LINKS']]], 282 | 'Balance' : [ 0xc, ['unsigned char']], 283 | 'Reserved' : [ 0xd, ['array', 3, ['unsigned char']]], 284 | } ], 285 | '_RTL_AVL_TABLE' : [ 0x38, { 286 | 'BalancedRoot' : [ 0x0, ['_RTL_BALANCED_LINKS']], 287 | 'OrderedPointer' : [ 0x10, ['pointer', ['void']]], 288 | 'WhichOrderedElement' : [ 0x14, ['unsigned long']], 289 | 'NumberGenericTableElements' : [ 0x18, ['unsigned long']], 290 | 'DepthOfTree' : [ 0x1c, ['unsigned long']], 291 | 'RestartKey' : [ 0x20, ['pointer', ['_RTL_BALANCED_LINKS']]], 292 | 'DeleteCount' : [ 0x24, ['unsigned long']], 293 | 'CompareRoutine' : [ 0x28, ['pointer', ['void']]], 294 | 'AllocateRoutine' : [ 0x2c, ['pointer', ['void']]], 295 | 'FreeRoutine' : [ 0x30, ['pointer', ['void']]], 296 | 'TableContext' : [ 0x34, ['pointer', ['void']]], 297 | } ], 298 | # define shim cache handle objects found on Windows 8.x platforms. 299 | 'SHIM_CACHE_HANDLE' : [ 0x8, { 300 | 'eresource' : [ 0x0, ['pointer', ['_ERESOURCE']]], 301 | 'rtl_avl_table' : [ 0x4, ['pointer', ['_RTL_AVL_TABLE']]], 302 | } ], 303 | } 304 | 305 | shimcache_objs_x64 = { 306 | '_RTL_BALANCED_LINKS' : [ 0x20, { 307 | 'Parent' : [ 0x0, ['pointer64', ['_RTL_BALANCED_LINKS']]], 308 | 'LeftChild' : [ 0x8, ['pointer64', ['_RTL_BALANCED_LINKS']]], 309 | 'RightChild' : [ 0x10, ['pointer64', ['_RTL_BALANCED_LINKS']]], 310 | 'Balance' : [ 0x18, ['unsigned char']], 311 | 'Reserved' : [ 0x19, ['array', 3, ['unsigned char']]], 312 | } ], 313 | '_RTL_AVL_TABLE' : [ 0x68, { 314 | 'BalancedRoot' : [ 0x0, ['_RTL_BALANCED_LINKS']], 315 | 'OrderedPointer' : [ 0x20, ['pointer64', ['void']]], 316 | 'WhichOrderedElement' : [ 0x28, ['unsigned long']], 317 | 'NumberGenericTableElements' : [ 0x2c, ['unsigned long']], 318 | 'DepthOfTree' : [ 0x30, ['unsigned long']], 319 | 'RestartKey' : [ 0x38, ['pointer64', ['_RTL_BALANCED_LINKS']]], 320 | 'DeleteCount' : [ 0x40, ['unsigned long']], 321 | 'CompareRoutine' : [ 0x48, ['pointer64', ['void']]], 322 | 'AllocateRoutine' : [ 0x50, ['pointer64', ['void']]], 323 | 'FreeRoutine' : [ 0x58, ['pointer64', ['void']]], 324 | 'TableContext' : [ 0x60, ['pointer64', ['void']]], 325 | } ], 326 | 'SHIM_CACHE_HANDLE' : [ 0x10, { 327 | 'eresource' : [ 0x0, ['pointer', ['_ERESOURCE']]], 328 | 'rtl_avl_table' : [ 0x8, ['pointer', ['_RTL_AVL_TABLE']]], 329 | } ], 330 | } 331 | 332 | ############################################################################### 333 | # Complex Object Definitions 334 | ############################################################################### 335 | class ShimCacheEntry(obj.CType): 336 | """An entry in the Shimcache LRU list. This complext object abstract the 337 | variations in the LRU list entry structure""" 338 | 339 | def get_file_size(self): 340 | """Return the file size if available, otherwise None""" 341 | if hasattr(self, 'FileSize') and self.FileSize >= 0: 342 | return self.FileSize 343 | else: 344 | return None 345 | 346 | def get_last_modified(self): 347 | """Windows 8 & 10 store the last modified in a ListEntry attribute, 348 | where as all other versions store it as an attribute of the 349 | ShimCacheEntry object.""" 350 | if hasattr(self, 'ListEntryDetail'): 351 | return self.ListEntryDetail.LastModified 352 | else: 353 | return self.LastModified 354 | 355 | def get_last_update(self): 356 | """Windows XP provides a LastUpdate attribute""" 357 | if hasattr(self, 'LastUpdate'): 358 | return self.LastUpdate 359 | else: 360 | return None 361 | 362 | def get_file_path(self, encoding = 'ascii'): 363 | """Return the shimcache entry file path, stripping any non-utf16 364 | characters; default encoding is ascii""" 365 | 366 | #the Path attribute has Buffer and Length attributes on every OS except 367 | #XP, in which case it is a null termined string; return the appropriate 368 | #path value here 369 | if not hasattr(self.Path, 'Buffer'): 370 | return self.Path 371 | 372 | file_path = self.obj_vm.read(self.Path.Buffer, self.Path.Length) or '' 373 | 374 | #remove any non-UTF16 characters 375 | file_path = file_path.decode('utf16', 'ignore') 376 | 377 | #re-encode is specified encoding 378 | file_path = file_path.encode(encoding, 'ignore') 379 | 380 | return file_path 381 | 382 | def get_exec_flag(self): 383 | """Checks if InsertFlags fields has been bitwise OR'd with a value of 2. 384 | This behavior was observed when processes are created by CSRSS.""" 385 | 386 | exec_flag = '' 387 | if hasattr(self, 'ListEntryDetail') and hasattr(self.ListEntryDetail, 'InsertFlags'): 388 | exec_flag = self.ListEntryDetail.InsertFlags & 0x2 == 2 389 | elif hasattr(self, 'InsertFlags'): 390 | exec_flag = self.InsertFlags & 0x2 == 2 391 | 392 | return exec_flag 393 | 394 | def __str__(self): 395 | """String representation of ShimCacheEntry (intended for debugging)""" 396 | 397 | blob_off, blob_val = None,None 398 | try: 399 | blob_off = self.BlobSize.obj_offset 400 | blob_val = self.BlobSize.v() 401 | except AttributeError as e: 402 | pass 403 | 404 | try: 405 | last_mod_offset = self.LastModified.obj_offset 406 | except AttributeError as e: 407 | last_mod_offset = self.ListEntryDetail.LastModified.obj_offset 408 | 409 | try: 410 | last_upd_offset = self.LastUpdate.obj_offset 411 | except AttributeError as e: 412 | last_upd_offset = None 413 | 414 | if hasattr(self, 'ListEntry'): 415 | shim_str = "Shimcache Entry at (0x{0:08x})\n".format(self.ListEntry) + \ 416 | "\tFlink (0x{0:08x}) = 0x{1:08x}\n".format(self.ListEntry, self.ListEntry.Flink.dereference().obj_offset) + \ 417 | "\tBlink (0x{0:08x}) = 0x{1:08x}\n".format(self.ListEntry.obj_offset+self.ListEntry.Flink.size(), self.ListEntry.Blink.dereference().obj_offset) + \ 418 | "\tPath (0x{0:08x}) = {1}\n".format(self.Path.obj_offset, self.get_file_path()) + \ 419 | "\tPath Size (0x{0:08x}) = {1:d}\n".format(self.Path.Length.obj_offset, self.Path.Length.v()) + \ 420 | "\tLast Mod (0x{0:08x}) = {1}\n".format(last_mod_offset, self.get_last_modified()) 421 | if blob_off and blob_val: 422 | shim_str += "\tBlob Size (0x{0:08x}) = {1:d}\n".format(self.BlobSize.obj_offset, self.BlobSize.v()) 423 | else: 424 | shim_str = "Shimcache Entry at (0x{0:08x})\n".format(self.obj_offset) + \ 425 | "\tPath (0x{0:08x}) = {1}\n".format(self.Path.obj_offset, self.get_file_path()) + \ 426 | "\tFile Sz (0x{0:08x}) = {1}\n".format(self.FileSize.obj_offset, self.get_file_size()) + \ 427 | "\tLast Mod (0x{0:08x}) = {1}\n".format(last_mod_offset, self.get_last_modified()) + \ 428 | "\tLast Upd (0x{0:08x}) = {1}\n".format(last_upd_offset, self.get_last_update()) 429 | 430 | return shim_str 431 | 432 | def is_valid(self): 433 | """Shim cache validation is limited to ensuring that a subset of the 434 | pointers in the LIST_ENTRY field are valid (similar to validation of 435 | ERESOURCE)""" 436 | 437 | if not obj.CType.is_valid(self): 438 | debug.debug("Invalid SHIM_CACHE_ENTRY object at 0x{0:08x}".format(self.v())) 439 | return False 440 | 441 | #shim entries on Windows XP do not have list entry attributes; in this case, 442 | #perform a different set of validations 443 | if not hasattr(self, 'ListEntry'): 444 | return self.LastModified and self.LastModified.is_valid() and \ 445 | self.LastUpdate and self.LastUpdate.is_valid() and \ 446 | self.FileSize and self.FileSize.is_valid() 447 | 448 | # on some platforms ListEntry.Blink is null, so this cannot be validated 449 | if (self.ListEntry.Flink != None and 450 | self.ListEntry.Blink.v() != self.ListEntry.Flink.v() and 451 | self.ListEntry.Flink.Blink == self.ListEntry.Flink.Blink.dereference().obj_offset): 452 | 453 | debug.debug("SHIM_CACHE_ENTRY candidate found at 0x{0:08x} (v) 0x{1:08x} (p)".format(self.v(), self.obj_vm.vtop(self.obj_offset))) 454 | debug.debug("\tListEntry.Flink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Flink, self.ListEntry.Flink.dereference().obj_offset)) 455 | debug.debug("\tListEntry.Blink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Blink, self.ListEntry.Blink.dereference().obj_offset)) 456 | debug.debug("\tListEntry.Flink.Blink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Flink.Blink, self.ListEntry.Flink.Blink.dereference().obj_offset)) 457 | debug.debug("\tListEntry.Blink.Flink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Blink.Flink, self.ListEntry.Blink.Flink.dereference().obj_offset)) 458 | return True 459 | else: 460 | debug.debug("Invalid SHIM_CACHE_ENTRY candidate found at 0x{0:08x} (v) 0x{1:08x} (p)".format(self.v(), self.obj_vm.vtop(self.obj_offset))) 461 | debug.debug("\tListEntry.Flink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Flink, self.ListEntry.Flink.dereference().obj_offset)) 462 | debug.debug("\tListEntry.Blink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Blink, self.ListEntry.Blink.dereference().obj_offset)) 463 | debug.debug("\tListEntry.Flink.Blink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Flink.Blink, self.ListEntry.Flink.Blink.dereference().obj_offset)) 464 | debug.debug("\tListEntry.Blink.Flink (0x{0:08x}) = 0x{1:08x}".format(self.ListEntry.Blink.Flink, self.ListEntry.Blink.Flink.dereference().obj_offset)) 465 | 466 | debug.debug("Invalid SHIM_CACHE_ENTRY candidate at 0x{0:08x} (v) 0x{1:08x} (p)".format(self.v(), self.obj_vm.vtop(self.obj_offset))) 467 | 468 | return False 469 | 470 | ############################################################################### 471 | # Complex Object Definition 472 | ############################################################################### 473 | class _RTL_AVL_TABLE(obj.CType): 474 | """Override the RTL_AVL_TABLE object to include a ShimCache-specific 475 | validation method""" 476 | 477 | def is_valid(self, page_start, page_end): 478 | """This function implements the following validations: 479 | 1) BalancedRoot.Parent points to the _RTL_AVL_TABLE object 480 | 2) Allocate, Compare, and Free functions points to virtual memory 481 | offsets on the same memory page as the loaded module 482 | 3) Allocate, Compare, and Free function pointers are unique values 483 | """ 484 | 485 | if self.BalancedRoot.Parent != self.BalancedRoot: 486 | debug.debug("0x{0:08x} (0x{1:08x}) - RTL_AVL_TABLE.BalancedRoot.Parent (0x{2:08x}) != RTL_AVL_TABLE.BalancedRoot (0x{3:08x})".format( \ 487 | self.v(), 488 | self.obj_vm.vtop(self.v()), 489 | self.BalancedRoot.Parent, 490 | self.BalancedRoot)) 491 | return False 492 | 493 | elif self.AllocateRoutine < page_start or self.AllocateRoutine > page_end: 494 | debug.debug("RTL_AVL_TABLE.AllocateRoutine pointer (0x{0:08x}) not between 0x{1:08x} - 0x{2:08x}".format(self.AllocateRoutine, page_start, page_end)) 495 | return False 496 | 497 | elif self.CompareRoutine < page_start or self.CompareRoutine > page_end: 498 | debug.debug("RTL_AVL_TABLE.CompareRoutine pointer (0x{0:08x}) not between 0x{1:08x} - 0x{2:08x}".format(self.CompareRoutine, page_start, page_end)) 499 | return False 500 | 501 | elif self.FreeRoutine < page_start or self.FreeRoutine > page_end: 502 | debug.debug("RTL_AVL_TABLE.FreeRoutine pointer (0x{0:08x}) not between 0x{1:08x} - 0x{2:08x}".format(self.FreeRoutine, page_start, page_end)) 503 | return False 504 | 505 | elif (self.AllocateRoutine == self.CompareRoutine) or \ 506 | (self.AllocateRoutine == self.FreeRoutine) or \ 507 | (self.CompareRoutine == self.FreeRoutine): 508 | debug.debug("RTL_AVL_TABLE.AllocateRoutine (0x{0:08x}), CompareRoutine (0x{0:08x}), FreeRoutine (0x{0:08x}) not unique".format(\ 509 | self.AllocateRoutine, 510 | self.FreeRoutine, 511 | self.CompareRoutine)) 512 | return False 513 | 514 | debug.debug("RTL_AVL_TABLE candidate found at 0x{0:08x} (v) 0x{1:08x} (p)".format(self.v(), self.obj_vm.vtop(self.obj_offset))) 515 | debug.debug("\tBalancedRoot = 0x{0:08x}".format(self.BalancedRoot)) 516 | debug.debug("\tBalancedRoot.Parent = 0x{0:08x}".format(self.BalancedRoot.Parent)) 517 | debug.debug("\tAllocateRoutine = 0x{0:08x}".format(self.AllocateRoutine)) 518 | debug.debug("\tCompareRoutine = 0x{0:08x}".format(self.CompareRoutine)) 519 | debug.debug("\tFreeRoutine = 0x{0:08x}".format(self.FreeRoutine)) 520 | 521 | return True 522 | 523 | ############################################################################### 524 | # Complex Object Definition 525 | ############################################################################### 526 | class _ERESOURCE(obj.CType): 527 | """Shimcache consists of ERESOURCE + RTL_AVL_TABLE + LIST_ENTRY""" 528 | 529 | def is_valid(self): 530 | """Validate that the ERESOURCE object's LIST_ENTRY pointer are valid 531 | and that the SharedWaiters fields are 0""" 532 | 533 | if not obj.CType.is_valid(self): 534 | debug.debug("Invalid _ERESOURCE candidate at 0x{0:08x}".format(self.v())) 535 | return False 536 | 537 | if (self.SystemResourcesList.Flink != None and 538 | self.SystemResourcesList.Blink != None and 539 | self.SystemResourcesList.Blink.v() != self.SystemResourcesList.Flink.v() and 540 | self.SystemResourcesList.Flink.Blink == self.v() and 541 | self.SystemResourcesList.Blink.Flink == self.v() and 542 | self.NumberOfSharedWaiters == 0 and 543 | (self.SharedWaiters == 0 or self.SharedWaiters.dereference_as("_KSEMAPHORE").is_valid())): 544 | 545 | debug.debug("_ERESOURCE candidate found at 0x{0:08x} (v) 0x{1:08x} (p)".format(self.v(), self.obj_vm.vtop(self.obj_offset))) 546 | debug.debug("\tSystemResourcesList.Flink (0x{0:08x}) = 0x{1:08x}".format(self.SystemResourcesList.Flink, self.SystemResourcesList.Flink.dereference().obj_offset)) 547 | debug.debug("\tSystemResourcesList.Blink (0x{0:08x}) = 0x{1:08x}".format(self.SystemResourcesList.Blink, self.SystemResourcesList.Blink.dereference().obj_offset)) 548 | debug.debug("\tSystemResourcesList.Flink.Blink (0x{0:08x}) = 0x{1:08x}".format(self.SystemResourcesList.Flink.Blink, self.SystemResourcesList.Flink.Blink.dereference().obj_offset)) 549 | debug.debug("\tSystemResourcesList.Blink.Flink (0x{0:08x}) = 0x{1:08x}".format(self.SystemResourcesList.Blink.Flink, self.SystemResourcesList.Blink.Flink.dereference().obj_offset)) 550 | debug.debug("\tSharedWaiters = 0x{0:08x}".format(self.SharedWaiters)) 551 | debug.debug("\tNumberOfSharedWaiters = {0:d}".format(self.NumberOfSharedWaiters)) 552 | return True 553 | 554 | debug.debug("Invalid _ERESOURCE candidate at 0x{0:08x} (v) 0x{1:08x} (p)".format(self.v(), self.obj_vm.vtop(self.obj_offset))) 555 | return False 556 | 557 | ############################################################################### 558 | # Complex Object Definition 559 | ############################################################################### 560 | class ShimCacheHandle(obj.CType): 561 | """A Shim cache handle consists of two sequential pointers: the first to an 562 | ERESOURCE object and the second to an RTL_AVL_TABLE object. The shim 563 | cache handle is used on Windows 8 & 10 platforms.""" 564 | 565 | def __init__(self, *args, **kwargs): 566 | # used to store a pointer to the head of the shim cache LRU list; this 567 | # field is set upon successful validation of the object 568 | self.shim_cache_head = None 569 | obj.CType.__init__(self, *args, **kwargs) 570 | 571 | def get_head(self): 572 | """Return the head of the shim cache LRU list via this handle object""" 573 | if self.shim_cache_head is None: 574 | self.is_valid() 575 | return self.shim_cache_head 576 | 577 | def is_valid(self, page_start, page_end): 578 | """Validates that the object contains a pointer to a valid ERESOURCE 579 | object followed by a pointer to a valid RTL_AVL_TABLE object; this 580 | function requires the memory page range of the handle pointer in 581 | order to validate the RTL_AVL_TABLE object""" 582 | 583 | if not obj.CType.is_valid(self): 584 | debug.debug("Invalid SHIM_CACHE_HANDLE object at 0x{0:08x}".format(self.v())) 585 | return False 586 | 587 | if self.eresource.dereference_as("_ERESOURCE").is_valid(): 588 | rtl_avl_table = self.rtl_avl_table.dereference_as("_RTL_AVL_TABLE") 589 | if rtl_avl_table.is_valid(page_start, page_end): 590 | 591 | offset_shim = rtl_avl_table.v() + rtl_avl_table.size() 592 | debug.debug("Testing for LRU at 0x{0:08x} (v) 0x{1:08x} (p)".format(offset_shim, self.obj_vm.vtop(offset_shim))) 593 | shim_cache_head = obj.Object("SHIM_CACHE_ENTRY", offset = offset_shim, vm = self.obj_vm) 594 | 595 | if shim_cache_head.is_valid(): 596 | debug.info("Shimcache found at 0x{0:08x}".format(shim_cache_head)) 597 | debug.debug("\t_RTL_AVL_TABLE: 0x{0:08x} 0x{1:08x}".format(rtl_avl_table, rtl_avl_table.obj_vm.vtop(rtl_avl_table.obj_offset))) 598 | debug.debug("\tSHIM_CACHE: 0x{0:08x} 0x{1:08x}".format(shim_cache_head, shim_cache_head.obj_vm.vtop(shim_cache_head.obj_offset))) 599 | self.shim_cache_head = shim_cache_head 600 | return True 601 | 602 | debug.debug("Invalid SHIM_CACHE_HANDLE candidate at 0x{0:08x} (v) 0x{1:08x} (p)".format(self.v(), self.obj_vm.vtop(self.obj_offset))) 603 | return False 604 | 605 | ############################################################################### 606 | # x86 Profile Modifications (borrowed from plugins/registry/shimcache.py) 607 | ############################################################################### 608 | class ShimCacheEntryTypeXPSP2x86(obj.ProfileModification): 609 | """A shimcache entry on Windows XP SP2 (x86)""" 610 | before = ['WindowsObjectClasses'] 611 | conditions = {'os': lambda x: x == 'windows', 612 | 'major': lambda x: x == 5, 613 | 'minor': lambda x: x == 1, 614 | 'memory_model': lambda x: x == '32bit', 615 | 'vtype_module': lambda x: x == 'volatility.plugins.overlays.windows.xp_sp2_x86_vtypes',} 616 | def modification(self, profile): 617 | profile.vtypes.update(shimcache_xp_x86) 618 | profile.vtypes.update(shimcache_xp_sp2_x86) 619 | 620 | class ShimCacheEntryTypeXPSP3x86(obj.ProfileModification): 621 | """A shimcache entry on Windows XP SP3 (x86)""" 622 | before = ['WindowsObjectClasses'] 623 | conditions = {'os': lambda x: x == 'windows', 624 | 'major': lambda x: x == 5, 625 | 'minor': lambda x: x == 1, 626 | 'memory_model': lambda x: x == '32bit', 627 | 'vtype_module': lambda x: x == 'volatility.plugins.overlays.windows.xp_sp3_x86_vtypes',} 628 | def modification(self, profile): 629 | profile.vtypes.update(shimcache_xp_x86) 630 | profile.vtypes.update(shimcache_xp_sp3_x86) 631 | 632 | class ShimCacheEntryType2003x86(obj.ProfileModification): 633 | """A shimcache entry on Windows Server 2003 (x86)""" 634 | before = ['WindowsObjectClasses'] 635 | conditions = {'os': lambda x: x == 'windows', 636 | 'major': lambda x: x == 5, 637 | 'minor': lambda x: x == 2, 638 | 'memory_model': lambda x: x == '32bit'} 639 | def modification(self, profile): 640 | profile.vtypes.update(shimcache_2003_x86) 641 | 642 | class ShimCacheEntryTypeVistax86(obj.ProfileModification): 643 | """A shimcache entry on Windows Vista (x86) and Windows Server 2008 (x86)""" 644 | before = ['WindowsObjectClasses'] 645 | conditions = {'os': lambda x: x == 'windows', 646 | 'major': lambda x: x == 6, 647 | 'minor': lambda x: x == 0, 648 | 'memory_model': lambda x: x == '32bit'} 649 | def modification(self, profile): 650 | profile.vtypes.update(shimcache_vista_x86) 651 | 652 | class ShimCacheEntryTypeWin7x86(obj.ProfileModification): 653 | """A shimcache entry on Windows 7 (x86) and Windows Server 2008 R2 (x86)""" 654 | before = ['WindowsObjectClasses'] 655 | conditions = {'os': lambda x: x == 'windows', 656 | 'major': lambda x: x == 6, 657 | 'minor': lambda x: x == 1, 658 | 'memory_model': lambda x: x == '32bit'} 659 | def modification(self, profile): 660 | profile.vtypes.update(shimcache_win7_x86) 661 | 662 | class ShimCacheEntryTypeWin8x86(obj.ProfileModification): 663 | """A shimcache entry on Windows 8, 8.1, 2012, and 2012 R2 (x86)""" 664 | before = ['WindowsObjectClasses'] 665 | conditions = {'os': lambda x: x == 'windows', 666 | 'major': lambda x: x == 6, 667 | 'minor': lambda x: x in (2,3), 668 | 'memory_model': lambda x: x == '32bit'} 669 | def modification(self, profile): 670 | profile.vtypes.update(shimcache_win8_x86) 671 | profile.vtypes.update(shimcache_win8_x86_detail) 672 | 673 | class ShimCacheEntryTypeWin10x86(obj.ProfileModification): 674 | """A shimcache entry on Windows 10 (x86)""" 675 | before = ['WindowsObjectClasses'] 676 | conditions = {'os': lambda x: x == 'windows', 677 | 'major': lambda x: x == 6, 678 | 'minor': lambda x: x == 4, 679 | 'memory_model': lambda x: x == '32bit'} 680 | def modification(self, profile): 681 | profile.vtypes.update(shimcache_win10_x86) 682 | profile.vtypes.update(shimcache_win10_x86_detail) 683 | 684 | ############################################################################### 685 | # x64 Profile Modifications (borrowed from plugins/registry/shimcache.py) 686 | ############################################################################### 687 | class ShimCacheEntryType2003x64(obj.ProfileModification): 688 | """A shimcache entry on Windows Server 2003 (x64)""" 689 | before = ['WindowsObjectClasses'] 690 | conditions = {'os': lambda x: x == 'windows', 691 | 'major': lambda x: x == 5, 692 | 'minor': lambda x: x == 2, 693 | 'memory_model': lambda x: x == '64bit'} 694 | def modification(self, profile): 695 | profile.vtypes.update(shimcache_2003_x64) 696 | 697 | class ShimCacheEntryTypeVistax64(obj.ProfileModification): 698 | """A shimcache entry on Windows Vista (x64) and Windows Server 2008 (x64)""" 699 | before = ['WindowsObjectClasses'] 700 | conditions = {'os': lambda x: x == 'windows', 701 | 'major': lambda x: x == 6, 702 | 'minor': lambda x: x == 0, 703 | 'memory_model': lambda x: x == '64bit'} 704 | def modification(self, profile): 705 | profile.vtypes.update(shimcache_vista_x64) 706 | 707 | class ShimCacheEntryTypeWin7x64(obj.ProfileModification): 708 | """A shimcache entry on Windows 7 (x64) and Windows Server 2008 R2 (x64)""" 709 | before = ['WindowsObjectClasses'] 710 | conditions = {'os': lambda x: x == 'windows', 711 | 'major': lambda x: x == 6, 712 | 'minor': lambda x: x == 1, 713 | 'memory_model': lambda x: x == '64bit'} 714 | def modification(self, profile): 715 | profile.vtypes.update(shimcache_win7_x64) 716 | 717 | class ShimCacheEntryTypeWin8x64(obj.ProfileModification): 718 | """A shimcache entry on Windows 8, 8.1, 2012, and 2012 R2 (x64)""" 719 | before = ['WindowsObjectClasses'] 720 | conditions = {'os': lambda x: x == 'windows', 721 | 'major': lambda x: x == 6, 722 | 'minor': lambda x: x in (2,3), 723 | 'memory_model': lambda x: x == '64bit'} 724 | def modification(self, profile): 725 | profile.vtypes.update(shimcache_win8_x64) 726 | profile.vtypes.update(shimcache_win8_x64_detail) 727 | 728 | class ShimCacheEntryTypeWin10x64(obj.ProfileModification): 729 | """A shimcache entry on Windows 10 (x64)""" 730 | before = ['WindowsObjectClasses'] 731 | conditions = {'os': lambda x: x == 'windows', 732 | 'major': lambda x: x == 6, 733 | 'minor': lambda x: x == 4, 734 | 'memory_model': lambda x: x == '64bit'} 735 | def modification(self, profile): 736 | profile.vtypes.update(shimcache_win10_x64) 737 | profile.vtypes.update(shimcache_win10_x64_detail) 738 | 739 | class ShimCachex86(obj.ProfileModification): 740 | """The shimcache on x86 platforms""" 741 | before = ['WindowsObjectClasses'] 742 | conditions = {'os': lambda x: x == 'windows', 743 | 'memory_model': lambda x: x == '32bit'} 744 | def modification(self, profile): 745 | profile.vtypes.update(shimcache_objs_x86) 746 | 747 | class ShimCachex64(obj.ProfileModification): 748 | """The shimcache on x64 platforms""" 749 | before = ['WindowsObjectClasses'] 750 | conditions = {'os': lambda x: x == 'windows', 751 | 'memory_model': lambda x: x == '64bit'} 752 | def modification(self, profile): 753 | profile.vtypes.update(shimcache_objs_x64) 754 | 755 | class ShimCacheObjectClasses(obj.ProfileModification): 756 | conditions = {'os': lambda x: x == 'windows'} 757 | 758 | def modification(self, profile): 759 | profile.object_classes.update({'_ERESOURCE': _ERESOURCE}) 760 | profile.object_classes.update({'_RTL_AVL_TABLE': _RTL_AVL_TABLE}) 761 | profile.object_classes.update({'SHIM_CACHE_ENTRY' : ShimCacheEntry}) 762 | profile.object_classes.update({'SHIM_CACHE_HANDLE': ShimCacheHandle}) 763 | 764 | ############################################################################### 765 | # Miscellaneous helper function to print bytes at an offset 766 | ############################################################################### 767 | def print_bytes(addr_space, offset, num_bytes, bytes_per_line=32): 768 | for chunk in range(offset, offset + num_bytes, bytes_per_line): 769 | bytes = addr_space.read(chunk, bytes_per_line) 770 | if bytes: 771 | debug.info(' '.join("%02x" % ord(b) for b in bytes)) 772 | else: 773 | debug.info("Error reading bytes at {0:08x}".format(offset)) 774 | 775 | ############################################################################### 776 | # ShimCacheMem Plugin 777 | ############################################################################### 778 | class ShimCacheMem(common.AbstractWindowsCommand): 779 | """Parses the Application Compatibility Shim Cache stored in kernel memory""" 780 | 781 | # list of NT kernel modules that could contain the shimcache 782 | NT_KRNL_MODS = ['ntoskrnl.exe', 'ntkrnlpa.exe', 'ntkrnlmp.exe', 'ntkrpamp.exe'] 783 | 784 | ########################################################################### 785 | # 786 | ########################################################################### 787 | def __init__(self, config, *args, **kwargs): 788 | common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs) 789 | 790 | config.add_option("CLEAN_FILE_PATHS", 791 | short_option = 'c', 792 | default = False, 793 | help = "Replaces 'SYSVOL' with 'C:\' and strips UNC\Unicode paths; provided as a convenience for analysts", 794 | action = "store_true") 795 | 796 | config.add_option("PRINT_OFFSETS", 797 | short_option = 'P', 798 | default = False, 799 | help = "Print virtual and physical offsets of each shim cache entry \ 800 | (intended for debug/analysis of raw memory images). \ 801 | Only available in 'text' output.", 802 | action = "store_true") 803 | 804 | config.add_option("IGNORE_WIN_APPS", 805 | short_option = 'i', 806 | default = False, 807 | help = "Excludes Windows App entries (Windows 10 only)", 808 | action = "store_true") 809 | 810 | # TODO: pull system/computer name out of the registry instead of asking 811 | # user to specify 812 | config.add_option("SYSTEM_NAME", 813 | default = "", 814 | help = "System name to add to timeline header (only when --print_offsets is not selected)") 815 | 816 | # used to track eresource object sizes depending on platform bitness 817 | self.eresource_sz = None 818 | self.eresource_alignment = None 819 | 820 | ########################################################################### 821 | # 822 | ########################################################################### 823 | def parse_shim_win_xp(self, shim_prcs_space, shim_vad): 824 | """Parses the shim cache in the provided process address space at the 825 | provided virtual address.""" 826 | 827 | SHIM_MAGIC_HEADER = '\xef\xbe\xad\xde' 828 | SHIM_NUM_ENTRIES_OFFSET = 0x8 829 | SHIM_MAX_ENTRIES = 0x60 #96 max entries in XP shim cache 830 | SHIM_LRU_OFFSET = 0x10 831 | SHIM_HEADER_SIZE = 0x190 832 | SHIM_CACHE_ENTRY_SIZE = 0x228 833 | 834 | shim_entries = [] #used to store parsed shim cache entries 835 | 836 | #validate the VAD starts with the magic value (0xDEADBEEF) 837 | data = shim_prcs_space.read(shim_vad.Start, 4) 838 | if data == SHIM_MAGIC_HEADER: 839 | debug.debug("Shim cache magic header found at 0x{0:08x} (0x{1:08x})".format(shim_vad.Start, shim_prcs_space.vtop(shim_vad.Start))) 840 | else: 841 | if data is None: 842 | debug.error("Unable to read VAD at offset 0x{0:x}. This may indicate a corrupt or partial memory dump.".format(shim_vad.Start)) 843 | else: 844 | debug.error("Unexpected magic value '{0}' found in VAD. This may indicate a corrupt or partial memory dump.".format(binascii.hexlify(data))) 845 | return None 846 | 847 | #validate that the number of entries doesn't exceed the max value on Windows XP 848 | num_entries = obj.Object("unsigned int", offset = shim_vad.Start + SHIM_NUM_ENTRIES_OFFSET, vm = shim_prcs_space) 849 | if num_entries > SHIM_MAX_ENTRIES: 850 | debug.error("Number of entries found in cache ({0}) exceeds XP maximum ({1}); aborting.".format(num_entries, SHIM_MAX_ENTRIES)) 851 | return None 852 | 853 | #there is a table at SHIM_LRU_OFFSET that maintains the contains indexes 854 | #used to maintain the order of the shim cache entries; the index table 855 | #must be walked sequentially to calculate the offset of the next entry 856 | #in the shim cache 857 | cache_idx_ptr = shim_vad.Start + SHIM_LRU_OFFSET 858 | 859 | for x in range(0, num_entries): 860 | 861 | #fetch the value of the index using the pointer into the index table 862 | cache_idx_val = obj.Object("unsigned long", offset = cache_idx_ptr, vm = shim_prcs_space) 863 | 864 | #increment the pointer to the next index vlaue 865 | cache_idx_ptr += 4 866 | 867 | #index value cannot exceed the maximum number of entries in a shim cache 868 | if cache_idx_val > SHIM_MAX_ENTRIES-1: 869 | debug.warning("Invalid index value ({0}) found in shim cache LRU table at offset 0x{1:x}.".format(cache_idx_val, cache_idx_ptr)) 870 | continue 871 | 872 | #fetch the shim entry at the specified index 873 | shim_entry_offset = shim_vad.Start + SHIM_HEADER_SIZE + (SHIM_CACHE_ENTRY_SIZE * cache_idx_val) 874 | shim_entry = obj.Object("SHIM_CACHE_ENTRY", offset = shim_entry_offset, vm = shim_prcs_space) 875 | if not shim_entry.is_valid(): 876 | debug.warning("Shim entry contains one or more invalid fields:\n{0}".format(str(shim_entry))) 877 | shim_entries.append(shim_entry) 878 | 879 | return shim_entries 880 | 881 | ########################################################################### 882 | # 883 | ########################################################################### 884 | def find_shim_win_xp(self, addr_space): 885 | """Implements the algorithm to search for the shim cache on Windows XP 886 | (x86). The algorithm consists of the following: 887 | 888 | 1) Find the ShimSharedMemory section object, which every XP user 889 | process has a handle to. 890 | 2) Using the section object, find the process that contains the 891 | shim cache (always winlogon.exe) and the virtual offset in the 892 | process where the shim cache begins 893 | 3) Use the VAD to obtain the corresponding process memory space 894 | 4) Parse the shim cache 895 | """ 896 | shim_prcs_offset = None #offset of process ERESOURCE object containing 897 | #shim cache (winlogon.exe) 898 | shim_prcs_vad = None #virtual address in shim cache process containing 899 | #shim cache 900 | 901 | debug.debug("Searching for ShimSharedMemory section handle...") 902 | 903 | #search all tasks for a section handle named ShimSharedMemory 904 | for task in taskmods.DllList(self._config).calculate(): 905 | 906 | debug.debug("\tChecking {0}".format(task.ImageFileName)) 907 | pid = task.UniqueProcessId 908 | if task.ObjectTable.HandleTableList: 909 | for handle in task.ObjectTable.handles(): 910 | if not handle.is_valid(): 911 | continue 912 | 913 | #check if the handle is to the ShimSharedMemory section 914 | object_type = handle.get_object_type() 915 | if object_type == "Section" and str(handle.NameInfo.Name or '') == "ShimSharedMemory": 916 | debug.debug("\tFound ShimSharedMemory handle in {0} ({1})".format(task.ImageFileName, task.UniqueProcessId)) 917 | 918 | #ShimSharedMemory handle points to a section object 919 | shim_section = handle.dereference_as("_SECTION_OBJECT") 920 | debug.debug("\tShim section object found at (0x{0:08x}) 0x{1:08x}".format(shim_section.obj_offset, shim_section.obj_vm.vtop(shim_section.obj_offset))) 921 | debug.debug("\tSectionObject.Segment (0x{0:08x}) = 0x{1:08x}".format(shim_section.Segment, shim_section.Segment.dereference().obj_offset)) 922 | 923 | #segment field in section object contains additional pointers to shim cache 924 | shim_segment = shim_section.Segment.dereference_as("_SEGMENT") 925 | debug.debug("\tShim segment found at (0x{0:08x}) 0x{1:08x}".format(shim_segment.obj_offset, shim_segment.obj_vm.vtop(shim_segment.obj_offset))) 926 | debug.debug("\tShim Process Offset (0x{0:08x}) = 0x{1:08x}".format(shim_segment.u1.obj_offset, shim_segment.u1.v())) 927 | debug.debug("\tShim Process VAD (0x{0:08x}) = 0x{1:08x}".format(shim_segment.u2.obj_offset, shim_segment.u2.v())) 928 | 929 | #u1 is the offset of the EPROCESS object of the process 930 | #that contains the shim cache (winlogon.exe on XP); u2 931 | #is the virtual address of the cache within that process 932 | shim_prcs_offset = shim_segment.u1.v() 933 | shim_prcs_vad = shim_segment.u2.v() 934 | 935 | break 936 | 937 | #check if process and virtual address of shim were found in previous task 938 | if shim_prcs_offset and shim_prcs_vad: 939 | 940 | #find the process using the shim process offset obtained above 941 | shim_prcs = None 942 | for proc in tasks.pslist(addr_space): 943 | if proc.obj_offset == shim_prcs_offset: 944 | shim_prcs = proc 945 | debug.debug("Process '{0}' ({1}) contains shim cache at virtual address 0x{2:08x}".format(shim_prcs.ImageFileName, shim_prcs.UniqueProcessId, shim_segment.u2.v())) 946 | break 947 | 948 | if shim_prcs is None: 949 | debug.error("Unable to find process at offset 0x{0:08x}. This may indicate a corrupt or partial memory dump.".format(shim_prcs_offset)) 950 | return None 951 | 952 | #process found; find the VAD that correponds to the virtual address offset within the process 953 | shim_vad, shim_prcs_space = None, None 954 | for vad, prcs_space in shim_prcs.get_vads(): 955 | if vad.Start == shim_segment.u2.v(): 956 | debug.debug("Found VAD at 0x{0:x} - 0x{1:x} in {2} ({3})".format(vad.Start, vad.Start+vad.Length, shim_prcs.ImageFileName, shim_prcs.UniqueProcessId)) 957 | shim_vad = vad 958 | shim_prcs_space = prcs_space 959 | 960 | if shim_vad is None or shim_prcs_space is None: 961 | debug.error("Unable to find VAD at 0x{0:x} in process {1} ({2}). This may indicate a corrupt or partial memory dump.".format(shim_prcs_offset, shim_prcs.ImageFileName, shim_prcs.UniqueProcessId)) 962 | return None 963 | 964 | shim_entries = self.parse_shim_win_xp(shim_prcs_space, shim_vad) 965 | return shim_entries 966 | else: 967 | debug.debug("\tNo valid shim section or segment object found in {0} ({1})".format(task.ImageFileName, task.UniqueProcessId)) 968 | 969 | return None 970 | 971 | ########################################################################### 972 | # 973 | ########################################################################### 974 | def find_shim_win_2k3(self, addr_space): 975 | """Implements the algorithm to search for the shim cache on Windows 2000 976 | (x64) through Windows 7 / 2008 R2. The algorithm consists of the following: 977 | 978 | 1) Find the NT kernel module's .data and PAGE sections 979 | 2) Iterate over every 4/8 bytes (depending on OS bitness) in the .data 980 | section and test for the following: 981 | a) offset represents a valid RTL_AVL_TABLE object 982 | b) RTL_AVL_TABLE is preceeded by an ERESOURCE object 983 | c) RTL_AVL_TABLE is followed by the beginning of the SHIM LRU list 984 | """ 985 | data_sec_offset, data_sec_size = self.get_module_section_range(addr_space, self.NT_KRNL_MODS, ".data") 986 | mod_page_offset, mod_page_size = self.get_module_section_range(addr_space, self.NT_KRNL_MODS, "PAGE") 987 | 988 | debug.debug("Scanning range 0x{0:08x} - 0x{1:08x}".format(data_sec_offset, data_sec_offset + data_sec_size)) 989 | 990 | # pointer to head of shim cache (if found) 991 | shim_cache_head = None 992 | 993 | # get pointer size for OS bitness 994 | addr_size = addr_space.profile.get_obj_size("address") 995 | 996 | # iterate over NT kernel module's .data section 997 | for offset in range(data_sec_offset, data_sec_offset + data_sec_size, addr_size): 998 | 999 | # test if current offset is a valid _RTL_AVL_TABLE object; module's page 1000 | # size is used to validate pointers within the AVL table 1001 | rtl_avl_table = obj.Object("_RTL_AVL_TABLE", offset = offset, vm = addr_space) 1002 | if not rtl_avl_table.is_valid(mod_page_offset, mod_page_offset + mod_page_size): 1003 | continue 1004 | 1005 | # calculate relative offset of ERESOURCE, which must preceed AVL table 1006 | offset_ersrc_rel = self.eresource_sz + ((offset - self.eresource_sz) % self.eresource_alignment) 1007 | offset_ersrc = offset - offset_ersrc_rel 1008 | 1009 | # test if calculated offset is a valid _ERESOURCE object 1010 | eresource = obj.Object("_ERESOURCE", offset = offset_ersrc, vm = addr_space) 1011 | if not eresource.is_valid(): 1012 | continue 1013 | 1014 | # calculate offset of shim LRU list that follows AVL table 1015 | offset_shim_list = offset + rtl_avl_table.size() 1016 | 1017 | debug.debug("Shimcache list candidate found at 0x{0:08x}".format(offset_shim_list)) 1018 | shim_cache_head = obj.Object("SHIM_CACHE_ENTRY", offset = offset_shim_list, vm = addr_space) 1019 | 1020 | if not shim_cache_head.is_valid(): 1021 | shim_cache_head = None 1022 | continue 1023 | else: 1024 | debug.debug("Shimcache found at 0x{0:08x}".format(shim_cache_head)) 1025 | debug.debug("\t_ERESOURCE: 0x{0:08x} 0x{1:08x}".format(eresource, eresource.obj_vm.vtop(eresource.obj_offset))) 1026 | debug.debug("\t_RTL_AVL_TABLE: 0x{0:08x} 0x{1:08x}".format(rtl_avl_table, rtl_avl_table.obj_vm.vtop(rtl_avl_table.obj_offset))) 1027 | debug.debug("\tSHIM_CACHE: 0x{0:08x} 0x{1:08x}".format(shim_cache_head, shim_cache_head.obj_vm.vtop(shim_cache_head.obj_offset))) 1028 | break 1029 | 1030 | return shim_cache_head 1031 | 1032 | ########################################################################### 1033 | # 1034 | ########################################################################### 1035 | def find_shim_win_8(self, addr_space, module_list): 1036 | """Implements the algorithm to search for the shim cache on Windows 8 1037 | and 8.1. Returns up to two shim caches. On Windows 8+, there are two 1038 | caches, though only one is relevent to the shim cache. The algorithm 1039 | is as follows: 1040 | 1041 | 1) Find the ntoskrnl.exe (Windows 8) or ahcache.sys (Windows 8.1) 1042 | module's .data and PAGE sections 1043 | 2) Iterate over every 4/8 bytes (depending on OS bitness) in the 1044 | .data section and test for the following: 1045 | a) offset is a pointer to a handle, consisting of two pointers: 1046 | i) pointer to an RTL_AVL_TABLE object 1047 | ii) pointer to an ERESOURCE object 1048 | """ 1049 | data_sec_offset, data_sec_size = self.get_module_section_range(addr_space, module_list, ".data") 1050 | mod_page_offset, mod_page_size = self.get_module_section_range(addr_space, module_list, "PAGE") 1051 | 1052 | debug.debug("Scanning range 0x{0:08x} - 0x{1:08x}".format(data_sec_offset, data_sec_offset + data_sec_size)) 1053 | 1054 | # get pointer size for OS bitness 1055 | addr_size = addr_space.profile.get_obj_size("address") 1056 | 1057 | # list to store the two expected caches 1058 | shim_cache_list = [] 1059 | 1060 | # iterate over ahcache kernel module's .data section in search of *two* SHIM handles 1061 | for offset in range(data_sec_offset, data_sec_offset + data_sec_size, addr_size): 1062 | 1063 | debug.debug("Testing for shim handle at 0x{0:8x} 0x{1:8x}".format(offset, addr_space.vtop(offset))) 1064 | shim_handle_ptr = obj.Object("Pointer", offset = offset, vm = addr_space) 1065 | shim_handle = shim_handle_ptr.dereference_as("SHIM_CACHE_HANDLE") 1066 | 1067 | # module's page size is used to validate pointers within the AVL table 1068 | if shim_handle.is_valid(mod_page_offset, mod_page_offset + mod_page_size): 1069 | shim_cache_head = shim_handle.get_head() 1070 | debug.debug("Shim handle at 0x{0:08x} (0x{1:08x}) points to a valid shim cache at 0x{2:8x}".format(offset, addr_space.vtop(offset), shim_cache_head)) 1071 | shim_cache_list.append(shim_cache_head) 1072 | if len(shim_cache_list) == 2: 1073 | break 1074 | 1075 | if len(shim_cache_list) != 2: 1076 | debug.warning("Unable to find Windows 8 caches") 1077 | shim_cache_list = [None, None] 1078 | 1079 | return shim_cache_list 1080 | 1081 | ########################################################################### 1082 | # 1083 | ########################################################################### 1084 | def get_module_section_range(self, addr_space, module_list, section_name): 1085 | """Locates the size and offset of the specified module section""" 1086 | 1087 | debug.debug("Searching for '{0}' section in the following kernel module(s): {1}".format(section_name, ', '.join(module_list))) 1088 | 1089 | krnl_mod = None 1090 | 1091 | for module in modules.lsmod(addr_space): 1092 | if str(module.BaseDllName or '').lower() in module_list: 1093 | krnl_mod = module 1094 | debug.debug("Found kernel module '{0}' at offset 0x{1:08x}".format(krnl_mod.BaseDllName, krnl_mod.DllBase)) 1095 | break 1096 | else: 1097 | debug.debug("Ignoring module {0}".format(module.BaseDllName)) 1098 | 1099 | if krnl_mod == None: 1100 | debug.error("Unable to locate kernel module(s): {0}".format(', '.join(module_list))) 1101 | return -1,-1 1102 | 1103 | debug.debug("Searching for {0} section...".format(section_name)) 1104 | 1105 | section = None 1106 | 1107 | # code taken from Win32KBase._section_chunks (win32_core.py) 1108 | dos_header = obj.Object("_IMAGE_DOS_HEADER", offset = krnl_mod.DllBase, vm = krnl_mod.obj_vm) 1109 | if dos_header: 1110 | try: 1111 | nt_header = dos_header.get_nt_header() 1112 | 1113 | for sec in nt_header.get_sections(): 1114 | if str(sec.Name or '').lower() == section_name.lower(): 1115 | section = sec 1116 | debug.debug("Found {0} section at 0x{1:08x}".format(section_name, section.VirtualAddress)) 1117 | debug.debug("\tModule Base: 0x{0:08x}".format(krnl_mod.DllBase)) 1118 | debug.debug("\tVirtual Address: 0x{0:08x}".format(section.VirtualAddress)) 1119 | debug.debug("\tVirtual Size: 0x{0:08x}".format(section.Misc.VirtualSize)) 1120 | debug.debug("\tPhysical Address: 0x{0:08x}".format(section.obj_vm.vtop(section.obj_offset))) 1121 | break 1122 | else: 1123 | debug.debug("Ignoring section {0} at address {1}".format(sec.Name, sec.VirtualAddress)) 1124 | 1125 | except ValueError: 1126 | ## This catches PE header parsing exceptions 1127 | pass 1128 | else: 1129 | debug.error("Unable to instantiate DOS header for kernel module") 1130 | return -1,-1 1131 | 1132 | if section == None: 1133 | debug.error("Unable to locate section in kernel module {0}.".format(krnl_mod.BaseDllName)) 1134 | return -1,-1 1135 | 1136 | section_offset = krnl_mod.DllBase + section.VirtualAddress 1137 | section_size = section.Misc.VirtualSize 1138 | debug.debug("Found {0} section at 0x{1:08x} with size 0x{2:x}".format(section_name, section.VirtualAddress, section_size)) 1139 | 1140 | return section_offset, section_size 1141 | 1142 | ########################################################################### 1143 | # 1144 | ########################################################################### 1145 | def calculate(self): 1146 | """Find and dump the shimcache from memory""" 1147 | 1148 | if self._config.SYSTEM_NAME != "": 1149 | self._config.update("SYSTEM_NAME", str(self._config.SYSTEM_NAME)) 1150 | 1151 | debug.debug("Shimcache Memory Dump") 1152 | 1153 | addr_space = utils.load_as(self._config) 1154 | 1155 | #plugin supports XPx64, 2003, 2003R2, Vista, 2008, 2008R2, and 7 1156 | os_vsn_maj = addr_space.profile.metadata.get('major', 0) 1157 | os_vsn_min = addr_space.profile.metadata.get('minor', 0) 1158 | 1159 | # get size and alignment of _ERESOURCE object to use during scanning 1160 | self.eresource_sz = addr_space.profile.get_obj_size("_ERESOURCE") 1161 | 1162 | # it would be more appropriate to define the alignment as an attribute 1163 | # of the ERESOURCE object based on platform bitness 1164 | memory_model = addr_space.profile.metadata.get('memory_model', '32bit') 1165 | if memory_model == '32bit': 1166 | self.eresource_alignment = 0x10 1167 | else: 1168 | self.eresource_alignment = 0x20 1169 | 1170 | # pointer to the head of the shim cache LRU list 1171 | shim_cache_head = None 1172 | 1173 | # plugin currently supports XP x86 (5.1) - Windows 10 (6.4) 1174 | if (os_vsn_maj, os_vsn_min) not in [(5,1),(5,2),(6,0),(6,1),(6,2),(6,3),(6,4)]: 1175 | debug.error("Plugin does not support Windows {0}.{1}. Plugin supports 5.1 (XP) - 6.4 (Windows 10)".format(os_vsn_maj, os_vsn_min)) 1176 | return 1177 | 1178 | #################################### 1179 | # Windows XP x86 1180 | #################################### 1181 | if (os_vsn_maj == 5 and os_vsn_min <= 1): 1182 | shim_cache_list = self.find_shim_win_xp(addr_space) 1183 | if shim_cache_list is None: 1184 | debug.error("XP shim cache not found") 1185 | else: 1186 | for sequence, shim_entry in enumerate(shim_cache_list, start=1): 1187 | yield (sequence, 1188 | shim_entry.get_file_path(), 1189 | shim_entry.get_file_size(), 1190 | shim_entry.get_last_modified(), 1191 | shim_entry.get_last_update(), 1192 | '', #exec flag not present on XP 1193 | shim_entry.obj_offset, 1194 | shim_entry.obj_vm.vtop(shim_entry.obj_offset)) 1195 | debug.debug("Shimcache parsed with {0:d} entries".format(len(shim_cache_list))) 1196 | return 1197 | 1198 | #################################### 1199 | #Windows XP x64, 2003/2003R2, Vista/2008, 7/2008 R2 1200 | #################################### 1201 | elif (os_vsn_maj == 5 and os_vsn_min == 2) or (os_vsn_maj == 6 and os_vsn_min <= 1): 1202 | shim_cache_head = self.find_shim_win_2k3(addr_space) 1203 | 1204 | #################################### 1205 | #Windows 8/2012, 8.1/2012R2, 10 1206 | #################################### 1207 | elif (os_vsn_maj == 6 and os_vsn_min >= 2): 1208 | if os_vsn_min == 2: 1209 | # two sequential caches exist on Windows 8+; on Windows 8 x64, the 1210 | # first cache contains the shim cache. On Windows 8 x86, 8.1 x86/x64, 1211 | # and 10, the second cache contains the shim cache 1212 | shim_cache_list = self.find_shim_win_8(addr_space, self.NT_KRNL_MODS) 1213 | if memory_model == '64bit': 1214 | shim_cache_head = shim_cache_list[0] 1215 | else: 1216 | shim_cache_head = shim_cache_list[1] 1217 | 1218 | elif os_vsn_min in (3, 4): 1219 | # On Windows 8.1 & 10, the second cache is the relevent shim cache 1220 | _, shim_cache_head = self.find_shim_win_8(addr_space, ["ahcache.sys"]) 1221 | 1222 | # if shim cache was found, iterate through the results 1223 | if shim_cache_head: 1224 | debug.debug("Shimcache found at 0x{0:08x}".format(shim_cache_head.obj_offset)) 1225 | 1226 | for sequence, shim_entry in enumerate(shim_cache_head.ListEntry.list_of_type("SHIM_CACHE_ENTRY", "ListEntry"), start=1): 1227 | 1228 | if shim_entry.ListEntry.Flink.Blink != shim_entry.ListEntry.Flink.Blink.dereference().obj_offset: 1229 | debug.warning("Invalid list entry pointer in shimcache entry {0} at 0x{1:08x} (0x{2:08x}); subsequent entries are likely invalid".format(sequence, shim_entry.ListEntry, shim_entry.obj_vm.vtop(shim_entry.obj_offset))) 1230 | debug.warning(shim_entry) 1231 | 1232 | # last item in the shim cache is empty 1233 | if shim_entry.ListEntry.Flink == shim_cache_head: 1234 | debug.debug("End of shim cache list") 1235 | break 1236 | 1237 | yield (sequence, 1238 | shim_entry.get_file_path(), 1239 | shim_entry.get_file_size(), 1240 | shim_entry.get_last_modified(), 1241 | None, #last update only present on XP 1242 | shim_entry.get_exec_flag(), 1243 | shim_entry.obj_offset, 1244 | shim_entry.obj_vm.vtop(shim_entry.obj_offset)) 1245 | 1246 | debug.debug("Shimcache parsed with {0:d} entries".format(sequence)) 1247 | else: 1248 | debug.error("Shimcache not found") 1249 | 1250 | return 1251 | 1252 | ########################################################################### 1253 | # Output Rendering 1254 | ########################################################################### 1255 | def unified_output(self, data): 1256 | """This standardizes the output formatting""" 1257 | 1258 | row = [("Order", int), 1259 | ("Last Modified", str), 1260 | ("Last Update", str), 1261 | ("Exec Flag", str), 1262 | ("File Size", str), # str so that 'None' can be used 1263 | ("File Path", str), 1264 | ] 1265 | 1266 | if self._config.SYSTEM_NAME: 1267 | row.insert(0, ("System Name", str)) 1268 | 1269 | return TreeGrid(row, self.generator(data)) 1270 | 1271 | def generator(self, data): 1272 | """This yields data according to the unified output format""" 1273 | 1274 | time_fmt = '%Y-%m-%d %H:%M:%S' 1275 | 1276 | for sequence, file_path, file_size, last_modified, last_update, exec_flag, offset_virtual, offset_physical in data: 1277 | 1278 | # clean-up paths; intended as a convenience for analysts 1279 | if self._config.CLEAN_FILE_PATHS: 1280 | file_path = file_path.replace("SYSVOL", "C:").replace("\\??\\","").replace("\\\\?\\","") 1281 | 1282 | # exclude Windows App entries (Windows 10 only) 1283 | if self._config.IGNORE_WIN_APPS: 1284 | if last_modified == 0 and file_path[0] == '0': 1285 | continue 1286 | 1287 | # explicit format conversion is required here due to a bug in 1288 | # WinTimeStamp.__format__ that prevents providing a custom format 1289 | # specification to WinTimeStamp.format() 1290 | last_modified_str = last_modified.as_datetime().strftime(time_fmt) or '' 1291 | 1292 | # only set execution flag if a value exists 1293 | exec_flag_str = '' 1294 | if exec_flag in (0,1): 1295 | exec_flag_str = 'True' if exec_flag == 1 else 'False' 1296 | 1297 | # last update is only available on Windows XP 1298 | last_update_str = '' 1299 | if last_update is not None: 1300 | last_update_str = last_update.as_datetime().strftime(time_fmt) or '' 1301 | 1302 | row = [sequence, 1303 | last_modified_str, 1304 | last_update_str, 1305 | exec_flag_str, 1306 | str(file_size or ''), 1307 | str(file_path),] 1308 | 1309 | if self._config.SYSTEM_NAME: 1310 | row.insert(0, self._config.SYSTEM_NAME) 1311 | 1312 | yield (0, row) 1313 | 1314 | def render_csv(self, outfd, data): 1315 | """Renders the ShimCache entries as CSV""" 1316 | self.render_text(outfd, data, delim=',') 1317 | 1318 | def render_text(self, outfd, data, delim=None): 1319 | """Renders the ShimCache entries as text""" 1320 | 1321 | time_fmt = '%Y-%m-%d %H:%M:%S' 1322 | 1323 | tbl_hdr = [("Order", "5"), 1324 | ("Last Modified", "21"), 1325 | ("Last Update", "21"), 1326 | ("Exec Flag", "10"), 1327 | ("File Size", "10"), 1328 | ("File Path", "")] 1329 | 1330 | row_fmt = '{0:5} {1:21} {2:21} {3:10} {4:10} {5}\n' 1331 | 1332 | # if system name is included update header and row format 1333 | if self._config.SYSTEM_NAME: 1334 | tbl_hdr.insert(0, ("System Name", "15")) 1335 | row_fmt = '{0:15} {1:5} {2:21} {3:21} {4:10} {5:10} {6}\n' 1336 | 1337 | # override normal output and print virtual/physical offsets instead 1338 | if self._config.PRINT_OFFSETS: 1339 | tbl_hdr = [("Order", "5"), 1340 | ("Offset (v)", "18"), 1341 | ("Offset (p)", "10"), 1342 | ("File Path", ""),] 1343 | row_fmt = '{0:5} 0x{1:010x} 0x{2:08x} {3}\n' 1344 | 1345 | # check if a delimiter was specified (provides code reuse for CSV option) 1346 | if delim: 1347 | outfd.write("{}\n".format(delim.join(x[0] for x in tbl_hdr))) 1348 | else: 1349 | self.table_header(outfd, tbl_hdr) 1350 | 1351 | # loop over data and write out 1352 | for sequence, file_path, file_size, last_modified, last_update, exec_flag, offset_virtual, offset_physical in data: 1353 | 1354 | # clean-up paths; intended as a convenience for analysts 1355 | if self._config.CLEAN_FILE_PATHS: 1356 | file_path = file_path.replace("SYSVOL", "C:").replace("\\??\\","").replace("\\\\?\\","") 1357 | 1358 | # exclude Windows App entries (Windows 10 only) 1359 | if self._config.IGNORE_WIN_APPS: 1360 | if last_modified == 0 and file_path[0] == '0': 1361 | continue 1362 | 1363 | # explicit format conversion is required here due to a bug in 1364 | # WinTimeStamp.__format__ that prevents providing a custom format 1365 | # specification to WinTimeStamp.format() 1366 | last_modified_str = last_modified.as_datetime().strftime(time_fmt) or '' 1367 | 1368 | # only set execution flag if a value exists 1369 | exec_flag_str = '' 1370 | if exec_flag in (0,1): 1371 | exec_flag_str = 'True' if exec_flag == 1 else 'False' 1372 | 1373 | # last update is only available on Windows XP 1374 | last_update_str = '' 1375 | if last_update is not None: 1376 | last_update_str = last_update.as_datetime().strftime(time_fmt) or '' 1377 | 1378 | # override standard output and print virtual/physical offsets instead 1379 | if self._config.PRINT_OFFSETS: 1380 | outfd.write(row_fmt.format( 1381 | sequence, 1382 | offset_virtual, 1383 | offset_physical, 1384 | file_path, 1385 | )) 1386 | continue 1387 | 1388 | # print row data 1389 | row_flds = [sequence, 1390 | last_modified_str, 1391 | last_update_str, 1392 | exec_flag_str, 1393 | file_size or '', 1394 | file_path, 1395 | ] 1396 | 1397 | # optionally prepend system name 1398 | if self._config.SYSTEM_NAME: 1399 | row_flds.insert(0, self._config.SYSTEM_NAME) 1400 | 1401 | if delim: 1402 | outfd.write("{}\n".format(delim.join(str(x) for x in row_flds))) 1403 | else: 1404 | outfd.write(row_fmt.format(*row_flds)) --------------------------------------------------------------------------------