Package winappdbg :: Module crash
[hide private]
[frames] | no frames]

Source Code for Module winappdbg.crash

   1  # Copyright (c) 2009-2010, Mario Vilas 
   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  #     * Redistributions of source code must retain the above copyright notice, 
   8  #       this list of conditions and the following disclaimer. 
   9  #     * Redistributions in binary form must reproduce the above copyright 
  10  #       notice,this list of conditions and the following disclaimer in the 
  11  #       documentation and/or other materials provided with the distribution. 
  12  #     * Neither the name of the copyright holder nor the names of its 
  13  #       contributors may be used to endorse or promote products derived from 
  14  #       this software without specific prior written permission. 
  15  # 
  16  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  17  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
  18  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
  19  # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
  20  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  21  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
  22  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
  23  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
  24  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
  25  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  26  # POSSIBILITY OF SUCH DAMAGE. 
  27   
  28  """ 
  29  Crash logging module. 
  30   
  31  @group Crash reporting: 
  32      Crash, CrashContainer, CrashTable, CrashTableMSSQL, 
  33      VolatileCrashContainer, DummyCrashContainer 
  34  """ 
  35   
  36  __revision__ = "$Id: crash.py 744 2010-07-11 20:47:15Z qvasimodo $" 
  37   
  38  __all__ = [ 
  39   
  40      # Object that represents a crash in the debugee. 
  41      'Crash', 
  42   
  43      # Container that can store Crash objects in a DBM database. 
  44      'CrashContainer', 
  45   
  46      # Container that can store Crash objects in an SQLite database file. 
  47      'CrashTable', 
  48   
  49      # Container that can store Crash objects in a Microsoft SQL database. 
  50      'CrashTableMSSQL', 
  51   
  52      # Volatile container that does not store Crash objects. 
  53      'VolatileCrashContainer', 
  54   
  55      # Fake Crash container. 
  56      'DummyCrashContainer', 
  57   
  58  ] 
  59   
  60  from system import MemoryAddresses, PathOperations 
  61  from textio import HexDump, CrashDump 
  62  import win32 
  63   
  64  import os 
  65  import time 
  66  import zlib 
  67  import pprint 
  68  import traceback 
  69   
  70  # lazy imports 
  71  anydbm = None 
  72  sqlite = None 
  73  pyodbc = None 
  74   
  75  #============================================================================== 
  76   
  77  # Secure alternative to pickle, use it if present. 
  78  try: 
  79      import cerealizer as pickle 
80 81 # There is no optimization function for cerealized objects. 82 - def optimize(picklestring):
83 return picklestring
84 85 # Note: it's important NOT to provide backwards compatibility, otherwise 86 # it'd be just the same as not having this! To disable this security 87 # upgrade simply uncomment the following line: 88 # raise ImportError, "Fallback to pickle for backwards compatibility" 89 90 # If cerealizer is not present fallback to the insecure pickle module. 91 except ImportError: 92 93 # Optimized version of pickle in C. 94 try: 95 import cPickle as pickle 96 97 # If all fails fallback to the classic pickle module. 98 except ImportError: 99 import pickle 100 101 # Try to use the pickle optimizer if found. 102 try: 103 from pickletools import optimize 104 except ImportError:
105 - def optimize(picklestring):
106 return picklestring
107
108 #============================================================================== 109 110 -class ContainerBase(object):
111 """ 112 Base class for Container types. The code implemented here deals with 113 marshalling and unmarshalling of Crash objects and keys, and it's 114 independent of the database used later. 115 116 @type optimizeKeys: bool 117 @cvar optimizeKeys: C{True} to optimize the marshalling of keys, C{False} 118 otherwise. Only used with the C{pickle} module, ignored when using the 119 more secure C{cerealizer} module. 120 121 @type optimizeValues: bool 122 @cvar optimizeValues: C{True} to optimize the marshalling of keys, C{False} 123 otherwise. Only used with the C{pickle} module, ignored when using the 124 more secure C{cerealizer} module. 125 126 @type compressKeys: bool 127 @cvar compressKeys: C{True} to compress keys when marshalling, C{False} 128 to leave them uncompressed. 129 130 @type compressValues: bool 131 @cvar compressValues: C{True} to compress values when marshalling, C{False} 132 to leave them uncompressed. 133 134 @type escapeKeys: bool 135 @cvar escapeKeys: C{True} to escape keys when marshalling, C{False} 136 to leave them uncompressed. 137 138 @type escapeValues: bool 139 @cvar escapeValues: C{True} to escape values when marshalling, C{False} 140 to leave them uncompressed. 141 142 @type binaryKeys: bool 143 @cvar binaryKeys: C{True} to marshall keys to binary format (the Python 144 C{buffer} type), C{False} to use text marshalled keys (C{str} type). 145 146 @type binaryValues: bool 147 @cvar binaryValues: C{True} to marshall values to binary format (the Python 148 C{buffer} type), C{False} to use text marshalled values (C{str} type). 149 150 @see: L{CrashContainer}, L{CrashTable}, L{VolatileCrashContainer}, 151 L{DummyCrashContainer} 152 """ 153 154 optimizeKeys = False 155 optimizeValues = False 156 compressKeys = False 157 compressValues = False 158 escapeKeys = False 159 escapeValues = False 160 binaryKeys = False 161 binaryValues = False 162
163 - def __init__(self, initialKeys = None):
164 """ 165 @type initialKeys: dict( key S{->} marshalled key ) 166 @param initialKeys: 167 (Optional) Use this dictionary (by reference) to add and match keys 168 to their marshalled counterparts. 169 """ 170 if initialKeys is None: 171 self.__keys = dict() 172 else: 173 self.__keys = initialKeys
174
175 - def __len__(self):
176 """ 177 @rtype: int 178 @return: Count of known keys. 179 """ 180 return len(self.__keys)
181
182 - def __bool__(self):
183 """ 184 @rtype: bool 185 @return: C{False} if there are no known keys. 186 """ 187 return bool(self.__keys)
188
189 - def __contains__(self, crash):
190 """ 191 @type crash: L{Crash} 192 @param crash: Crash object. 193 194 @rtype: bool 195 @return: 196 C{True} if a Crash object with the same key is in the container. 197 """ 198 return self.has_key( crash.key() )
199
200 - def has_key(self, key):
201 """ 202 @type key: L{Crash} key. 203 @param key: Key to find. 204 205 @rtype: bool 206 @return: C{True} if the key is present in the set of known keys. 207 """ 208 return key in self.__keys
209
210 - def iterkeys(self):
211 """ 212 @rtype: iterator 213 @return: Iterator of known L{Crash} keys. 214 """ 215 return self.__keys.iterkeys()
216
217 - def remove_key(self, key):
218 """ 219 Removes the given key from the set of known keys. 220 221 @type key: L{Crash} key. 222 @param key: Key to remove. 223 """ 224 del self.__keys[key]
225
226 - def marshall_key(self, key):
227 """ 228 Marshalls a Crash key to be used in the database. 229 230 @see: L{__init__} 231 232 @type key: L{Crash} key. 233 @param key: Key to convert. 234 235 @rtype: str or buffer 236 @return: Converted key. 237 """ 238 if key in self.__keys: 239 return self.__keys[key] 240 if self.optimizeKeys: 241 # May return different marshalled versions for the same key. 242 skey = pickle.dumps(key, protocol = pickle.HIGHEST_PROTOCOL) 243 skey = optimize(skey) 244 else: 245 # Always returns the same marshalled version for each key. 246 skey = pickle.dumps(key, protocol = 0) 247 if self.compressKeys: 248 skey = zlib.compress(skey, zlib.Z_BEST_COMPRESSION) 249 if self.escapeKeys: 250 skey = skey.encode('hex') 251 if self.binaryKeys: 252 skey = buffer(skey) 253 self.__keys[key] = skey 254 return skey
255
256 - def unmarshall_key(self, key):
257 """ 258 Unmarshalls a Crash key read from the database. 259 260 @type key: str or buffer 261 @param key: Key to convert. 262 263 @rtype: L{Crash} key. 264 @return: Converted key. 265 """ 266 key = str(key) 267 if self.escapeKeys: 268 key = key.decode('hex') 269 if self.compressKeys: 270 key = zlib.decompress(key) 271 key = pickle.loads(key) 272 return key
273
274 - def marshall_value(self, value, storeMemoryMap = False):
275 """ 276 Marshalls a Crash object to be used in the database. 277 The C{memoryMap} member is B{NOT} stored here. 278 279 @warning: Setting the C{storeMemoryMap} argument to C{True} can lead to 280 a severe performance penalty! 281 282 @type value: L{Crash} 283 @param value: Object to convert. 284 285 @type storeMemoryMap: bool 286 @param storeMemoryMap: C{True} to store the memory map, C{False} 287 otherwise. 288 289 @rtype: str 290 @return: Converted object. 291 """ 292 if hasattr(value, 'memoryMap'): 293 crash = value 294 memoryMap = crash.memoryMap 295 try: 296 crash.memoryMap = None 297 if storeMemoryMap and memoryMap is not None: 298 # convert the generator to a list 299 crash.memoryMap = list(memoryMap) 300 if self.optimizeValues: 301 value = pickle.dumps(crash, protocol = pickle.HIGHEST_PROTOCOL) 302 value = optimize(value) 303 else: 304 value = pickle.dumps(crash, protocol = 0) 305 finally: 306 crash.memoryMap = memoryMap 307 del memoryMap 308 del crash 309 if self.compressValues: 310 value = zlib.compress(value, zlib.Z_BEST_COMPRESSION) 311 if self.escapeValues: 312 value = value.encode('hex') 313 if self.binaryValues: 314 value = buffer(value) 315 return value
316
317 - def unmarshall_value(self, value):
318 """ 319 Unmarshalls a Crash object read from the database. 320 321 @type value: str 322 @param value: Object to convert. 323 324 @rtype: L{Crash} 325 @return: Converted object. 326 """ 327 value = str(value) 328 if self.escapeValues: 329 value = value.decode('hex') 330 if self.compressValues: 331 value = zlib.decompress(value) 332 value = pickle.loads(value) 333 return value
334
335 #============================================================================== 336 337 -class CrashContainer (ContainerBase):
338 """ 339 Manages a database of persistent Crash objects, trying to avoid duplicates. 340 341 Uses a DBM database file for persistency. 342 343 @see: L{Crash.key} 344 """ 345 346 optimizeKeys = False 347 optimizeValues = True 348 compressKeys = False 349 compressValues = True 350 escapeKeys = False 351 escapeValues = False 352 binaryKeys = False 353 binaryValues = False 354 355 # The interface is meant to be similar to a Python set. 356 # However it may not be necessary to implement all of the set methods. 357 # Other methods like get, has_key, iterkeys and itervalues 358 # are dictionary-like. 359
360 - class __CrashContainerIterator (object):
361 """ 362 Iterator of Crash objects. Returned by L{CrashContainer.__iter__}. 363 """ 364
365 - def __init__(self, container):
366 """ 367 @type container: L{CrashContainer} 368 @param container: Crash set to iterate. 369 """ 370 # It's important to keep a reference to the CrashContainer, 371 # rather than it's underlying database. 372 # Otherwise the destructor of CrashContainer may close the 373 # database while we're still iterating it. 374 # 375 # TODO: lock the database when iterating it. 376 # 377 self.__container = container 378 self.__keys_iter = container.iterkeys()
379
380 - def next(self):
381 """ 382 @rtype: L{Crash} 383 @return: A B{copy} of a Crash object in the L{CrashContainer}. 384 @raise StopIteration: No more items left. 385 """ 386 key = self.__keys_iter.next() 387 return self.__container.get(key)
388
389 - def __init__(self, filename = None, allowRepeatedKeys = False):
390 """ 391 @type filename: str 392 @param filename: (Optional) File name for crash database. 393 If no filename is specified, the container is volatile. 394 395 Volatile containers are stored only in memory and 396 destroyed when they go out of scope. 397 398 @type allowRepeatedKeys: bool 399 @param allowRepeatedKeys: 400 Currently not supported, always use C{False}. 401 """ 402 if allowRepeatedKeys: 403 raise NotImplementedError 404 self.__filename = filename 405 if filename: 406 global anydbm 407 if not anydbm: 408 import anydbm 409 self.__db = anydbm.open(filename, 'c') 410 keys = dict([ (self.unmarshall_key(mk), mk) 411 for mk in self.__db.keys() ]) 412 ContainerBase.__init__(self, keys) 413 else: 414 self.__db = dict() 415 ContainerBase.__init__(self)
416
417 - def __del__(self):
418 "Class destructor. Closes the database when this object is destroyed." 419 try: 420 if self.__filename: 421 self.__db.close() 422 except: 423 pass
424
425 - def __iter__(self):
426 """ 427 @see: L{itervalues} 428 @rtype: iterator 429 @return: Iterator of the contained L{Crash} objects. 430 """ 431 return self.itervalues()
432
433 - def itervalues(self):
434 """ 435 @rtype: iterator 436 @return: Iterator of the contained L{Crash} objects. 437 438 @warning: A B{copy} of each object is returned, 439 so any changes made to them will be lost. 440 441 To preserve changes do the following: 442 1. Keep a reference to the object. 443 2. Delete the object from the set. 444 3. Modify the object and add it again. 445 """ 446 return self.__CrashContainerIterator(self)
447
448 - def add(self, crash):
449 """ 450 Adds a new crash to the container. 451 If the crash appears to be already known, it's ignored. 452 453 @see: L{Crash.key} 454 455 @type crash: L{Crash} 456 @param crash: Crash object to add. 457 """ 458 # XXX TODO 459 # Support duplicated keys 460 if crash not in self: 461 key = crash.key() 462 skey = self.marshall_key(key) 463 data = self.marshall_value(crash, storeMemoryMap = True) 464 self.__db[skey] = data
465
466 - def remove(self, crash):
467 """ 468 Removes a crash from the container. 469 470 @type crash: L{Crash} 471 @param crash: Crash object to remove. 472 """ 473 key = crash.key() 474 skey = self.marshall_key(key) 475 del self.__db[skey] 476 self.remove_key(key)
477
478 - def get(self, key):
479 """ 480 Retrieves a crash from the container. 481 482 @type key: L{Crash} unique key. 483 @param key: Key of the crash to get. 484 485 @rtype: L{Crash} object. 486 @return: Crash matching the given key. 487 488 @see: L{iterkeys} 489 @warning: A B{copy} of each object is returned, 490 so any changes made to them will be lost. 491 492 To preserve changes do the following: 493 1. Keep a reference to the object. 494 2. Delete the object from the set. 495 3. Modify the object and add it again. 496 """ 497 skey = self.marshall_key(key) 498 data = self.__db[skey] 499 crash = self.unmarshall_value(data) 500 return crash
501
502 #============================================================================== 503 504 -class CrashTable (ContainerBase):
505 """ 506 Manages a database of persistent Crash objects, trying to avoid duplicates 507 only when requested. 508 509 Uses an SQLite database file for persistency. 510 511 @see: L{Crash.key} 512 """ 513 514 optimizeKeys = True 515 optimizeValues = True 516 compressKeys = True 517 compressValues = True 518 escapeKeys = False 519 escapeValues = False 520 binaryKeys = True 521 binaryValues = True 522 523 # XXX TODO 524 # add support for deleting crashes 525 # add support for batch operations on crashes 526 # (maybe with user-defined sql queries?) 527 # add constraints to foreign keys 528 # (need to check compatibility issues first) 529 530 _table_definition = ( 531 "CREATE TABLE Crashes (" 532 533 # Sequential row IDs. 534 "id INTEGER PRIMARY KEY," 535 536 # These are the bare minimum columns required to store the objects. 537 # The rest are just for convenience. 538 "timeStamp TEXT," # float converted to GMT timestamp 539 "key BLOB," # the pickled key 540 "pickle BLOB," # the pickled object 541 542 # Exploitability test. 543 "isExploitable TEXT," # the result 544 "isExploitableRule TEXT," # the matched rule 545 546 # Event description. 547 "eventCode INTEGER," 548 "pid INTEGER," 549 "tid INTEGER," 550 "pc INTEGER," 551 "sp INTEGER," 552 "fp INTEGER," 553 "labelPC TEXT," 554 555 # Exception description. 556 "exceptionCode INTEGER," 557 "exceptionAddress INTEGER," 558 "exceptionLabel TEXT," 559 "firstChance INTEGER," # 0 or 1 560 "faultType INTEGER," 561 "faultAddress INTEGER," 562 "faultLabel TEXT," 563 "faultDisasm TEXT," # dumped 564 "stackTrace TEXT," # dumped stackTracePretty 565 566 # Additional information. 567 "commandLine TEXT," 568 "environment TEXT," # joined 569 "notes TEXT" # joined 570 ")" 571 ) 572 _insert_row = ( 573 "INSERT INTO Crashes VALUES (null, %s)" 574 % ','.join(['?'] * (len(_table_definition.split(',')) - 1)) 575 ) 576 577 _select_pickle = "SELECT pickle FROM Crashes" 578 _select_key = "SELECT key FROM Crashes" 579 _select_count = "SELECT COUNT(*) FROM Crashes" 580 581 _memory_table_definition = ( 582 "CREATE TABLE Memory (" 583 "id INTEGER PRIMARY KEY," # Sequential row IDs. 584 "Crash INTEGER," # Row ID in the Crashes table. 585 "Address INTEGER," # Value of mbi.BaseAddress. 586 "Size INTEGER," # Value of mbi.RegionSize. 587 "State TEXT," # Value of mbi.State. 588 "Access TEXT," # Value of mbi.Protect. 589 "Type TEXT," # Value of mbi.Type. 590 "File TEXT," # Value of mbi.filename. 591 "Data BLOB" # Value of mbi.content (compressed?). 592 ")" 593 ) 594 _memory_insert_row = ( 595 "INSERT INTO Memory VALUES (null, %s)" 596 % ','.join(['?'] * (len(_memory_table_definition.split(',')) - 1)) 597 ) 598 599 # XXX TODO 600 # this coupling won't behave all that well with subclasses of Crash... 601 # maybe the Crash class should have a way of reporting the CrashTable what 602 # columns to use and what data to put in each column
603 - def _get_row_values(self, crash):
604 """ 605 Private method, do not use. 606 """ 607 timeStamp = time.asctime( time.gmtime( crash.timeStamp ) ) 608 key = self.marshall_key( crash.key() ) 609 pickle = self.marshall_value(crash) 610 isExploitable, isExploitableRule, _ = crash.isExploitable() 611 eventCode = crash.eventCode 612 pid = crash.pid 613 tid = crash.tid 614 pc = crash.pc 615 sp = crash.sp 616 fp = crash.fp 617 labelPC = crash.labelPC 618 exceptionCode = crash.exceptionCode 619 exceptionAddress = crash.exceptionAddress 620 exceptionLabel = crash.exceptionLabel 621 firstChance = crash.firstChance # int(bool(crash.firstChance)) 622 faultType = crash.faultType 623 faultAddress = crash.faultAddress 624 faultLabel = crash.faultLabel 625 faultDisasm = CrashDump.dump_code(crash.faultDisasm, crash.pc) 626 stackTrace = CrashDump.dump_stack_trace_with_labels( 627 crash.stackTracePretty) 628 commandLine = crash.commandLine 629 if type(crash.environmentData) == type(u''): 630 environment = u'\0'.join(crash.environmentData) + u'\0' 631 else: 632 environment = '\0'.join(crash.environmentData) + '\0' 633 notes = crash.notesReport() 634 return ( 635 timeStamp, 636 key, 637 pickle, 638 isExploitable, 639 isExploitableRule, 640 eventCode, 641 pid, 642 tid, 643 pc, 644 sp, 645 fp, 646 labelPC, 647 exceptionCode, 648 exceptionAddress, 649 exceptionLabel, 650 firstChance, 651 faultType, 652 faultAddress, 653 faultLabel, 654 faultDisasm, 655 stackTrace, 656 commandLine, 657 environment, 658 notes, 659 )
660
661 - def _memory_get_row_values(self, CrashID, mbi):
662 """ 663 Private method, do not use. 664 """ 665 666 # State (free or allocated). 667 if mbi.State == win32.MEM_RESERVE: 668 State = "Reserved" 669 elif mbi.State == win32.MEM_COMMIT: 670 State = "Commited" 671 elif mbi.State == win32.MEM_FREE: 672 State = "Free" 673 else: 674 State = "Unknown" 675 676 # Page protection bits (R/W/X/G). 677 if mbi.State != win32.MEM_COMMIT: 678 Protect = "" 679 else: 680 if mbi.Protect & win32.PAGE_NOACCESS: 681 Protect = "--- " 682 elif mbi.Protect & win32.PAGE_READONLY: 683 Protect = "R-- " 684 elif mbi.Protect & win32.PAGE_READWRITE: 685 Protect = "RW- " 686 elif mbi.Protect & win32.PAGE_WRITECOPY: 687 Protect = "RC- " 688 elif mbi.Protect & win32.PAGE_EXECUTE: 689 Protect = "--X " 690 elif mbi.Protect & win32.PAGE_EXECUTE_READ: 691 Protect = "R-- " 692 elif mbi.Protect & win32.PAGE_EXECUTE_READWRITE: 693 Protect = "RW- " 694 elif mbi.Protect & win32.PAGE_EXECUTE_WRITECOPY: 695 Protect = "RCX " 696 else: 697 Protect = "??? " 698 if mbi.Protect & win32.PAGE_GUARD: 699 Protect += "G" 700 else: 701 Protect += "-" 702 if mbi.Protect & win32.PAGE_NOCACHE: 703 Protect += "N" 704 else: 705 Protect += "-" 706 if mbi.Protect & win32.PAGE_WRITECOMBINE: 707 Protect += "W" 708 else: 709 Protect += "-" 710 711 # Type (file mapping, executable image, or private memory). 712 if mbi.Type == win32.MEM_IMAGE: 713 Type = "Image" 714 elif mbi.Type == win32.MEM_MAPPED: 715 Type = "Mapped" 716 elif mbi.Type == win32.MEM_PRIVATE: 717 Type = "Private" 718 elif mbi.Type == 0: 719 Type = "" 720 else: 721 Type = "Unknown" 722 723 # Memory contents. 724 # 725 # XXX TODO 726 # Storing this in the db is insane! :( 727 # Each memory dump has to be placed in an external file, using random 728 # filenames to avoid collisions. Then the filenames can be stored in 729 # this column instead of the data. 730 # 731 content = mbi.content 732 if not content: 733 content = '' 734 else: 735 content = zlib.compress(content, zlib.Z_BEST_COMPRESSION) 736 content = buffer(content) 737 738 # Return a tuple to pass to Cursor.execute(). 739 return ( 740 CrashID, 741 mbi.BaseAddress, 742 mbi.RegionSize, 743 State, 744 Protect, 745 Type, 746 mbi.filename, 747 content, 748 )
749
750 - def __init__(self, location = None, allowRepeatedKeys = True):
751 """ 752 @type location: str 753 @param location: (Optional) Location of the crash database. 754 If the location is a filename, it's an SQLite database file. 755 756 If no location is specified, the container is volatile. 757 Volatile containers are stored only in memory and 758 destroyed when they go out of scope. 759 760 @type allowRepeatedKeys: bool 761 @param allowRepeatedKeys: 762 If C{True} all L{Crash} objects are stored. 763 764 If C{False} any L{Crash} object with the same key as a 765 previously existing object will be ignored. 766 """ 767 768 # Open the database file. 769 location, dbtype, db, cursor = self._connect(location) 770 771 # Store the database connection objects. 772 self._location = location 773 self._dbtype = dbtype 774 self._db = db 775 self._cursor = cursor 776 777 # Create the tables if needed. 778 try: 779 self._cursor.execute(self._table_definition) 780 self._cursor.execute(self._memory_table_definition) 781 self._db.commit() 782 except Exception: 783 ## raise # XXX DEBUG 784 pass 785 786 # Populate the cache of existing keys. 787 self._allowRepeatedKeys = allowRepeatedKeys 788 keys = dict() 789 self._cursor.execute(self._select_key) 790 for row in self._cursor: 791 marshalled_key = row[0] 792 unmarshalled_key = self.unmarshall_key(marshalled_key) 793 keys[unmarshalled_key] = marshalled_key 794 ContainerBase.__init__(self, keys) 795 796 # Get the number of crashes stored in the database. 797 self._cursor.execute(self._select_count) 798 count = 0 799 for row in self._cursor: 800 count = long(row[0]) 801 self._count = count
802
803 - def _connect(self, location):
804 """ 805 Open the given SQLite file. 806 807 @note: This is a private method and you shouldn't need to call it. 808 809 @type location: str 810 @param location: (Optional) Location of the crash database. 811 If the location is a filename, it's an SQLite database file. 812 813 If no location is specified, the container is volatile. 814 Volatile containers are stored only in memory and 815 destroyed when they go out of scope. 816 817 @rtype: tuple( str, str, database, cursor ) 818 @return: 819 Tuple of location, database type, database object, cursor object. 820 """ 821 822 # Load the SQLite module. 823 global sqlite 824 if not sqlite: 825 try: 826 import sqlite3 as sqlite 827 except ImportError: 828 from pysqlite2 import sqlite 829 830 # Connect to the SQL database. 831 if location is None: 832 location = ':memory:' 833 db = sqlite.connect(location) 834 cursor = db.cursor() 835 836 # Return the database connection. 837 return location, 'sqlite', db, cursor
838
839 - def add(self, crash):
840 """ 841 Adds a new crash to the container. 842 843 @note: 844 When the C{allowRepeatedKeys} parameter of the constructor 845 is set to C{False}, duplicated crashes are ignored. 846 847 @see: L{Crash.key} 848 849 @type crash: L{Crash} 850 @param crash: Crash object to add. 851 """ 852 853 # Filter out repeated crashes if requested. 854 if self._allowRepeatedKeys or crash not in self: 855 856 # Insert the row into the table. 857 row_values = self._get_row_values(crash) 858 self._cursor.execute(self._insert_row, row_values) 859 860 # Save the memory snapshot, if any. 861 if hasattr(crash, 'memoryMap') and crash.memoryMap: 862 cid = self._cursor.lastrowid 863 for mbi in crash.memoryMap: 864 row_values = self._memory_get_row_values(cid, mbi) 865 self._cursor.execute(self._memory_insert_row, row_values) 866 867 # Commit the changes to the database. 868 # On error discard the changes and raise an exception. 869 try: 870 self._db.commit() 871 except Exception: 872 self._db.rollback() 873 raise 874 875 # Increment the counter of crashes. 876 self._count += 1
877
878 - def __iter__(self):
879 """ 880 @rtype: iterator 881 @return: Iterator of the contained L{Crash} objects. 882 """ 883 self._cursor.execute(self._select_pickle) 884 for row in self._cursor: 885 crash = row[0] 886 crash = self.unmarshall_value(crash) 887 yield crash
888
889 - def __len__(self):
890 """ 891 @rtype: int 892 @return: Count of L{Crash} elements in the container. 893 """ 894 return self._count
895
896 #============================================================================== 897 898 -class CrashTableMSSQL (CrashTable):
899 """ 900 Manages a database of persistent Crash objects, trying to avoid duplicates 901 only when requested. 902 903 Uses a Microsoft SQL database for persistency. 904 905 @see: L{Crash.key} 906 """ 907 908 optimizeKeys = True 909 optimizeValues = True 910 compressKeys = True 911 compressValues = True 912 escapeKeys = False 913 escapeValues = False 914 binaryKeys = True 915 binaryValues = True 916 917 _table_definition = ( 918 "CREATE TABLE Crashes (" 919 920 # Sequential row IDs. 921 "id INTEGER IDENTITY(1,1) PRIMARY KEY CLUSTERED," 922 923 # These are the bare minimum columns required to store the objects. 924 # The rest are just for convenience. 925 "timeStamp TEXT NOT NULL," # float converted to GMT timestamp 926 "pickled_key IMAGE NOT NULL," # the pickled key 927 "pickled_obj IMAGE NOT NULL," # the pickled object 928 929 # Exploitability test. 930 "isExploitable TEXT," # the result 931 "isExploitableRule TEXT," # the matched rule 932 933 # Event description. 934 "eventCode BIGINT," 935 "pid BIGINT," 936 "tid BIGINT," 937 "pc BIGINT," 938 "sp BIGINT," 939 "fp BIGINT," 940 "labelPC TEXT," 941 942 # Exception description. 943 "exceptionCode BIGINT," 944 "exceptionAddress BIGINT," 945 "exceptionLabel TEXT," 946 "firstChance INTEGER," # 0 or 1 947 "faultType INTEGER," 948 "faultAddress BIGINT," 949 "faultLabel TEXT," 950 "faultDisasm TEXT," # dumped 951 "stackTrace TEXT," # dumped stackTracePretty 952 953 # Additional notes. 954 "notes TEXT" # joined 955 ")" 956 ) 957 _insert_row = ( 958 "INSERT INTO Crashes VALUES (%s)" 959 % ', '.join(list('?' * 22)) 960 ) 961 962 _select_pickle = "SELECT pickled_obj FROM Crashes" 963 _select_key = "SELECT pickled_key FROM Crashes" 964 _select_count = "SELECT COUNT(*) FROM Crashes" 965 966 _memory_table_definition = ( 967 "CREATE TABLE Memory (" 968 "id INTEGER IDENTITY(1,1) PRIMARY KEY CLUSTERED," # Sequential row IDs. 969 "Crash INTEGER," # Row ID in the Crashes table. 970 "Address BIGINT NOT NULL," # Value of mbi.BaseAddress. 971 "Size BIGINT NOT NULL," # Value of mbi.RegionSize. 972 "State TEXT NOT NULL," # Value of mbi.State. 973 "Access TEXT NOT NULL," # Value of mbi.Protect. 974 "Type TEXT NOT NULL," # Value of mbi.Type. 975 "Filename TEXT," # Value of mbi.filename. 976 "Data IMAGE" # Value of mbi.content (compressed?). 977 ")" 978 ) 979 _memory_insert_row = ( 980 "INSERT INTO Memory VALUES (%s)" 981 % ', '.join(list('?' * 8)) 982 ) 983
984 - def _connect(self, connectionString):
985 """ 986 Connect to a remote SQL database using the given connection string. 987 988 @note: This is a private method and you shouldn't need to call it. 989 990 @type connectionString: str 991 @param connectionString: (Optional) ODBC connection string. 992 993 @rtype: tuple( str, str, database, cursor ) 994 @return: 995 Tuple of location, database type, database object, cursor object. 996 """ 997 998 # Load the pyODBC module. 999 global pyodbc 1000 if not pyodbc: 1001 import pyodbc 1002 1003 # Connect to the SQL database. 1004 db = pyodbc.connect(connectionString, autocommit=True) 1005 cursor = db.cursor() 1006 1007 # Return the database connection. 1008 return connectionString, 'odbc', db, cursor
1009
1010 #============================================================================== 1011 1012 -class VolatileCrashContainer(CrashContainer):
1013 """ 1014 Manages a database of volatile Crash objects, 1015 trying to avoid duplicates if requested. 1016 1017 @see: L{Crash.key} 1018 """ 1019
1020 - def __init__(self, allowRepeatedKeys = True):
1021 """ 1022 Volatile containers are stored only in memory and 1023 destroyed when they go out of scope. 1024 1025 @type allowRepeatedKeys: bool 1026 @param allowRepeatedKeys: 1027 If C{True} all L{Crash} objects are stored. 1028 1029 If C{False} any L{Crash} object with the same key as a 1030 previously existing object will be ignored. 1031 """ 1032 super(VolatileCrashContainer, self).__init__() 1033 self.__allowRepeatedKeys = allowRepeatedKeys 1034 self.__dict = dict() 1035 self.__set = set()
1036
1037 - def __contains__(self, crash):
1038 """ 1039 @type crash: L{Crash} 1040 @param crash: Crash object. 1041 1042 @rtype: bool 1043 @return: C{True} if the Crash object is in the container. 1044 """ 1045 return self._dict.has_key( crash.key() )
1046
1047 - def __iter__(self):
1048 """ 1049 @see: L{itervalues} 1050 @rtype: iterator 1051 @return: Iterator of the contained L{Crash} objects. 1052 """ 1053 return self.itervalues()
1054
1055 - def __len__(self):
1056 """ 1057 @rtype: int 1058 @return: Count of L{Crash} elements in the container. 1059 """ 1060 return len(self.__set)
1061
1062 - def __bool__(self):
1063 """ 1064 @rtype: bool 1065 @return: C{False} if the container is empty. 1066 """ 1067 return bool(len(self))
1068
1069 - def add(self, crash):
1070 """ 1071 Adds a new crash to the container. 1072 1073 @note: 1074 When the C{allowRepeatedKeys} parameter of the constructor 1075 is set to C{False}, duplicated crashes are ignored. 1076 1077 @see: L{Crash.key} 1078 1079 @type crash: L{Crash} 1080 @param crash: Crash object to add. 1081 """ 1082 key = crash.key() 1083 if self.__allowRepeatedKeys or key not in self.__dict: 1084 self.__dict[key] = crash 1085 self.__set.add(crash)
1086
1087 - def has_key(self, key):
1088 """ 1089 @type key: L{Crash} unique key. 1090 @param key: Key of the crash to get. 1091 1092 @rtype: bool 1093 @return: C{True} if a matching L{Crash} object is in the container. 1094 """ 1095 return self.__dict.has_key(key)
1096
1097 - def iterkeys(self):
1098 """ 1099 @rtype: iterator 1100 @return: Iterator of the contained L{Crash} object keys. 1101 1102 @see: L{get} 1103 @warning: A B{copy} of each object is returned, 1104 so any changes made to them will be lost. 1105 1106 To preserve changes do the following: 1107 1. Keep a reference to the object. 1108 2. Delete the object from the set. 1109 3. Modify the object and add it again. 1110 """ 1111 return self.__dict.iterkeys()
1112
1113 - def itervalues(self):
1114 """ 1115 @rtype: iterator 1116 @return: Iterator of the contained L{Crash} objects. 1117 1118 @warning: A B{copy} of each object is returned, 1119 so any changes made to them will be lost. 1120 1121 To preserve changes do the following: 1122 1. Keep a reference to the object. 1123 2. Delete the object from the set. 1124 3. Modify the object and add it again. 1125 """ 1126 return iter(self.__set)
1127
1128 #============================================================================== 1129 1130 -class DummyCrashContainer(CrashContainer):
1131 """ 1132 Fakes a database of volatile Crash objects, 1133 trying to mimic part of it's interface, but 1134 doesn't actually store anything. 1135 1136 @see: L{Crash.key} 1137 """ 1138
1139 - def __init__(self, allowRepeatedKeys = True):
1140 """ 1141 Fake containers don't store L{Crash} objects, but they implement the 1142 interface properly. 1143 1144 @type allowRepeatedKeys: bool 1145 @param allowRepeatedKeys: 1146 If C{True} the len() of the container returns the total number 1147 of L{Crash} objects added. 1148 1149 If C{False} the len() of the container returns the total number 1150 of L{Crash} objects keys added. 1151 """ 1152 super(DummyCrashContainer, self).__init__() 1153 self.__keys = set() 1154 self.__count = 0 1155 self.__allowRepeatedKeys = allowRepeatedKeys
1156
1157 - def __contains__(self, crash):
1158 """ 1159 @type crash: L{Crash} 1160 @param crash: Crash object. 1161 1162 @rtype: bool 1163 @return: C{True} if the Crash object is in the container. 1164 """ 1165 return crash.key() in self.__keys
1166
1167 - def __len__(self):
1168 """ 1169 @rtype: int 1170 @return: Count of L{Crash} elements in the container. 1171 """ 1172 if self.__allowRepeatedKeys: 1173 return self.__count 1174 return len(self.__keys)
1175
1176 - def __bool__(self):
1177 """ 1178 @rtype: bool 1179 @return: C{False} if the container is empty. 1180 """ 1181 return bool(len(self))
1182
1183 - def add(self, crash):
1184 """ 1185 Adds a new crash to the container. 1186 1187 @note: 1188 When the C{allowRepeatedKeys} parameter of the constructor 1189 is set to C{False}, duplicated crashes are ignored. 1190 1191 @see: L{Crash.key} 1192 1193 @type crash: L{Crash} 1194 @param crash: Crash object to add. 1195 """ 1196 self.__keys.add( crash.key() ) 1197 self.__count += 1
1198
1199 - def has_key(self, key):
1200 """ 1201 @type key: L{Crash} unique key. 1202 @param key: Key of the crash to get. 1203 1204 @rtype: bool 1205 @return: C{True} if a matching L{Crash} object is in the container. 1206 """ 1207 return self.__dict.has_key(key)
1208
1209 - def iterkeys(self):
1210 """ 1211 @rtype: iterator 1212 @return: Iterator of the contained L{Crash} object keys. 1213 1214 @see: L{get} 1215 @warning: A B{copy} of each object is returned, 1216 so any changes made to them will be lost. 1217 1218 To preserve changes do the following: 1219 1. Keep a reference to the object. 1220 2. Delete the object from the set. 1221 3. Modify the object and add it again. 1222 """ 1223 return iter(self.__dict)
1224
1225 #============================================================================== 1226 1227 -class Crash (object):
1228 """ 1229 Represents a crash, bug, or another interesting event in the debugee. 1230 1231 @group Key: 1232 key 1233 1234 @group Report: 1235 briefReport, fullReport, notesReport, isExploitable 1236 1237 @group Notes: 1238 addNote, getNotes, iterNotes, hasNotes, clearNotes, notes 1239 1240 @group Miscellaneous: 1241 fetch_extra_data 1242 1243 @group Basic information: 1244 timeStamp, eventCode, eventName, pid, tid, registers, labelPC 1245 1246 @group Optional information: 1247 debugString, 1248 modFileName, 1249 lpBaseOfDll, 1250 exceptionCode, 1251 exceptionName, 1252 exceptionDescription, 1253 exceptionAddress, 1254 exceptionLabel, 1255 firstChance, 1256 faultType, 1257 faultAddress, 1258 faultLabel, 1259 isOurBreakpoint, 1260 isSystemBreakpoint, 1261 stackTrace, 1262 stackTracePC, 1263 stackTraceLabels, 1264 stackTracePretty 1265 1266 @group Extra information: 1267 commandLine, 1268 registersPeek, 1269 stackRange, 1270 stackFrame, 1271 stackPeek, 1272 faultCode, 1273 faultMem, 1274 faultPeek, 1275 faultDisasm, 1276 memoryMap 1277 1278 @type timeStamp: float 1279 @ivar timeStamp: Timestamp as returned by time.time(). 1280 1281 @type notes: list( str ) 1282 @ivar notes: List of strings, each string is a note. 1283 1284 @type eventCode: int 1285 @ivar eventCode: Event code as defined by the Win32 API. 1286 1287 @type eventName: str 1288 @ivar eventName: Event code user-friendly name. 1289 1290 @type pid: int 1291 @ivar pid: Process global ID. 1292 1293 @type tid: int 1294 @ivar tid: Thread global ID. 1295 1296 @type commandLine: None or str 1297 @ivar commandLine: Command line for the target process. 1298 1299 C{None} if unapplicable or unable to retrieve. 1300 1301 @type environmentData: None or list of str 1302 @ivar environmentData: Environment data for the target process. 1303 1304 C{None} if unapplicable or unable to retrieve. 1305 1306 @type environment: None or dict( str S{->} str ) 1307 @ivar environment: Environment variables for the target process. 1308 1309 C{None} if unapplicable or unable to retrieve. 1310 1311 @type registers: dict( str S{->} int ) 1312 @ivar registers: Dictionary mapping register names to their values. 1313 1314 @type registersPeek: None or dict( str S{->} str ) 1315 @ivar registersPeek: Dictionary mapping register names to the data they point to. 1316 1317 C{None} if unapplicable or unable to retrieve. 1318 1319 @type labelPC: None or str 1320 @ivar labelPC: Label pointing to the program counter. 1321 1322 C{None} or invalid if unapplicable or unable to retrieve. 1323 1324 @type debugString: None or str 1325 @ivar debugString: Debug string sent by the debugee. 1326 1327 C{None} if unapplicable or unable to retrieve. 1328 1329 @type exceptionCode: None or int 1330 @ivar exceptionCode: Exception code as defined by the Win32 API. 1331 1332 C{None} if unapplicable or unable to retrieve. 1333 1334 @type exceptionName: None or str 1335 @ivar exceptionName: Exception code user-friendly name. 1336 1337 C{None} if unapplicable or unable to retrieve. 1338 1339 @type exceptionDescription: None or str 1340 @ivar exceptionDescription: Exception description. 1341 1342 C{None} if unapplicable or unable to retrieve. 1343 1344 @type exceptionAddress: None or int 1345 @ivar exceptionAddress: Memory address where the exception occured. 1346 1347 C{None} if unapplicable or unable to retrieve. 1348 1349 @type exceptionLabel: None or str 1350 @ivar exceptionLabel: Label pointing to the exception address. 1351 1352 C{None} or invalid if unapplicable or unable to retrieve. 1353 1354 @type faultType: None or int 1355 @ivar faultType: Access violation type. 1356 Only applicable to memory faults. 1357 Should be one of the following constants: 1358 1359 - L{win32.ACCESS_VIOLATION_TYPE_READ} 1360 - L{win32.ACCESS_VIOLATION_TYPE_WRITE} 1361 - L{win32.ACCESS_VIOLATION_TYPE_DEP} 1362 1363 C{None} if unapplicable or unable to retrieve. 1364 1365 @type faultAddress: None or int 1366 @ivar faultAddress: Access violation memory address. 1367 Only applicable to memory faults. 1368 1369 C{None} if unapplicable or unable to retrieve. 1370 1371 @type faultLabel: None or str 1372 @ivar faultLabel: Label pointing to the access violation memory address. 1373 Only applicable to memory faults. 1374 1375 C{None} if unapplicable or unable to retrieve. 1376 1377 @type firstChance: None or bool 1378 @ivar firstChance: 1379 C{True} for first chance exceptions, C{False} for second chance. 1380 1381 C{None} if unapplicable or unable to retrieve. 1382 1383 @type isOurBreakpoint: bool 1384 @ivar isOurBreakpoint: 1385 C{True} for breakpoints defined by the L{Debug} class, 1386 C{False} otherwise. 1387 1388 C{None} if unapplicable. 1389 1390 @type isSystemBreakpoint: bool 1391 @ivar isSystemBreakpoint: 1392 C{True} for known system-defined breakpoints, 1393 C{False} otherwise. 1394 1395 C{None} if unapplicable. 1396 1397 @type modFileName: None or str 1398 @ivar modFileName: File name of module where the program counter points to. 1399 1400 C{None} or invalid if unapplicable or unable to retrieve. 1401 1402 @type lpBaseOfDll: None or int 1403 @ivar lpBaseOfDll: Base of module where the program counter points to. 1404 1405 C{None} if unapplicable or unable to retrieve. 1406 1407 @type stackTrace: None or tuple of tuple( int, int, str ) 1408 @ivar stackTrace: 1409 Stack trace of the current thread as a tuple of 1410 ( frame pointer, return address, module filename ). 1411 1412 C{None} or empty if unapplicable or unable to retrieve. 1413 1414 @type stackTracePretty: None or tuple of tuple( int, str ) 1415 @ivar stackTracePretty: 1416 Stack trace of the current thread as a tuple of 1417 ( frame pointer, return location ). 1418 1419 C{None} or empty if unapplicable or unable to retrieve. 1420 1421 @type stackTracePC: None or tuple( int... ) 1422 @ivar stackTracePC: Tuple of return addresses in the stack trace. 1423 1424 C{None} or empty if unapplicable or unable to retrieve. 1425 1426 @type stackTraceLabels: None or tuple( str... ) 1427 @ivar stackTraceLabels: 1428 Tuple of labels pointing to the return addresses in the stack trace. 1429 1430 C{None} or empty if unapplicable or unable to retrieve. 1431 1432 @type stackRange: tuple( int, int ) 1433 @ivar stackRange: 1434 Stack beginning and end pointers, in memory addresses order. 1435 1436 C{None} if unapplicable or unable to retrieve. 1437 1438 @type stackFrame: None or str 1439 @ivar stackFrame: Data pointed to by the stack pointer. 1440 1441 C{None} or empty if unapplicable or unable to retrieve. 1442 1443 @type stackPeek: None or dict( int S{->} str ) 1444 @ivar stackPeek: Dictionary mapping stack offsets to the data they point to. 1445 1446 C{None} or empty if unapplicable or unable to retrieve. 1447 1448 @type faultCode: None or str 1449 @ivar faultCode: Data pointed to by the program counter. 1450 1451 C{None} or empty if unapplicable or unable to retrieve. 1452 1453 @type faultMem: None or str 1454 @ivar faultMem: Data pointed to by the exception address. 1455 1456 C{None} or empty if unapplicable or unable to retrieve. 1457 1458 @type faultPeek: None or dict( intS{->} str ) 1459 @ivar faultPeek: Dictionary mapping guessed pointers at L{faultMem} to the data they point to. 1460 1461 C{None} or empty if unapplicable or unable to retrieve. 1462 1463 @type faultDisasm: None or tuple of tuple( long, int, str, str ) 1464 @ivar faultDisasm: Dissassembly around the program counter. 1465 1466 C{None} or empty if unapplicable or unable to retrieve. 1467 1468 @type memoryMap: None or list of L{win32.MemoryBasicInformation} objects. 1469 @ivar memoryMap: Memory snapshot of the program. May contain the actual 1470 data from the entire process memory if requested. 1471 See L{fetch_extra_data} for more details. 1472 1473 C{None} or empty if unapplicable or unable to retrieve. 1474 """ 1475
1476 - def __init__(self, event):
1477 """ 1478 @type event: L{Event} 1479 @param event: Event object for crash. 1480 """ 1481 1482 # First of all, take the timestamp. 1483 self.timeStamp = time.time() 1484 1485 # Notes are initially empty. 1486 self.notes = list() 1487 1488 # Get the process and thread, but dont't store them in the DB. 1489 process = event.get_process() 1490 thread = event.get_thread() 1491 1492 # The following properties are always retrieved for all events. 1493 self.eventCode = event.get_event_code() 1494 self.eventName = event.get_event_name() 1495 self.pid = event.get_pid() 1496 self.tid = event.get_tid() 1497 self.registers = dict(thread.get_context()) 1498 self.labelPC = process.get_label_at_address(self.pc) 1499 1500 # The following properties are only retrieved for some events. 1501 self.commandLine = None 1502 self.environment = None 1503 self.environmentData = None 1504 self.registersPeek = None 1505 self.debugString = None 1506 self.modFileName = None 1507 self.lpBaseOfDll = None 1508 self.exceptionCode = None 1509 self.exceptionName = None 1510 self.exceptionDescription = None 1511 self.exceptionAddress = None 1512 self.exceptionLabel = None 1513 self.firstChance = None 1514 self.faultType = None 1515 self.faultAddress = None 1516 self.faultLabel = None 1517 self.isOurBreakpoint = None 1518 self.isSystemBreakpoint = None 1519 self.stackTrace = None 1520 self.stackTracePC = None 1521 self.stackTraceLabels = None 1522 self.stackTracePretty = None 1523 self.stackRange = None 1524 self.stackFrame = None 1525 self.stackPeek = None 1526 self.faultCode = None 1527 self.faultMem = None 1528 self.faultPeek = None 1529 self.faultDisasm = None 1530 self.memoryMap = None 1531 1532 # Get information for debug string events. 1533 if self.eventCode == win32.OUTPUT_DEBUG_STRING_EVENT: 1534 self.debugString = event.get_debug_string() 1535 1536 # Get information for module load and unload events. 1537 # For create and exit process events, get the information 1538 # for the main module. 1539 elif self.eventCode in (win32.CREATE_PROCESS_DEBUG_EVENT, 1540 win32.EXIT_PROCESS_DEBUG_EVENT, 1541 win32.LOAD_DLL_DEBUG_EVENT, 1542 win32.UNLOAD_DLL_DEBUG_EVENT): 1543 aModule = event.get_module() 1544 self.modFileName = event.get_filename() 1545 if not self.modFileName: 1546 self.modFileName = aModule.get_filename() 1547 self.lpBaseOfDll = event.get_module_base() 1548 if not self.lpBaseOfDll: 1549 self.lpBaseOfDll = aModule.get_base() 1550 1551 # Get some information for exception events. 1552 # To get the remaining information call fetch_extra_data(). 1553 elif self.eventCode == win32.EXCEPTION_DEBUG_EVENT: 1554 1555 # Exception information. 1556 self.exceptionCode = event.get_exception_code() 1557 self.exceptionName = event.get_exception_name() 1558 self.exceptionDescription = event.get_exception_description() 1559 self.exceptionAddress = event.get_exception_address() 1560 self.firstChance = event.is_first_chance() 1561 self.exceptionLabel = process.get_label_at_address( 1562 self.exceptionAddress) 1563 if self.exceptionCode in (win32.EXCEPTION_ACCESS_VIOLATION, 1564 win32.EXCEPTION_GUARD_PAGE, 1565 win32.EXCEPTION_IN_PAGE_ERROR): 1566 self.faultType = event.get_fault_type() 1567 self.faultAddress = event.get_fault_address() 1568 self.faultLabel = process.get_label_at_address( 1569 self.faultAddress) 1570 elif self.exceptionCode in (win32.EXCEPTION_BREAKPOINT, 1571 win32.EXCEPTION_SINGLE_STEP): 1572 self.isOurBreakpoint = hasattr(event, 'breakpoint') \ 1573 and event.breakpoint 1574 self.isSystemBreakpoint = \ 1575 process.is_system_defined_breakpoint(self.exceptionAddress) 1576 1577 # Stack trace. 1578 try: 1579 self.stackTracePretty = thread.get_stack_trace_with_labels() 1580 except Exception, e: 1581 pass 1582 try: 1583 self.stackTrace = thread.get_stack_trace() 1584 stackTracePC = [ ra for (_,ra,_) in self.stackTrace ] 1585 self.stackTracePC = tuple(stackTracePC) 1586 stackTraceLabels = [ process.get_label_at_address(ra) \ 1587 for ra in self.stackTracePC ] 1588 self.stackTraceLabels = tuple(stackTraceLabels) 1589 except Exception, e: 1590 pass
1591
1592 - def fetch_extra_data(self, event, takeMemorySnapshot = 0):
1593 """ 1594 Fetch extra data from the L{Event} object. 1595 1596 @note: This is only needed for exceptions. Since this method may take 1597 a little longer to run, it's best to call it only after you've 1598 determined the crash is interesting and you want to save it. 1599 1600 @type event: L{Event} 1601 @param event: Event object for crash. 1602 1603 @type takeMemorySnapshot: int 1604 @param takeMemorySnapshot: 1605 Memory snapshot behavior: 1606 - C{0} to take no memory information (default). 1607 - C{1} to take only the memory map. 1608 See L{Process.get_memory_map}. 1609 - C{2} to take a full memory snapshot. 1610 See L{Process.take_memory_snapshot}. 1611 - C{3} to take a full memory snapshot generator. 1612 See L{Process.generate_memory_snapshot}. 1613 """ 1614 1615 # Get the process and thread, but dont't store them in the DB. 1616 process = event.get_process() 1617 thread = event.get_thread() 1618 1619 # Get the command line for the target process. 1620 try: 1621 self.commandLine = process.get_command_line() 1622 except Exception: 1623 ## raise # XXX DEBUG 1624 pass 1625 1626 # Get the environment variables for the target process. 1627 try: 1628 self.environmentData = process.get_environment_data() 1629 self.environment = process.parse_environment_data( 1630 self.environmentData) 1631 except Exception: 1632 ## raise # XXX DEBUG 1633 pass 1634 1635 # Get some information for exception events. 1636 if self.eventCode == win32.EXCEPTION_DEBUG_EVENT: 1637 1638 # Data pointed to by registers. 1639 self.registersPeek = thread.peek_pointers_in_registers() 1640 1641 # Module that raised the exception. 1642 aModule = process.get_module_at_address(self.pc) 1643 if aModule is not None: 1644 self.modFileName = aModule.get_filename() 1645 self.lpBaseOfDll = aModule.get_base() 1646 1647 # Contents of the stack frame. 1648 try: 1649 self.stackRange = thread.get_stack_range() 1650 except Exception, e: 1651 pass 1652 try: 1653 self.stackFrame = thread.get_stack_frame() 1654 stackFrame = self.stackFrame 1655 except Exception, e: 1656 self.stackFrame = thread.peek_stack_data() 1657 stackFrame = self.stackFrame[:64] 1658 if stackFrame: 1659 self.stackPeek = process.peek_pointers_in_data(stackFrame) 1660 1661 # Code that raised the exception. 1662 self.faultCode = thread.peek_code_bytes() 1663 try: 1664 self.faultDisasm = thread.disassemble_around_pc(32) 1665 except Exception, e: 1666 ## raise # XXX DEBUG 1667 pass 1668 1669 # For memory related exceptions, get the memory contents 1670 # of the location that caused the exception to be raised. 1671 if self.pc != self.exceptionAddress and self.exceptionCode in ( 1672 win32.EXCEPTION_ACCESS_VIOLATION, 1673 win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, 1674 win32.EXCEPTION_DATATYPE_MISALIGNMENT, 1675 win32.EXCEPTION_IN_PAGE_ERROR, 1676 win32.EXCEPTION_STACK_OVERFLOW, 1677 win32.EXCEPTION_GUARD_PAGE, 1678 ): 1679 self.faultMem = process.peek(self.exceptionAddress, 64) 1680 if self.faultMem: 1681 self.faultPeek = process.peek_pointers_in_data(self.faultMem) 1682 1683 # Take a snapshot of the process memory. Additionally get the 1684 # memory contents if requested. 1685 if takeMemorySnapshot == 1: 1686 self.memoryMap = process.get_memory_map() 1687 mappedFilenames = process.get_mapped_filenames(self.memoryMap) 1688 for mbi in self.memoryMap: 1689 mbi.filename = mappedFilenames.get(mbi.BaseAddress, None) 1690 mbi.content = None 1691 elif takeMemorySnapshot == 2: 1692 self.memoryMap = process.take_memory_snapshot() 1693 elif takeMemorySnapshot == 3: 1694 self.memoryMap = process.generate_memory_snapshot()
1695 1696 @property
1697 - def pc(self):
1698 """ 1699 Value of the program counter register. 1700 1701 @rtype: int 1702 """ 1703 try: 1704 return self.registers['Eip'] # i386 1705 except KeyError: 1706 return self.registers['Rip'] # amd64
1707 1708 @property
1709 - def sp(self):
1710 """ 1711 Value of the stack pointer register. 1712 1713 @rtype: int 1714 """ 1715 try: 1716 return self.registers['Esp'] # i386 1717 except KeyError: 1718 return self.registers['Rsp'] # amd64
1719 1720 @property
1721 - def fp(self):
1722 """ 1723 Value of the frame pointer register. 1724 1725 @rtype: int 1726 """ 1727 try: 1728 return self.registers['Ebp'] # i386 1729 except KeyError: 1730 return self.registers['Rbp'] # amd64
1731
1732 - def __str__(self):
1733 return self.fullReport()
1734
1735 - def key(self):
1736 """ 1737 Generates an approximately unique key for the Crash object. 1738 1739 This key can be used as an heuristic to determine if two crashes were 1740 caused by the same software error. Ideally it should be treated as an 1741 opaque object. 1742 1743 @rtype: (opaque) 1744 @return: Crash unique key. 1745 """ 1746 if self.labelPC: 1747 eip = self.labelPC 1748 else: 1749 eip = self.pc 1750 if self.stackTraceLabels: 1751 trace = self.stackTraceLabels 1752 else: 1753 trace = self.stackTracePC 1754 return ( 1755 self.eventCode, 1756 self.exceptionCode, 1757 eip, 1758 trace, 1759 self.debugString, 1760 )
1761
1762 - def isExploitable(self):
1763 """ 1764 Guess how likely is it that the bug causing the crash can be leveraged 1765 into an exploitable vulnerability. 1766 1767 @note: Don't take this as an equivalent of a real exploitability 1768 analysis, that can only be done by a human being! This is only 1769 a guideline, useful for example to sort crashes - placing the most 1770 interesting ones at the top. 1771 1772 @see: The heuristics are similar to those of the B{!exploitable} 1773 extension for I{WinDBG}, which can be downloaded from here: 1774 1775 U{http://www.codeplex.com/msecdbg} 1776 1777 @rtype: tuple( str, str ) 1778 @return: The first element of the tuple is the result of the analysis, 1779 being one of the following: 1780 1781 - Not an exception 1782 - Not exploitable 1783 - Not likely exploitable 1784 - Unknown 1785 - Probably exploitable 1786 - Exploitable 1787 1788 The second element of the tuple is a code to identify the matched 1789 heuristic rule. 1790 1791 The second element of the tuple is a description string of the 1792 reason behind the result. 1793 """ 1794 1795 # Terminal rules 1796 1797 if self.eventCode != win32.EXCEPTION_DEBUG_EVENT: 1798 return ("Not an exception", "NotAnException", "The event is not an exception.") 1799 1800 if self.stackRange and self.pc is not None and self.stackRange[0] <= self.pc < self.stackRange[1]: 1801 return ("Exploitable", "StackCodeExecution", "Code execution from the stack is considered exploitable.") 1802 1803 # This rule is NOT from !exploitable 1804 if self.stackRange and self.sp is not None and not (self.stackRange[0] <= self.sp < self.stackRange[1]): 1805 return ("Exploitable", "StackPointerCorruption", "Stack pointer corruption is considered exploitable.") 1806 1807 if self.exceptionCode == win32.EXCEPTION_ILLEGAL_INSTRUCTION: 1808 return ("Exploitable", "IllegalInstruction", "An illegal instruction exception indicates that the attacker controls execution flow.") 1809 1810 if self.exceptionCode == win32.EXCEPTION_PRIV_INSTRUCTION: 1811 return ("Exploitable", "PrivilegedInstruction", "A privileged instruction exception indicates that the attacker controls execution flow.") 1812 1813 if self.exceptionCode == win32.EXCEPTION_GUARD_PAGE: 1814 return ("Exploitable", "GuardPage", "A guard page violation indicates a stack overflow has occured, and the stack of another thread was reached (possibly the overflow length is not controlled by the attacker).") 1815 1816 if self.exceptionCode == win32.STATUS_STACK_BUFFER_OVERRUN: 1817 return ("Exploitable", "GSViolation", "An overrun of a protected stack buffer has been detected. This is considered exploitable, and must be fixed.") 1818 1819 if self.exceptionCode == win32.STATUS_HEAP_CORRUPTION: 1820 return ("Exploitable", "HeapCorruption", "Heap Corruption has been detected. This is considered exploitable, and must be fixed.") 1821 1822 if self.exceptionCode == win32.EXCEPTION_ACCESS_VIOLATION: 1823 nearNull = self.faultAddress is None or MemoryAddresses.align_address_to_page_start(self.faultAddress) == 0 1824 controlFlow = self.__is_control_flow() 1825 blockDataMove = self.__is_block_data_move() 1826 if self.faultType == win32.EXCEPTION_EXECUTE_FAULT: 1827 if nearNull: 1828 return ("Probably exploitable", "DEPViolation", "User mode DEP access violations are probably exploitable if near NULL.") 1829 else: 1830 return ("Exploitable", "DEPViolation", "User mode DEP access violations are exploitable.") 1831 elif self.faultType == win32.EXCEPTION_WRITE_FAULT: 1832 if nearNull: 1833 return ("Probably exploitable", "WriteAV", "User mode write access violations that are near NULL are probably exploitable.") 1834 else: 1835 return ("Exploitable", "WriteAV", "User mode write access violations that are not near NULL are exploitable.") 1836 elif self.faultType == win32.EXCEPTION_READ_FAULT: 1837 if self.faultAddress == self.pc: 1838 if nearNull: 1839 return ("Probably exploitable", "ReadAVonIP", "Access violations at the instruction pointer are probably exploitable if near NULL.") 1840 else: 1841 return ("Exploitable", "ReadAVonIP", "Access violations at the instruction pointer are exploitable if not near NULL.") 1842 if controlFlow: 1843 if nearNull: 1844 return ("Probably exploitable", "ReadAVonControlFlow", "Access violations near null in control flow instructions are considered probably exploitable.") 1845 else: 1846 return ("Exploitable", "ReadAVonControlFlow", "Access violations not near null in control flow instructions are considered exploitable.") 1847 if blockDataMove: 1848 return ("Probably exploitable", "ReadAVonBlockMove", "This is a read access violation in a block data move, and is therefore classified as probably exploitable.") 1849 1850 # Rule: Tainted information used to control branch addresses is considered probably exploitable 1851 # Rule: Tainted information used to control the target of a later write is probably exploitable 1852 1853 # Non terminal rules 1854 1855 # XXX TODO add rule to check if code is in writeable memory (probably exploitable) 1856 1857 # XXX TODO maybe we should be returning a list of tuples instead? 1858 1859 result = ("Unknown", "Unknown", "Exploitability unknown.") 1860 1861 if self.exceptionCode == win32.EXCEPTION_ACCESS_VIOLATION: 1862 if self.faultType == win32.EXCEPTION_READ_FAULT: 1863 if nearNull: 1864 result = ("Not likely exploitable", "ReadAVNearNull", "This is a user mode read access violation near null, and is probably not exploitable.") 1865 1866 elif self.exceptionCode == win32.EXCEPTION_INT_DIVIDE_BY_ZERO: 1867 result = ("Not likely exploitable", "DivideByZero", "This is an integer divide by zero, and is probably not exploitable.") 1868 1869 elif self.exceptionCode == win32.EXCEPTION_FLT_DIVIDE_BY_ZERO: 1870 result = ("Not likely exploitable", "DivideByZero", "This is a floating point divide by zero, and is probably not exploitable.") 1871 1872 elif self.exceptionCode in (win32.EXCEPTION_BREAKPOINT, win32.STATUS_WX86_BREAKPOINT): 1873 result = ("Unknown", "Breakpoint", "While a breakpoint itself is probably not exploitable, it may also be an indication that an attacker is testing a target. In either case breakpoints should not exist in production code.") 1874 1875 # Rule: If the stack contains unknown symbols in user mode, call that out 1876 # Rule: Tainted information used to control the source of a later block move unknown, but called out explicitly 1877 # Rule: Tainted information used as an argument to a function is an unknown risk, but called out explicitly 1878 # Rule: Tainted information used to control branch selection is an unknown risk, but called out explicitly 1879 1880 return result
1881
1882 - def __is_control_flow(self):
1883 """ 1884 Private method to tell if the instruction pointed to by the program 1885 counter is a control flow instruction. 1886 1887 Currently only works for x86 and amd64 architectures. 1888 """ 1889 jump_instructions = ( 1890 'jmp', 'jecxz', 'jcxz', 1891 'ja', 'jnbe', 'jae', 'jnb', 'jb', 'jnae', 'jbe', 'jna', 'jc', 'je', 1892 'jz', 'jnc', 'jne', 'jnz', 'jnp', 'jpo', 'jp', 'jpe', 'jg', 'jnle', 1893 'jge', 'jnl', 'jl', 'jnge', 'jle', 'jng', 'jno', 'jns', 'jo', 'js' 1894 ) 1895 call_instructions = ( 'call', 'ret', 'retn' ) 1896 loop_instructions = ( 'loop', 'loopz', 'loopnz', 'loope', 'loopne' ) 1897 control_flow_instructions = call_instructions + loop_instructions + \ 1898 jump_instructions 1899 isControlFlow = False 1900 instruction = None 1901 if self.pc is not None and self.faultDisasm: 1902 for disasm in self.faultDisasm: 1903 if disasm[0] == self.pc: 1904 instruction = disasm[2].lower().strip() 1905 break 1906 if instruction: 1907 for x in control_flow_instructions: 1908 if x in instruction: 1909 isControlFlow = True 1910 break 1911 return isControlFlow
1912
1913 - def __is_block_data_move(self):
1914 """ 1915 Private method to tell if the instruction pointed to by the program 1916 counter is a block data move instruction. 1917 1918 Currently only works for x86 and amd64 architectures. 1919 """ 1920 block_data_move_instructions = ('movs', 'stos', 'lods') 1921 isBlockDataMove = False 1922 instruction = None 1923 if self.pc is not None and self.faultDisasm: 1924 for disasm in self.faultDisasm: 1925 if disasm[0] == self.pc: 1926 instruction = disasm[2].lower().strip() 1927 break 1928 if instruction: 1929 for x in block_data_move_instructions: 1930 if x in instruction: 1931 isBlockDataMove = True 1932 break 1933 return isBlockDataMove
1934
1935 - def briefReport(self):
1936 """ 1937 @rtype: str 1938 @return: Short description of the event. 1939 """ 1940 if self.exceptionCode is not None: 1941 if self.exceptionCode == win32.EXCEPTION_BREAKPOINT: 1942 if self.isOurBreakpoint: 1943 what = "Breakpoint hit" 1944 elif self.isSystemBreakpoint: 1945 what = "System breakpoint hit" 1946 else: 1947 what = "Assertion failed" 1948 elif self.exceptionDescription: 1949 what = self.exceptionDescription 1950 elif self.exceptionName: 1951 what = self.exceptionName 1952 else: 1953 what = "Exception %s" % HexDump.integer(self.exceptionCode) 1954 if self.firstChance: 1955 chance = 'first' 1956 else: 1957 chance = 'second' 1958 if self.exceptionLabel: 1959 where = self.exceptionLabel 1960 elif self.exceptionAddress: 1961 where = HexDump.address(self.exceptionAddress) 1962 elif self.labelPC: 1963 where = self.labelPC 1964 else: 1965 where = HexDump.address(self.pc) 1966 msg = "%s (%s chance) at %s" % (what, chance, where) 1967 elif self.debugString is not None: 1968 if self.labelPC: 1969 where = self.labelPC 1970 else: 1971 where = HexDump.address(self.pc) 1972 msg = "Debug string from %s: %r" % (where, self.debugString) 1973 else: 1974 if self.labelPC: 1975 where = self.labelPC 1976 else: 1977 where = HexDump.address(self.pc) 1978 msg = "%s (%s) at %s" % ( 1979 self.eventName, 1980 HexDump.integer(self.eventCode), 1981 where 1982 ) 1983 return msg
1984
1985 - def fullReport(self, bShowNotes = True):
1986 """ 1987 @type bShowNotes: bool 1988 @param bShowNotes: C{True} to show the user notes, C{False} otherwise. 1989 1990 @rtype: str 1991 @return: Long description of the event. 1992 """ 1993 msg = self.briefReport() 1994 msg += '\n' 1995 1996 if win32.sizeof(win32.LPVOID) == 4: 1997 width = 16 1998 else: 1999 width = 8 2000 2001 if self.eventCode == win32.EXCEPTION_DEBUG_EVENT: 2002 (exploitability, expcode, expdescription) = self.isExploitable() 2003 msg += '\nSecurity risk level: %s\n' % exploitability 2004 msg += ' %s\n' % expdescription 2005 2006 if bShowNotes and self.notes: 2007 msg += '\nNotes:\n' 2008 msg += self.notesReport() 2009 2010 if self.commandLine: 2011 msg += '\nCommand line: %s\n' % self.commandLine 2012 2013 if self.environment: 2014 msg += '\nEnvironment: %s\n' % pprint.pformat(self.environment) 2015 2016 if not self.labelPC: 2017 base = HexDump.address(self.lpBaseOfDll) 2018 if self.modFileName: 2019 fn = PathOperations.pathname_to_filename(self.modFileName) 2020 msg += '\nRunning in %s (%s)\n' % (fn, base) 2021 else: 2022 msg += '\nRunning in module at %s\n' % base 2023 2024 if self.registers: 2025 msg += '\nRegisters:\n' 2026 msg += CrashDump.dump_registers(self.registers) 2027 if self.registersPeek: 2028 msg += '\n' 2029 msg += CrashDump.dump_registers_peek(self.registers, 2030 self.registersPeek, 2031 width = width) 2032 2033 if self.faultDisasm: 2034 msg += '\nCode disassembly:\n' 2035 msg += CrashDump.dump_code(self.faultDisasm, self.pc) 2036 2037 if self.stackTrace: 2038 msg += '\nStack trace:\n' 2039 if self.stackTracePretty: 2040 msg += CrashDump.dump_stack_trace_with_labels( 2041 self.stackTracePretty) 2042 else: 2043 msg += CrashDump.dump_stack_trace(self.stackTrace) 2044 2045 if self.stackFrame: 2046 if self.stackPeek: 2047 msg += '\nStack pointers:\n' 2048 msg += CrashDump.dump_stack_peek(self.stackPeek, width = width) 2049 msg += '\nStack dump:\n' 2050 msg += HexDump.hexblock(self.stackFrame, self.sp, width = width) 2051 2052 if self.faultCode and not self.modFileName: 2053 msg += '\nCode dump:\n' 2054 msg += HexDump.hexblock(self.faultCode, self.pc, width = width) 2055 2056 if self.faultMem: 2057 if self.faultPeek: 2058 msg += '\nException address pointers:\n' 2059 msg += CrashDump.dump_data_peek(self.faultPeek, 2060 self.exceptionAddress, 2061 width = width) 2062 msg += '\nException address dump:\n' 2063 msg += HexDump.hexblock(self.faultMem, self.exceptionAddress, 2064 width = width) 2065 2066 if self.memoryMap: 2067 msg += '\nMemory map:\n' 2068 mappedFileNames = dict() 2069 for mbi in self.memoryMap: 2070 if hasattr(mbi, 'filename') and mbi.filename: 2071 mappedFileNames[mbi.BaseAddress] = mbi.filename 2072 msg += CrashDump.dump_memory_map(self.memoryMap, mappedFileNames) 2073 2074 if not msg.endswith('\n\n'): 2075 if not msg.endswith('\n'): 2076 msg += '\n' 2077 msg += '\n' 2078 return msg
2079
2080 - def notesReport(self):
2081 """ 2082 @rtype: str 2083 @return: All notes, merged and formatted for a report. 2084 """ 2085 msg = '' 2086 if self.notes: 2087 for n in self.notes: 2088 n = n.strip('\n') 2089 if '\n' in n: 2090 n = n.strip('\n') 2091 msg += ' * %s\n' % n.pop(0) 2092 for x in n: 2093 msg += ' %s\n' % x 2094 else: 2095 msg += ' * %s\n' % n 2096 return msg
2097
2098 - def addNote(self, msg):
2099 """ 2100 Add a note to the crash event. 2101 2102 @type msg: str 2103 @param msg: Note text. 2104 """ 2105 self.notes.append(msg)
2106
2107 - def clearNotes(self):
2108 """ 2109 Clear the notes of this crash event. 2110 """ 2111 self.notes = list()
2112
2113 - def getNotes(self):
2114 """ 2115 Get the list of notes of this crash event. 2116 2117 @rtype: list( str ) 2118 @return: List of notes. 2119 """ 2120 return self.notes
2121
2122 - def iterNotes(self):
2123 """ 2124 Iterate the notes of this crash event. 2125 2126 @rtype: listiterator 2127 @return: Iterator of the list of notes. 2128 """ 2129 return self.notes.__iter__()
2130
2131 - def hasNotes(self):
2132 """ 2133 @rtype: bool 2134 @return: C{True} if there are notes for this crash event. 2135 """ 2136 return bool( self.notes )
2137