1#!/usr/bin/env python 2""" 3ezhexviewer.py 4 5A simple hexadecimal viewer based on easygui. It should work on any platform 6with Python 2.x or 3.x. 7 8Usage: ezhexviewer.py [file] 9 10Usage in a python application: 11 12 import ezhexviewer 13 ezhexviewer.hexview_file(filename) 14 ezhexviewer.hexview_data(data) 15 16 17ezhexviewer project website: http://www.decalage.info/python/ezhexviewer 18 19ezhexviewer is copyright (c) 2012-2019, Philippe Lagadec (http://www.decalage.info) 20All rights reserved. 21 22Redistribution and use in source and binary forms, with or without modification, 23are permitted provided that the following conditions are met: 24 25 * Redistributions of source code must retain the above copyright notice, this 26 list of conditions and the following disclaimer. 27 * Redistributions in binary form must reproduce the above copyright notice, 28 this list of conditions and the following disclaimer in the documentation 29 and/or other materials provided with the distribution. 30 31THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 32ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 33WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 34DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 35FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 36DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 37SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 39OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 40OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 41""" 42 43#------------------------------------------------------------------------------ 44# CHANGELOG: 45# 2012-09-17 v0.01 PL: - first version 46# 2012-10-04 v0.02 PL: - added license 47# 2016-09-06 v0.50 PL: - added main function for entry points in setup.py 48# 2016-10-26 PL: - fixed to run on Python 2+3 49# 2017-03-23 v0.51 PL: - fixed display of control characters (issue #151) 50# 2017-04-26 PL: - fixed absolute imports (issue #141) 51# 2018-09-15 v0.54 PL: - easygui is now a dependency 52 53__version__ = '0.54' 54 55#----------------------------------------------------------------------------- 56# TODO: 57# + options to set title and msg 58 59# === IMPORTS ================================================================ 60 61import sys, os 62 63# IMPORTANT: it should be possible to run oletools directly as scripts 64# in any directory without installing them with pip or setup.py. 65# In that case, relative imports are NOT usable. 66# And to enable Python 2+3 compatibility, we need to use absolute imports, 67# so we add the oletools parent folder to sys.path (absolute+normalized path): 68_thismodule_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) 69# print('_thismodule_dir = %r' % _thismodule_dir) 70_parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..')) 71# print('_parent_dir = %r' % _thirdparty_dir) 72if not _parent_dir in sys.path: 73 sys.path.insert(0, _parent_dir) 74 75import easygui 76 77# === PYTHON 2+3 SUPPORT ====================================================== 78 79if sys.version_info[0] >= 3: 80 # Python 3 specific adaptations 81 # py3 range = py2 xrange 82 xrange = range 83 PYTHON3 = True 84else: 85 PYTHON3 = False 86 87def xord(char): 88 ''' 89 workaround for ord() to work on characters from a bytes string with 90 Python 2 and 3. If s is a bytes string, s[i] is a bytes string of 91 length 1 on Python 2, but it is an integer on Python 3... 92 xord(c) returns ord(c) if c is a bytes string, or c if it is already 93 an integer. 94 :param char: int or bytes of length 1 95 :return: ord(c) if bytes, c if int 96 ''' 97 if isinstance(char, int): 98 return char 99 else: 100 return ord(char) 101 102def bchr(x): 103 ''' 104 workaround for chr() to return a bytes string of length 1 with 105 Python 2 and 3. On Python 3, chr returns a unicode string, but 106 on Python 2 it is a bytes string. 107 bchr() always returns a bytes string on Python 2+3. 108 :param x: int 109 :return: chr(x) as a bytes string 110 ''' 111 if PYTHON3: 112 # According to the Python 3 documentation, bytes() can be 113 # initialized with an iterable: 114 return bytes([x]) 115 else: 116 return chr(x) 117 118#------------------------------------------------------------------------------ 119# The following code (hexdump3 only) is a modified version of the hex dumper 120# recipe published on ASPN by Sebastien Keim and Raymond Hattinger under the 121# PSF license. I added the startindex parameter. 122# see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/142812 123# PSF license: http://docs.python.org/license.html 124# Copyright (c) 2001-2012 Python Software Foundation; All Rights Reserved 125 126FILTER = b''.join([(len(repr(bchr(x)))<=4 and x>=0x20) and bchr(x) or b'.' for x in range(256)]) 127 128def hexdump3(src, length=8, startindex=0): 129 """ 130 Returns a hexadecimal dump of a binary string. 131 length: number of bytes per row. 132 startindex: index of 1st byte. 133 """ 134 result=[] 135 for i in xrange(0, len(src), length): 136 s = src[i:i+length] 137 hexa = ' '.join(["%02X" % xord(x) for x in s]) 138 printable = s.translate(FILTER) 139 if PYTHON3: 140 # On Python 3, need to convert printable from bytes to str: 141 printable = printable.decode('latin1') 142 result.append("%08X %-*s %s\n" % (i+startindex, length*3, hexa, printable)) 143 return ''.join(result) 144 145# end of PSF-licensed code. 146#------------------------------------------------------------------------------ 147 148 149def hexview_data (data, msg='', title='ezhexviewer', length=16, startindex=0): 150 hex = hexdump3(data, length=length, startindex=startindex) 151 easygui.codebox(msg=msg, title=title, text=hex) 152 153 154def hexview_file (filename, msg='', title='ezhexviewer', length=16, startindex=0): 155 data = open(filename, 'rb').read() 156 hexview_data(data, msg=msg, title=title, length=length, startindex=startindex) 157 158 159# === MAIN =================================================================== 160 161def main(): 162 try: 163 filename = sys.argv[1] 164 except: 165 filename = easygui.fileopenbox() 166 if filename: 167 try: 168 hexview_file(filename, msg='File: %s' % filename) 169 except: 170 easygui.exceptionbox(msg='Error:', title='ezhexviewer') 171 172 173if __name__ == '__main__': 174 main() 175