# Copyright (c) 2011-2015 Rusty Wagner # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import struct import io import thread import Threads DATA_ORIGINAL = 0 DATA_CHANGED = 1 DATA_INSERTED = 2 class BinaryAccessor: def read_uint8(self, ofs): return struct.unpack('B', self.read(ofs, 1))[0] def read_uint16(self, ofs): return struct.unpack('H', self.read(ofs, 2))[0] def read_uint32_be(self, ofs): return struct.unpack('>I', self.read(ofs, 4))[0] def read_uint64_be(self, ofs): return struct.unpack('>Q', self.read(ofs, 8))[0] def read_int8(self, ofs): return struct.unpack('b', self.read(ofs, 1))[0] def read_int16(self, ofs): return struct.unpack('h', self.read(ofs, 2))[0] def read_int32_be(self, ofs): return struct.unpack('>i', self.read(ofs, 4))[0] def read_int64_be(self, ofs): return struct.unpack('>q', self.read(ofs, 8))[0] def write_uint8(self, ofs, val): return self.write(ofs, struct.pack('B', val)) == 1 def write_uint16(self, ofs, val): return self.write(ofs, struct.pack('H', val)) == 2 def write_uint32_be(self, ofs, val): return self.write(ofs, struct.pack('>I', val)) == 4 def write_uint64_be(self, ofs, val): return self.write(ofs, struct.pack('>Q', val)) == 8 def write_int8(self, ofs, val): return self.write(ofs, struct.pack('b', val)) == 1 def write_int16(self, ofs, val): return self.write(ofs, struct.pack('h', val)) == 2 def write_int32_be(self, ofs, val): return self.write(ofs, struct.pack('>i', val)) == 4 def write_int64_be(self, ofs, val): return self.write(ofs, struct.pack('>q', val)) == 8 def end(self): return self.start() + len(self) def __str__(self): return self.read(0, len(self)) def __getitem__(self, offset): if type(offset) == slice: start = offset.start end = offset.stop if start is None: start = self.start() if end is None: end = self.start() + len(self) if end < 0: end = self.start() + len(self) + end if (offset.step is None) or (offset.step == 1): return self.read(start, end - start) else: result = "" for i in xrange(start, end, offset.step): part = self.read(i, 1) if len(part) == 0: return result result += part return result result = self.read(offset, 1) if len(result) == 0: raise IndexError return result def __setitem__(self, offset, value): if type(offset) == slice: start = offset.start end = offset.stop if start is None: start = self.start() if end is None: end = self.start() + len(self) if end < 0: end = self.start() + len(self) + end if (offset.step is None) or (offset.step == 1): if end < start: return if len(value) != (end - start): self.remove(start, end - start) self.insert(start, value) else: self.write(start, value) else: rel_offset = 0 j = 0 for i in xrange(start, end, offset.step): if j < len(value): self.write(i + rel_offset, value[j]) else: self.remove(i + rel_offset) rel_offset -= 1 else: if self.write(offset, value) == 0: raise IndexError def __delitem__(self, offset): if type(offset) == slice: start = offset.start end = offset.stop if start is None: start = self.start() if end is None: end = self.start() + len(self) if end < 0: end = self.start() + len(self) + end if (offset.step is None) or (offset.step == 1): if end < start: return self.remove(start, end - start) else: rel_offset = 0 for i in xrange(start, end, offset.step): self.remove(i + rel_offset) rel_offset -= 1 else: if self.remove(offset, 1) == 0: raise IndexError class WriteUndoEntry: def __init__(self, data, offset, old_contents, new_contents, old_mod): self.data = data self.offset = offset self.old_contents = old_contents self.new_contents = new_contents self.old_mod = old_mod class InsertUndoEntry: def __init__(self, data, offset, contents): self.data = data self.offset = offset self.contents = contents class RemoveUndoEntry: def __init__(self, data, offset, old_contents, old_mod): self.data = data self.offset = offset self.old_contents = old_contents self.old_mod = old_mod class BinaryData(BinaryAccessor): def __init__(self, data = ""): self.data = data self.modification = [DATA_ORIGINAL] * len(data) self.modified = False self.callbacks = [] self.undo_buffer = [] self.redo_buffer = [] self.temp_undo_buffer = [] self.unmodified_undo_index = 0 self.symbols_by_name = {} self.symbols_by_addr = {} self.default_arch = None def read(self, ofs, size): return self.data[ofs:(ofs + size)] def write(self, ofs, data): if len(data) == 0: return 0 if ofs == len(self.data): return self.insert(len(self.data), data) if ofs >= len(self.data): return 0 append = "" if (ofs + len(data)) > len(self.data): append = data[len(self.data)-ofs:] data = data[0:len(self.data)-ofs] undo_entry = WriteUndoEntry(self, ofs, self.data[ofs:ofs+len(data)], data, self.modification[ofs:ofs+len(data)]) self.insert_undo_entry(undo_entry, self.undo_write, self.redo_write) self.data = self.data[0:ofs] + data + self.data[ofs+len(data):] for i in xrange(ofs, ofs + len(data)): if self.modification[i] == DATA_ORIGINAL: self.modification[i] = DATA_CHANGED for cb in self.callbacks: if hasattr(cb, "notify_data_write"): cb.notify_data_write(self, ofs, data) self.modified = True if len(append) > 0: return len(data) + self.insert(len(self.data), append) return len(data) def insert(self, ofs, data): if len(data) == 0: return 0 if ofs > len(self.data): return 0 undo_entry = InsertUndoEntry(self, ofs, data) self.insert_undo_entry(undo_entry, self.undo_insert, self.redo_insert) self.data = self.data[0:ofs] + data + self.data[ofs:] self.modification[ofs:ofs] = [DATA_INSERTED] * len(data) for cb in self.callbacks: if hasattr(cb, "notify_data_insert"): cb.notify_data_insert(self, ofs, data) self.modified = True return len(data) def remove(self, ofs, size): if size == 0: return 0 if ofs >= len(self.data): return 0 if (ofs + size) > len(self.data): size = len(self.data) - ofs undo_entry = RemoveUndoEntry(self, ofs, self.data[ofs:ofs+size], self.modification[ofs:ofs+size]) self.insert_undo_entry(undo_entry, self.undo_remove, self.redo_remove) self.data = self.data[0:ofs] + self.data[ofs+size:] del self.modification[ofs:ofs+size] for cb in self.callbacks: if hasattr(cb, "notify_data_remove"): cb.notify_data_remove(self, ofs, size) self.modified = True return size def get_modification(self, ofs, size): return self.modification[ofs:ofs+size] def add_callback(self, cb): self.callbacks.append(cb) def remove_callback(self, cb): self.callbacks.remove(cb) def save(self, filename): f = io.open(filename, 'wb') f.write(self.data) f.close() self.modification = [DATA_ORIGINAL] * len(self.data) self.modified = False self.unmodified_undo_index = len(self.undo_buffer) def start(self): return 0 def __len__(self): return len(self.data) def is_modified(self): return self.modified def find(self, regex, addr): match = regex.search(self.data, addr) if match == None: return -1 return match.start() def commit_undo(self, before_loc, after_loc): if len(self.temp_undo_buffer) == 0: return if len(self.undo_buffer) < self.unmodified_undo_index: self.unmodified_undo_index = -1 entries = self.temp_undo_buffer self.temp_undo_buffer = [] self.undo_buffer.append([before_loc, after_loc, entries]) self.redo_buffer = [] def insert_undo_entry(self, data, undo_func, redo_func): self.temp_undo_buffer.append([data, undo_func, redo_func]) def undo_write(self, entry): self.data = self.data[0:entry.offset] + entry.old_contents + self.data[entry.offset + len(entry.old_contents):] self.modification = self.modification[0:entry.offset] + entry.old_mod + self.modification[entry.offset + len(entry.old_mod):] for cb in self.callbacks: if hasattr(cb, "notify_data_write"): cb.notify_data_write(self, entry.offset, entry.old_contents) def redo_write(self, entry): self.data = self.data[0:entry.offset] + entry.new_contents + self.data[entry.offset + len(entry.new_contents):] for i in xrange(entry.offset, entry.offset + len(entry.new_contents)): if self.modification[i] == DATA_ORIGINAL: self.modification[i] = DATA_CHANGED for cb in self.callbacks: if hasattr(cb, "notify_data_write"): cb.notify_data_write(self, entry.offset, entry.new_contents) self.modified = True def undo_insert(self, entry): self.data = self.data[0:entry.offset] + self.data[entry.offset + len(entry.contents):] self.modification = self.modification[0:entry.offset] + self.modification[entry.offset + len(entry.contents):] for cb in self.callbacks: if hasattr(cb, "notify_data_remove"): cb.notify_data_remove(self, entry.offset, len(entry.contents)) def redo_insert(self, entry): self.data = self.data[0:entry.offset] + entry.contents + self.data[entry.offset:] self.modification[entry.offset:entry.offset] = [DATA_INSERTED] * len(entry.contents) for cb in self.callbacks: if hasattr(cb, "notify_data_insert"): cb.notify_data_insert(self, entry.offset, entry.contents) self.modified = True def undo_remove(self, entry): self.data = self.data[0:entry.offset] + entry.old_contents + self.data[entry.offset:] self.modification = self.modification[0:entry.offset] + entry.old_mod + self.modification[entry.offset:] for cb in self.callbacks: if hasattr(cb, "notify_data_insert"): cb.notify_data_insert(self, entry.offset, entry.old_contents) def redo_remove(self, entry): self.data = self.data[0:entry.offset] + self.data[entry.offset + len(entry.old_contents):] self.modification = self.modification[0:entry.offset] + self.modification[entry.offset + len(entry.old_contents):] for cb in self.callbacks: if hasattr(cb, "notify_data_remove"): cb.notify_data_remove(self, entry.offset, len(entry.old_contents)) self.modified = True def undo(self): if len(self.undo_buffer) == 0: return None undo_desc = self.undo_buffer.pop() self.redo_buffer.append(undo_desc) for entry in undo_desc[2][::-1]: entry[1](entry[0]) self.modified = (len(self.undo_buffer) != self.unmodified_undo_index) return undo_desc[0] def redo(self): if len(self.redo_buffer) == 0: return None redo_desc = self.redo_buffer.pop() self.undo_buffer.append(redo_desc) for entry in redo_desc[2]: entry[2](entry[0]) self.modified = (len(self.undo_buffer) != self.unmodified_undo_index) return redo_desc[1] def architecture(self): return self.default_arch class BinaryFile(BinaryData): def __init__(self, filename): f = io.open(filename, 'rb') data = f.read() f.close() BinaryData.__init__(self, data)