Package rekall :: Package plugins :: Package windows :: Package malware :: Module callbacks
[frames] | no frames]

Source Code for Module rekall.plugins.windows.malware.callbacks

  1  # Rekall Memory Forensics 
  2  # Copyright (c) 2010, 2011, 2012 Michael Ligh <michael.ligh@mnin.org> 
  3  # Copyright 2013 Google Inc. All Rights Reserved. 
  4  # 
  5  # This program is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 2 of the License, or (at 
  8  # your option) any later version. 
  9  # 
 10  # This program is distributed in the hope that it will be useful, but 
 11  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 13  # General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the Free Software 
 17  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 18  # 
 19   
 20  # pylint: disable=protected-access 
 21   
 22  from rekall import obj 
 23  from rekall.plugins.windows import common 
 24  from rekall.plugins.overlays.windows import pe_vtypes 
 25  from rekall_lib import utils 
 26   
 27   
 28  callback_types = { 
 29      '_NOTIFICATION_PACKET' : [0x10, { 
 30          'ListEntry' : [0x0, ['_LIST_ENTRY']], 
 31          'DriverObject' : [0x8, ['pointer', ['_DRIVER_OBJECT']]], 
 32          'NotificationRoutine' : [0xC, ['unsigned int']], 
 33      }], 
 34      '_KBUGCHECK_CALLBACK_RECORD' : [0x20, { 
 35          'Entry' : [0x0, ['_LIST_ENTRY']], 
 36          'CallbackRoutine' : [0x8, ['unsigned int']], 
 37          'Buffer' : [0xC, ['pointer', ['void']]], 
 38          'Length' : [0x10, ['unsigned int']], 
 39          'Component' : [0x14, ['pointer', ['String', dict(length=64)]]], 
 40          'Checksum' : [0x18, ['pointer', ['unsigned int']]], 
 41          'State' : [0x1C, ['unsigned char']], 
 42      }], 
 43      '_KBUGCHECK_REASON_CALLBACK_RECORD' : [0x1C, { 
 44          'Entry' : [0x0, ['_LIST_ENTRY']], 
 45          'CallbackRoutine' : [0x8, ['unsigned int']], 
 46          'Component' : [0xC, ['pointer', ['String', dict(length=8)]]], 
 47          'Checksum' : [0x10, ['pointer', ['unsigned int']]], 
 48          'Reason' : [0x14, ['unsigned int']], 
 49          'State' : [0x18, ['unsigned char']], 
 50      }], 
 51      '_SHUTDOWN_PACKET' : [0xC, { 
 52          'Entry' : [0x0, ['_LIST_ENTRY']], 
 53          'DeviceObject' : [0x8, ['pointer', ['_DEVICE_OBJECT']]], 
 54      }], 
 55      '_EX_CALLBACK_ROUTINE_BLOCK' : [0x8, { 
 56          'RundownProtect' : [0x0, ['unsigned int']], 
 57          'Function' : [0x4, ['unsigned int']], 
 58          'Context' : [0x8, ['unsigned int']], 
 59      }], 
 60      '_GENERIC_CALLBACK' : [0xC, { 
 61          'Callback' : [0x4, ['pointer', ['void']]], 
 62          'Associated' : [0x8, ['pointer', ['void']]], 
 63      }], 
 64      '_REGISTRY_CALLBACK_LEGACY' : [0x38, { 
 65          'CreateTime' : [0x0, ['WinFileTime', {}]], 
 66      }], 
 67      '_REGISTRY_CALLBACK' : [None, { 
 68          'ListEntry' : [0x0, ['_LIST_ENTRY']], 
 69          'Function' : [0x1C, ['pointer', ['void']]], 
 70      }], 
 71      '_DBGPRINT_CALLBACK' : [0x14, { 
 72          'Function' : [0x8, ['pointer', ['void']]], 
 73      }], 
 74      '_NOTIFY_ENTRY_HEADER' : [None, { 
 75          'ListEntry' : [0x0, ['_LIST_ENTRY']], 
 76          'EventCategory' : [0x8, ['Enumeration', dict( 
 77              target='long', choices={ 
 78                  0: 'EventCategoryReserved', 
 79                  1: 'EventCategoryHardwareProfileChange', 
 80                  2: 'EventCategoryDeviceInterfaceChange', 
 81                  3: 'EventCategoryTargetDeviceChange'})]], 
 82          'CallbackRoutine' : [0x14, ['unsigned int']], 
 83          'DriverObject' : [0x1C, ['pointer', ['_DRIVER_OBJECT']]], 
 84      }], 
 85      } 
 86   
 87   
 88  callback_types_x64 = { 
 89      '_GENERIC_CALLBACK' : [ 0x18, { 
 90          'Callback' : [ 0x8, ['pointer', ['void']]], 
 91          'Associated' : [ 0x10, ['pointer', ['void']]], 
 92      } ], 
 93      '_NOTIFICATION_PACKET' : [ 0x30, { 
 94          'ListEntry' : [ 0x0, ['_LIST_ENTRY']], 
 95          'DriverObject' : [ 0x10, ['pointer', ['_DRIVER_OBJECT']]], 
 96          'NotificationRoutine' : [ 0x18, ['address']], 
 97      } ], 
 98      '_SHUTDOWN_PACKET' : [ 0xC, { 
 99          'Entry' : [ 0x0, ['_LIST_ENTRY']], 
100          'DeviceObject' : [ 0x10, ['pointer', ['_DEVICE_OBJECT']]], 
101      } ], 
102      '_DBGPRINT_CALLBACK' : [ 0x14, { 
103          'Function' : [ 0x10, ['pointer', ['void']]], 
104      } ], 
105      '_NOTIFY_ENTRY_HEADER' : [ None, { 
106          'ListEntry' : [ 0x0, ['_LIST_ENTRY']], 
107          'EventCategory' : [ 0x10, ['Enumeration', dict( 
108              target = 'long', choices = { 
109              0: 'EventCategoryReserved', 
110              1: 'EventCategoryHardwareProfileChange', 
111              2: 'EventCategoryDeviceInterfaceChange', 
112              3: 'EventCategoryTargetDeviceChange'})]], 
113          'CallbackRoutine' : [ 0x20, ['address']], 
114          'DriverObject' : [ 0x30, ['pointer', ['_DRIVER_OBJECT']]], 
115      }], 
116      '_REGISTRY_CALLBACK' : [ 0x50, { 
117          'ListEntry' : [ 0x0, ['_LIST_ENTRY']], 
118          'Function' : [ 0x20, ['pointer', ['void']]], # other could be 28 
119      }], 
120   
121      # reactos/include/ddk/wdm.h :987 
122      '_KBUGCHECK_CALLBACK_RECORD' : [None, { 
123          'Entry' : [0x0, ['_LIST_ENTRY']], 
124          'CallbackRoutine' : [0x10, ['Pointer']], 
125          'Component' : [0x28, ['Pointer', dict( 
126              target='String', 
127              target_args=dict( 
128                  length=8 
129              ) 
130          )]], 
131      }], 
132   
133      # reactos/include/ddk/wdm.h :962 
134      '_KBUGCHECK_REASON_CALLBACK_RECORD' : [None, { 
135          'Entry' : [0x0, ['_LIST_ENTRY']], 
136          'CallbackRoutine' : [0x10, ['Pointer']], 
137          'Component' : [0x18, ['Pointer', dict( 
138              target='String', 
139          )]], 
140      }], 
141  } 
142   
143   
144 -class _SHUTDOWN_PACKET(obj.Struct):
145 """Class for shutdown notification callbacks""" 146
147 - def sanity_check(self, vm):
148 """ 149 Perform some checks to see if this object can exist in the provided 150 address space. 151 """ 152 153 if (not vm.is_valid_address(self.Entry.Flink) or 154 not vm.is_valid_address(self.Entry.Blink) or 155 not vm.is_valid_address(self.DeviceObject)): 156 return False 157 158 # Dereference the device object 159 device = self.DeviceObject.dereference(vm=vm) 160 161 # Carve out the device's object header and check its type 162 object_header = self.obj_profile.Object( 163 "_OBJECT_HEADER", 164 offset=(device.obj_offset - 165 self.obj_profile.get_obj_offset("_OBJECT_HEADER", "Body")), 166 vm=vm) 167 168 return object_header.get_object_type(vm) == "Device"
169
170 -class AbstractCallbackScanner(common.PoolScanner):
171 """Return the offset of the callback, no object headers"""
172 173
174 -class PoolScanFSCallback(AbstractCallbackScanner):
175 """PoolScanner for File System Callbacks""" 176 checks = [('PoolTagCheck', dict(tag="IoFs")), 177 ('CheckPoolSize', dict(condition=lambda x: x == 0x18)), 178 ('CheckPoolType', dict(non_paged=True, paged=True, 179 free=True)), 180 ] 181
182 - def scan(self, **kwargs):
183 for pool_header in super(PoolScanFSCallback, self).scan(**kwargs): 184 callback = self.profile.Object( 185 '_NOTIFICATION_PACKET', offset=pool_header.end(), 186 vm=self.address_space) 187 188 yield ("IoRegisterFsRegistrationChange", 189 callback.NotificationRoutine, None)
190 191
192 -class PoolScanShutdownCallback(AbstractCallbackScanner):
193 """PoolScanner for Shutdown Callbacks""" 194 checks = [('PoolTagCheck', dict(tag="IoSh")), 195 ('CheckPoolSize', dict(condition=lambda x: x == 0x18)), 196 ('CheckPoolType', dict(non_paged=True, paged=True, 197 free=True)), 198 ('CheckPoolIndex', dict(value=0)), 199 ] 200
201 - def __init__(self, kernel_address_space=None, **kwargs):
202 super(PoolScanShutdownCallback, self).__init__(**kwargs) 203 self.kernel_address_space = kernel_address_space
204
205 - def scan(self, offset=0, **kwargs):
206 for pool_header in super(PoolScanShutdownCallback, self).scan( 207 offset=offset, **kwargs): 208 209 # Instantiate the object in physical space but give it a native VM 210 # of kernel space 211 callback = self.profile._SHUTDOWN_PACKET( 212 offset=pool_header.end(), vm=self.address_space) 213 214 if not callback.sanity_check(self.kernel_address_space): 215 continue 216 217 # Get the callback's driver object. We've already 218 # checked the sanity of the device object pointer. 219 driver_obj = callback.DeviceObject.dereference( 220 vm=self.kernel_address_space).DriverObject 221 222 function_pointer = driver_obj.MajorFunction['IRP_MJ_SHUTDOWN'] 223 details = driver_obj.DriverName 224 225 yield "IoRegisterShutdownNotification", function_pointer, details
226 227
228 -class PoolScanGenericCallback(AbstractCallbackScanner):
229 """PoolScanner for Generic Callbacks""" 230 checks = [('PoolTagCheck', dict(tag="Cbrb")), 231 ('CheckPoolSize', dict(condition=lambda x: x == 0x18)), 232 ('CheckPoolType', dict(non_paged=True, paged=True, free=True)), 233 ] 234
235 - def scan(self, **kwargs):
236 """ 237 Enumerate generic callbacks of the following types: 238 239 * PsSetCreateProcessNotifyRoutine 240 * PsSetThreadCreateNotifyRoutine 241 * PsSetLoadImageNotifyRoutine 242 * CmRegisterCallback (on XP only) 243 * DbgkLkmdRegisterCallback (on Windows 7 only) 244 245 The only issue is that you can't distinguish between the types by just 246 finding the generic callback structure 247 """ 248 for pool_header in super(PoolScanGenericCallback, self).scan(**kwargs): 249 callback = self.profile.Object( 250 '_GENERIC_CALLBACK', offset=pool_header.end(), 251 vm=self.address_space) 252 253 yield "GenericKernelCallback", callback.Callback, None
254 255
256 -class PoolScanDbgPrintCallback(AbstractCallbackScanner):
257 """PoolScanner for DebugPrint Callbacks on Vista and 7""" 258 checks = [('PoolTagCheck', dict(tag="DbCb")), 259 ('CheckPoolSize', dict(condition=lambda x: x == 0x20)), 260 ('CheckPoolType', dict(non_paged=True, paged=True, free=True)), 261 ] 262
263 - def scan(self, offset=0, **kwargs):
264 """Enumerate DebugPrint callbacks on Vista and 7""" 265 for pool_header in super(PoolScanDbgPrintCallback, self).scan( 266 offset=offset, **kwargs): 267 268 callback = self.profile.Object( 269 '_DBGPRINT_CALLBACK', offset=pool_header.end(), 270 vm=self.address_space) 271 272 yield "DbgSetDebugPrintCallback", callback.Function, None
273 274
275 -class PoolScanRegistryCallback(AbstractCallbackScanner):
276 """PoolScanner for DebugPrint Callbacks on Vista and 7""" 277 checks = [('PoolTagCheck', dict(tag="CMcb")), 278 # Seen as 0x38 on Vista SP2 and 0x30 on 7 SP0 279 ('CheckPoolSize', dict(condition=lambda x: x >= 0x38)), 280 ('CheckPoolType', dict(non_paged=True, paged=True, free=True)), 281 ('CheckPoolIndex', dict(value=4)), 282 ] 283
284 - def scan(self, offset=0, **kwargs):
285 """ 286 Enumerate registry callbacks on Vista and 7. 287 288 These callbacks are installed via CmRegisterCallback 289 or CmRegisterCallbackEx. 290 """ 291 for pool_header in super(PoolScanRegistryCallback, self).scan( 292 offset=offset, **kwargs): 293 294 callback = self.profile.Object( 295 '_REGISTRY_CALLBACK', offset=pool_header.end(), 296 vm=self.address_space) 297 298 yield "CmRegisterCallback", callback.Function, None
299 300
301 -class PoolScanPnp9(AbstractCallbackScanner):
302 """PoolScanner for Pnp9 (EventCategoryHardwareProfileChange)""" 303 checks = [('MultiPoolTagCheck', dict(tags=["Pnp9", "PnpD", "PnpC"])), 304 # seen as 0x2C on W7, 0x28 on vistasp0 (4 less but needs 8 less) 305 ('CheckPoolSize', dict(condition=lambda x: x >= 0x30)), 306 ('CheckPoolType', dict(non_paged=True, paged=True, free=True)), 307 ('CheckPoolIndex', dict(value=1)), 308 ] 309
310 - def __init__(self, kernel_address_space=None, **kwargs):
311 self.kernel_address_space = kernel_address_space 312 super(PoolScanPnp9, self).__init__(**kwargs)
313
314 - def scan(self, offset=0, **kwargs):
315 """Enumerate IoRegisterPlugPlayNotification""" 316 for pool_header in super(PoolScanPnp9, self).scan( 317 offset=offset, **kwargs): 318 entry = self.profile.Object( 319 "_NOTIFY_ENTRY_HEADER", offset=pool_header.end(), 320 vm=self.address_space) 321 322 # Dereference the driver object pointer 323 driver = entry.DriverObject.dereference( 324 vm=self.kernel_address_space) 325 326 # Instantiate an object header for the driver name 327 header = self.profile.Object( 328 "_OBJECT_HEADER", 329 offset=(driver.obj_offset - 330 driver.obj_profile.get_obj_offset( 331 "_OBJECT_HEADER", "Body")), 332 vm=driver.obj_vm) 333 334 # Grab the object name 335 driver_name = header.NameInfo.Name.v() 336 337 yield entry.EventCategory, entry.CallbackRoutine, driver_name
338 339
340 -class CallbackScan(common.WindowsCommandPlugin):
341 """Print system-wide notification routines by scanning for them. 342 343 Note this plugin is quite inefficient - consider using the callbacks plugin 344 instead. 345 """ 346 347 __name = "callback_scan" 348
349 - def __init__(self, scan_in_kernel_address_space=False, **kwargs):
350 super(CallbackScan, self).__init__(**kwargs) 351 self.scan_in_kernel_address_space = scan_in_kernel_address_space 352 353 if self.profile.metadata("arch") == "I386": 354 # Add some plugin specific vtypes. 355 self.profile.add_types(callback_types) 356 self.profile.add_classes({ 357 '_SHUTDOWN_PACKET': _SHUTDOWN_PACKET, 358 }) 359 360 self.profile = self.profile.copy() 361 pe_vtypes.PEProfile.Initialize(self.profile) 362 363 else: 364 raise obj.ProfileError("This plugin only supports 32 bit profiles " 365 "for now.")
366
367 - def get_kernel_callbacks(self):
368 """ 369 Enumerate the Create Process, Create Thread, and Image Load callbacks. 370 371 On some systems, the byte sequences will be inaccurate or the exported 372 function will not be found. In these cases, the PoolScanGenericCallback 373 scanner will pick up the pool associated with the callbacks. 374 """ 375 376 routines = ["PspLoadImageNotifyRoutine", 377 "PspCreateThreadNotifyRoutine", 378 "PspCreateProcessNotifyRoutine"] 379 380 for symbol in routines: 381 # The list is an array of 8 _EX_FAST_REF objects 382 callbacks = self.profile.get_constant_object( 383 symbol, 384 target="Array", 385 target_args=dict( 386 count=8, 387 target='_EX_FAST_REF', 388 target_args=dict( 389 target="_GENERIC_CALLBACK", 390 ) 391 ) 392 ) 393 394 for callback in callbacks: 395 if callback.Callback: 396 yield "GenericKernelCallback", callback.Callback, None
397
398 - def get_bugcheck_callbacks(self):
399 """ 400 Enumerate generic Bugcheck callbacks. 401 402 Note: These structures don't exist in tagged pools, but you can find 403 them via KDDEBUGGER_DATA64 on all versions of Windows. 404 """ 405 KeBugCheckCallbackListHead = self.profile.get_constant_object( 406 "KeBugCheckCallbackListHead", "Pointer", target_args=dict( 407 target='_LIST_ENTRY')) 408 409 for l in KeBugCheckCallbackListHead.list_of_type( 410 "_KBUGCHECK_CALLBACK_RECORD", "Entry"): 411 yield ("KeBugCheckCallbackListHead", l.CallbackRoutine, 412 l.Component.dereference())
413
415 """ 416 Enumerate registry change callbacks. 417 418 On XP these are registered using CmRegisterCallback. 419 420 On Vista and Windows 7, these callbacks are registered using the 421 CmRegisterCallbackEx function. 422 """ 423 # The vector is an array of 100 _EX_FAST_REF objects 424 addrs = self.profile.get_constant_object( 425 "CmpCallBackVector", 426 target="Array", 427 target_args=dict( 428 count=100, 429 target="_EX_FAST_REF") 430 ) 431 432 for addr in addrs: 433 callback = addr.dereference_as("_EX_CALLBACK_ROUTINE_BLOCK") 434 if callback: 435 yield "Registry", callback.Function, None
436
438 """ 439 Enumerate Bugcheck Reason callbacks. 440 """ 441 bugs = self.profile.get_constant_object( 442 "KeBugCheckReasonCallbackListHead", 443 target="_LIST_ENTRY") 444 445 for l in bugs.list_of_type( 446 "_KBUGCHECK_REASON_CALLBACK_RECORD", "Entry"): 447 yield ("KeRegisterBugCheckReasonCallback", l.CallbackRoutine, 448 l.Component.dereference())
449
450 - def generate_hits(self):
451 # Get the OS version we're analyzing 452 version = self.profile.metadata('version') 453 454 # Run through the hits but 455 address_space = self.physical_address_space 456 if self.scan_in_kernel_address_space: 457 address_space = self.kernel_address_space 458 459 # Get a scanner group - this will scan for all these in one pass. 460 scanners = dict( 461 PoolScanFSCallback=PoolScanFSCallback( 462 address_space=address_space, 463 profile=self.profile), 464 465 PoolScanShutdownCallback=PoolScanShutdownCallback( 466 profile=self.profile, 467 address_space=address_space, 468 kernel_address_space=self.kernel_address_space), 469 470 PoolScanGenericCallback=PoolScanGenericCallback( 471 address_space=address_space, 472 profile=self.profile), 473 ) 474 475 # Valid for Vista and later 476 if version >= 6.0: 477 scanners.update( 478 PoolScanDbgPrintCallback=PoolScanDbgPrintCallback( 479 address_space=address_space, 480 profile=self.profile), 481 482 PoolScanRegistryCallback=PoolScanRegistryCallback( 483 address_space=address_space, 484 profile=self.profile), 485 486 PoolScanPnp9=PoolScanPnp9( 487 profile=self.profile, 488 address_space=address_space, 489 kernel_address_space=self.kernel_address_space), 490 ) 491 492 for scanner in scanners.values(): 493 for info in scanner.scan(): 494 yield info 495 496 # First few routines are valid on all OS versions 497 for info in self.get_bugcheck_callbacks(): 498 yield info 499 500 for info in self.get_bugcheck_reason_callbacks(): 501 yield info 502 503 for info in self.get_kernel_callbacks(): 504 yield info 505 506 # Valid for XP 507 if version == 5.1: 508 for info in self.get_registry_callbacks_legacy(): 509 yield info
510
511 - def render(self, renderer):
512 renderer.table_header([("Type", "type", "36"), 513 ("Callback", "callback", "[addrpad]"), 514 ("Symbol", "symbol", "50"), 515 ("Details", "details", ""), 516 ]) 517 518 for (sym, cb, detail) in self.generate_hits(): 519 symbol_name = utils.FormattedAddress( 520 self.session.address_resolver, cb) 521 renderer.table_row(sym, cb, symbol_name, detail)
522 523
524 -class Callbacks(common.WindowsCommandPlugin):
525 """Enumerate callback routines. 526 527 This plugin just enumerates installed callback routines from various 528 sources. It does not scan for them. 529 530 This plugin is loosely based on the original Volatility plugin of the same 531 name but much expanded using new information. 532 533 Reference: 534 <http://www.codemachine.com/notes.html> 535 """ 536 537 name = "callbacks" 538 539 table_header = [ 540 dict(name="type", width=36), 541 dict(name="offset", style="address"), 542 dict(name="callback", style="address"), 543 dict(name="symbol", width=50), 544 dict(name="details"), 545 ] 546 547
548 - def __init__(self, *args, **kwargs):
549 super(Callbacks, self).__init__(*args, **kwargs) 550 551 self.profile.add_types(callback_types_x64)
552
553 - def get_generic_callbacks(self):
554 resolver = self.session.address_resolver 555 for table, table_length in [ 556 ("nt!PspLoadImageNotifyRoutine", 557 "nt!PspLoadImageNotifyRoutineCount"), 558 ("nt!PspCreateThreadNotifyRoutine", 559 "nt!PspCreateThreadNotifyRoutineCount"), 560 ("nt!PspCreateProcessNotifyRoutine", 561 "nt!PspCreateProcessNotifyRoutineCount")]: 562 array_length = resolver.get_constant_object( 563 table_length, "unsigned long long") 564 565 array = resolver.get_constant_object( 566 table, 567 target="Array", 568 count=array_length, 569 target_args=dict( 570 target="_EX_FAST_REF", 571 target_args=dict( 572 target="_GENERIC_CALLBACK" 573 ) 574 ) 575 ) 576 577 for callback in array: 578 function = callback.Callback 579 yield (table, callback, function, 580 utils.FormattedAddress(resolver, function))
581
582 - def get_bugcheck_callbacks(self):
583 resolver = self.session.address_resolver 584 585 for list_head_name, type in [ 586 ("nt!KeBugCheckCallbackListHead", "_KBUGCHECK_CALLBACK_RECORD"), 587 ("nt!KeBugCheckReasonCallbackListHead", 588 "_KBUGCHECK_REASON_CALLBACK_RECORD")]: 589 list_head = resolver.get_constant_object( 590 list_head_name, "_LIST_ENTRY") 591 592 for record in list_head.list_of_type(type, "Entry"): 593 function = record.CallbackRoutine 594 595 yield (list_head_name, 596 record, 597 function, 598 utils.FormattedAddress(resolver, function), 599 record.Component)
600
601 - def collect(self):
602 for x in self.get_generic_callbacks(): 603 yield x 604 605 for x in self.get_bugcheck_callbacks(): 606 yield x
607