1
+ # Copyright (c) 2019, Sylvain Peyrefitte
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24
+
25
+ # Authors: Sylvain Peyrefitte <citronneur@gmail.com>
26
+
27
+ # Date: 2019-01-15
28
+ # Version: 1.0
29
+ #
30
+ # Volatility Framework plugin to dump Structure associate with Windows Notification Facilities
31
+ #
32
+ # Usage:
33
+ # 1) Move wnf.py to volatility/plugins in the
34
+ # Volatilty Framework path.
35
+ # 2) Run: python vol.py -f dump_from_windows_system.vmem
36
+ # --profile=Selected_Profile wnf
37
+ #------------------------------------------------------------------------------------
38
+
39
+ import volatility .plugins .common as common
40
+ import volatility .utils as utils
41
+ import volatility .win32 as win32
42
+ import volatility .obj as obj
43
+ from volatility .renderers import TreeGrid
44
+ from volatility .renderers .basic import Address , Hex , Bytes
45
+
46
+ WNF_types = {
47
+ # Header of every WNF struct
48
+ '_WNF_CONTEXT_HEADER' :[0x4 , {
49
+ 'NodeTypeCode' :[0x0 , ['unsigned short' ]],
50
+ 'NodeByteSize' :[0x2 , ['unsigned short' ]]
51
+ }],
52
+
53
+ '_WNF_PROCESS_CONTEXT' :[0x88 , {
54
+ 'Header' : [0x0 , ['_WNF_CONTEXT_HEADER' ]],
55
+ 'EProcess' : [0x8 , ['pointer64' , ['_EPROCESS' ]]],
56
+ 'WnfContexts' : [0x10 , ['_LIST_ENTRY' ]],
57
+ 'Unk1ListHead' : [0x40 , ['_LIST_ENTRY' ]],
58
+ 'SubscriptionListHead' : [0x58 , ['_LIST_ENTRY' ]],
59
+ 'Unk2ListHead' : [0x70 , ['_LIST_ENTRY' ]],
60
+ 'Event' : [0x80 , ['pointer64' , ['_KEVENT' ]]]
61
+ }],
62
+
63
+ '_WNF_SUBSCRIPTION_CONTEXT' :[0x88 , {
64
+ 'Header' : [0x0 , ['_WNF_CONTEXT_HEADER' ]],
65
+ 'SubscriptionContexts' : [0x18 , ['_LIST_ENTRY' ]],
66
+ 'EProcess' : [0x28 , ['pointer' , ['_EPROCESS' ]]],
67
+ 'NameInstance' : [0x30 , ['pointer' , ['_WNF_NAME_INSTANCE_CONTEXT' ]]],
68
+ 'WnfId' : [0x38 , ['unsigned long long' ]],
69
+ 'NameSubscriptionContexts' : [0x40 , ['_LIST_ENTRY' ]]
70
+ }],
71
+
72
+ '_WNF_NAME_INSTANCE_CONTEXT' : [0xa8 , {
73
+ 'Header' : [0x0 , ['_WNF_CONTEXT_HEADER' ]],
74
+ 'NameInstanceContexts' : [0x10 , ['_LIST_ENTRY' ]],
75
+ 'WnfId' : [0x28 , ['unsigned long long' ]],
76
+ 'ScopeInstance' : [0x30 , ['pointer' , ['_WNF_SCOPE_INSTANCE_CONTEXT' ]]],
77
+ 'DataSize' : [0x38 , ['unsigned long long' ]], # Potential available data
78
+ 'WnfData' : [0x58 , ['pointer' , ['_WNF_STATE_DATA' ]]],
79
+ 'NameSubscriptionContexts' : [0x78 , ['_LIST_ENTRY' ]], # List of subscription for this name
80
+ }],
81
+
82
+ '_WNF_SCOPE_INSTANCE_CONTEXT' : [0x50 , {
83
+ 'Header' : [0x0 , ['_WNF_CONTEXT_HEADER' ]],
84
+ 'Scope' : [0x10 , ['unsigned int' ]],
85
+ 'ScopeInstanceContexts' : [0x20 , ['_LIST_ENTRY' ]],
86
+ 'ScopeMapInstanceHead' : [0x28 , ['pointer' , ['void' ]]],
87
+ 'RootNameInstance' : [0x38 , ['pointer' , ['void' ]]] # Pointer to root name instance tree
88
+ }],
89
+
90
+ '_WNF_STATE_DATA' : [None , {
91
+ 'Header' : [0x0 , ['_WNF_CONTEXT_HEADER' ]],
92
+ 'DataSize' : [0x4 , ['unsigned int' ]]
93
+ }],
94
+
95
+ '_WNF_SCOPE_MAP_CONTEXT' :[0x90 , {
96
+ 'Header' : [0x0 , ['_WNF_CONTEXT_HEADER' ]],
97
+ 'ScopeInstanceContextsHead' : [0x8 , ['pointer' , ['_WNF_SCOPE_INSTANCE_CONTEXT' ]]],
98
+ 'ScopeInstanceContextsTail' : [0x10 , ['pointer' , ['_WNF_SCOPE_INSTANCE_CONTEXT' ]]]
99
+ }]
100
+ }
101
+ class _WNF_SCOPE_INSTANCE_CONTEXT (obj .CType ):
102
+ """
103
+ Add some usefull function to access some importants elements
104
+ """
105
+ def get_root_name_context_instance (self ):
106
+ """
107
+ This is because name instance is track over a tree
108
+ :return: _WNF_NAME_INSTANCE_CONTEXT
109
+ """
110
+ return obj .Object (
111
+ '_WNF_NAME_INSTANCE_CONTEXT' ,
112
+ offset = self .RootNameInstance .dereference ().obj_offset - 0x10 ,
113
+ vm = self .obj_vm ,
114
+ parent = self .obj_parent ,
115
+ native_vm = self .obj_native_vm ,
116
+ name = '_WNF_NAME_INSTANCE_CONTEXT'
117
+ )
118
+
119
+ def get_scope_map_instance (self ):
120
+ """
121
+ Track scope instance
122
+ :return: _WNF_SCOPE_MAP_CONTEXT
123
+ """
124
+ return obj .Object (
125
+ '_WNF_SCOPE_MAP_CONTEXT' ,
126
+ offset = self .ScopeMapInstanceHead .dereference ().obj_offset - 0x20 ,
127
+ vm = self .obj_vm ,
128
+ parent = self .obj_parent ,
129
+ native_vm = self .obj_native_vm ,
130
+ name = '_WNF_SCOPE_MAP_CONTEXT'
131
+ )
132
+
133
+ class WnfObjectTypes (obj .ProfileModification ):
134
+ """
135
+ Update profile with WNF types
136
+ """
137
+ conditions = {'os' : lambda x : x == 'windows' }
138
+ def modification (self , profile ):
139
+ profile .vtypes .update (WNF_types )
140
+ profile .object_classes .update ({
141
+ '_WNF_SCOPE_INSTANCE_CONTEXT' : _WNF_SCOPE_INSTANCE_CONTEXT
142
+ })
143
+
144
+ class Wnf (common .AbstractWindowsCommand ):
145
+ """
146
+ Dump WNF name ids for a particular process
147
+ """
148
+
149
+ def __init__ (self , config , * args , ** kwargs ):
150
+ common .AbstractWindowsCommand .__init__ (self , config , * args )
151
+ config .add_option ('pid' , short_option = 'p' , default = None , type = "int" ,
152
+ help = 'PID of target process' , action = "store" )
153
+
154
+ config .add_option ('wnfid' , short_option = 'i' , default = None , type = "str" ,
155
+ help = 'ID of wnf name' , action = "store" )
156
+
157
+ config .add_option ('scope' , short_option = 's' , default = None , type = "str" ,
158
+ help = 'WNF Scope address' , action = "store" )
159
+
160
+ config .add_option ('map' , short_option = 'm' , default = None , type = "str" ,
161
+ help = 'WNF Scope Map address' , action = "store" )
162
+
163
+
164
+ def calculate (self ):
165
+ """
166
+ Walk through all or specific processes
167
+ """
168
+ addr_space = utils .load_as (self ._config )
169
+ for task in win32 .tasks .pslist (addr_space ):
170
+ if self ._config .pid is not None :
171
+ if task .UniqueProcessId .v () == self ._config .pid :
172
+ yield task
173
+ break
174
+ else :
175
+ yield task
176
+
177
+ def unified_output (self , data ):
178
+ return TreeGrid ([
179
+ ("Subscriber" , Address ),
180
+ ("pid" , int ),
181
+ ("Process Name" , str ),
182
+ ("WnfName" , Address ),
183
+ ("WnfId" , Hex ),
184
+ ("Version" , int ),
185
+ ("LifeTime" , int ),
186
+ ("DataScope" , int ),
187
+ ("IsPermanent" , int ),
188
+ ("ScopeMap" , Address ),
189
+ ("Scope" , Address ),
190
+ ("ScopeType" , int ),
191
+ ("HasData" , str ),
192
+ ("DataSize" , int )
193
+ ], self .generator (data ))
194
+
195
+ def generator (self , tasks ):
196
+ for task in tasks :
197
+ wnf_process_context = task .WnfContext .dereference_as ('_WNF_PROCESS_CONTEXT' )
198
+ for subscriber in wnf_process_context .SubscriptionListHead .list_of_type ("_WNF_SUBSCRIPTION_CONTEXT" , "SubscriptionContexts" ):
199
+ # case of subscriber head
200
+ if subscriber .Header .NodeTypeCode != 0x905 :
201
+ continue
202
+
203
+ # sometimes reference a not in memory object (explorer.exe)
204
+ # very strange...
205
+ if subscriber .NameInstance .Header .NodeTypeCode != 0x903 :
206
+ clear = subscriber .WnfId ^ 0x41C64E6DA3BC0074
207
+ else :
208
+ clear = subscriber .NameInstance .WnfId ^ 0x41C64E6DA3BC0074
209
+
210
+ if self ._config .WNFID and int (self ._config .wnfid , 16 ) != clear :
211
+ continue
212
+
213
+ if self ._config .Scope and int (self ._config .scope , 16 ) != subscriber .NameInstance .ScopeInstance .v ():
214
+ continue
215
+
216
+ yield (0 , [
217
+ Address (subscriber .v ()),
218
+ task .UniqueProcessId .v (),
219
+ str (task .ImageFileName ),
220
+ Address (subscriber .NameInstance ),
221
+ Hex (clear ),
222
+ int (clear & 0xf ),
223
+ int (clear >> 4 & 0x3 ),
224
+ int (clear >> 6 & 0xf ),
225
+ int (clear >> 0xa & 0x1 ),
226
+ Address (subscriber .NameInstance .ScopeInstance .get_scope_map_instance ().v ()),
227
+ Address (subscriber .NameInstance .ScopeInstance ),
228
+ int (subscriber .NameInstance .ScopeInstance .Scope ),
229
+ str (bool (subscriber .NameInstance .WnfData )),
230
+ int (subscriber .NameInstance .WnfData .DataSize )
231
+ ])
232
+
233
+
234
+ def hexdump (src , length = 16 ):
235
+ """
236
+ Dump string into hex format
237
+ :param src: memory
238
+ :param length: length of line
239
+ :return: human readable stringand hex
240
+ """
241
+ FILTER = '' .join ([(len (repr (chr (x ))) == 3 ) and chr (x ) or '.' for x in range (256 )])
242
+ lines = []
243
+ for c in xrange (0 , len (src ), length ):
244
+ chars = src [c :c + length ]
245
+ hex = ' ' .join (["%02x" % ord (x ) for x in chars ])
246
+ printable = '' .join (["%s" % ((ord (x ) <= 127 and FILTER [ord (x )]) or '.' ) for x in chars ])
247
+ lines .append ("%04x %-*s %s\n " % (c , length * 3 , hex , printable ))
248
+ return '' .join (lines )
249
+
250
+ class WnfData (common .AbstractWindowsCommand ):
251
+ """
252
+ Dump WNF data Associate to a subscriber
253
+ """
254
+
255
+ def __init__ (self , config , * args , ** kwargs ):
256
+ common .AbstractWindowsCommand .__init__ (self , config , * args )
257
+
258
+ config .add_option ('subscriber' , short_option = 's' , default = None , type = "str" ,
259
+ help = 'Address of subsciber instance' , action = "store" )
260
+
261
+ def calculate (self ):
262
+ addr_space = utils .load_as (self ._config )
263
+ for task in win32 .tasks .pslist (addr_space ):
264
+ wnf_process_context = task .WnfContext .dereference_as ('_WNF_PROCESS_CONTEXT' )
265
+ for subscriber in wnf_process_context .SubscriptionListHead .list_of_type ("_WNF_SUBSCRIPTION_CONTEXT" , "SubscriptionContexts" ):
266
+ if subscriber == int (self ._config .subscriber , 16 ) and bool (subscriber .NameInstance .WnfData ):
267
+ return addr_space .read (subscriber .NameInstance .WnfData + 8 , 4096 )
268
+
269
+ def render_text (self , outfd , data ):
270
+ print hexdump (data )
0 commit comments