""" A curses-based interactive Python console with a hexadecimal view of a file. Useful to hack around or try to guess the format of binary files. """ import curses, curses.textpad import sys, traceback, atexit from cStringIO import StringIO from struct import pack, unpack def init(): global f, stdscr stdscr = None if len(sys.argv) != 2: print >> sys.stderr, "usage: hexamine.py " sys.exit(2) f = open(sys.argv[1], 'rb') # Initialize curses stdscr=curses.initscr() atexit.register(done) # Turn off echoing of keys, and enter cbreak mode, # where no buffering is performed on keyboard input curses.noecho() ; curses.cbreak() # In keypad mode, escape sequences for special keys # (like the cursor keys) will be interpreted and # a special value like curses.KEY_LEFT will be returned stdscr.keypad(1) stdscr.clear() def done(): stdscr.keypad(0) curses.echo() ; curses.nocbreak() curses.endwin() if sys.exc_info() != (None, None, None): traceback.print_exc() # Print the exception init() stdscr_y, stdscr_x = stdscr.getmaxyx() headerwidth = 12 itemwidth = 9 columns = (stdscr_x - headerwidth) // itemwidth if columns < 1: columns = 1 while columns & (columns-1): columns -= 1 # down to a power of two lineskip = 4*columns pageskip = lineskip * (stdscr_y-2) p = 0 promptwin = stdscr.subwin(stdscr_y-1, 4) promptpad = curses.textpad.Textbox(promptwin) extralines = [] def go(position): global p p = position def search(value, position=None): if position is None: position = p value = pack('l', value) f.seek(position+4) data = '?' while data: data = f.read(4) if data == value: go(f.tell()-4) return f.tell()-4 f.seek(0) for i in xrange(position//4): data = f.read(4) if data == value: go(f.tell()-4) print 'search wrapped' return f.tell()-4 print 'not found' return None s=search def display(): global p, filesize if p < 0: p = 0 f.seek(0,2) filesize = f.tell() if p >= filesize: p = filesize - lineskip*(stdscr_y//2) f.seek(((p//4) & -columns) * 4) for y in range(stdscr_y-1-len(extralines)): line = ['%08x: ' % f.tell()] for j in range(columns): data = f.read(4) if len(data) == 4: line.append('%08x' % unpack('l', data)) else: while data: line.append('%02x' % ord(data[0])) data = data[1:] stdscr.move(y, 0) stdscr.clrtoeol() if len(line) > 1: stdscr.addstr(y, 0, ' '.join(line)) for y in range(len(extralines)): stdscr.move(stdscr_y-1-len(extralines) + y, 0) stdscr.clrtoeol() stdscr.addstr(stdscr_y-1-len(extralines) + y, 0, extralines[y], curses.A_BOLD) stdscr.move(stdscr_y-1, 0) # Move the cursor stdscr.addstr(stdscr_y-1, 0, '>>>') stdscr.refresh() promptwin.refresh() def dispatch(c): global p if c==curses.KEY_DOWN: p += lineskip elif c==curses.KEY_UP: p -= lineskip elif c==curses.KEY_NPAGE: p += pageskip elif c==curses.KEY_PPAGE: p -= pageskip elif c==curses.KEY_HOME: p = 0 elif c==curses.KEY_END: p = filesize-lineskip*(stdscr_y//2) elif c==4: sys.exit(0) elif c==curses.KEY_ENTER or c==10: stdscr.move(stdscr_y-1, 0) # Move the cursor stdscr.refresh() cmd = promptpad.gather() promptwin.clear() del extralines[:] run(cmd) else: promptpad.do_command(c) def run(cmd): if cmd: old_stdout = sys.stdout old_stderr = sys.stderr try: sys.stdout = sys.stderr = fbuf = StringIO() try: co = compile(cmd, '', 'single') exec co in globals() except: traceback.print_exc() finally: sys.stdout = old_stdout sys.stderr = old_stderr fbuf.seek(0) extralines[:] = [s.rstrip()[:stdscr_x] for s in fbuf.readlines()] if len(extralines) > stdscr_y//2: extralines[:] = extralines[:stdscr_y//2] + ['(...)'] while 1: display() c=stdscr.getch() # Get a keystroke dispatch(c)