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

Source Code for Module winappdbg.textio

   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  Functions for text input, logging or text output. 
  30   
  31  @group Debugging: 
  32      DebugLog 
  33   
  34  @group Crash reporting: 
  35      CrashDump 
  36   
  37  @group Miscellaneous: 
  38      HexDump, 
  39      HexInput, 
  40      HexOutput, 
  41      Table, 
  42      Logger 
  43  """ 
  44   
  45  __revision__ = "$Id: textio.py 689 2010-06-20 13:56:12Z qvasimodo $" 
  46   
  47  __all__ =   [ 
  48                  'HexDump', 
  49                  'HexInput', 
  50                  'HexOutput', 
  51                  'Table', 
  52                  'CrashDump', 
  53                  'DebugLog', 
  54                  'Logger', 
  55              ] 
  56   
  57  import win32 
  58   
  59  import time 
  60  import struct 
  61  import traceback 
62 63 #------------------------------------------------------------------------------ 64 65 -class HexInput (object):
66 """ 67 Static functions for user input parsing. 68 The counterparts for each method are in the L{HexOutput} class. 69 """ 70 @classmethod
71 - def __new__(cls, *argv, **argd):
72 'Don\'t try to instance this class, it\'s just a namespace!' 73 raise NotImplementedError
74 75 @staticmethod
76 - def integer(token):
77 """ 78 Convert numeric strings into integers. 79 80 @type token: str 81 @param token: String to parse. 82 83 @rtype: int 84 @return: Parsed integer value. 85 """ 86 token = token.strip() 87 if token.startswith('0x'): 88 result = int(token, 16) # hexadecimal 89 elif token.startswith('0b'): 90 result = int(token[2:], 2) # binary 91 elif token.startswith('0o'): 92 result = int(token, 8) # octal 93 else: 94 try: 95 result = int(token) # decimal 96 except ValueError: 97 result = int(token, 16) # hexadecimal (no "0x" prefix) 98 return result
99 100 @staticmethod
101 - def address(token):
102 """ 103 Convert numeric strings into memory addresses. 104 105 @type token: str 106 @param token: String to parse. 107 108 @rtype: int 109 @return: Parsed integer value. 110 """ 111 return int(token, 16)
112 113 @staticmethod
114 - def hexadecimal(token):
115 """ 116 Convert a strip of hexadecimal numbers into binary data. 117 118 @type token: str 119 @param token: String to parse. 120 121 @rtype: str 122 @return: Parsed string value. 123 """ 124 token = ''.join([ c for c in token if c.isalnum() ]) 125 if len(token) % 2 != 0: 126 raise ValueError, "Missing characters in hex data" 127 data = '' 128 for i in xrange(0, len(token), 2): 129 x = token[i:i+2] 130 d = int(x, 16) 131 s = struct.pack('<B', d) 132 data += s 133 return data
134 135 @staticmethod
136 - def pattern(token):
137 """ 138 Convert an hexadecimal search pattern into a POSIX regular expression. 139 140 For example, the following pattern:: 141 142 "B8 0? ?0 ?? ??" 143 144 Would match the following data:: 145 146 "B8 0D F0 AD BA" # mov eax, 0xBAADF00D 147 148 @type token: str 149 @param token: String to parse. 150 151 @rtype: str 152 @return: Parsed string value. 153 """ 154 token = ''.join([ c for c in token if c == '?' or c.isalnum() ]) 155 if len(token) % 2 != 0: 156 raise ValueError, "Missing characters in hex data" 157 regexp = '' 158 for i in xrange(0, len(token), 2): 159 x = token[i:i+2] 160 if x == '??': 161 regexp += '.' 162 elif x[0] == '?': 163 f = '\\x%%.1x%s' % x[1] 164 x = ''.join([ f % c for c in xrange(0, 0x10) ]) 165 regexp = '%s[%s]' % (regexp, x) 166 elif x[1] == '?': 167 f = '\\x%s%%.1x' % x[0] 168 x = ''.join([ f % c for c in xrange(0, 0x10) ]) 169 regexp = '%s[%s]' % (regexp, x) 170 else: 171 regexp = '%s\\x%s' % (regexp, x) 172 return regexp
173 174 @classmethod
175 - def integer_list_file(cls, filename):
176 """ 177 Read a list of integers from a file. 178 179 The file format is: 180 181 - # anywhere in the line begins a comment 182 - leading and trailing spaces are ignored 183 - empty lines are ignored 184 - integers can be specified as: 185 - decimal numbers ("100" is 100) 186 - hexadecimal numbers ("0x100" is 256) 187 - binary numbers ("0b100" is 4) 188 - octal numbers ("0100" is 64) 189 190 @type filename: str 191 @param filename: Name of the file to read. 192 193 @rtype: list( int ) 194 @return: List of integers read from the file. 195 """ 196 count = 0 197 result = list() 198 fd = open(filename, 'r') 199 for line in fd: 200 count = count + 1 201 if '#' in line: 202 line = line[ : line.find('#') ] 203 line = line.strip() 204 if line: 205 try: 206 value = cls.integer(line) 207 except ValueError, e: 208 msg = "Error in line %d of %s: %s" 209 msg = msg % (count, filename, str(e)) 210 raise ValueError, msg 211 result.append(value) 212 return result
213 214 @classmethod
215 - def string_list_file(cls, filename):
216 """ 217 Read a list of string values from a file. 218 219 The file format is: 220 221 - # anywhere in the line begins a comment 222 - leading and trailing spaces are ignored 223 - empty lines are ignored 224 - strings cannot span over a single line 225 226 @type filename: str 227 @param filename: Name of the file to read. 228 229 @rtype: list 230 @return: List of integers and strings read from the file. 231 """ 232 count = 0 233 result = list() 234 fd = open(filename, 'r') 235 for line in fd: 236 count = count + 1 237 if '#' in line: 238 line = line[ : line.find('#') ] 239 line = line.strip() 240 if line: 241 result.append(line) 242 return result
243 244 @classmethod
245 - def mixed_list_file(cls, filename):
246 """ 247 Read a list of mixed values from a file. 248 249 The file format is: 250 251 - # anywhere in the line begins a comment 252 - leading and trailing spaces are ignored 253 - empty lines are ignored 254 - strings cannot span over a single line 255 - integers can be specified as: 256 - decimal numbers ("100" is 100) 257 - hexadecimal numbers ("0x100" is 256) 258 - binary numbers ("0b100" is 4) 259 - octal numbers ("0100" is 64) 260 261 @type filename: str 262 @param filename: Name of the file to read. 263 264 @rtype: list 265 @return: List of integers and strings read from the file. 266 """ 267 count = 0 268 result = list() 269 fd = open(filename, 'r') 270 for line in fd: 271 count = count + 1 272 if '#' in line: 273 line = line[ : line.find('#') ] 274 line = line.strip() 275 if line: 276 try: 277 value = cls.integer(line) 278 except ValueError, e: 279 value = line 280 result.append(value) 281 return result
282
283 #------------------------------------------------------------------------------ 284 285 -class HexOutput (object):
286 """ 287 Static functions for user output parsing. 288 The counterparts for each method are in the L{HexInput} class. 289 290 @type integer_size: int 291 @cvar integer_size: Size in characters of an outputted integer. 292 This value is platform dependent. 293 294 @type address_size: int 295 @cvar address_size: Size in characters of an outputted address. 296 This value is platform dependent. 297 """ 298 @classmethod
299 - def __new__(cls, *argv, **argd):
300 'Don\'t try to instance this class, it\'s just a namespace!' 301 raise NotImplementedError
302 303 integer_size = len('%x' % (win32.DWORD(-1).value))+2 304 address_size = len('%x' % (win32.SIZE_T(-1).value))+2 305 306 @classmethod
307 - def integer(cls, integer):
308 """ 309 @type integer: int 310 @param integer: Integer. 311 312 @rtype: str 313 @return: Text output. 314 """ 315 return ('0x%%.%dx' % (cls.integer_size - 2)) % integer
316 317 @classmethod
318 - def address(cls, address):
319 """ 320 @type address: int 321 @param address: Memory address. 322 323 @rtype: str 324 @return: Text output. 325 """ 326 return ('0x%%.%dx' % (cls.address_size - 2)) % address
327 328 @staticmethod
329 - def hexadecimal(data):
330 """ 331 Convert binary data to a string of hexadecimal numbers. 332 333 @type data: str 334 @param data: Binary data. 335 336 @rtype: str 337 @return: Hexadecimal representation. 338 """ 339 return HexDump.hexadecimal(data, separator = '')
340 341 @classmethod
342 - def integer_list_file(cls, filename, values):
343 """ 344 Write a list of integers to a file. 345 If a file of the same name exists, it's contents are replaced. 346 347 See L{HexInput.integer_list_file} for a description of the file format. 348 349 @type filename: str 350 @param filename: Name of the file to write. 351 352 @type values: list( int ) 353 @param values: List of integers to write to the file. 354 """ 355 fd = open(filename, 'w') 356 for integer in values: 357 print >> fd, cls.integer(integer) 358 fd.close()
359 360 @classmethod
361 - def string_list_file(cls, filename, values):
362 """ 363 Write a list of strings to a file. 364 If a file of the same name exists, it's contents are replaced. 365 366 See L{HexInput.string_list_file} for a description of the file format. 367 368 @type filename: str 369 @param filename: Name of the file to write. 370 371 @type values: list( int ) 372 @param values: List of strings to write to the file. 373 """ 374 fd = open(filename, 'w') 375 for string in values: 376 print >> fd, string 377 fd.close()
378 379 @classmethod
380 - def mixed_list_file(cls, filename, values):
381 """ 382 Write a list of mixed values to a file. 383 If a file of the same name exists, it's contents are replaced. 384 385 See L{HexInput.mixed_list_file} for a description of the file format. 386 387 @type filename: str 388 @param filename: Name of the file to write. 389 390 @type values: list( int ) 391 @param values: List of mixed values to write to the file. 392 """ 393 fd = open(filename, 'w') 394 for original in values: 395 try: 396 parsed = cls.integer(original) 397 except TypeError: 398 parsed = repr(original) 399 print >> fd, parsed 400 fd.close()
401
402 #------------------------------------------------------------------------------ 403 404 -class HexDump (object):
405 """ 406 Static functions for hexadecimal dumps. 407 408 @type integer_size: int 409 @cvar integer_size: Size in characters of an outputted integer. 410 This value is platform dependent. 411 412 @type address_size: int 413 @cvar address_size: Size in characters of an outputted address. 414 This value is platform dependent. 415 """ 416 @classmethod
417 - def __new__(cls, *argv, **argd):
418 'Don\'t try to instance this class, it\'s just a namespace!' 419 raise NotImplementedError
420 421 integer_size = len('%x' % (win32.DWORD(-1).value)) 422 address_size = len('%x' % (win32.SIZE_T(-1).value)) 423 424 @classmethod
425 - def integer(cls, integer):
426 """ 427 @type integer: int 428 @param integer: Integer. 429 430 @rtype: str 431 @return: Text output. 432 """ 433 return ('%%.%dX' % cls.integer_size) % integer
434 435 @classmethod
436 - def address(cls, address):
437 """ 438 @type address: int 439 @param address: Memory address. 440 441 @rtype: str 442 @return: Text output. 443 """ 444 return ('%%.%dX' % cls.address_size) % address
445 446 @staticmethod
447 - def printable(data):
448 """ 449 Replace unprintable characters with dots. 450 451 @type data: str 452 @param data: Binary data. 453 454 @rtype: str 455 @return: Printable text. 456 """ 457 result = '' 458 for c in data: 459 if 32 < ord(c) < 128: 460 result += c 461 else: 462 result += '.' 463 return result
464 465 @staticmethod
466 - def hexadecimal(data, separator = ''):
467 """ 468 Convert binary data to a string of hexadecimal numbers. 469 470 @type data: str 471 @param data: Binary data. 472 473 @type separator: str 474 @param separator: 475 Separator between the hexadecimal representation of each character. 476 477 @rtype: str 478 @return: Hexadecimal representation. 479 """ 480 return separator.join( [ '%.2x' % ord(c) for c in data ] )
481 482 @staticmethod
483 - def hexa_word(data, separator = ' '):
484 """ 485 Convert binary data to a string of hexadecimal WORDs. 486 487 @type data: str 488 @param data: Binary data. 489 490 @type separator: str 491 @param separator: 492 Separator between the hexadecimal representation of each WORD. 493 494 @rtype: str 495 @return: Hexadecimal representation. 496 """ 497 if len(data) & 1 != 0: 498 data += '\0' 499 return separator.join( [ '%.4x' % struct.unpack('<H', data[i:i+2])[0] \ 500 for i in xrange(0, len(data), 2) ] )
501 502 @staticmethod
503 - def hexa_dword(data, separator = ' '):
504 """ 505 Convert binary data to a string of hexadecimal DWORDs. 506 507 @type data: str 508 @param data: Binary data. 509 510 @type separator: str 511 @param separator: 512 Separator between the hexadecimal representation of each DWORD. 513 514 @rtype: str 515 @return: Hexadecimal representation. 516 """ 517 if len(data) & 3 != 0: 518 data += '\0' * (4 - (len(data) & 3)) 519 return separator.join( [ '%.8x' % struct.unpack('<L', data[i:i+4])[0] \ 520 for i in xrange(0, len(data), 4) ] )
521 522 @staticmethod
523 - def hexa_qword(data, separator = ' '):
524 """ 525 Convert binary data to a string of hexadecimal QWORDs. 526 527 @type data: str 528 @param data: Binary data. 529 530 @type separator: str 531 @param separator: 532 Separator between the hexadecimal representation of each QWORD. 533 534 @rtype: str 535 @return: Hexadecimal representation. 536 """ 537 if len(data) & 7 != 0: 538 data += '\0' * (8 - (len(data) & 7)) 539 return separator.join( [ '%.16x' % struct.unpack('<Q', data[i:i+8])[0]\ 540 for i in xrange(0, len(data), 8) ] )
541 542 @classmethod
543 - def hexline(cls, data, separator = ' ', width = None):
544 """ 545 Dump a line of hexadecimal numbers from binary data. 546 547 @type data: str 548 @param data: Binary data. 549 550 @type separator: str 551 @param separator: 552 Separator between the hexadecimal representation of each character. 553 554 @type width: int 555 @param width: 556 (Optional) Maximum number of characters to convert per text line. 557 This value is also used for padding. 558 559 @rtype: str 560 @return: Multiline output text. 561 """ 562 if width is None: 563 fmt = '%s %s' 564 else: 565 fmt = '%%-%ds %%-%ds' % ((len(separator)+2)*width-1, width) 566 return fmt % (cls.hexadecimal(data, separator), cls.printable(data))
567 568 @classmethod
569 - def hexblock(cls, data, address = None, separator = ' ', width = 8):
570 """ 571 Dump a block of hexadecimal numbers from binary data. 572 Also show a printable text version of the data. 573 574 @type data: str 575 @param data: Binary data. 576 577 @type address: str 578 @param address: Memory address where the data was read from. 579 580 @type separator: str 581 @param separator: 582 Separator between the hexadecimal representation of each character. 583 584 @type width: int 585 @param width: 586 (Optional) Maximum number of characters to convert per text line. 587 588 @rtype: str 589 @return: Multiline output text. 590 """ 591 return cls.hexblock_cb(cls.hexline, data, address, width, 592 cb_kwargs = {'width' : width, 'separator' : separator})
593 594 @classmethod
595 - def hexblock_cb(cls, callback, data, address = None, width = 16, 596 cb_args = (), cb_kwargs = {}):
597 """ 598 Dump a block of binary data using a callback function to convert each 599 line of text. 600 601 @type callback: function 602 @param callback: Callback function to convert each line of data. 603 604 @type data: str 605 @param data: Binary data. 606 607 @type address: str 608 @param address: 609 (Optional) Memory address where the data was read from. 610 611 @type cb_args: str 612 @param cb_args: 613 (Optional) Arguments to pass to the callback function. 614 615 @type cb_kwargs: str 616 @param cb_kwargs: 617 (Optional) Keyword arguments to pass to the callback function. 618 619 @type width: int 620 @param width: 621 (Optional) Maximum number of bytes to convert per text line. 622 623 @rtype: str 624 @return: Multiline output text. 625 """ 626 result = '' 627 if address is None: 628 for i in xrange(0, len(data), width): 629 result = '%s%s\n' % ( result, \ 630 callback(data[i:i+width], *cb_args, **cb_kwargs) ) 631 else: 632 for i in xrange(0, len(data), width): 633 result = '%s%s: %s\n' % ( result, cls.address(address), \ 634 callback(data[i:i+width], *cb_args, **cb_kwargs) ) 635 address += width 636 return result
637 638 @classmethod
639 - def hexblock_byte(cls, data, address = None, separator = ' ', width = 16):
640 """ 641 Dump a block of hexadecimal BYTEs from binary data. 642 643 @type data: str 644 @param data: Binary data. 645 646 @type address: str 647 @param address: Memory address where the data was read from. 648 649 @type separator: str 650 @param separator: 651 Separator between the hexadecimal representation of each BYTE. 652 653 @type width: int 654 @param width: 655 (Optional) Maximum number of BYTEs to convert per text line. 656 657 @rtype: str 658 @return: Multiline output text. 659 """ 660 return cls.hexblock_cb(cls.hexadecimal, data, address, width, 661 cb_kwargs = {'separator': separator})
662 663 @classmethod
664 - def hexblock_word(cls, data, address = None, separator = ' ', width = 8):
665 """ 666 Dump a block of hexadecimal WORDs from binary data. 667 668 @type data: str 669 @param data: Binary data. 670 671 @type address: str 672 @param address: Memory address where the data was read from. 673 674 @type separator: str 675 @param separator: 676 Separator between the hexadecimal representation of each WORD. 677 678 @type width: int 679 @param width: 680 (Optional) Maximum number of WORDs to convert per text line. 681 682 @rtype: str 683 @return: Multiline output text. 684 """ 685 return cls.hexblock_cb(cls.hexa_word, data, address, width * 2, 686 cb_kwargs = {'separator': separator})
687 688 @classmethod
689 - def hexblock_dword(cls, data, address = None, separator = ' ', width = 4):
690 """ 691 Dump a block of hexadecimal DWORDs from binary data. 692 693 @type data: str 694 @param data: Binary data. 695 696 @type address: str 697 @param address: Memory address where the data was read from. 698 699 @type separator: str 700 @param separator: 701 Separator between the hexadecimal representation of each DWORD. 702 703 @type width: int 704 @param width: 705 (Optional) Maximum number of DWORDs to convert per text line. 706 707 @rtype: str 708 @return: Multiline output text. 709 """ 710 return cls.hexblock_cb(cls.hexa_dword, data, address, width * 4, 711 cb_kwargs = {'separator': separator})
712 713 @classmethod
714 - def hexblock_qword(cls, data, address = None, separator = ' ', width = 2):
715 """ 716 Dump a block of hexadecimal QWORDs from binary data. 717 718 @type data: str 719 @param data: Binary data. 720 721 @type address: str 722 @param address: Memory address where the data was read from. 723 724 @type separator: str 725 @param separator: 726 Separator between the hexadecimal representation of each QWORD. 727 728 @type width: int 729 @param width: 730 (Optional) Maximum number of QWORDs to convert per text line. 731 732 @rtype: str 733 @return: Multiline output text. 734 """ 735 return cls.hexblock_cb(cls.hexa_qword, data, address, width * 8, 736 cb_kwargs = {'separator': separator})
737
738 #------------------------------------------------------------------------------ 739 740 -class Table (object):
741 """ 742 Text based table. The number of columns and the width of each column 743 is automatically calculated. 744 """ 745
746 - def __init__(self, sep = ' '):
747 """ 748 @type sep: str 749 @param sep: Separator between cells in each row. 750 """ 751 self.__cols = list() 752 self.__width = list() 753 self.__sep = sep
754
755 - def addRow(self, *row):
756 """ 757 Add a row to the table. All items are converted to strings. 758 759 @type row: tuple 760 @keyword row: Each argument is a cell in the table. 761 """ 762 row = [ str(item) for item in row ] 763 len_row = [ len(item) for item in row ] 764 width = self.__width 765 len_old = len(width) 766 len_new = len(row) 767 known = min(len_old, len_new) 768 missing = len_new - len_old 769 if missing > 0: 770 width.extend( len_row[ -missing : ] ) 771 self.__width = [ max( width[i], len_row[i] ) for i in xrange(len_new) ] 772 self.__cols.append(row)
773
774 - def justify(self, column, direction):
775 """ 776 Make the text in a column left or right justified. 777 778 @type column: int 779 @param column: Index of the column. 780 781 @type direction: int 782 @param direction: 783 C{1} to justify left, 784 C{-1} to justify right. 785 786 @raise IndexError: Bad column index. 787 @raise ValueError: Bad direction value. 788 """ 789 if direction == -1: 790 self.__width[column] = abs(self.__width[column]) 791 elif direction == 1: 792 self.__width[column] = - abs(self.__width[column]) 793 else: 794 raise ValueError, "Bad direction value."
795
796 - def getOutput(self):
797 """ 798 Get the text output for the table. 799 800 @rtype: str 801 @return: Text output. 802 """ 803 return '%s\n' % '\n'.join( self.yieldOutput() )
804
805 - def yieldOutput(self):
806 """ 807 Generate the text output for the table. 808 809 @rtype: generator of str 810 @return: Text output. 811 """ 812 width = self.__width 813 if width: 814 num_cols = len(width) 815 fmt = ['%%%ds' % -w for w in width] 816 if width[-1] > 0: 817 fmt[-1] = '%s' 818 fmt = self.__sep.join(fmt) 819 for row in self.__cols: 820 row.extend( [''] * (num_cols - len(row)) ) 821 yield fmt % tuple(row)
822
823 #------------------------------------------------------------------------------ 824 825 -class CrashDump (object):
826 """ 827 Static functions for crash dumps. 828 829 @type reg_template: str 830 @cvar reg_template: Template for the L{dump_registers} method. 831 """ 832 @classmethod
833 - def __new__(cls, *argv, **argd):
834 'Don\'t try to instance this class, it\'s just a namespace!' 835 raise NotImplementedError
836 837 # Templates for the dump_registers method. 838 reg_template = { 839 'i386' : ( 840 'eax=%(Eax).8x ebx=%(Ebx).8x ecx=%(Ecx).8x edx=%(Edx).8x esi=%(Esi).8x edi=%(Edi).8x\n' 841 'eip=%(Eip).8x esp=%(Esp).8x ebp=%(Ebp).8x %(efl_dump)s\n' 842 'cs=%(SegCs).4x ss=%(SegSs).4x ds=%(SegDs).4x es=%(SegEs).4x fs=%(SegFs).4x gs=%(SegGs).4x efl=%(EFlags).8x\n' 843 ), 844 'amd64' : ( 845 'rax=%(Rax).16x rbx=%(Rbx).16x rcx=%(Rcx).16x\n' 846 'rdx=%(Rdx).16x rsi=%(Rsi).16x rdi=%(Rdi).16x\n' 847 'rip=%(Rip).16x rsp=%(Rsp).16x rbp=%(Rbp).16x\n' 848 ' r8=%(R8).16x r9=%(R9).16x r10=%(R10).16x\n' 849 'r11=%(R11).16x r12=%(R12).16x r13=%(R13).16x\n' 850 'r14=%(R14).16x r15=%(R15).16x\n' 851 '%(efl_dump)s\n' 852 'cs=%(SegCs).4x ss=%(SegSs).4x ds=%(SegDs).4x es=%(SegEs).4x fs=%(SegFs).4x gs=%(SegGs).4x efl=%(EFlags).8x\n' 853 ), 854 } 855 856 @staticmethod
857 - def dump_flags(efl):
858 """ 859 Dump the x86 processor flags. 860 The output mimics that of the WinDBG debugger. 861 862 @type efl: int 863 @param efl: Value of the eFlags register. 864 865 @rtype: str 866 @return: Text suitable for logging. 867 """ 868 if efl is None: 869 return '' 870 if win32.CONTEXT.arch not in ('i386', 'amd64'): 871 raise NotImplementedError 872 efl_dump = 'iopl=%1d' % ((efl & 0x3000) >> 12) 873 if efl & 0x100000: 874 efl_dump += ' vip' 875 else: 876 efl_dump += ' ' 877 if efl & 0x80000: 878 efl_dump += ' vif' 879 else: 880 efl_dump += ' ' 881 # 0x20000 ??? 882 if efl & 0x800: 883 efl_dump += ' ov' # Overflow 884 else: 885 efl_dump += ' no' # No overflow 886 if efl & 0x400: 887 efl_dump += ' dn' # Downwards 888 else: 889 efl_dump += ' up' # Upwards 890 if efl & 0x200: 891 efl_dump += ' ei' # Enable interrupts 892 else: 893 efl_dump += ' di' # Disable interrupts 894 # 0x100 trap flag 895 if efl & 0x80: 896 efl_dump += ' ng' # Negative 897 else: 898 efl_dump += ' pl' # Positive 899 if efl & 0x40: 900 efl_dump += ' zr' # Zero 901 else: 902 efl_dump += ' nz' # Nonzero 903 if efl & 0x10: 904 efl_dump += ' ac' # Auxiliary carry 905 else: 906 efl_dump += ' na' # No auxiliary carry 907 # 0x8 ??? 908 if efl & 0x4: 909 efl_dump += ' pe' # Parity odd 910 else: 911 efl_dump += ' po' # Parity even 912 # 0x2 ??? 913 if efl & 0x1: 914 efl_dump += ' cy' # Carry 915 else: 916 efl_dump += ' nc' # No carry 917 return efl_dump
918 919 @classmethod
920 - def dump_registers(cls, registers):
921 """ 922 Dump the x86 processor register values. 923 The output mimics that of the WinDBG debugger. 924 925 @type registers: dict( str S{->} int ) 926 @param registers: Dictionary mapping register names to their values. 927 928 @rtype: str 929 @return: Text suitable for logging. 930 """ 931 if registers is None: 932 return '' 933 arch = win32.CONTEXT.arch 934 if arch not in ('i386', 'amd64'): 935 raise NotImplementedError 936 registers = registers.copy() 937 registers['efl_dump'] = cls.dump_flags( registers['EFlags'] ) 938 return cls.reg_template[arch] % registers
939 940 @staticmethod
941 - def dump_registers_peek(registers, data, separator = ' ', width = 16):
942 """ 943 Dump data pointed to by the given registers, if any. 944 945 @type registers: dict( str S{->} int ) 946 @param registers: Dictionary mapping register names to their values. 947 This value is returned by L{Thread.get_context}. 948 949 @type data: dict( str S{->} str ) 950 @param data: Dictionary mapping register names to the data they point to. 951 This value is returned by L{Thread.peek_pointers_in_registers}. 952 953 @rtype: str 954 @return: Text suitable for logging. 955 """ 956 if None in (registers, data): 957 return '' 958 names = data.keys() 959 names.sort() 960 result = '' 961 for reg_name in names: 962 tag = reg_name.lower() 963 dumped = HexDump.hexline(data[reg_name], separator, width) 964 result += '%s -> %s\n' % (tag, dumped) 965 return result
966 967 @staticmethod
968 - def dump_data_peek(data, base = 0, separator = ' ', width = 16):
969 """ 970 Dump data from pointers guessed within the given binary data. 971 972 @type data: str 973 @param data: Dictionary mapping offsets to the data they point to. 974 975 @type base: int 976 @param base: Base offset. 977 978 @rtype: str 979 @return: Text suitable for logging. 980 """ 981 if data is None: 982 return '' 983 pointers = data.keys() 984 pointers.sort() 985 result = '' 986 for offset in pointers: 987 dumped = HexDump.hexline(data[offset], separator, width) 988 result += '%s -> %s' % (HexDump.address(base + offset), dumped) 989 return result
990 991 @staticmethod
992 - def dump_stack_peek(data, separator = ' ', width = 16):
993 """ 994 Dump data from pointers guessed within the given stack dump. 995 996 @type data: str 997 @param data: Dictionary mapping stack offsets to the data they point to. 998 999 @rtype: str 1000 @return: Text suitable for logging. 1001 """ 1002 if data is None: 1003 return '' 1004 pointers = data.keys() 1005 pointers.sort() 1006 result = '' 1007 if pointers: 1008 if win32.CONTEXT.arch == 'i386': 1009 spreg = 'esp' 1010 elif win32.CONTEXT.arch == 'i386': 1011 spreg = 'rsp' 1012 else: 1013 spreg = 'STACK' # just a generic tag 1014 tag_fmt = '[%s+0x%%.%dx]' % (spreg, len( '%x' % pointers[-1] ) ) 1015 for offset in pointers: 1016 dumped = HexDump.hexline(data[offset], separator, width) 1017 tag = tag_fmt % offset 1018 result += '%s -> %s\n' % (tag, dumped) 1019 return result
1020 1021 @staticmethod
1022 - def dump_stack_trace(stack_trace):
1023 """ 1024 Dump a stack trace, as returned by L{Thread.get_stack_trace} with the 1025 C{bUseLabels} parameter set to C{False}. 1026 1027 @type stack_trace: list( int, int, str ) 1028 @param stack_trace: Stack trace as a list of tuples of 1029 ( return address, frame pointer, module filename ) 1030 1031 @rtype: str 1032 @return: Text suitable for logging. 1033 """ 1034 if stack_trace is None: 1035 return '' 1036 table = Table() 1037 table.addRow('Frame', 'Origin', 'Module') 1038 for (fp, ra, mod) in stack_trace: 1039 table.addRow( HexDump.address(fp), HexDump.address(ra), mod ) 1040 return table.getOutput()
1041 1042 @staticmethod
1043 - def dump_stack_trace_with_labels(stack_trace):
1044 """ 1045 Dump a stack trace, 1046 as returned by L{Thread.get_stack_trace_with_labels}. 1047 1048 @type stack_trace: list( int, int, str ) 1049 @param stack_trace: Stack trace as a list of tuples of 1050 ( return address, frame pointer, module filename ) 1051 1052 @rtype: str 1053 @return: Text suitable for logging. 1054 """ 1055 if stack_trace is None: 1056 return '' 1057 table = Table() 1058 table.addRow('Frame', 'Origin') 1059 for (fp, label) in stack_trace: 1060 table.addRow( HexDump.address(fp), label ) 1061 return table.getOutput()
1062 1063 # TODO 1064 # + Instead of a star when EIP points to, it would be better to show 1065 # any register value (or other values like the exception address) that 1066 # points to a location in the dissassembled code. 1067 # + It'd be very useful to show some labels here. 1068 # + It'd be very useful to show register contents for code at EIP 1069 @staticmethod
1070 - def dump_code(disassembly, pc = None, bLowercase = True):
1071 """ 1072 Dump a disassembly. Optionally mark where the program counter is. 1073 1074 @type disassembly: list of tuple( int, int, str, str ) 1075 @param disassembly: Disassembly dump as returned by 1076 L{Process.disassemble} or L{Thread.disassemble_around_pc}. 1077 1078 @type pc: int 1079 @param pc: (Optional) Program counter. 1080 1081 @type bLowercase: bool 1082 @param bLowercase: (Optional) If C{True} convert the code to lowercase. 1083 1084 @rtype: str 1085 @return: Text suitable for logging. 1086 """ 1087 if disassembly is None: 1088 return '' 1089 table = Table(sep = ' | ') 1090 for (addr, size, code, dump) in disassembly: 1091 if bLowercase: 1092 code = code.lower() 1093 if addr == pc: 1094 addr = ' * %s' % HexDump.address(addr) 1095 else: 1096 addr = ' %s' % HexDump.address(addr) 1097 table.addRow(addr, dump, code) 1098 table.justify(1, 1) 1099 return table.getOutput()
1100 1101 @staticmethod
1102 - def dump_code_line(disassembly_line, bShowAddress = True, 1103 bShowDump = True, 1104 bLowercase = True, 1105 dwDumpWidth = None, 1106 dwCodeWidth = None):
1107 """ 1108 Dump a single line of code. To dump a block of code use L{dump_code}. 1109 1110 @type disassembly_line: tuple( int, int, str, str ) 1111 @param disassembly_line: Single item of the list returned by 1112 L{Process.disassemble} or L{Thread.disassemble_around_pc}. 1113 1114 @type bShowAddress: bool 1115 @param bShowAddress: (Optional) If C{True} show the memory address. 1116 1117 @type bShowDump: bool 1118 @param bShowDump: (Optional) If C{True} show the hexadecimal dump. 1119 1120 @type bLowercase: bool 1121 @param bLowercase: (Optional) If C{True} convert the code to lowercase. 1122 1123 @type dwDumpWidth: int or None 1124 @param dwDumpWidth: (Optional) Width in characters of the hex dump. 1125 1126 @type dwCodeWidth: int or None 1127 @param dwCodeWidth: (Optional) Width in characters of the code. 1128 1129 @rtype: str 1130 @return: Text suitable for logging. 1131 """ 1132 (addr, size, code, dump) = disassembly_line 1133 dump = dump.replace(' ', '') 1134 result = list() 1135 fmt = '' 1136 if bShowAddress: 1137 result.append( HexDump.address(addr) ) 1138 fmt += '%%%ds:' % HexDump.address_size 1139 if bShowDump: 1140 result.append(dump) 1141 if dwDumpWidth: 1142 fmt += ' %%-%ds' % dwDumpWidth 1143 else: 1144 fmt += ' %s' 1145 if bLowercase: 1146 code = code.lower() 1147 result.append(code) 1148 if dwCodeWidth: 1149 fmt += ' %%-%ds' % dwCodeWidth 1150 else: 1151 fmt += ' %s' 1152 return fmt % tuple(result)
1153 1154 @staticmethod
1155 - def dump_memory_map(memoryMap, mappedFilenames = None):
1156 """ 1157 Dump the memory map of a process. Optionally show the filenames for 1158 memory mapped files as well. 1159 1160 @type memoryMap: list( L{win32.MemoryBasicInformation} ) 1161 @param memoryMap: Memory map returned by L{Process.get_memory_map}. 1162 1163 @type mappedFilenames: dict( int S{->} str ) 1164 @param mappedFilenames: (Optional) Memory mapped filenames 1165 returned by L{Process.get_mapped_filenames}. 1166 1167 @rtype: str 1168 @return: Text suitable for logging. 1169 """ 1170 table = Table() 1171 if mappedFilenames: 1172 table.addRow("Address", "Size", "State", "Access", "Type", "File") 1173 else: 1174 table.addRow("Address", "Size", "State", "Access", "Type") 1175 1176 # For each memory block in the map... 1177 for mbi in memoryMap: 1178 1179 # Address and size of memory block. 1180 BaseAddress = HexDump.address(mbi.BaseAddress) 1181 RegionSize = HexDump.address(mbi.RegionSize) 1182 1183 # State (free or allocated). 1184 mbiState = mbi.State 1185 if mbiState == win32.MEM_RESERVE: 1186 State = "Reserved" 1187 elif mbiState == win32.MEM_COMMIT: 1188 State = "Commited" 1189 elif mbiState == win32.MEM_FREE: 1190 State = "Free" 1191 else: 1192 State = "Unknown" 1193 1194 # Page protection bits (R/W/X/G). 1195 if mbiState != win32.MEM_COMMIT: 1196 Protect = "" 1197 else: 1198 mbiProtect = mbi.Protect 1199 if mbiProtect & win32.PAGE_NOACCESS: 1200 Protect = "--- " 1201 elif mbiProtect & win32.PAGE_READONLY: 1202 Protect = "R-- " 1203 elif mbiProtect & win32.PAGE_READWRITE: 1204 Protect = "RW- " 1205 elif mbiProtect & win32.PAGE_WRITECOPY: 1206 Protect = "RC- " 1207 elif mbiProtect & win32.PAGE_EXECUTE: 1208 Protect = "--X " 1209 elif mbiProtect & win32.PAGE_EXECUTE_READ: 1210 Protect = "R-X " 1211 elif mbiProtect & win32.PAGE_EXECUTE_READWRITE: 1212 Protect = "RWX " 1213 elif mbiProtect & win32.PAGE_EXECUTE_WRITECOPY: 1214 Protect = "RCX " 1215 else: 1216 Protect = "??? " 1217 if mbiProtect & win32.PAGE_GUARD: 1218 Protect += "G" 1219 else: 1220 Protect += "-" 1221 if mbiProtect & win32.PAGE_NOCACHE: 1222 Protect += "N" 1223 else: 1224 Protect += "-" 1225 if mbiProtect & win32.PAGE_WRITECOMBINE: 1226 Protect += "W" 1227 else: 1228 Protect += "-" 1229 1230 # Type (file mapping, executable image, or private memory). 1231 mbiType = mbi.Type 1232 if mbiType == win32.MEM_IMAGE: 1233 Type = "Image" 1234 elif mbiType == win32.MEM_MAPPED: 1235 Type = "Mapped" 1236 elif mbiType == win32.MEM_PRIVATE: 1237 Type = "Private" 1238 elif mbiType == 0: 1239 Type = "" 1240 else: 1241 Type = "Unknown" 1242 1243 # Output a row in the table. 1244 if mappedFilenames: 1245 FileName = mappedFilenames.get(mbi.BaseAddress, '') 1246 table.addRow( BaseAddress, RegionSize, State, Protect, Type, FileName ) 1247 else: 1248 table.addRow( BaseAddress, RegionSize, State, Protect, Type ) 1249 1250 # Return the table output. 1251 return table.getOutput()
1252
1253 #------------------------------------------------------------------------------ 1254 1255 -class DebugLog (object):
1256 'Static functions for debug logging.' 1257 @classmethod
1258 - def __new__(cls, *argv, **argd):
1259 'Don\'t try to instance this class, it\'s just a namespace!' 1260 raise NotImplementedError
1261 1262 @staticmethod
1263 - def log_text(text):
1264 """ 1265 Log lines of text, inserting a timestamp. 1266 1267 @type text: str 1268 @param text: Text to log. 1269 1270 @rtype: str 1271 @return: Log line. 1272 """ 1273 if text.endswith('\n'): 1274 text = text[:-len('\n')] 1275 #text = text.replace('\n', '\n\t\t') # text CSV 1276 ltime = time.strftime("%X") 1277 msecs = (time.time() % 1) * 1000 1278 return '[%s.%04d] %s' % (ltime, msecs, text)
1279 #return '[%s.%04d]\t%s' % (ltime, msecs, text) # text CSV 1280 1281 @classmethod
1282 - def log_event(cls, event, text = None):
1283 """ 1284 Log lines of text associated with a debug event. 1285 1286 @type event: L{Event} 1287 @param event: Event object. 1288 1289 @type text: str 1290 @param text: (Optional) Text to log. If no text is provided the default 1291 is to show a description of the event itself. 1292 1293 @rtype: str 1294 @return: Log line. 1295 """ 1296 if not text: 1297 if event.get_event_code() == win32.EXCEPTION_DEBUG_EVENT: 1298 what = event.get_exception_description() 1299 if event.is_first_chance(): 1300 what = '%s (first chance)' % what 1301 else: 1302 what = '%s (second chance)' % what 1303 try: 1304 address = event.get_fault_address() 1305 except NotImplementedError: 1306 address = event.get_exception_address() 1307 else: 1308 what = event.get_event_name() 1309 address = event.get_thread().get_pc() 1310 label = event.get_process().get_label_at_address(address) 1311 if label: 1312 where = '%s (%s)' % (HexDump.address(address), label) 1313 else: 1314 where = HexDump.address(address) 1315 text = '%s at %s' % (what, where) 1316 text = 'pid %d tid %d: %s' % (event.get_pid(), event.get_tid(), text) 1317 #text = 'pid %d tid %d:\t%s' % (event.get_pid(), event.get_tid(), text) # text CSV 1318 return cls.log_text(text)
1319
1320 #------------------------------------------------------------------------------ 1321 1322 -class Logger(object):
1323 """ 1324 Logs text to standard output and/or a text file. 1325 1326 @type logfile: str or None 1327 @ivar logfile: Append messahes to this text file. 1328 1329 @type verbose: bool 1330 @ivar verbose: C{True} to print messages to standard output. 1331 1332 @type fd: file 1333 @ivar fd: File object where log messages are printed to. 1334 C{None} if no log file is used. 1335 """ 1336
1337 - def __init__(self, logfile = None, verbose = True):
1338 """ 1339 @type logfile: str or None 1340 @param logfile: Append messahes to this text file. 1341 1342 @type verbose: bool 1343 @param verbose: C{True} to print messages to standard output. 1344 """ 1345 self.verbose = verbose 1346 self.logfile = logfile 1347 if self.logfile: 1348 self.fd = open(self.logfile, 'a+')
1349
1350 - def __logfile_error(self, e):
1351 """ 1352 Shows an error message to standard error 1353 if the log file can't be written to. 1354 1355 Used internally. 1356 1357 @type e: Exception 1358 @param e: Exception raised when trying to write to the log file. 1359 """ 1360 from sys import stderr 1361 msg = "Warning, error writing log file %s: %s\n" 1362 msg = msg % (self.logfile, str(e)) 1363 stderr.write(DebugLog.log_text(msg)) 1364 self.logfile = None 1365 self.fd = None
1366
1367 - def __do_log(self, text):
1368 """ 1369 Writes the given text verbatim into the log file (if any) 1370 and/or standard input (if the verbose flag is turned on). 1371 1372 Used internally. 1373 1374 @type text: str 1375 @param text: Text to print. 1376 """ 1377 if self.verbose: 1378 print text 1379 if self.logfile: 1380 try: 1381 self.fd.writelines('%s\n' % text) 1382 except IOError, e: 1383 self.__logfile_error(e)
1384
1385 - def log_text(self, text):
1386 """ 1387 Log lines of text, inserting a timestamp. 1388 1389 @type text: str 1390 @param text: Text to log. 1391 """ 1392 self.__do_log( DebugLog.log_text(text) )
1393
1394 - def log_event(self, event, text = None):
1395 """ 1396 Log lines of text associated with a debug event. 1397 1398 @type event: L{Event} 1399 @param event: Event object. 1400 1401 @type text: str 1402 @param text: (Optional) Text to log. If no text is provided the default 1403 is to show a description of the event itself. 1404 """ 1405 self.__do_log( DebugLog.log_event(event, text) )
1406
1407 - def log_exc(self):
1408 """ 1409 Log lines of text associated with the last Python exception. 1410 """ 1411 self.__do_log( 'Exception raised: %s' % traceback.format_exc() )
1412