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