1
2 '''
3 Debugging monitor for Peach Agent. Uses pydbgeng to monitor processes and
4 detect faults. Would be nice to also eventually do other things like
5 "if we hit this method" or whatever.
6
7 @author: Michael Eddington
8 @version: $Id: Peach.Agent.debugger-pysrc.html 1138 2008-08-16 19:39:03Z meddingt $
9 '''
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 '''
39 <!-- Use a windows debugger to monitor for faults -->
40 <Monitor class="debugger.WindowsDebugger">
41 <!-- [Optional] Specify a symbol path. By default microsoft symbols server is used -->
42 <Param name="SymbolsPath" value="SRV*http://msdl.microsoft.com/download/symbols" />
43
44 <!-- *** Must select only one of the following: CommandLine, ProcessName, or KernelConnectionString *** -->
45
46 <!-- Command line. This agent can optionally start the process instead -->
47 <!-- of attaching to it. -->
48 <Param name="CommandLine" value="c:\fullpath\program.exe" />
49
50 <!-- Name of process to attach debugger. -->
51 <Param name="ProcessName" value="program.exe" />
52
53 <!-- KD connection string. Kernel debugger connection string. -->
54 <Param name="KernelConnectionString" value="com:pipe,port=\\\\.\\pipe\\com_1,resets=0" />
55
56 </Monitor>
57 '''
58
59 '''
60 <!-- Use Microsoft AppVerifier to monitor for faults -->
61 <Monitor class="debugger.WindowsAppVerifier">
62
63 <!-- Application to monitor -->
64 <Param name="Application" value="notepad.exe" />
65
66 <!-- Enable one or more checks -->
67 <Param name="EnableTest" value="Detect heap corruptions" />
68 <Param name="EnableTest" value="Detect invalid handle usage" />
69 </Monitor>
70 '''
71
72 try:
73 import pydbg, win32process, win32api, win32pdhutil, win32con
74 import struct, sys, time
75 from threading import Thread, Event, Lock
76 from Peach.agent import Monitor
77
78 import struct, sys, time,os
79 import comtypes
80 from ctypes import *
81 from comtypes import HRESULT, COMError
82 from comtypes.client import CreateObject, GetEvents, PumpEvents
83 from comtypes.hresult import S_OK, E_FAIL, E_UNEXPECTED, E_INVALIDARG
84 from comtypes.automation import IID
85
87 '''
88 Agent that uses the Microsoft AppVerifier tool to detect faults on
89 running processes. AppVerifier can be downloaded from Microsoft via
90 the following url: http://www.microsoft.com/technet/prodtechnol/windows/appcompatibility/appverifier.mspx
91 '''
92
93 checks = ["COM", "Exceptions", "Handles", "Heaps", "Locks", "Memory", "RPC", "Threadpool", "TLS"]
94
96 self._image = str(args['Application'])
97 self._appManager = CreateObject("{597c1ef7-fc28-451e-8273-417c6c9244ed}")
98
99 try:
100 self._appManager._DisableImage(self._image)
101 self._appManager._RemoveImageLogs(self._image)
102 except:
103 pass
104
106 '''
107 Enables the default checks for an image (exe).
108 '''
109 image = self._appManager.Images.Add(image)
110 for check in image.Checks:
111 if check.Name in self.checks:
112 check.Enabled = True
113 else:
114 check.Enabled = False
115
117 self._appManager.Images.Remove(image)
118
120 '''
121 Get lof files for an image
122 '''
123
124 logFiles = []
125 for log in self._appManager.Logs(image):
126
127 try:
128 os.unlink("appVerifierTmp.xml")
129 except:
130 pass
131
132 try:
133 log.SaveAsXML("appVerifierTmp.xml", "SRV*http://msdl.microsoft.com/download/symbols")
134 except:
135 pass
136
137 fd = open("appVerifierTmp.xml","rb+")
138 logFiles.append(fd.read())
139 fd.close()
140 os.unlink("appVerifierTmp.xml")
141
142 return logFiles
143
145 logs = self._appManager.Logs(image)
146 for idx in range(logs.Count):
147 try:
148 logs.Remove(0)
149 except:
150 print "Warning: Caught error removing App Verifier logs."
151 pass
152
154 '''
155 Called right before start of test.
156 '''
157 self._EnableImage(self._image)
158
160 '''
161 Called right after a test.
162 '''
163 self._DisableImage(self._image)
164
166 '''
167 Get any monitored data.
168 '''
169 ret = {}
170 count = 0
171 logs = self.imageLogs
172
173
174
175
176
177 if logs == None:
178 logs = self.imageLogs = self._GetImageLogs(self._image)
179
180 self._RemoveImageLogs(self._image)
181
182 for log in logs:
183 count += 1
184 ret["AppVerifier_%d.xml" % count] = log
185
186 return ret
187
189 '''
190 Check if a fault was detected.
191 '''
192
193 time.sleep(0.15)
194
195 self.imageLogs = self._GetImageLogs(self._image)
196 self._RemoveImageLogs(self._image)
197
198 for log in self.imageLogs:
199 if len(log) > 300:
200 return True
201
202 self.imageLogs = None
203 return False
204
206 '''
207 Called when a fault was detected.
208 '''
209 pass
210
212 '''
213 Called when Agent is shutting down.
214 '''
215 try:
216 self._DisableImage(self._image)
217 except:
218 pass
219
220
232
235 print "WindowsDebuggerThread: Starting process"
236 self.dbg = pydbg.pydbg()
237 self.dbg.set_callback(pydbg.EXCEPTION_ACCESS_VIOLATION, handleAccessViolation)
238
239 if self._command != None:
240 self.dbg.load(self._command, self._params)
241 else:
242 self.dbg.attach(self._pid)
243
244 WindowsDebugger.started.set()
245 self.dbg.debug_event_loop()
246
248 '''
249 Windows debugger monitor. Uses PaiMei pydbg module
250 '''
275
289
291 if self.thread.isAlive():
292 try:
293 win32process.TerminateProcess(self.thread.dbg.h_process, 0)
294 except:
295 pass
296
297 self.thread.join()
298
300 '''
301 Try and get pid for a process by name.
302 '''
303
304 try:
305 win32pdhutil.GetPerformanceAttributes('Process','ID Process',str)
306 except:
307 sys.stdout.write("WindowsDebugger: Unable to locate process [%s]\n" % str)
308 raise
309
310 pids = win32pdhutil.FindPerformanceAttributesByName(str)
311
312
313 try:
314 pids.remove(win32api.GetCurrentProcessId())
315 except ValueError:
316 pass
317
318 return pids[0]
319
321 '''
322 Called right before start of test.
323 '''
324 if not self.thread.isAlive():
325 self.thread.join()
326 self._StartDebugger()
327
329 '''
330 Called right after a test.
331 '''
332 pass
333
347
362
364 '''
365 Called when a fault was detected.
366 '''
367 self._StopDebugger()
368
369
370 if self._pid != None:
371 self._stopRun = True
372
374 '''
375 Called when Agent is shutting down.
376 '''
377 self._StopDebugger()
378
381
382
383
384
385
386
387
388
389
390
391
392 '''
393 @author: Pedram Amini
394 @license: GNU General Public License 2.0 or later
395 @contact: pedram.amini@gmail.com
396 @organization: www.openrce.org
397 '''
398
411
412
414 '''
415 @todo: Add persistant data support (disk / MySQL)
416 '''
417
418 bins = {}
419 last_crash = None
420 pydbg = None
421
422
430
431
432
466
467
468
470 '''
471 For the last recorded crash, generate and return a report containing the disassemly around the violating
472 address, the ID of the offending thread, the call stack and the SEH unwind.
473 '''
474
475 if self.last_crash.write_violation:
476 direction = "write to"
477 else:
478 direction = "read from"
479
480 synopsis = "0x%08x %s from thread %d caused access violation\nwhen attempting to %s 0x%08x\n\n" % \
481 (
482 self.last_crash.exception_address, \
483 self.last_crash.disasm, \
484 self.last_crash.violation_thread_id, \
485 direction, \
486 self.last_crash.violation_address \
487 )
488
489 synopsis += self.last_crash.context_dump
490
491 synopsis += "\ndisasm around:\n"
492 for (ea, inst) in self.last_crash.disasm_around:
493 synopsis += "\t0x%08x %s\n" % (ea, inst)
494
495 if len(self.last_crash.stack_unwind):
496 synopsis += "\nstack unwind:\n"
497 for addr in self.last_crash.stack_unwind:
498 synopsis += "\t%08x\n" % addr
499
500 if len(self.last_crash.seh_unwind):
501 synopsis += "\nSEH unwind:\n"
502 for (addr, handler) in self.last_crash.seh_unwind:
503 try:
504 disasm = self.pydbg.disasm(handler)
505 except:
506 disasm = "[INVALID]"
507
508 synopsis += "\t%08x -> %08x: %s\n" % (addr, handler, disasm)
509
510 return synopsis + "\n"
511
512 except:
513 pass
514
515 try:
516
517
518
519
520
521
522 import PyDbgEng
523 import comtypes
524 from ctypes import *
525 from comtypes import HRESULT, COMError
526 from comtypes.client import CreateObject, GetEvents, PumpEvents
527 from comtypes.hresult import S_OK
528 from comtypes.automation import IID
529 from comtypes.gen import DbgEng
530
531 - class _DbgEventHandler(PyDbgEng.IDebugOutputCallbacksSink, PyDbgEng.IDebugEventCallbacksSink):
532
533 buff = ''
534
535 - def Output(self, this, Mask, Text):
538
540
541 return PyDbgEng.DbgEng.DEBUG_EVENT_EXCEPTION | PyDbgEng.DbgEng.DEBUG_FILTER_INITIAL_BREAKPOINT
542
543 - def Exception(self, dbg, ExceptionCode, ExceptionFlags, ExceptionRecord,
544 ExceptionAddress, NumberParameters, ExceptionInformation0, ExceptionInformation1,
545 ExceptionInformation2, ExceptionInformation3, ExceptionInformation4,
546 ExceptionInformation5, ExceptionInformation6, ExceptionInformation7,
547 ExceptionInformation8, ExceptionInformation9, ExceptionInformation10,
548 ExceptionInformation11, ExceptionInformation12, ExceptionInformation13,
549 ExceptionInformation14, FirstChance):
550
551 if WindowsDebugEngine.handlingFault.isSet() or WindowsDebugEngine.handledFault.isSet():
552
553
554 return DbgEng.DEBUG_STATUS_NO_CHANGE
555
556 try:
557
558 WindowsDebugEngine.handlingFault.set()
559
560
561
562
563 frames_filled = 0
564 stack_frames = dbg.get_stack_trace(100)
565 for i in range(100):
566 eip = stack_frames[i].InstructionOffset
567 if (eip == 0):
568 break
569 frames_filled += 1
570
571
572
573
574 dbg.idebug_registers.OutputRegisters(DbgEng.DEBUG_OUTCTL_THIS_CLIENT, DbgEng.DEBUG_REGISTERS_ALL)
575 _DbgEventHandler.buff += "\n\n"
576
577
578
579
580 frames_count = 100
581 frames_buffer = create_string_buffer( frames_count * sizeof(DbgEng._DEBUG_STACK_FRAME) )
582 frames_buffer_ptr = cast(frames_buffer, POINTER(DbgEng._DEBUG_STACK_FRAME))
583
584 dbg.idebug_control.GetStackTrace(0, 0, 0, frames_buffer_ptr, frames_count)
585 dbg.idebug_control.OutputStackTrace(DbgEng.DEBUG_OUTCTL_THIS_CLIENT, frames_buffer_ptr, frames_filled,
586 DbgEng.DEBUG_STACK_ARGUMENTS |
587 DbgEng.DEBUG_STACK_FUNCTION_INFO |
588 DbgEng.DEBUG_STACK_SOURCE_LINE |
589 DbgEng.DEBUG_STACK_FRAME_ADDRESSES |
590 DbgEng.DEBUG_STACK_COLUMN_NAMES |
591 DbgEng.DEBUG_STACK_FRAME_NUMBERS |
592 DbgEng.DEBUG_STACK_PARAMETERS )
593
594 _DbgEventHandler.buff += "\n\n"
595
596
597
598
599 dbg.idebug_client.WriteDumpFile(c_char_p("dumpfile.core"), DbgEng.DEBUG_DUMP_SMALL)
600 minidump = None
601
602 try:
603 f = open('dumpfile.core', 'rb+')
604 minidump = f.read()
605 f.close()
606
607 except:
608 pass
609
610
611
612
613 handle = dbg.idebug_control.AddExtension(c_char_p("C:\\Program Files\\Debugging Tools for Windows\\winext\\ext.dll"), 0)
614 try:
615 dbg.idebug_control.Execute(DbgEng.DEBUG_OUTCTL_THIS_CLIENT, c_char_p("!analyze -v"), DbgEng.DEBUG_EXECUTE_ECHO)
616 except:
617 pass
618
619 dbg.idebug_control.RemoveExtension(handle)
620
621 WindowsDebugEngine.lock.acquire()
622
623 if minidump:
624 WindowsDebugEngine.crashInfo = { 'StackTrace.txt' : _DbgEventHandler.buff, 'MiniDump.dmp' : minidump }
625 else:
626 WindowsDebugEngine.crashInfo = { 'StackTrace.txt' : _DbgEventHandler.buff }
627
628 WindowsDebugEngine.fault = True
629 WindowsDebugEngine.lock.release()
630
631 except:
632 sys.stdout.write(repr(sys.exc_info()[0]) + "\n")
633 raise
634
635 WindowsDebugEngine.handledFault.set()
636
637
638 return DbgEng.DEBUG_STATUS_NO_CHANGE
639
640
643 WindowsDebugEngine.handlingFault.clear()
644
645
646 comtypes._ole32.CoInitializeEx(None, comtypes.COINIT_APARTMENTTHREADED)
647
648 self._eventHandler = _DbgEventHandler()
649
650 if self.KernelConnectionString:
651 self.dbg = PyDbgEng.KernelAttacher( connection_string = connection_string,
652 follow_forks = True,
653 event_callbacks_sink = self._eventHandler,
654 output_callbacks_sink = self._eventHandler,
655 symbols_path = self.SymbolsPath)
656
657 elif self.CommandLine:
658 self.dbg = PyDbgEng.ProcessCreator(command_line = self.CommandLine,
659 follow_forks = True,
660 event_callbacks_sink = self._eventHandler,
661 output_callbacks_sink = self._eventHandler,
662 symbols_path = self.SymbolsPath)
663
664 elif self.ProcessName:
665 self.dbg = PyDbgEng.ProcessAttacher(pid = self.GetProcessIdByName(self.ProcessName),
666 event_callbacks_sink = self._eventHandler,
667 output_callbacks_sink = self._eventHandler,
668 symbols_path = self.SymbolsPath)
669
670 else:
671 raise Exception("Didn't find way to start debugger... bye bye!!")
672
673 WindowsDebugEngineThread.Quit = Event()
674 WindowsDebugEngine.started.set()
675
676 self.dbg.event_loop_with_user_callback(self.Callback, 10)
677 self.dbg.__del__()
678
679 - def Callback(self = None, stuff = None, stuff2 = None):
680 PumpEvents(0.1)
681 if WindowsDebugEngineThread.Quit.isSet():
682 self.dbg.idebug_client.TerminateProcesses()
683 return True
684
686 '''
687 Try and get pid for a process by name.
688 '''
689
690 ourPid = -1
691 procname = procname.lower()
692
693 try:
694 ourPid = win32api.GetCurrentProcessId()
695
696 except:
697 pass
698
699 pids = win32process.EnumProcesses()
700 for pid in pids:
701 if ourPid == pid:
702 continue
703
704 try:
705 hPid = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION | win32con.PROCESS_VM_READ, 0, pid)
706
707 try:
708 mids = win32process.EnumProcessModules(hPid)
709 for mid in mids:
710 name = str(win32process.GetModuleFileNameEx(hPid, mid))
711
712 if name.lower().find(procname) != -1:
713 return pid
714
715 finally:
716 win32api.CloseHandle(hPid)
717 except:
718 pass
719
720 return None
721
723 '''
724 Windows debugger agent. This debugger agent is based on the windbg engine and
725 supports the following features:
726
727 * User mode debugging
728 * Kernel mode debugging
729 * x86 and x64
730 * Symbols and symbol server
731
732 '''
734 Thread.__init__(self)
735 WindowsDebugEngine.started = Event()
736
737
738 WindowsDebugEngine.handlingFault = Event()
739
740 WindowsDebugEngine.handledFault = Event()
741 WindowsDebugEngine.lock = Lock()
742 WindowsDebugEngine.crashInfo = None
743 WindowsDebugEngine.fault = False
744 self.thread = None
745
746 if args.has_key('CommandLine'):
747 self.CommandLine = str(args['CommandLine'])
748 else:
749 self.CommandLine = None
750
751 if args.has_key('ProcessName'):
752 self.ProcessName = str(args['ProcessName'])
753 else:
754 self.ProcessName = None
755
756 if args.has_key('KernelConnectionString'):
757 self.KernelConnectionString = str(args['KernelConnectionString'])
758 else:
759 self.KernelConnectionString = None
760
761 if args.has_key('SymbolsPath'):
762 self.SymbolsPath = str(args['SymbolsPath'])
763 else:
764 self.SymbolsPath = "SRV*http://msdl.microsoft.com/download/symbols"
765
766 if self.CommandLine == None and self.ProcessName == None and self.KernelConnectionString == None:
767 raise Exception("Unable to create WindowsDebugger Instance!!!!!")
768
769
798
806
808 return self.thread and self.thread.isAlive()
809
816
831
853
855 '''
856 Called when a fault was detected.
857 '''
858 self._StopDebugger()
859
861 '''
862 Called when Agent is shutting down.
863 '''
864 self._StopDebugger()
865
866 except:
867 pass
868
869
870 try:
871
872 from pygdb.mi import Gdb
873
875
878
880
881 self.gdb = gdb = Gdb(None)
882 gdb.file(self._command)
883
884 gdb.run(self._params).wait()
885
886 if UnixGdb.quit.isSet():
887 return
888
889 UnixGdb.handlingFault.set()
890
891 buff = "Crash output from GDB\n"
892 buff+= "=====================\n\n"
893 buff+= "break: reason: %s, thread-id: %s\n\n" % (result.reason, result.thread_id)
894 buff+= self._getLocals(gdb)
895 buff+= "\n-------------------\n"
896 buff+= self._getArguments(gdb)
897 buff+= "\n-------------------\n"
898 buff+= self._getStack(gdb)
899 buff+= "\n-------------------\n"
900 buff+= self._getRegisters(gdb)
901
902 gdb.quit()
903
904 UnixGdb.lock.acquire()
905 UnixGdb.creashInfo = { 'DebuggerOutput.txt' : buff }
906 UnixGdb.fault = True
907 UnixGdb.lock.release()
908 UnixGdb.handledFault.set()
909
911 buff = "Frame Locals:\n"
912 try:
913 result = gdb.stack_list_locals().wait()
914 for local in resul.locals:
915 buff += "\t%s = %s\n" % (local.name, local.value)
916 except:
917 pass
918
919 return buff
920
922 buff = "Frame Arguments:\n"
923 try:
924 result = gdb.stack_list_arguments().wait()
925 for frame in result.stack_args.frame:
926 buff += "\tframe: " + frame.level + "\n"
927 for arg in frame.args:
928 buff += "\t\t%s = %s\n" % (arg.name, arg.value)
929 except:
930 pass
931
932 return buff
933
935 buff = "Stack:\n"
936 try:
937 result = gdb.stack_list_frames().wait()
938 for frame in result.stack.frame:
939 buff += "\t%s, at %s:%s\n" % (frame.func, frame.file, frame.line)
940 except:
941 pass
942
943 return buff
944
946 buff = "Registers:\n"
947 try:
948 names = gdb.data_list_register_names().wait()
949 result = gdb.data_list_register_values(regno=registers).wait()
950 for register in result.register_values:
951 name = names.register_names[int(register.number)]
952 buff += "\t%s: %s\n" % (name, register.value)
953 except:
954 pass
955
956 return buff
957
959 '''
960 Unix GDB monitor. This debugger monitor uses the gdb
961 debugger via pygdb wrapper. Tested under Linux and OS X.
962
963 * Collect core files
964 * User mode debugging
965 * Capturing stack trace, registers, etc
966 * Symbols is available
967 '''
968
970
971 UnixGdb.quit = Event()
972 UnixGdb.started = Event()
973 UnixGdb.handlingFault = Event()
974 UnixGdb.handledFault = Event()
975 UnixGdb.lock = Lock()
976 UnixGdb.crashInfo = None
977 UnixGdb.fault = False
978
979 if args.has_key('Command'):
980 self._command = str(args['Command'])
981 self._params = str(args['Params'])
982 self._pid = None
983
984 elif args.has_key('ProcessName'):
985 self._command = None
986 self._params = None
987 self._pid = self.GetProcessIdByName(str(args['ProcessName']))
988
989 else:
990 raise Exception("Unable to create UnixGdb! Error in params!")
991
1008
1010
1011 if self.thread.isAlive():
1012 UnixGdb.quit.set()
1013 UnixGdb.started.clear()
1014 self.thread.gdb.quit()
1015 self.thread.join()
1016 time.sleep(0.25)
1017
1019 return self.thread != None and self.thread.isAlive()
1020
1027
1029 '''
1030 Get any monitored data.
1031 '''
1032 UnixGdb.lock.acquire()
1033 if UnixGdb.crashInfo != None:
1034 ret = UnixGdb.crashInfo
1035 UnixGdb.crashInfo = None
1036 _GdbEventHandler.buff = ""
1037 UnixGdb.lock.release()
1038 return ret
1039
1040 UnixGdb.lock.release()
1041 return None
1042
1044 '''
1045 Check if a fault was detected.
1046 '''
1047
1048 time.sleep(0.15)
1049
1050 if not UnixGdb.handlingFault.isSet():
1051 return False
1052
1053 UnixGdb.handledFault.wait()
1054 UnixGdb.lock.acquire()
1055
1056 if UnixGdb.fault or not self.thread.isAlive():
1057 print ">>>>>> RETURNING FAULT <<<<<<<<<"
1058 UnixGdb.fault = False
1059 UnixGdb.lock.release()
1060 return True
1061
1062 UnixGdb.lock.release()
1063 return False
1064
1066 '''
1067 Called when a fault was detected.
1068 '''
1069 self._StopDebugger()
1070
1072 '''
1073 Called when Agent is shutting down.
1074 '''
1075 self._StopDebugger()
1076
1077 except:
1078 pass
1079
1080
1081