├── .gitignore ├── Analysis.py ├── ArchitectureDialog.py ├── Arm.py ├── AssembleDialog.py ├── BinaryData.py ├── CHighlight.py ├── DisassemblerView.py ├── ElfFile.py ├── FindDialog.py ├── Fonts.py ├── HelpView.py ├── HexEditor.py ├── LICENSE ├── MachOFile.py ├── PEFile.py ├── PPC.py ├── Preferences.py ├── PythonConsole.py ├── PythonHighlight.py ├── RunWindow.py ├── Structure.py ├── TerminalEmulator.py ├── TerminalProcess.py ├── TerminalView.py ├── TextEditor.py ├── TextLines.py ├── Threads.py ├── Transform.py ├── Util.py ├── View.py ├── X86.py ├── binja.py ├── docs └── python_api.html ├── images ├── aboutback.png ├── copy.xpm ├── cut.xpm ├── icon.icns ├── icon.ico ├── icon.png ├── new.xpm ├── open.xpm ├── paste.xpm ├── save.xpm └── split.xpm ├── nasm.py └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /ArchitectureDialog.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from PySide.QtCore import * 17 | from PySide.QtGui import * 18 | 19 | 20 | class ArchitectureDialog(QDialog): 21 | def __init__(self, parent): 22 | super(ArchitectureDialog, self).__init__(parent) 23 | 24 | self.setWindowTitle("Select Architecture") 25 | 26 | settings = QSettings("Binary Ninja", "Binary Ninja") 27 | 28 | layout = QVBoxLayout() 29 | 30 | self.arch = QComboBox() 31 | self.arch.setEditable(False) 32 | self.arch.addItems(["x86", "x86_64", "arm", "ppc", "quark"]) 33 | 34 | lastArch = settings.value("disassemble/arch", "x86") 35 | lastArchIndex = self.arch.findText(lastArch) 36 | if lastArchIndex != -1: 37 | self.arch.setCurrentIndex(lastArchIndex) 38 | 39 | archLayout = QHBoxLayout() 40 | archLayout.setContentsMargins(0, 0, 0, 0) 41 | archLayout.addWidget(QLabel("Architecture:")) 42 | archLayout.addWidget(self.arch) 43 | layout.addLayout(archLayout) 44 | 45 | self.cancelButton = QPushButton("Cancel") 46 | self.cancelButton.clicked.connect(self.closeRequest) 47 | self.cancelButton.setAutoDefault(False) 48 | 49 | self.okButton = QPushButton("OK") 50 | self.okButton.clicked.connect(self.ok) 51 | self.okButton.setAutoDefault(True) 52 | 53 | buttonLayout = QHBoxLayout() 54 | buttonLayout.setContentsMargins(0, 0, 0, 0) 55 | buttonLayout.addStretch(1) 56 | buttonLayout.addWidget(self.cancelButton) 57 | buttonLayout.addWidget(self.okButton) 58 | layout.addLayout(buttonLayout) 59 | self.setLayout(layout) 60 | 61 | def saveSettings(self): 62 | settings = QSettings("Binary Ninja", "Binary Ninja") 63 | settings.setValue("disassemble/arch", self.arch.currentText()) 64 | 65 | def ok(self): 66 | self.result = str(self.arch.currentText()) 67 | self.saveSettings() 68 | self.accept() 69 | 70 | def closeRequest(self): 71 | self.result = None 72 | self.close() 73 | 74 | -------------------------------------------------------------------------------- /AssembleDialog.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from PySide.QtCore import * 17 | from PySide.QtGui import * 18 | from Fonts import * 19 | from TextEditor import * 20 | import nasm 21 | 22 | 23 | class AssembleDialog(QDialog): 24 | def __init__(self, parent): 25 | super(AssembleDialog, self).__init__(parent) 26 | 27 | self.setWindowTitle("Assemble") 28 | 29 | settings = QSettings("Binary Ninja", "Binary Ninja") 30 | 31 | layout = QVBoxLayout() 32 | 33 | self.data = BinaryData(settings.value("assemble/text", "")) 34 | self.edit = ViewFrame(TextEditor, self.data, "", [TextEditor]) 35 | self.edit.setMinimumSize(512, 384) 36 | layout.addWidget(self.edit, 1) 37 | 38 | self.closeButton = QPushButton("Close") 39 | self.closeButton.clicked.connect(self.closeRequest) 40 | self.closeButton.setAutoDefault(False) 41 | 42 | self.assembleButton = QPushButton("Assemble") 43 | self.assembleButton.clicked.connect(self.assemble) 44 | self.assembleButton.setAutoDefault(True) 45 | 46 | buttonLayout = QHBoxLayout() 47 | buttonLayout.setContentsMargins(0, 0, 0, 0) 48 | buttonLayout.addStretch(1) 49 | buttonLayout.addWidget(self.assembleButton) 50 | buttonLayout.addWidget(self.closeButton) 51 | layout.addLayout(buttonLayout) 52 | self.setLayout(layout) 53 | 54 | def saveSettings(self): 55 | settings = QSettings("Binary Ninja", "Binary Ninja") 56 | settings.setValue("assemble/text", self.data.read(0, len(self.data))) 57 | 58 | def assemble(self): 59 | data, error = nasm.assemble(str(self.data.read(0, len(self.data)))) 60 | if error is not None: 61 | QMessageBox.critical(self, "Assemble Failed", error, QMessageBox.Ok) 62 | return 63 | 64 | self.output = data 65 | self.saveSettings() 66 | self.accept() 67 | 68 | def closeRequest(self): 69 | self.saveSettings() 70 | self.close() 71 | 72 | -------------------------------------------------------------------------------- /BinaryData.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import struct 17 | import io 18 | import thread 19 | import Threads 20 | 21 | DATA_ORIGINAL = 0 22 | DATA_CHANGED = 1 23 | DATA_INSERTED = 2 24 | 25 | 26 | class BinaryAccessor: 27 | def read_uint8(self, ofs): 28 | return struct.unpack('B', self.read(ofs, 1))[0] 29 | 30 | def read_uint16(self, ofs): 31 | return struct.unpack('H', self.read(ofs, 2))[0] 50 | 51 | def read_uint32_be(self, ofs): 52 | return struct.unpack('>I', self.read(ofs, 4))[0] 53 | 54 | def read_uint64_be(self, ofs): 55 | return struct.unpack('>Q', self.read(ofs, 8))[0] 56 | 57 | def read_int8(self, ofs): 58 | return struct.unpack('b', self.read(ofs, 1))[0] 59 | 60 | def read_int16(self, ofs): 61 | return struct.unpack('h', self.read(ofs, 2))[0] 80 | 81 | def read_int32_be(self, ofs): 82 | return struct.unpack('>i', self.read(ofs, 4))[0] 83 | 84 | def read_int64_be(self, ofs): 85 | return struct.unpack('>q', self.read(ofs, 8))[0] 86 | 87 | def write_uint8(self, ofs, val): 88 | return self.write(ofs, struct.pack('B', val)) == 1 89 | 90 | def write_uint16(self, ofs, val): 91 | return self.write(ofs, struct.pack('H', val)) == 2 110 | 111 | def write_uint32_be(self, ofs, val): 112 | return self.write(ofs, struct.pack('>I', val)) == 4 113 | 114 | def write_uint64_be(self, ofs, val): 115 | return self.write(ofs, struct.pack('>Q', val)) == 8 116 | 117 | def write_int8(self, ofs, val): 118 | return self.write(ofs, struct.pack('b', val)) == 1 119 | 120 | def write_int16(self, ofs, val): 121 | return self.write(ofs, struct.pack('h', val)) == 2 140 | 141 | def write_int32_be(self, ofs, val): 142 | return self.write(ofs, struct.pack('>i', val)) == 4 143 | 144 | def write_int64_be(self, ofs, val): 145 | return self.write(ofs, struct.pack('>q', val)) == 8 146 | 147 | def end(self): 148 | return self.start() + len(self) 149 | 150 | def __str__(self): 151 | return self.read(0, len(self)) 152 | 153 | def __getitem__(self, offset): 154 | if type(offset) == slice: 155 | start = offset.start 156 | end = offset.stop 157 | if start is None: 158 | start = self.start() 159 | if end is None: 160 | end = self.start() + len(self) 161 | if end < 0: 162 | end = self.start() + len(self) + end 163 | 164 | if (offset.step is None) or (offset.step == 1): 165 | return self.read(start, end - start) 166 | else: 167 | result = "" 168 | for i in xrange(start, end, offset.step): 169 | part = self.read(i, 1) 170 | if len(part) == 0: 171 | return result 172 | result += part 173 | return result 174 | 175 | result = self.read(offset, 1) 176 | if len(result) == 0: 177 | raise IndexError 178 | return result 179 | 180 | def __setitem__(self, offset, value): 181 | if type(offset) == slice: 182 | start = offset.start 183 | end = offset.stop 184 | if start is None: 185 | start = self.start() 186 | if end is None: 187 | end = self.start() + len(self) 188 | if end < 0: 189 | end = self.start() + len(self) + end 190 | 191 | if (offset.step is None) or (offset.step == 1): 192 | if end < start: 193 | return 194 | if len(value) != (end - start): 195 | self.remove(start, end - start) 196 | self.insert(start, value) 197 | else: 198 | self.write(start, value) 199 | else: 200 | rel_offset = 0 201 | j = 0 202 | for i in xrange(start, end, offset.step): 203 | if j < len(value): 204 | self.write(i + rel_offset, value[j]) 205 | else: 206 | self.remove(i + rel_offset) 207 | rel_offset -= 1 208 | else: 209 | if self.write(offset, value) == 0: 210 | raise IndexError 211 | 212 | def __delitem__(self, offset): 213 | if type(offset) == slice: 214 | start = offset.start 215 | end = offset.stop 216 | if start is None: 217 | start = self.start() 218 | if end is None: 219 | end = self.start() + len(self) 220 | if end < 0: 221 | end = self.start() + len(self) + end 222 | 223 | if (offset.step is None) or (offset.step == 1): 224 | if end < start: 225 | return 226 | self.remove(start, end - start) 227 | else: 228 | rel_offset = 0 229 | for i in xrange(start, end, offset.step): 230 | self.remove(i + rel_offset) 231 | rel_offset -= 1 232 | else: 233 | if self.remove(offset, 1) == 0: 234 | raise IndexError 235 | 236 | 237 | class WriteUndoEntry: 238 | def __init__(self, data, offset, old_contents, new_contents, old_mod): 239 | self.data = data 240 | self.offset = offset 241 | self.old_contents = old_contents 242 | self.new_contents = new_contents 243 | self.old_mod = old_mod 244 | 245 | class InsertUndoEntry: 246 | def __init__(self, data, offset, contents): 247 | self.data = data 248 | self.offset = offset 249 | self.contents = contents 250 | 251 | class RemoveUndoEntry: 252 | def __init__(self, data, offset, old_contents, old_mod): 253 | self.data = data 254 | self.offset = offset 255 | self.old_contents = old_contents 256 | self.old_mod = old_mod 257 | 258 | 259 | class BinaryData(BinaryAccessor): 260 | def __init__(self, data = ""): 261 | self.data = data 262 | self.modification = [DATA_ORIGINAL] * len(data) 263 | self.modified = False 264 | self.callbacks = [] 265 | self.undo_buffer = [] 266 | self.redo_buffer = [] 267 | self.temp_undo_buffer = [] 268 | self.unmodified_undo_index = 0 269 | self.symbols_by_name = {} 270 | self.symbols_by_addr = {} 271 | self.default_arch = None 272 | 273 | def read(self, ofs, size): 274 | return self.data[ofs:(ofs + size)] 275 | 276 | def write(self, ofs, data): 277 | if len(data) == 0: 278 | return 0 279 | if ofs == len(self.data): 280 | return self.insert(len(self.data), data) 281 | if ofs >= len(self.data): 282 | return 0 283 | append = "" 284 | if (ofs + len(data)) > len(self.data): 285 | append = data[len(self.data)-ofs:] 286 | data = data[0:len(self.data)-ofs] 287 | 288 | undo_entry = WriteUndoEntry(self, ofs, self.data[ofs:ofs+len(data)], data, self.modification[ofs:ofs+len(data)]) 289 | self.insert_undo_entry(undo_entry, self.undo_write, self.redo_write) 290 | 291 | self.data = self.data[0:ofs] + data + self.data[ofs+len(data):] 292 | for i in xrange(ofs, ofs + len(data)): 293 | if self.modification[i] == DATA_ORIGINAL: 294 | self.modification[i] = DATA_CHANGED 295 | for cb in self.callbacks: 296 | if hasattr(cb, "notify_data_write"): 297 | cb.notify_data_write(self, ofs, data) 298 | self.modified = True 299 | if len(append) > 0: 300 | return len(data) + self.insert(len(self.data), append) 301 | return len(data) 302 | 303 | def insert(self, ofs, data): 304 | if len(data) == 0: 305 | return 0 306 | if ofs > len(self.data): 307 | return 0 308 | 309 | undo_entry = InsertUndoEntry(self, ofs, data) 310 | self.insert_undo_entry(undo_entry, self.undo_insert, self.redo_insert) 311 | 312 | self.data = self.data[0:ofs] + data + self.data[ofs:] 313 | self.modification[ofs:ofs] = [DATA_INSERTED] * len(data) 314 | for cb in self.callbacks: 315 | if hasattr(cb, "notify_data_insert"): 316 | cb.notify_data_insert(self, ofs, data) 317 | self.modified = True 318 | return len(data) 319 | 320 | def remove(self, ofs, size): 321 | if size == 0: 322 | return 0 323 | if ofs >= len(self.data): 324 | return 0 325 | if (ofs + size) > len(self.data): 326 | size = len(self.data) - ofs 327 | 328 | undo_entry = RemoveUndoEntry(self, ofs, self.data[ofs:ofs+size], self.modification[ofs:ofs+size]) 329 | self.insert_undo_entry(undo_entry, self.undo_remove, self.redo_remove) 330 | 331 | self.data = self.data[0:ofs] + self.data[ofs+size:] 332 | del self.modification[ofs:ofs+size] 333 | for cb in self.callbacks: 334 | if hasattr(cb, "notify_data_remove"): 335 | cb.notify_data_remove(self, ofs, size) 336 | self.modified = True 337 | return size 338 | 339 | def get_modification(self, ofs, size): 340 | return self.modification[ofs:ofs+size] 341 | 342 | def add_callback(self, cb): 343 | self.callbacks.append(cb) 344 | 345 | def remove_callback(self, cb): 346 | self.callbacks.remove(cb) 347 | 348 | def save(self, filename): 349 | f = io.open(filename, 'wb') 350 | f.write(self.data) 351 | f.close() 352 | self.modification = [DATA_ORIGINAL] * len(self.data) 353 | self.modified = False 354 | self.unmodified_undo_index = len(self.undo_buffer) 355 | 356 | def start(self): 357 | return 0 358 | 359 | def __len__(self): 360 | return len(self.data) 361 | 362 | def is_modified(self): 363 | return self.modified 364 | 365 | def find(self, regex, addr): 366 | match = regex.search(self.data, addr) 367 | if match == None: 368 | return -1 369 | return match.start() 370 | 371 | def commit_undo(self, before_loc, after_loc): 372 | if len(self.temp_undo_buffer) == 0: 373 | return 374 | if len(self.undo_buffer) < self.unmodified_undo_index: 375 | self.unmodified_undo_index = -1 376 | entries = self.temp_undo_buffer 377 | self.temp_undo_buffer = [] 378 | self.undo_buffer.append([before_loc, after_loc, entries]) 379 | self.redo_buffer = [] 380 | 381 | def insert_undo_entry(self, data, undo_func, redo_func): 382 | self.temp_undo_buffer.append([data, undo_func, redo_func]) 383 | 384 | def undo_write(self, entry): 385 | self.data = self.data[0:entry.offset] + entry.old_contents + self.data[entry.offset + len(entry.old_contents):] 386 | self.modification = self.modification[0:entry.offset] + entry.old_mod + self.modification[entry.offset + len(entry.old_mod):] 387 | for cb in self.callbacks: 388 | if hasattr(cb, "notify_data_write"): 389 | cb.notify_data_write(self, entry.offset, entry.old_contents) 390 | 391 | def redo_write(self, entry): 392 | self.data = self.data[0:entry.offset] + entry.new_contents + self.data[entry.offset + len(entry.new_contents):] 393 | for i in xrange(entry.offset, entry.offset + len(entry.new_contents)): 394 | if self.modification[i] == DATA_ORIGINAL: 395 | self.modification[i] = DATA_CHANGED 396 | for cb in self.callbacks: 397 | if hasattr(cb, "notify_data_write"): 398 | cb.notify_data_write(self, entry.offset, entry.new_contents) 399 | self.modified = True 400 | 401 | def undo_insert(self, entry): 402 | self.data = self.data[0:entry.offset] + self.data[entry.offset + len(entry.contents):] 403 | self.modification = self.modification[0:entry.offset] + self.modification[entry.offset + len(entry.contents):] 404 | for cb in self.callbacks: 405 | if hasattr(cb, "notify_data_remove"): 406 | cb.notify_data_remove(self, entry.offset, len(entry.contents)) 407 | 408 | def redo_insert(self, entry): 409 | self.data = self.data[0:entry.offset] + entry.contents + self.data[entry.offset:] 410 | self.modification[entry.offset:entry.offset] = [DATA_INSERTED] * len(entry.contents) 411 | for cb in self.callbacks: 412 | if hasattr(cb, "notify_data_insert"): 413 | cb.notify_data_insert(self, entry.offset, entry.contents) 414 | self.modified = True 415 | 416 | def undo_remove(self, entry): 417 | self.data = self.data[0:entry.offset] + entry.old_contents + self.data[entry.offset:] 418 | self.modification = self.modification[0:entry.offset] + entry.old_mod + self.modification[entry.offset:] 419 | for cb in self.callbacks: 420 | if hasattr(cb, "notify_data_insert"): 421 | cb.notify_data_insert(self, entry.offset, entry.old_contents) 422 | 423 | def redo_remove(self, entry): 424 | self.data = self.data[0:entry.offset] + self.data[entry.offset + len(entry.old_contents):] 425 | self.modification = self.modification[0:entry.offset] + self.modification[entry.offset + len(entry.old_contents):] 426 | for cb in self.callbacks: 427 | if hasattr(cb, "notify_data_remove"): 428 | cb.notify_data_remove(self, entry.offset, len(entry.old_contents)) 429 | self.modified = True 430 | 431 | def undo(self): 432 | if len(self.undo_buffer) == 0: 433 | return None 434 | 435 | undo_desc = self.undo_buffer.pop() 436 | self.redo_buffer.append(undo_desc) 437 | 438 | for entry in undo_desc[2][::-1]: 439 | entry[1](entry[0]) 440 | 441 | self.modified = (len(self.undo_buffer) != self.unmodified_undo_index) 442 | return undo_desc[0] 443 | 444 | def redo(self): 445 | if len(self.redo_buffer) == 0: 446 | return None 447 | 448 | redo_desc = self.redo_buffer.pop() 449 | self.undo_buffer.append(redo_desc) 450 | 451 | for entry in redo_desc[2]: 452 | entry[2](entry[0]) 453 | 454 | self.modified = (len(self.undo_buffer) != self.unmodified_undo_index) 455 | return redo_desc[1] 456 | 457 | def architecture(self): 458 | return self.default_arch 459 | 460 | 461 | class BinaryFile(BinaryData): 462 | def __init__(self, filename): 463 | f = io.open(filename, 'rb') 464 | data = f.read() 465 | f.close() 466 | BinaryData.__init__(self, data) 467 | 468 | -------------------------------------------------------------------------------- /CHighlight.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from TextLines import * 17 | 18 | 19 | class CHighlight(Highlight): 20 | def __init__(self, data): 21 | self.keywords = ["const", "bool", "char", "int", "short", "long", "float", "double", "signed", "unsigned", 22 | "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "size_t", 23 | "ssize_t", "ptrdiff_t", "struct", "union", "enum", "return", "if", "else", "for", "while", "do", 24 | "break", "continue", "goto", "switch", "case", "default", "void", "sizeof", "typedef", "static", 25 | "extern", "__cdecl", "__stdcall", "__fastcall", "__subarch", "__noreturn"] 26 | self.identifiers = ["stdin", "stdout", "stderr", "min", "max", "abs", "__undefined", "__rdtsc", 27 | "__rdtsc_low", "__rdtsc_high", "__next_arg", "__prev_arg", "__byteswap", "__syscall", "va_list", 28 | "va_start", "va_arg", "va_end"] 29 | self.values = ["NULL", "true", "false"] 30 | 31 | def update_line(self, line, text): 32 | long_comment = False 33 | if line.highlight_state.style == HIGHLIGHT_COMMENT: 34 | long_comment = True 35 | 36 | if text.find("//") != -1: 37 | token = HighlightToken(text.find("//"), len(text) - text.find("//"), HighlightState(HIGHLIGHT_COMMENT)) 38 | line.tokens.append(token) 39 | text = text[0:text.find("//")] 40 | 41 | if text.find('#') != -1: 42 | token = HighlightToken(text.find('#'), len(text) - text.find('#'), HighlightState(HIGHLIGHT_DIRECTIVE)) 43 | line.tokens.append(token) 44 | text = text[0:text.find('#')] 45 | 46 | tokens = self.simple_tokenize(text) 47 | for i in xrange(0, len(tokens)): 48 | token = tokens[i] 49 | if (token[1] == "/") and (i > 0) and (tokens[i - 1][1] == '*') and (token[0] == (tokens[i - 1][0] + 1)): 50 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_COMMENT))) 51 | long_comment = False 52 | elif (token[1] == "/") and ((i + 1) < len(tokens)) and (tokens[i + 1][1] == '*') and (token[0] == (tokens[i + 1][0] - 1)): 53 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_COMMENT))) 54 | long_comment = True 55 | elif long_comment: 56 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_COMMENT))) 57 | elif token[1] in self.keywords: 58 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_KEYWORD))) 59 | elif token[1] in self.identifiers: 60 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_IDENTIFIER))) 61 | elif token[1] in self.values: 62 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_VALUE))) 63 | elif (token[1][0] == '"') or (token[1][0] == '\''): 64 | self.append_escaped_string_tokens(line, token[0], token[1]) 65 | elif (token[1][0] >= '0') and (token[1][0] <= '9'): 66 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_VALUE))) 67 | 68 | if long_comment: 69 | return HighlightState(HIGHLIGHT_COMMENT) 70 | return HighlightState(HIGHLIGHT_NONE) 71 | 72 | 73 | highlightTypes["C source"] = CHighlight 74 | 75 | -------------------------------------------------------------------------------- /ElfFile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from BinaryData import * 17 | from Structure import * 18 | from HexEditor import * 19 | from View import * 20 | 21 | 22 | class ElfFile(BinaryAccessor): 23 | def __init__(self, data): 24 | self.data = data 25 | self.valid = False 26 | self.callbacks = [] 27 | self.symbols_by_name = {} 28 | self.symbols_by_addr = {} 29 | if not self.is_elf(): 30 | return 31 | 32 | try: 33 | self.tree = Structure(self.data) 34 | self.header = self.tree.struct("ELF header", "header") 35 | self.header.struct("ELF identification", "ident") 36 | 37 | self.header.ident.uint32("magic") 38 | self.header.ident.uint8("file_class") 39 | self.header.ident.uint8("encoding") 40 | self.header.ident.uint8("version") 41 | self.header.ident.uint8("abi") 42 | self.header.ident.uint8("abi_version") 43 | self.header.ident.bytes(7, "pad") 44 | 45 | self.header.uint16("type") 46 | self.header.uint16("arch") 47 | self.header.uint32("version") 48 | 49 | self.symbol_table_section = None 50 | self.dynamic_symbol_table_section = None 51 | 52 | if self.header.ident.file_class == 1: # 32-bit 53 | self.header.uint32("entry") 54 | self.header.uint32("program_header_offset") 55 | self.header.uint32("section_header_offset") 56 | self.header.uint32("flags") 57 | self.header.uint16("header_size") 58 | self.header.uint16("program_header_size") 59 | self.header.uint16("program_header_count") 60 | self.header.uint16("section_header_size") 61 | self.header.uint16("section_header_count") 62 | self.header.uint16("string_table") 63 | 64 | try: 65 | self.sections = self.tree.array(self.header.section_header_count, "sections") 66 | for i in range(0, self.header.section_header_count): 67 | section = self.sections[i] 68 | section.seek(self.header.section_header_offset + (i * 40)) 69 | section.uint32("name") 70 | section.uint32("type") 71 | section.uint32("flags") 72 | section.uint32("addr") 73 | section.uint32("offset") 74 | section.uint32("size") 75 | section.uint32("link") 76 | section.uint32("info") 77 | section.uint32("align") 78 | section.uint32("entry_size") 79 | 80 | if section.type == 2: 81 | self.symbol_table_section = section 82 | elif section.type == 11: 83 | self.dynamic_symbol_table_section = section 84 | except: 85 | # Section headers are not required to load an ELF, skip errors 86 | self.sections = self.tree.array(0, "sections") 87 | pass 88 | 89 | self.program_headers = self.tree.array(self.header.program_header_count, "programHeaders") 90 | for i in range(0, self.header.program_header_count): 91 | header = self.program_headers[i] 92 | header.seek(self.header.program_header_offset + (i * 32)) 93 | header.uint32("type") 94 | header.uint32("offset") 95 | header.uint32("virtual_addr") 96 | header.uint32("physical_addr") 97 | header.uint32("file_size") 98 | header.uint32("memory_size") 99 | header.uint32("flags") 100 | header.uint32("align") 101 | 102 | # Parse symbol tables 103 | self.symbols_by_name["_start"] = self.entry() 104 | self.symbols_by_addr[self.entry()] = "_start" 105 | 106 | try: 107 | if self.symbol_table_section: 108 | self.symbol_table = self.tree.array(self.symbol_table_section.size / 16, "Symbols", "symbols") 109 | self.parse_symbol_table_32(self.symbol_table, self.symbol_table_section, self.sections[self.symbol_table_section.link]) 110 | 111 | if self.dynamic_symbol_table_section: 112 | self.dynamic_symbol_table = self.tree.array(self.dynamic_symbol_table_section.size / 16, "Symbols", "symbols") 113 | self.parse_symbol_table_32(self.dynamic_symbol_table, self.dynamic_symbol_table_section, self.sections[self.dynamic_symbol_table_section.link]) 114 | except: 115 | # Skip errors in symbol table 116 | pass 117 | 118 | # Parse relocation tables 119 | self.plt = {} 120 | for section in self.sections: 121 | if section.type == 9: 122 | self.parse_reloc_32(section) 123 | elif section.type == 4: 124 | self.parse_reloca_32(section) 125 | elif self.header.ident.file_class == 2: # 64-bit 126 | self.header.uint64("entry") 127 | self.header.uint64("program_header_offset") 128 | self.header.uint64("section_header_offset") 129 | self.header.uint32("flags") 130 | self.header.uint16("header_size") 131 | self.header.uint16("program_header_size") 132 | self.header.uint16("program_header_count") 133 | self.header.uint16("section_header_size") 134 | self.header.uint16("section_header_count") 135 | self.header.uint16("string_table") 136 | 137 | try: 138 | self.sections = self.tree.array(self.header.section_header_count, "sections") 139 | for i in range(0, self.header.section_header_count): 140 | section = self.sections[i] 141 | section.seek(self.header.section_header_offset + (i * 64)) 142 | section.uint32("name") 143 | section.uint32("type") 144 | section.uint64("flags") 145 | section.uint64("addr") 146 | section.uint64("offset") 147 | section.uint64("size") 148 | section.uint32("link") 149 | section.uint32("info") 150 | section.uint64("align") 151 | section.uint64("entry_size") 152 | 153 | if section.type == 2: 154 | self.symbol_table_section = section 155 | elif section.type == 11: 156 | self.dynamic_symbol_table_section = section 157 | except: 158 | # Section headers are not required to load an ELF, skip errors 159 | self.sections = self.tree.array(0, "sections") 160 | pass 161 | 162 | self.program_headers = self.tree.array(self.header.program_header_count, "program_headers") 163 | for i in range(0, self.header.program_header_count): 164 | header = self.program_headers[i] 165 | header.seek(self.header.program_header_offset + (i * 56)) 166 | header.uint32("type") 167 | header.uint32("flags") 168 | header.uint64("offset") 169 | header.uint64("virtual_addr") 170 | header.uint64("physical_addr") 171 | header.uint64("file_size") 172 | header.uint64("memory_size") 173 | header.uint64("align") 174 | 175 | # Parse symbol tables 176 | self.symbols_by_name["_start"] = self.entry() 177 | self.symbols_by_addr[self.entry()] = "_start" 178 | 179 | try: 180 | if self.symbol_table_section: 181 | self.symbol_table = self.tree.array(self.symbol_table_section.size / 24, "Symbols", "symbols") 182 | self.parse_symbol_table_64(self.symbol_table, self.symbol_table_section, self.sections[self.symbol_table_section.link]) 183 | 184 | if self.dynamic_symbol_table_section: 185 | self.dynamic_symbol_table = self.tree.array(self.dynamic_symbol_table_section.size / 24, "Symbols", "symbols") 186 | self.parse_symbol_table_64(self.dynamic_symbol_table, self.dynamic_symbol_table_section, self.sections[self.dynamic_symbol_table_section.link]) 187 | except: 188 | # Skip errors in symbol table 189 | pass 190 | 191 | # Parse relocation tables 192 | self.plt = {} 193 | for section in self.sections: 194 | if section.type == 9: 195 | self.parse_reloc_64(section) 196 | elif section.type == 4: 197 | self.parse_reloca_64(section) 198 | 199 | self.tree.complete() 200 | self.valid = True 201 | except: 202 | self.valid = False 203 | 204 | if self.valid: 205 | self.data.add_callback(self) 206 | 207 | def read_string_table(self, strings, offset): 208 | end = strings.find("\x00", offset) 209 | return strings[offset:end] 210 | 211 | def parse_symbol_table_32(self, table, section, string_table): 212 | strings = self.data.read(string_table.offset, string_table.size) 213 | for i in range(0, section.size / 16): 214 | table[i].seek(section.offset + (i * 16)) 215 | table[i].uint32("name_offset") 216 | table[i].uint32("value") 217 | table[i].uint32("size") 218 | table[i].uint8("info") 219 | table[i].uint8("other") 220 | table[i].uint16("section") 221 | table[i].name = self.read_string_table(strings, table[i].name_offset) 222 | 223 | if len(table[i].name) > 0: 224 | self.symbols_by_name[table[i].name] = table[i].value 225 | self.symbols_by_addr[table[i].value] = table[i].name 226 | 227 | def parse_symbol_table_64(self, table, section, string_table): 228 | strings = self.data.read(string_table.offset, string_table.size) 229 | for i in range(0, section.size / 24): 230 | table[i].seek(section.offset + (i * 24)) 231 | table[i].uint32("name_offset") 232 | table[i].uint8("info") 233 | table[i].uint8("other") 234 | table[i].uint16("section") 235 | table[i].uint64("value") 236 | table[i].uint64("size") 237 | table[i].name = self.read_string_table(strings, table[i].name_offset) 238 | 239 | if len(table[i].name) > 0: 240 | self.symbols_by_name[table[i].name] = table[i].value 241 | self.symbols_by_addr[table[i].value] = table[i].name 242 | 243 | def parse_reloc_32(self, section): 244 | for i in range(0, section.size / 8): 245 | ofs = self.data.read_uint32(section.offset + (i * 8)) 246 | info = self.data.read_uint32(section.offset + (i * 8) + 4) 247 | sym = info >> 8 248 | reloc_type = info & 0xff 249 | if reloc_type == 7: # R_386_JUMP_SLOT 250 | self.plt[ofs] = self.dynamic_symbol_table[sym].name 251 | self.symbols_by_name[self.decorate_plt_name(self.dynamic_symbol_table[sym].name)] = ofs 252 | self.symbols_by_addr[ofs] = self.decorate_plt_name(self.dynamic_symbol_table[sym].name) 253 | 254 | def parse_reloca_32(self, section): 255 | for i in range(0, section.size / 12): 256 | ofs = self.data.read_uint32(section.offset + (i * 12)) 257 | info = self.data.read_uint32(section.offset + (i * 12) + 4) 258 | sym = info >> 8 259 | reloc_type = info & 0xff 260 | if reloc_type == 7: # R_386_JUMP_SLOT 261 | self.plt[ofs] = self.dynamic_symbol_table[sym].name 262 | self.symbols_by_name[self.decorate_plt_name(self.dynamic_symbol_table[sym].name)] = ofs 263 | self.symbols_by_addr[ofs] = self.decorate_plt_name(self.dynamic_symbol_table[sym].name) 264 | 265 | def parse_reloc_64(self, section): 266 | for i in range(0, section.size / 16): 267 | ofs = self.data.read_uint64(section.offset + (i * 16)) 268 | info = self.data.read_uint64(section.offset + (i * 16) + 8) 269 | sym = info >> 32 270 | reloc_type = info & 0xff 271 | if reloc_type == 7: # R_X86_64_JUMP_SLOT 272 | self.plt[ofs] = self.dynamic_symbol_table[sym].name 273 | self.symbols_by_name[self.decorate_plt_name(self.dynamic_symbol_table[sym].name)] = ofs 274 | self.symbols_by_addr[ofs] = self.decorate_plt_name(self.dynamic_symbol_table[sym].name) 275 | 276 | def parse_reloca_64(self, section): 277 | for i in range(0, section.size / 24): 278 | ofs = self.data.read_uint64(section.offset + (i * 24)) 279 | info = self.data.read_uint64(section.offset + (i * 24) + 8) 280 | sym = info >> 32 281 | reloc_type = info & 0xff 282 | if reloc_type == 7: # R_X86_64_JUMP_SLOT 283 | self.plt[ofs] = self.dynamic_symbol_table[sym].name 284 | self.symbols_by_name[self.decorate_plt_name(self.dynamic_symbol_table[sym].name)] = ofs 285 | self.symbols_by_addr[ofs] = self.decorate_plt_name(self.dynamic_symbol_table[sym].name) 286 | 287 | def read(self, ofs, len): 288 | result = "" 289 | while len > 0: 290 | cur = None 291 | for i in self.program_headers: 292 | if ((ofs >= i.virtual_addr) and (ofs < (i.virtual_addr + i.memory_size))) and (i.memory_size != 0): 293 | cur = i 294 | if cur == None: 295 | break 296 | 297 | prog_ofs = ofs - cur.virtual_addr 298 | mem_len = cur.memory_size - prog_ofs 299 | file_len = cur.file_size - prog_ofs 300 | if mem_len > len: 301 | mem_len = len 302 | if file_len > len: 303 | file_len = len 304 | 305 | if file_len <= 0: 306 | result += "\x00" * mem_len 307 | len -= mem_len 308 | ofs += mem_len 309 | continue 310 | 311 | result += self.data.read(cur.offset + prog_ofs, file_len) 312 | len -= file_len 313 | ofs += file_len 314 | 315 | return result 316 | 317 | def next_valid_addr(self, ofs): 318 | result = -1 319 | for i in self.program_headers: 320 | if (i.virtual_addr >= ofs) and (i.memory_size != 0) and ((result == -1) or (i.virtual_addr < result)): 321 | result = i.virtual_addr 322 | return result 323 | 324 | def get_modification(self, ofs, len): 325 | result = [] 326 | while len > 0: 327 | cur = None 328 | for i in self.program_headers: 329 | if ((ofs >= i.virtual_addr) and (ofs < (i.virtual_addr + i.memory_size))) and (i.memory_size != 0): 330 | cur = i 331 | if cur == None: 332 | break 333 | 334 | prog_ofs = ofs - cur.virtual_addr 335 | mem_len = cur.memory_size - prog_ofs 336 | file_len = cur.file_size - prog_ofs 337 | if mem_len > len: 338 | mem_len = len 339 | if file_len > len: 340 | file_len = len 341 | 342 | if file_len <= 0: 343 | result += [DATA_ORIGINAL] * mem_len 344 | len -= mem_len 345 | ofs += mem_len 346 | continue 347 | 348 | result += self.data.get_modification(cur.offset + prog_ofs, file_len) 349 | len -= file_len 350 | ofs += file_len 351 | 352 | return result 353 | 354 | def write(self, ofs, data): 355 | result = 0 356 | while len(data) > 0: 357 | cur = None 358 | for i in self.program_headers: 359 | if ((ofs >= i.virtual_addr) and (ofs < (i.virtual_addr + i.memory_size))) and (i.memory_size != 0): 360 | cur = i 361 | if cur == None: 362 | break 363 | 364 | prog_ofs = ofs - cur.virtual_addr 365 | mem_len = cur.memory_size - prog_ofs 366 | file_len = cur.file_size - prog_ofs 367 | if mem_len > len: 368 | mem_len = len 369 | if file_len > len: 370 | file_len = len 371 | 372 | if file_len <= 0: 373 | break 374 | 375 | result += self.data.write(cur.offset + prog_ofs, data[0:file_len]) 376 | data = data[file_len:] 377 | ofs += file_len 378 | 379 | return result 380 | 381 | def insert(self, ofs, data): 382 | return 0 383 | 384 | def remove(self, ofs, size): 385 | return 0 386 | 387 | def notify_data_write(self, data, ofs, contents): 388 | # Find sections that hold data backed by updated regions of the file 389 | for i in self.program_headers: 390 | if ((ofs + len(contents)) > i.offset) and (ofs < (i.offset + i.file_size)) and (i.memory_size != 0): 391 | # This section has been updated, compute which region has been changed 392 | from_start = ofs - i.offset 393 | data_ofs = 0 394 | length = len(contents) 395 | if from_start < 0: 396 | length += from_start 397 | data_ofs -= from_start 398 | from_start = 0 399 | if (from_start + length) > i.file_size: 400 | length = i.file_size - from_start 401 | 402 | # Notify callbacks 403 | if length > 0: 404 | for cb in self.callbacks: 405 | if hasattr(cb, "notify_data_write"): 406 | cb.notify_data_write(self, i.virtual_addr + from_start, 407 | contents[data_ofs:(data_ofs + length)]) 408 | 409 | def save(self, filename): 410 | self.data.save(filename) 411 | 412 | def start(self): 413 | result = None 414 | for i in self.program_headers: 415 | if ((result == None) or (i.virtual_addr < result)) and (i.memory_size != 0): 416 | result = i.virtual_addr 417 | return result 418 | 419 | def entry(self): 420 | return self.header.entry 421 | 422 | def __len__(self): 423 | max = None 424 | for i in self.program_headers: 425 | if ((max == None) or ((i.virtual_addr + i.memory_size) > max)) and (i.memory_size != 0): 426 | max = i.virtual_addr + i.memory_size 427 | return max - self.start() 428 | 429 | def is_elf(self): 430 | return self.data.read(0, 4) == "\x7fELF" 431 | 432 | def architecture(self): 433 | if self.header.arch == 2: 434 | return "sparc" 435 | if self.header.arch == 3: 436 | return "x86" 437 | if self.header.arch == 4: 438 | return "m68000" 439 | if self.header.arch == 8: 440 | return "mips" 441 | if self.header.arch == 15: 442 | return "pa_risc" 443 | if self.header.arch == 18: 444 | return "sparc_32plus" 445 | if self.header.arch == 20: 446 | return "ppc" 447 | if self.header.arch == 40: 448 | return "arm" 449 | if self.header.arch == 41: 450 | return "alpha" 451 | if self.header.arch == 43: 452 | return "sparc_v9" 453 | if self.header.arch == 62: 454 | return "x86_64" 455 | return None 456 | 457 | def decorate_plt_name(self, name): 458 | return name + "@PLT" 459 | 460 | def create_symbol(self, addr, name): 461 | self.symbols_by_name[name] = addr 462 | self.symbols_by_addr[addr] = name 463 | 464 | def delete_symbol(self, addr, name): 465 | if name in self.symbols_by_name: 466 | del(self.symbols_by_name[name]) 467 | if addr in self.symbols_by_addr: 468 | del(self.symbols_by_addr[addr]) 469 | 470 | def add_callback(self, cb): 471 | self.callbacks.append(cb) 472 | 473 | def remove_callback(self, cb): 474 | self.callbacks.remove(cb) 475 | 476 | def is_modified(self): 477 | return self.data.is_modified() 478 | 479 | def find(self, regex, addr): 480 | while (addr < self.end()) and (addr != -1): 481 | data = self.read(addr, 0xfffffffff) 482 | match = regex.search(data) 483 | if match != None: 484 | return match.start() + addr 485 | 486 | addr += len(data) 487 | addr = self.next_valid_addr(addr) 488 | 489 | return -1 490 | 491 | def has_undo_actions(self): 492 | return self.data.has_undo_actions() 493 | 494 | def commit_undo(self, before_loc, after_loc): 495 | self.data.commit_undo(before_loc, after_loc) 496 | 497 | def undo(self): 498 | self.data.undo() 499 | 500 | def redo(self): 501 | self.data.redo() 502 | 503 | 504 | class ElfViewer(HexEditor): 505 | def __init__(self, data, filename, view, parent): 506 | view.exe = ElfFile(data) 507 | super(ElfViewer, self).__init__(view.exe, filename, view, parent) 508 | view.register_navigate("exe", self, self.navigate) 509 | 510 | def getPriority(data, ext): 511 | if data.read(0, 4) == "\x7fELF": 512 | return 25 513 | return -1 514 | getPriority = staticmethod(getPriority) 515 | 516 | def getViewName(): 517 | return "ELF viewer" 518 | getViewName = staticmethod(getViewName) 519 | 520 | def getShortViewName(): 521 | return "ELF" 522 | getShortViewName = staticmethod(getShortViewName) 523 | 524 | def handlesNavigationType(name): 525 | return name == "exe" 526 | handlesNavigationType = staticmethod(handlesNavigationType) 527 | 528 | ViewTypes += [ElfViewer] 529 | 530 | -------------------------------------------------------------------------------- /FindDialog.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from PySide.QtCore import * 17 | from PySide.QtGui import * 18 | import re 19 | import sys 20 | 21 | 22 | class FindDialog(QDialog): 23 | SEARCH_ASCII = "ASCII" 24 | SEARCH_UTF16 = "UTF-16" 25 | SEARCH_UTF32 = "UTF-32" 26 | SEARCH_HEX = "Hex" 27 | SEARCH_REGEX = "Regular expression" 28 | 29 | def __init__(self, default_type, parent = None): 30 | super(FindDialog, self).__init__(parent) 31 | self.setWindowTitle("Find") 32 | 33 | layout = QVBoxLayout() 34 | 35 | hlayout = QHBoxLayout() 36 | hlayout.addWidget(QLabel("Search type:")) 37 | 38 | self.type_list = QComboBox() 39 | items = [FindDialog.SEARCH_ASCII, FindDialog.SEARCH_UTF16, 40 | FindDialog.SEARCH_UTF32, FindDialog.SEARCH_HEX, 41 | FindDialog.SEARCH_REGEX] 42 | for i in range(0, len(items)): 43 | self.type_list.addItem(items[i]) 44 | if items[i] == default_type: 45 | self.type_list.setCurrentIndex(i) 46 | hlayout.addWidget(self.type_list) 47 | layout.addLayout(hlayout) 48 | 49 | hlayout = QHBoxLayout() 50 | hlayout.addWidget(QLabel("Search string:")) 51 | 52 | self.data = QLineEdit() 53 | self.data.setMinimumSize(QSize(400, 0)) 54 | hlayout.addWidget(self.data) 55 | layout.addLayout(hlayout) 56 | 57 | hlayout = QHBoxLayout() 58 | find_button = QPushButton("Find") 59 | find_button.clicked.connect(self.find) 60 | find_button.setDefault(True) 61 | close_button = QPushButton("Close") 62 | close_button.clicked.connect(self.close) 63 | hlayout.addStretch(1) 64 | hlayout.addWidget(find_button) 65 | hlayout.addWidget(close_button) 66 | layout.addLayout(hlayout) 67 | 68 | self.setLayout(layout) 69 | self.data.setFocus(Qt.OtherFocusReason) 70 | 71 | def find(self): 72 | if self.search_regex() == None: 73 | return 74 | self.accept() 75 | 76 | def search_regex(self): 77 | try: 78 | if self.type_list.currentText() == FindDialog.SEARCH_REGEX: 79 | regex = self.data.text() 80 | else: 81 | if self.type_list.currentText() == FindDialog.SEARCH_HEX: 82 | string = self.data.text().replace(" ", "").replace("\t", "").decode("hex") 83 | elif self.type_list.currentText() == FindDialog.SEARCH_ASCII: 84 | string = self.data.text().encode("utf8") 85 | elif self.type_list.currentText() == FindDialog.SEARCH_UTF16: 86 | string = self.data.text().encode("utf-16le") 87 | elif self.type_list.currentText() == FindDialog.SEARCH_UTF32: 88 | string = self.data.text().encode("utf-32le") 89 | 90 | regex = "" 91 | for ch in string: 92 | if ((ch >= '0') and (ch <= '9')) or ((ch >= 'A') and (ch <= 'Z')) or ((ch >= 'a') and (ch <= 'z')): 93 | regex += ch 94 | else: 95 | regex += "\\x%.2x" % ord(ch) 96 | 97 | return re.compile(regex) 98 | except: 99 | QMessageBox.critical(self, "Error", "Invalid search string: " + str(sys.exc_info()[1])) 100 | return None 101 | 102 | def search_type(self): 103 | return self.type_list.currentText() 104 | 105 | -------------------------------------------------------------------------------- /Fonts.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import sys 17 | from PySide.QtCore import * 18 | from PySide.QtGui import * 19 | 20 | monospaceFont = None 21 | lineSpacing = None 22 | allowBold = None 23 | 24 | 25 | def getDefaultMonospaceFont(): 26 | if sys.platform == 'darwin': 27 | font = QFont('Monaco', 12) 28 | elif sys.platform.find('linux') != -1: 29 | font = QFont('Monospace', 10) 30 | elif sys.platform.find('freebsd') != -1: 31 | font = QFont('Bitstream Vera Sans Mono', 10) 32 | else: 33 | font = QFont('Courier', 10) 34 | return font 35 | 36 | def getMonospaceFont(): 37 | global monospaceFont 38 | if monospaceFont is None: 39 | settings = QSettings("Binary Ninja", "Binary Ninja") 40 | monospaceFont = settings.value("font", getDefaultMonospaceFont()) 41 | font = QFont(monospaceFont) 42 | font.setFixedPitch(True) 43 | font.setStyleHint(QFont.Courier) 44 | return font 45 | 46 | def setMonospaceFont(font): 47 | global monospaceFont 48 | if font is None: 49 | font = getDefaultMonospaceFont() 50 | monospaceFont = font 51 | settings = QSettings("Binary Ninja", "Binary Ninja") 52 | settings.setValue("font", monospaceFont) 53 | 54 | def getDefaultExtraFontSpacing(): 55 | if sys.platform == 'darwin': 56 | return 1 57 | if sys.platform.find('freebsd') != -1: 58 | return 2 59 | return 0 60 | 61 | def getExtraFontSpacing(): 62 | global lineSpacing 63 | if lineSpacing is None: 64 | settings = QSettings("Binary Ninja", "Binary Ninja") 65 | lineSpacing = int(settings.value("spacing", getDefaultExtraFontSpacing())) 66 | return lineSpacing 67 | 68 | def setExtraFontSpacing(spacing): 69 | global lineSpacing 70 | if spacing is None: 71 | spacing = getDefaultExtraFontSpacing() 72 | lineSpacing = spacing 73 | settings = QSettings("Binary Ninja", "Binary Ninja") 74 | settings.setValue("spacing", lineSpacing) 75 | 76 | def getFontVerticalOffset(): 77 | spacing = getExtraFontSpacing() 78 | return int((spacing + 1) / 2) 79 | 80 | def allowBoldFonts(): 81 | global allowBold 82 | if allowBold is None: 83 | settings = QSettings("Binary Ninja", "Binary Ninja") 84 | allowBold = int(settings.value("allow_bold", 1)) != 0 85 | return allowBold 86 | 87 | def setAllowBoldFonts(allow): 88 | global allowBold 89 | if allow is None: 90 | allow = True 91 | allowBold = allow 92 | settings = QSettings("Binary Ninja", "Binary Ninja") 93 | if allowBold: 94 | settings.setValue("allow_bold", 1) 95 | else: 96 | settings.setValue("allow_bold", 0) 97 | 98 | -------------------------------------------------------------------------------- /HelpView.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import sys 17 | import os 18 | from PySide.QtCore import * 19 | from PySide.QtGui import * 20 | from PySide.QtWebKit import * 21 | from PySide.QtNetwork import * 22 | from View import * 23 | 24 | 25 | class HelpView(QWebView): 26 | def __init__(self, data, filename, view, parent): 27 | super(HelpView, self).__init__(parent) 28 | 29 | self.data = data 30 | 31 | # Set contents 32 | self.setUrl(QUrl.fromLocalFile(str(self.data))) 33 | 34 | def closeRequest(self): 35 | return True 36 | 37 | def getPriority(data, filename): 38 | # Never use this view unless explicitly needed 39 | return -1 40 | getPriority = staticmethod(getPriority) 41 | 42 | def getViewName(): 43 | return "Help viewer" 44 | getViewName = staticmethod(getViewName) 45 | 46 | def getShortViewName(): 47 | return "Help" 48 | getShortViewName = staticmethod(getShortViewName) 49 | 50 | ViewTypes += [HelpView] 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /PEFile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from BinaryData import * 17 | from Structure import * 18 | from HexEditor import * 19 | from View import * 20 | 21 | 22 | class PEFile(BinaryAccessor): 23 | class SectionInfo: 24 | def __init__(self): 25 | self.virtual_size = None 26 | self.virtual_address = None 27 | self.size_of_raw_data = None 28 | self.pointer_to_raw_data = None 29 | self.characteristics = None 30 | 31 | def __init__(self, data): 32 | self.data = data 33 | self.valid = False 34 | self.callbacks = [] 35 | self.symbols_by_name = {} 36 | self.symbols_by_addr = {} 37 | if not self.is_pe(): 38 | return 39 | 40 | try: 41 | self.tree = Structure(self.data) 42 | self.mz = self.tree.struct("MZ header", "mz") 43 | self.mz.uint16("magic") 44 | self.mz.uint16("lastsize") 45 | self.mz.uint16("nblocks") 46 | self.mz.uint16("nreloc") 47 | self.mz.uint16("hdrsize") 48 | self.mz.uint16("minalloc") 49 | self.mz.uint16("maxalloc") 50 | self.mz.uint16("ss") 51 | self.mz.uint16("sp") 52 | self.mz.uint16("checksum") 53 | self.mz.uint16("ip") 54 | self.mz.uint16("cs") 55 | self.mz.uint16("relocpos") 56 | self.mz.uint16("noverlay") 57 | self.mz.bytes(8, "reserved1") 58 | self.mz.uint16("oem_id") 59 | self.mz.uint16("oem_info") 60 | self.mz.bytes(20, "reserved2") 61 | self.mz.uint32("pe_offset") 62 | 63 | self.header = self.tree.struct("PE header", "header") 64 | self.header.seek(self.mz.pe_offset) 65 | self.header.uint32("magic") 66 | self.header.uint16("machine") 67 | self.header.uint16("section_count") 68 | self.header.uint32("timestamp") 69 | self.header.uint32("coff_symbol_table") 70 | self.header.uint32("coff_symbol_count") 71 | self.header.uint16("optional_header_size") 72 | self.header.uint16("characteristics") 73 | 74 | self.header.struct("Optional header", "opt") 75 | self.header.opt.uint16("magic") 76 | self.header.opt.uint8("major_linker_version") 77 | self.header.opt.uint8("minor_linker_version") 78 | self.header.opt.uint32("size_of_code") 79 | self.header.opt.uint32("size_of_init_data") 80 | self.header.opt.uint32("size_of_uninit_data") 81 | self.header.opt.uint32("address_of_entry") 82 | self.header.opt.uint32("base_of_code") 83 | 84 | if self.header.opt.magic == 0x10b: # 32-bit 85 | self.bits = 32 86 | self.header.opt.uint32("base_of_data") 87 | self.header.opt.uint32("image_base") 88 | self.header.opt.uint32("section_align") 89 | self.header.opt.uint32("file_align") 90 | self.header.opt.uint16("major_os_version") 91 | self.header.opt.uint16("minor_os_version") 92 | self.header.opt.uint16("major_image_version") 93 | self.header.opt.uint16("minor_image_version") 94 | self.header.opt.uint16("major_subsystem_version") 95 | self.header.opt.uint16("minor_subsystem_version") 96 | self.header.opt.uint32("win32_version") 97 | self.header.opt.uint32("size_of_image") 98 | self.header.opt.uint32("size_of_headers") 99 | self.header.opt.uint32("checksum") 100 | self.header.opt.uint16("subsystem") 101 | self.header.opt.uint16("dll_characteristics") 102 | self.header.opt.uint32("size_of_stack_reserve") 103 | self.header.opt.uint32("size_of_stack_commit") 104 | self.header.opt.uint32("size_of_heap_reserve") 105 | self.header.opt.uint32("size_of_heap_commit") 106 | self.header.opt.uint32("loader_flags") 107 | self.header.opt.uint32("data_dir_count") 108 | elif self.header.opt.magic == 0x20b: # 64-bit 109 | self.bits = 64 110 | self.header.opt.uint64("image_base") 111 | self.header.opt.uint32("section_align") 112 | self.header.opt.uint32("file_align") 113 | self.header.opt.uint16("major_os_version") 114 | self.header.opt.uint16("minor_os_version") 115 | self.header.opt.uint16("major_image_version") 116 | self.header.opt.uint16("minor_image_version") 117 | self.header.opt.uint16("major_subsystem_version") 118 | self.header.opt.uint16("minor_subsystem_version") 119 | self.header.opt.uint32("win32_version") 120 | self.header.opt.uint32("size_of_image") 121 | self.header.opt.uint32("size_of_headers") 122 | self.header.opt.uint32("checksum") 123 | self.header.opt.uint16("subsystem") 124 | self.header.opt.uint16("dll_characteristics") 125 | self.header.opt.uint64("size_of_stack_reserve") 126 | self.header.opt.uint64("size_of_stack_commit") 127 | self.header.opt.uint64("size_of_heap_reserve") 128 | self.header.opt.uint64("size_of_heap_commit") 129 | self.header.opt.uint32("loader_flags") 130 | self.header.opt.uint32("data_dir_count") 131 | else: 132 | self.valid = False 133 | return 134 | 135 | self.image_base = self.header.opt.image_base 136 | 137 | self.data_dirs = self.header.array(self.header.opt.data_dir_count, "data_dirs") 138 | for i in xrange(0, self.header.opt.data_dir_count): 139 | self.data_dirs[i].uint32("virtual_address") 140 | self.data_dirs[i].uint32("size") 141 | 142 | self.sections = [] 143 | header_section_obj = PEFile.SectionInfo() 144 | header_section_obj.virtual_size = self.header.opt.size_of_headers 145 | header_section_obj.virtual_address = 0 146 | header_section_obj.size_of_raw_data = self.header.opt.size_of_headers 147 | header_section_obj.pointer_to_raw_data = 0 148 | header_section_obj.characteristics = 0 149 | self.sections.append(header_section_obj) 150 | 151 | self.tree.array(self.header.section_count, "sections") 152 | for i in xrange(0, self.header.section_count): 153 | section = self.tree.sections[i] 154 | section.seek(self.mz.pe_offset + self.header.optional_header_size + 24 + (i * 40)) 155 | section.bytes(8, "name") 156 | section.uint32("virtual_size") 157 | section.uint32("virtual_address") 158 | section.uint32("size_of_raw_data") 159 | section.uint32("pointer_to_raw_data") 160 | section.uint32("pointer_to_relocs") 161 | section.uint32("pointer_to_line_numbers") 162 | section.uint16("reloc_count") 163 | section.uint16("line_number_count") 164 | section.uint32("characteristics") 165 | 166 | section_obj = PEFile.SectionInfo() 167 | section_obj.virtual_size = section.virtual_size 168 | section_obj.virtual_address = section.virtual_address & ~(self.header.opt.section_align - 1) 169 | section_obj.size_of_raw_data = section.size_of_raw_data 170 | section_obj.pointer_to_raw_data = section.pointer_to_raw_data & ~(self.header.opt.file_align - 1) 171 | section_obj.characteristics = section.characteristics 172 | self.sections.append(section_obj) 173 | 174 | self.symbols_by_name["_start"] = self.entry() 175 | self.symbols_by_addr[self.entry()] = "_start" 176 | 177 | if self.header.opt.data_dir_count >= 2: 178 | self.imports = self.tree.array(0, "imports") 179 | for i in xrange(0, self.data_dirs[1].size / 20): 180 | if self.read(self.image_base + self.data_dirs[1].virtual_address + (i * 20), 4) == "\0\0\0\0": 181 | break 182 | if self.read(self.image_base + self.data_dirs[1].virtual_address + (i * 20) + 16, 4) == "\0\0\0\0": 183 | break 184 | self.imports.append() 185 | dll = self.imports[i] 186 | dll.seek(self.virtual_address_to_file_offset(self.image_base + self.data_dirs[1].virtual_address) + (i * 20)) 187 | dll.uint32("lookup") 188 | dll.uint32("timestamp") 189 | dll.uint32("forward_chain") 190 | dll.uint32("name") 191 | dll.uint32("iat") 192 | 193 | for dll in self.imports: 194 | name = self.read_string(self.image_base + dll.name).split('.') 195 | if len(name) > 1: 196 | name = '.'.join(name[0:-1]) 197 | else: 198 | name = name[0] 199 | 200 | entry_ofs = self.image_base + dll.lookup 201 | iat_ofs = self.image_base + dll.iat 202 | while True: 203 | if self.bits == 32: 204 | entry = self.read_uint32(entry_ofs) 205 | is_ordinal = (entry & 0x80000000) != 0 206 | entry &= 0x7fffffff 207 | else: 208 | entry = self.read_uint64(entry_ofs) 209 | is_ordinal = (entry & 0x8000000000000000) != 0 210 | entry &= 0x7fffffffffffffff 211 | 212 | if (not is_ordinal) and (entry == 0): 213 | break 214 | 215 | if is_ordinal: 216 | func = name + "!Ordinal%d" % (entry & 0xffff) 217 | else: 218 | func = name + "!" + self.read_string(self.image_base + entry + 2) 219 | 220 | self.symbols_by_name[func] = iat_ofs 221 | self.symbols_by_addr[iat_ofs] = func 222 | 223 | entry_ofs += self.bits / 8 224 | iat_ofs += self.bits / 8 225 | 226 | if (self.header.opt.data_dir_count >= 1) and (self.data_dirs[0].size >= 40): 227 | self.exports = self.tree.struct("Export directory", "exports") 228 | self.exports.seek(self.virtual_address_to_file_offset(self.image_base + self.data_dirs[0].virtual_address)) 229 | self.exports.uint32("characteristics") 230 | self.exports.uint32("timestamp") 231 | self.exports.uint16("major_version") 232 | self.exports.uint16("minor_version") 233 | self.exports.uint32("dll_name") 234 | self.exports.uint32("base") 235 | self.exports.uint32("function_count") 236 | self.exports.uint32("name_count") 237 | self.exports.uint32("address_of_functions") 238 | self.exports.uint32("address_of_names") 239 | self.exports.uint32("address_of_name_ordinals") 240 | 241 | self.exports.array(self.exports.function_count, "functions") 242 | for i in xrange(0, self.exports.function_count): 243 | self.exports.functions[i].seek(self.virtual_address_to_file_offset(self.image_base + self.exports.address_of_functions) + (i * 4)) 244 | self.exports.functions[i].uint32("address") 245 | 246 | self.exports.array(self.exports.name_count, "names") 247 | for i in xrange(0, self.exports.name_count): 248 | self.exports.names[i].seek(self.virtual_address_to_file_offset(self.image_base + self.exports.address_of_names) + (i * 4)) 249 | self.exports.names[i].uint32("address_of_name") 250 | 251 | self.exports.array(self.exports.name_count, "name_ordinals") 252 | for i in xrange(0, self.exports.name_count): 253 | self.exports.name_ordinals[i].seek(self.virtual_address_to_file_offset(self.image_base + self.exports.address_of_name_ordinals) + (i * 2)) 254 | self.exports.name_ordinals[i].uint16("ordinal") 255 | 256 | for i in xrange(0, self.exports.name_count): 257 | function_index = self.exports.name_ordinals[i].ordinal - self.exports.base 258 | address = self.image_base + self.exports.functions[function_index].address 259 | name = self.read_string(self.image_base + self.exports.names[i].address_of_name) 260 | 261 | self.symbols_by_addr[address] = name 262 | self.symbols_by_name[name] = address 263 | 264 | self.tree.complete() 265 | self.valid = True 266 | except: 267 | self.valid = False 268 | 269 | if self.valid: 270 | self.data.add_callback(self) 271 | 272 | def read_string(self, addr): 273 | result = "" 274 | while True: 275 | ch = self.read(addr, 1) 276 | addr += 1 277 | if (len(ch) == 0) or (ch == '\0'): 278 | break 279 | result += ch 280 | return result 281 | 282 | def virtual_address_to_file_offset(self, addr): 283 | for i in self.sections: 284 | if ((addr >= (self.image_base + i.virtual_address)) and (addr < (self.image_base + i.virtual_address + i.virtual_size))) and (i.virtual_size != 0): 285 | cur = i 286 | if cur == None: 287 | return None 288 | ofs = addr - (self.image_base + cur.virtual_address) 289 | return cur.pointer_to_raw_data + ofs 290 | 291 | def read(self, ofs, len): 292 | result = "" 293 | while len > 0: 294 | cur = None 295 | for i in self.sections: 296 | if ((ofs >= (self.image_base + i.virtual_address)) and (ofs < (self.image_base + i.virtual_address + i.virtual_size))) and (i.virtual_size != 0): 297 | cur = i 298 | if cur == None: 299 | break 300 | 301 | prog_ofs = ofs - (self.image_base + cur.virtual_address) 302 | mem_len = cur.virtual_size - prog_ofs 303 | file_len = cur.size_of_raw_data - prog_ofs 304 | if mem_len > len: 305 | mem_len = len 306 | if file_len > len: 307 | file_len = len 308 | 309 | if file_len <= 0: 310 | result += "\x00" * mem_len 311 | len -= mem_len 312 | ofs += mem_len 313 | continue 314 | 315 | result += self.data.read(cur.pointer_to_raw_data + prog_ofs, file_len) 316 | len -= file_len 317 | ofs += file_len 318 | 319 | return result 320 | 321 | def next_valid_addr(self, ofs): 322 | result = -1 323 | for i in self.sections: 324 | if ((self.image_base + i.virtual_address) >= ofs) and (i.virtual_size != 0) and ((result == -1) or ((self.image_base + i.virtual_address) < result)): 325 | result = self.image_base + i.virtual_address 326 | return result 327 | 328 | def get_modification(self, ofs, len): 329 | result = [] 330 | while len > 0: 331 | cur = None 332 | for i in self.sections: 333 | if ((ofs >= (self.image_base + i.virtual_address)) and (ofs < (self.image_base + i.virtual_address + i.virtual_size))) and (i.virtual_size != 0): 334 | cur = i 335 | if cur == None: 336 | break 337 | 338 | prog_ofs = ofs - (self.image_base + cur.virtual_address) 339 | mem_len = cur.virtual_size - prog_ofs 340 | file_len = cur.size_of_raw_data - prog_ofs 341 | if mem_len > len: 342 | mem_len = len 343 | if file_len > len: 344 | file_len = len 345 | 346 | if file_len <= 0: 347 | result += [DATA_ORIGINAL] * mem_len 348 | len -= mem_len 349 | ofs += mem_len 350 | continue 351 | 352 | result += self.data.get_modification(cur.pointer_to_raw_data + prog_ofs, file_len) 353 | len -= file_len 354 | ofs += file_len 355 | 356 | return result 357 | 358 | def write(self, ofs, data): 359 | result = 0 360 | while len(data) > 0: 361 | cur = None 362 | for i in self.sections: 363 | if ((ofs >= (self.image_base + i.virtual_address)) and (ofs < (self.image_base + i.virtual_address + i.virtual_size))) and (i.virtual_size != 0): 364 | cur = i 365 | if cur == None: 366 | break 367 | 368 | prog_ofs = ofs - (self.image_base + cur.virtual_address) 369 | mem_len = cur.virtual_size - prog_ofs 370 | file_len = cur.size_of_raw_data - prog_ofs 371 | if mem_len > len: 372 | mem_len = len 373 | if file_len > len: 374 | file_len = len 375 | 376 | if file_len <= 0: 377 | break 378 | 379 | result += self.data.write(cur.pointer_to_raw_data + prog_ofs, data[0:file_len]) 380 | data = data[file_len:] 381 | ofs += file_len 382 | 383 | return result 384 | 385 | def insert(self, ofs, data): 386 | return 0 387 | 388 | def remove(self, ofs, size): 389 | return 0 390 | 391 | def notify_data_write(self, data, ofs, contents): 392 | # Find sections that hold data backed by updated regions of the file 393 | for i in self.sections: 394 | if ((ofs + len(contents)) > i.pointer_to_raw_data) and (ofs < (i.pointer_to_raw_data + i.size_of_raw_data)) and (i.virtual_size != 0): 395 | # This section has been updated, compute which region has been changed 396 | from_start = ofs - i.pointer_to_raw_data 397 | data_ofs = 0 398 | length = len(contents) 399 | if from_start < 0: 400 | length += from_start 401 | data_ofs -= from_start 402 | from_start = 0 403 | if (from_start + length) > i.size_of_raw_data: 404 | length = i.size_of_raw_data - from_start 405 | 406 | # Notify callbacks 407 | if length > 0: 408 | for cb in self.callbacks: 409 | if hasattr(cb, "notify_data_write"): 410 | cb.notify_data_write(self, self.image_base + i.virtual_address + from_start, 411 | contents[data_ofs:(data_ofs + length)]) 412 | 413 | def save(self, filename): 414 | self.data.save(filename) 415 | 416 | def start(self): 417 | return self.image_base 418 | 419 | def entry(self): 420 | return self.image_base + self.header.opt.address_of_entry 421 | 422 | def __len__(self): 423 | max = None 424 | for i in self.sections: 425 | if ((max == None) or ((self.image_base + i.virtual_address + i.virtual_size) > max)) and (i.virtual_size != 0): 426 | max = self.image_base + i.virtual_address + i.virtual_size 427 | return max - self.start() 428 | 429 | def is_pe(self): 430 | if self.data.read(0, 2) != "MZ": 431 | return False 432 | ofs = self.data.read(0x3c, 4) 433 | if len(ofs) != 4: 434 | return False 435 | ofs = struct.unpack(". 15 | 16 | from PySide.QtCore import * 17 | from PySide.QtGui import * 18 | from Fonts import * 19 | 20 | 21 | class PreferencesDialog(QDialog): 22 | def __init__(self, parent = None): 23 | super(PreferencesDialog, self).__init__(parent) 24 | self.setWindowTitle("Preferences") 25 | 26 | layout = QVBoxLayout() 27 | 28 | group = QGroupBox("Monospace font") 29 | groupLayout = QVBoxLayout() 30 | 31 | hlayout = QHBoxLayout() 32 | hlayout.setContentsMargins(0, 0, 0, 0) 33 | self.font = getMonospaceFont() 34 | hlayout.addWidget(QLabel("Font: ")) 35 | self.fontLabel = QLabel("%s %d" % (self.font.family(), self.font.pointSize())) 36 | self.fontLabel.setFont(self.font) 37 | hlayout.addWidget(self.fontLabel, 1) 38 | selectFontButton = QPushButton("Select...") 39 | selectFontButton.clicked.connect(self.selectFont) 40 | hlayout.addWidget(selectFontButton) 41 | groupLayout.addLayout(hlayout) 42 | 43 | self.allowBold = QCheckBox("Allow bold fonts") 44 | self.allowBold.setChecked(allowBoldFonts()) 45 | groupLayout.addWidget(self.allowBold) 46 | 47 | hlayout = QHBoxLayout() 48 | hlayout.addWidget(QLabel("Line spacing:")) 49 | self.lineSpacing = QSpinBox() 50 | self.lineSpacing.setMinimum(0) 51 | self.lineSpacing.setMaximum(4) 52 | self.lineSpacing.setValue(getExtraFontSpacing()) 53 | hlayout.addWidget(self.lineSpacing) 54 | hlayout.addWidget(QLabel("pixels")) 55 | groupLayout.addLayout(hlayout) 56 | 57 | group.setLayout(groupLayout) 58 | layout.addWidget(group) 59 | 60 | hlayout = QHBoxLayout() 61 | defaults_button = QPushButton("Use defaults") 62 | defaults_button.clicked.connect(self.defaults) 63 | save_button = QPushButton("Save") 64 | save_button.clicked.connect(self.save) 65 | save_button.setDefault(True) 66 | close_button = QPushButton("Cancel") 67 | close_button.clicked.connect(self.close) 68 | hlayout.addWidget(defaults_button) 69 | hlayout.addStretch(1) 70 | hlayout.addWidget(close_button) 71 | hlayout.addWidget(save_button) 72 | layout.addLayout(hlayout) 73 | 74 | self.setLayout(layout) 75 | 76 | def save(self): 77 | setMonospaceFont(self.font) 78 | setExtraFontSpacing(self.lineSpacing.value()) 79 | setAllowBoldFonts(self.allowBold.isChecked()) 80 | self.accept() 81 | 82 | def selectFont(self): 83 | # The docs and actual behavior conflict here, it might break if we don't be very very 84 | # careful and paranoid, handling either return value order 85 | first, second = QFontDialog.getFont(self.font) 86 | 87 | if first and second: 88 | if hasattr(first, 'family'): 89 | self.font = first 90 | else: 91 | self.font = second 92 | self.fontLabel.setText("%s %d" % (self.font.family(), self.font.pointSize())) 93 | self.fontLabel.setFont(self.font) 94 | 95 | def defaults(self): 96 | setMonospaceFont(None) 97 | setExtraFontSpacing(None) 98 | setAllowBoldFonts(None) 99 | 100 | self.font = getMonospaceFont() 101 | self.fontLabel.setText("%s %d" % (self.font.family(), self.font.pointSize())) 102 | self.fontLabel.setFont(self.font) 103 | self.lineSpacing.setValue(getExtraFontSpacing()) 104 | self.allowBold.setChecked(allowBoldFonts()) 105 | 106 | -------------------------------------------------------------------------------- /PythonConsole.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from PySide.QtCore import * 17 | from PySide.QtGui import * 18 | from Fonts import * 19 | import code 20 | import sys 21 | import threading 22 | import Threads 23 | 24 | class PythonConsoleOutput(): 25 | def __init__(self, orig, error): 26 | self.stdout = orig 27 | self.error = error 28 | 29 | def write(self, data): 30 | console = None 31 | if "value" in dir(_python_console): 32 | console = _python_console.value 33 | 34 | if console is None: 35 | self.stdout.write(data) 36 | else: 37 | if self.error: 38 | Threads.run_on_gui_thread(lambda: console.write_stderr(data)) 39 | else: 40 | Threads.run_on_gui_thread(lambda: console.write_stdout(data)) 41 | 42 | class PythonConsoleInput(): 43 | def __init__(self, orig): 44 | self.stdin = orig 45 | 46 | def read(self, size): 47 | console = None 48 | if "value" in dir(_python_console): 49 | console = _python_console.value 50 | 51 | if console is None: 52 | return self.stdin.read(size) 53 | else: 54 | return console.read_stdin(size) 55 | 56 | def readline(self): 57 | console = None 58 | if "value" in dir(_python_console): 59 | console = _python_console.value 60 | 61 | if console is None: 62 | return self.stdin.readline() 63 | else: 64 | return console.readline_stdin() 65 | 66 | class PythonConsoleThread(threading.Thread): 67 | def __init__(self, console): 68 | threading.Thread.__init__(self) 69 | self.console = console 70 | self.globals = {"__name__":"__console__", "__doc__":None} 71 | self.code = None 72 | self.event = threading.Event() 73 | self.done = threading.Event() 74 | self.exit = False 75 | self.interpreter = code.InteractiveInterpreter(self.globals) 76 | 77 | # There is no way to interrupt a thread that isn't the main thread, so 78 | # to avoid not being able to close the app, create the thread as 79 | # as daemon thread. 80 | self.daemon = True 81 | 82 | # Set up environment with useful variables and functions 83 | self.globals["data"] = Threads.GuiObjectProxy(self.console.view.data) 84 | self.globals["exe"] = Threads.GuiObjectProxy(self.console.view.exe) 85 | self.globals["view"] = Threads.GuiObjectProxy(self.console.view) 86 | 87 | self.globals["current_view"] = Threads.GuiObjectProxy(lambda: self.console.view.view) 88 | self.globals["change_view"] = Threads.GuiObjectProxy(lambda type: self.console.view.setViewType(type)) 89 | self.globals["navigate"] = Threads.GuiObjectProxy(lambda type, pos: self.console.view.navigate(type, pos)) 90 | self.globals["create_file"] = Threads.GuiObjectProxy(lambda data: Threads.create_file(data)) 91 | 92 | self.globals["cursor"] = Threads.GuiObjectProxy(lambda: self.console.view.view.get_cursor_pos()) 93 | self.globals["set_cursor"] = Threads.GuiObjectProxy(lambda pos: self.console.view.view.set_cursor_pos(pos)) 94 | self.globals["selection_range"] = Threads.GuiObjectProxy(lambda: self.console.view.view.get_selection_range()) 95 | self.globals["set_selection_range"] = Threads.GuiObjectProxy(lambda start, end: self.console.view.view.set_selection_range(start, end)) 96 | self.globals["selection"] = Threads.GuiObjectProxy(lambda: self.get_selection()) 97 | self.globals["replace_selection"] = Threads.GuiObjectProxy(lambda value: self.replace_selection(value)) 98 | self.globals["write_at_cursor"] = Threads.GuiObjectProxy(lambda value: self.write_at_cursor(value)) 99 | 100 | self.globals["undo"] = Threads.GuiObjectProxy(lambda: self.console.view.undo()) 101 | self.globals["redo"] = Threads.GuiObjectProxy(lambda: self.console.view.redo()) 102 | self.globals["commit"] = Threads.GuiObjectProxy(lambda: self.console.view.commit_undo()) 103 | 104 | self.globals["copy"] = Threads.GuiObjectProxy(lambda value: self.copy(value)) 105 | self.globals["paste"] = Threads.GuiObjectProxy(lambda: self.console.view.view.paste()) 106 | self.globals["clipboard"] = Threads.GuiObjectProxy(lambda: self.get_clipboard()) 107 | 108 | # Helper APIs 109 | def get_selection(self): 110 | data = self.console.view.view.data 111 | range = self.console.view.view.get_selection_range() 112 | return data.read(range[0], range[1] - range[0]) 113 | 114 | def replace_selection(self, value): 115 | data = self.console.view.view.data 116 | range = self.console.view.view.get_selection_range() 117 | if (range[1] - range[0]) == len(value): 118 | result = data.write(range[0], value) 119 | else: 120 | data.remove(range[0], range[1] - range[0]) 121 | result = data.insert(range[0], value) 122 | self.console.view.view.set_cursor_pos(range[0] + result) 123 | return result 124 | 125 | def write_at_cursor(self, value): 126 | data = self.console.view.view.data 127 | pos = self.console.view.view.get_cursor_pos() 128 | result = data.write(pos, value) 129 | self.console.view.view.set_cursor_pos(pos + result) 130 | return result 131 | 132 | def copy(self, data): 133 | if type(data) != str: 134 | data = str(data) 135 | clipboard = QApplication.clipboard() 136 | clipboard.clear() 137 | mime = QMimeData() 138 | mime.setText(data.encode("string_escape").replace("\"", "\\\"")) 139 | mime.setData("application/octet-stream", QByteArray(data)) 140 | clipboard.setMimeData(mime) 141 | 142 | def get_clipboard(self): 143 | clipboard = QApplication.clipboard() 144 | mime = clipboard.mimeData() 145 | if mime.hasFormat("application/octet-stream"): 146 | return mime.data("application/octet-stream").data() 147 | elif mime.hasText(): 148 | return mime.text().encode("utf8") 149 | else: 150 | return None 151 | 152 | # Thread run loop 153 | def run(self): 154 | _python_console.value = self.console 155 | while not self.exit: 156 | self.event.wait() 157 | self.event.clear() 158 | if self.exit: 159 | break 160 | if self.code: 161 | self.interpreter.runsource(self.code) 162 | self.code = None 163 | self.done.set() 164 | 165 | class PythonConsoleLineEdit(QLineEdit): 166 | prevHistory = Signal(()) 167 | nextHistory = Signal(()) 168 | 169 | def __init__(self, *args): 170 | super(PythonConsoleLineEdit, self).__init__(*args) 171 | 172 | def event(self, event): 173 | if (event.type() == QEvent.KeyPress) and (event.key() == Qt.Key_Tab): 174 | self.insert("\t") 175 | return True 176 | if (event.type() == QEvent.KeyPress) and (event.key() == Qt.Key_Up): 177 | self.prevHistory.emit() 178 | return True 179 | if (event.type() == QEvent.KeyPress) and (event.key() == Qt.Key_Down): 180 | self.nextHistory.emit() 181 | return True 182 | return QLineEdit.event(self, event) 183 | 184 | class PythonConsole(QWidget): 185 | def __init__(self, view): 186 | super(PythonConsole, self).__init__(view) 187 | self.view = view 188 | 189 | font = getMonospaceFont() 190 | 191 | layout = QVBoxLayout() 192 | layout.setContentsMargins(0, 0, 0, 0) 193 | layout.setSpacing(0) 194 | 195 | self.output = QTextEdit() 196 | self.output.setFont(font) 197 | self.output.setReadOnly(True) 198 | layout.addWidget(self.output, 1) 199 | 200 | input_layout = QHBoxLayout() 201 | input_layout.setContentsMargins(4, 4, 4, 4) 202 | input_layout.setSpacing(4) 203 | 204 | self.prompt = QLabel(">>>") 205 | self.prompt.setFont(font) 206 | input_layout.addWidget(self.prompt) 207 | self.input = PythonConsoleLineEdit() 208 | self.input.setFont(font) 209 | self.input.returnPressed.connect(self.process_input) 210 | self.input.prevHistory.connect(self.prev_history) 211 | self.input.nextHistory.connect(self.next_history) 212 | input_layout.addWidget(self.input) 213 | 214 | layout.addLayout(input_layout, 0) 215 | 216 | self.setLayout(layout) 217 | self.setFocusPolicy(Qt.NoFocus) 218 | self.setMinimumSize(100, 100) 219 | 220 | size_policy = self.sizePolicy() 221 | size_policy.setVerticalStretch(1) 222 | self.setSizePolicy(size_policy) 223 | 224 | self.thread = PythonConsoleThread(self) 225 | self.thread.start() 226 | 227 | self.completion_timer = QTimer() 228 | self.completion_timer.setInterval(100) 229 | self.completion_timer.setSingleShot(False) 230 | self.completion_timer.timeout.connect(self.completion_timer_event) 231 | self.completion_timer.start() 232 | 233 | self.source = None 234 | self.running = False 235 | 236 | self.input_requested = False 237 | self.input_result = "" 238 | self.input_event = threading.Event() 239 | 240 | self.input_history = [] 241 | self.input_history_pos = None 242 | 243 | def stop(self): 244 | self.thread.exit = True 245 | self.thread.event.set() 246 | # Can't join here, as it might be stuck in user code 247 | 248 | def process_input(self): 249 | self.input_history += [str(self.input.text())] 250 | self.input_history_pos = None 251 | 252 | input = str(self.input.text()) + "\n" 253 | self.input.setText("") 254 | 255 | self.output.textCursor().movePosition(QTextCursor.End) 256 | fmt = QTextCharFormat() 257 | fmt.setForeground(QBrush(Qt.black)) 258 | if len(self.prompt.text()) > 0: 259 | self.output.textCursor().insertText(self.prompt.text() + " " + input, fmt) 260 | else: 261 | self.output.textCursor().insertText(input, fmt) 262 | self.output.ensureCursorVisible() 263 | 264 | if self.input_requested: 265 | # Request for data from stdin 266 | self.input_requested = False 267 | self.input.setEnabled(False) 268 | self.input_result = input 269 | self.input_event.set() 270 | return 271 | 272 | if self.source is not None: 273 | self.source = self.source + input 274 | if input != "\n": 275 | # Don't end multiline input until a blank line 276 | return 277 | input = self.source 278 | 279 | try: 280 | result = code.compile_command(input) 281 | except: 282 | result = False 283 | 284 | if result is None: 285 | if self.source is None: 286 | self.source = input 287 | else: 288 | self.source += input 289 | self.prompt.setText("...") 290 | return 291 | 292 | self.source = None 293 | self.prompt.setText(">>>") 294 | 295 | self.thread.code = input 296 | self.thread.event.set() 297 | self.running = True 298 | 299 | self.thread.done.wait(0.05) 300 | if self.thread.done.is_set(): 301 | self.thread.done.clear() 302 | self.running = False 303 | else: 304 | self.input.setEnabled(False) 305 | 306 | def prev_history(self): 307 | if len(self.input_history) == 0: 308 | return 309 | if self.input_history_pos is None: 310 | self.input_history_pos = len(self.input_history) 311 | if self.input_history_pos == 0: 312 | return 313 | self.input_history_pos -= 1 314 | self.input.setText(self.input_history[self.input_history_pos]) 315 | 316 | def next_history(self): 317 | if self.input_history_pos is None: 318 | return 319 | if (self.input_history_pos + 1) >= len(self.input_history): 320 | self.input_history_pos = None 321 | self.input.setText("") 322 | return 323 | self.input_history_pos += 1 324 | self.input.setText(self.input_history[self.input_history_pos]) 325 | 326 | def completion_timer_event(self): 327 | if self.thread.done.is_set(): 328 | self.thread.done.clear() 329 | self.running = False 330 | self.input.setEnabled(True) 331 | self.input.setFocus(Qt.OtherFocusReason) 332 | self.prompt.setText(">>>") 333 | 334 | def request_input(self): 335 | self.input_requested = True 336 | self.input.setEnabled(True) 337 | self.input.setFocus(Qt.OtherFocusReason) 338 | self.prompt.setText("") 339 | 340 | def write_stdout(self, data): 341 | self.output.textCursor().movePosition(QTextCursor.End) 342 | fmt = QTextCharFormat() 343 | fmt.setForeground(QBrush(Qt.blue)) 344 | self.output.textCursor().insertText(data, fmt) 345 | self.output.ensureCursorVisible() 346 | 347 | def write_stderr(self, data): 348 | self.output.textCursor().movePosition(QTextCursor.End) 349 | fmt = QTextCharFormat() 350 | fmt.setForeground(QBrush(Qt.red)) 351 | self.output.textCursor().insertText(data, fmt) 352 | self.output.ensureCursorVisible() 353 | 354 | def read_stdin(self, size): 355 | if Threads.is_gui_thread(): 356 | raise RuntimeError, "Cannot call read_stdin from GUI thread" 357 | 358 | if len(self.input_result) == 0: 359 | Threads.run_on_gui_thread(self.request_input) 360 | self.input_event.wait() 361 | self.input_event.clear() 362 | 363 | if len(self.input_result) > size: 364 | result = self.input_result[0:size] 365 | self.input_result = self.input_result[size:] 366 | return result 367 | 368 | result = self.input_result 369 | self.input_result = "" 370 | return 371 | 372 | def readline_stdin(self): 373 | if Threads.is_gui_thread(): 374 | raise RuntimeError, "Cannot call readline_stdin from GUI thread" 375 | 376 | if len(self.input_result) == 0: 377 | Threads.run_on_gui_thread(self.request_input) 378 | self.input_event.wait() 379 | self.input_event.clear() 380 | 381 | result = self.input_result 382 | self.input_result = "" 383 | return result 384 | 385 | 386 | sys.stderr = PythonConsoleOutput(sys.stderr, True) 387 | sys.stdout = PythonConsoleOutput(sys.stdout, False) 388 | sys.stdin = PythonConsoleInput(sys.stdin) 389 | _python_console = threading.local() 390 | 391 | -------------------------------------------------------------------------------- /PythonHighlight.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from TextLines import * 17 | 18 | 19 | class PythonHighlight(Highlight): 20 | def __init__(self, data): 21 | self.keywords = ["class", "def", "if", "elif", "else", "for", "in", "while", "return", "and", "or", "not", "is", 22 | "print", "try", "except", "finally", "raise", "as", "assert", "break", "continue", "exec", "pass", 23 | "lambda", "with", "yield"] 24 | self.identifiers = ["self", "super", "len", "del", "type", "repr", "str", "int", "bytes", "long", "hex", 25 | "range", "xrange"] 26 | self.values = ["None", "True", "False"] 27 | self.directives = ["import", "from", "global"] 28 | self.definitions = ["class", "def", "import", "from"] 29 | 30 | def update_line(self, line, text): 31 | if text.find('#') != -1: 32 | token = HighlightToken(text.find('#'), len(text) - text.find('#'), HighlightState(HIGHLIGHT_COMMENT)) 33 | line.tokens.append(token) 34 | text = text[0:text.find('#')] 35 | 36 | tokens = self.simple_tokenize(text) 37 | for i in xrange(0, len(tokens)): 38 | token = tokens[i] 39 | if token[1] in self.keywords: 40 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_KEYWORD))) 41 | elif token[1] in self.identifiers: 42 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_IDENTIFIER))) 43 | elif token[1] in self.values: 44 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_VALUE))) 45 | elif token[1] in self.directives: 46 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_DIRECTIVE))) 47 | elif (i > 0) and (tokens[i - 1][1] in self.definitions): 48 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_IDENTIFIER))) 49 | elif (token[1][0] == '"') or (token[1][0] == '\''): 50 | self.append_escaped_string_tokens(line, token[0], token[1]) 51 | elif (token[1][0] >= '0') and (token[1][0] <= '9'): 52 | line.tokens.append(HighlightToken(token[0], len(token[1]), HighlightState(HIGHLIGHT_VALUE))) 53 | 54 | return None 55 | 56 | 57 | highlightTypes["Python source"] = PythonHighlight 58 | 59 | -------------------------------------------------------------------------------- /RunWindow.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import shlex 17 | from PySide.QtCore import * 18 | from PySide.QtGui import * 19 | from TerminalView import * 20 | 21 | 22 | class RunWindow(QWidget): 23 | def __init__(self, parent, view, cmd): 24 | super(RunWindow, self).__init__(parent) 25 | self.view = view 26 | self.cmd = cmd 27 | 28 | vlayout = QVBoxLayout() 29 | vlayout.setContentsMargins(0, 0, 0, 0) 30 | vlayout.setSpacing(0) 31 | 32 | hlayout = QHBoxLayout() 33 | hlayout.setContentsMargins(4, 0, 4, 4) 34 | hlayout.addWidget(QLabel("Command arguments:")) 35 | hlayout.setSpacing(4) 36 | 37 | self.commandLine = QLineEdit() 38 | self.commandLine.returnPressed.connect(self.run) 39 | hlayout.addWidget(self.commandLine, 1) 40 | 41 | self.runButton = QPushButton("Run") 42 | self.runButton.clicked.connect(self.run) 43 | self.runButton.setAutoDefault(True) 44 | self.closeButton = QPushButton("Close") 45 | self.closeButton.clicked.connect(self.closePanel) 46 | self.closeButton.setAutoDefault(False) 47 | hlayout.addWidget(self.runButton) 48 | hlayout.addWidget(self.closeButton) 49 | 50 | vlayout.addLayout(hlayout) 51 | 52 | self.term = TerminalView(None, "", view, self) 53 | self.reinit = False 54 | 55 | vlayout.addWidget(self.term, 1) 56 | self.setLayout(vlayout) 57 | 58 | def run(self): 59 | if self.reinit: 60 | self.term.reinit() 61 | self.reinit = False 62 | return 63 | 64 | cmdLine = self.commandLine.text() 65 | args = [i.decode("string_escape") for i in shlex.split(cmdLine.encode('utf8'))] 66 | self.term.restart(self.cmd + args) 67 | 68 | def closePanel(self): 69 | self.term.closeRequest() 70 | self.view.terminal_closed() 71 | self.reinit = True 72 | 73 | def closeRequest(self): 74 | self.term.closeRequest() 75 | 76 | -------------------------------------------------------------------------------- /Structure.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from BinaryData import * 17 | 18 | 19 | class _ParserState: 20 | def __init__(self, data, ofs): 21 | self.data = data 22 | self.offset = ofs 23 | 24 | 25 | class Array: 26 | def __init__(self, state, count): 27 | self._state = state 28 | self.elements = [] 29 | for i in range(0, count): 30 | self.elements += [Structure(state.data, state)] 31 | 32 | def append(self): 33 | self.elements.append(Structure(self._state.data, self._state)) 34 | 35 | def getStart(self): 36 | start = None 37 | for i in self.elements: 38 | if (start == None) or (i.getStart() < start): 39 | start = i.getStart() 40 | if start == None: 41 | return 0 42 | return start 43 | 44 | def getSize(self): 45 | start = self.getStart() 46 | end = None 47 | for i in self.elements: 48 | if (end == None) or ((i.getStart() + i.getSize()) > end): 49 | end = i.getStart() + i.getSize() 50 | if end == None: 51 | return 0 52 | return end - start 53 | 54 | def complete(self): 55 | for i in self.elements: 56 | i.complete() 57 | 58 | def __getitem__(self, index): 59 | return self.elements[index] 60 | 61 | def __len__(self): 62 | return len(self.elements) 63 | 64 | 65 | class Structure: 66 | def __init__(self, data, state = None): 67 | self._data = data 68 | self._state = state 69 | if state == None: 70 | self._state = _ParserState(data, 0) 71 | self._names = {} 72 | self._order = [] 73 | self._start = {} 74 | self._size = {} 75 | self._type = {} 76 | 77 | def seek(self, ofs): 78 | self._state.offset = ofs 79 | 80 | def struct(self, name, id = None): 81 | if id == None: 82 | id = name 83 | result = Structure(self._data, self._state) 84 | self.__dict__[id] = result 85 | self._names[id] = name 86 | self._type[id] = "struct" 87 | self._order += [id] 88 | return result 89 | 90 | def array(self, count, name, id = None): 91 | if id == None: 92 | id = name 93 | result = Array(self._state, count) 94 | self.__dict__[id] = result 95 | self._names[id] = name 96 | self._type[id] = "array" 97 | self._order += [id] 98 | return result 99 | 100 | def bytes(self, count, name, id = None): 101 | if id == None: 102 | id = name 103 | result = self._data.read(self._state.offset, count) 104 | self.__dict__[id] = result 105 | self._names[id] = name 106 | self._start[id] = self._state.offset 107 | self._size[id] = count 108 | self._type[id] = "bytes" 109 | self._order += [id] 110 | self._state.offset += count 111 | return result 112 | 113 | def uint8(self, name, id = None): 114 | if id == None: 115 | id = name 116 | result = self._data.read_uint8(self._state.offset) 117 | self.__dict__[id] = result 118 | self._names[id] = name 119 | self._start[id] = self._state.offset 120 | self._size[id] = 1 121 | self._type[id] = "uint8" 122 | self._order += [id] 123 | self._state.offset += 1 124 | return result 125 | 126 | def uint16(self, name, id = None): 127 | if id == None: 128 | id = name 129 | result = self._data.read_uint16(self._state.offset) 130 | self.__dict__[id] = result 131 | self._names[id] = name 132 | self._start[id] = self._state.offset 133 | self._size[id] = 2 134 | self._type[id] = "uint16" 135 | self._order += [id] 136 | self._state.offset += 2 137 | return result 138 | 139 | def uint32(self, name, id = None): 140 | if id == None: 141 | id = name 142 | result = self._data.read_uint32(self._state.offset) 143 | self.__dict__[id] = result 144 | self._names[id] = name 145 | self._start[id] = self._state.offset 146 | self._size[id] = 4 147 | self._type[id] = "uint32" 148 | self._order += [id] 149 | self._state.offset += 4 150 | return result 151 | 152 | def uint64(self, name, id = None): 153 | if id == None: 154 | id = name 155 | result = self._data.read_uint64(self._state.offset) 156 | self.__dict__[id] = result 157 | self._names[id] = name 158 | self._start[id] = self._state.offset 159 | self._size[id] = 8 160 | self._type[id] = "uint64" 161 | self._order += [id] 162 | self._state.offset += 8 163 | return result 164 | 165 | def uint16_le(self, name, id = None): 166 | if id == None: 167 | id = name 168 | result = self._data.read_uint16_le(self._state.offset) 169 | self.__dict__[id] = result 170 | self._names[id] = name 171 | self._start[id] = self._state.offset 172 | self._size[id] = 2 173 | self._type[id] = "uint16_le" 174 | self._order += [id] 175 | self._state.offset += 2 176 | return result 177 | 178 | def uint32_le(self, name, id = None): 179 | if id == None: 180 | id = name 181 | result = self._data.read_uint32_le(self._state.offset) 182 | self.__dict__[id] = result 183 | self._names[id] = name 184 | self._start[id] = self._state.offset 185 | self._size[id] = 4 186 | self._type[id] = "uint32_le" 187 | self._order += [id] 188 | self._state.offset += 4 189 | return result 190 | 191 | def uint64_le(self, name, id = None): 192 | if id == None: 193 | id = name 194 | result = self._data.read_uint64_le(self._state.offset) 195 | self.__dict__[id] = result 196 | self._names[id] = name 197 | self._start[id] = self._state.offset 198 | self._size[id] = 8 199 | self._type[id] = "uint64_le" 200 | self._order += [id] 201 | self._state.offset += 8 202 | return result 203 | 204 | def uint16_be(self, name, id = None): 205 | if id == None: 206 | id = name 207 | result = self._data.read_uint16_be(self._state.offset) 208 | self.__dict__[id] = result 209 | self._names[id] = name 210 | self._start[id] = self._state.offset 211 | self._size[id] = 2 212 | self._type[id] = "uint16_be" 213 | self._order += [id] 214 | self._state.offset += 2 215 | return result 216 | 217 | def uint32_be(self, name, id = None): 218 | if id == None: 219 | id = name 220 | result = self._data.read_uint32_be(self._state.offset) 221 | self.__dict__[id] = result 222 | self._names[id] = name 223 | self._start[id] = self._state.offset 224 | self._size[id] = 4 225 | self._type[id] = "uint32_be" 226 | self._order += [id] 227 | self._state.offset += 4 228 | return result 229 | 230 | def uint64_be(self, name, id = None): 231 | if id == None: 232 | id = name 233 | result = self._data.read_uint64_be(self._state.offset) 234 | self.__dict__[id] = result 235 | self._names[id] = name 236 | self._start[id] = self._state.offset 237 | self._size[id] = 8 238 | self._type[id] = "uint64_be" 239 | self._order += [id] 240 | self._state.offset += 8 241 | return result 242 | 243 | def getStart(self): 244 | self.complete() 245 | start = None 246 | for i in self._order: 247 | if (start == None) or (self._start[i] < start): 248 | start = self._start[i] 249 | return start 250 | 251 | def getSize(self): 252 | start = self.getStart() 253 | end = None 254 | for i in self._order: 255 | if (end == None) or ((self._start[i] + self._size[i]) > end): 256 | end = self._start[i] + self._size[i] 257 | if end == None: 258 | return None 259 | return end - start 260 | 261 | def complete(self): 262 | for i in self._order: 263 | if (not self._start.has_key(i)) or (not self._size.has_key(i)): 264 | self.__dict__[i].complete() 265 | self._start[i] = self.__dict__[i].getStart() 266 | self._size[i] = self.__dict__[i].getSize() 267 | 268 | -------------------------------------------------------------------------------- /TerminalProcess.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import os 17 | import sys 18 | import struct 19 | import threading 20 | import time 21 | if os.name != "nt": 22 | # Windows doesn't do PTYs 23 | import pty 24 | import subprocess 25 | import select 26 | import fcntl 27 | import termios 28 | import signal 29 | from TerminalEmulator import * 30 | from Threads import * 31 | 32 | 33 | class TerminalUpdateThread(threading.Thread): 34 | def __init__(self, proc, data_pipe, exit_pipe): 35 | super(TerminalUpdateThread, self).__init__() 36 | self.proc = proc 37 | self.data_pipe = data_pipe 38 | self.exit_pipe = exit_pipe 39 | self.running = True 40 | self.alive = True 41 | 42 | def run(self): 43 | while self.running: 44 | self.proc.check_for_output(self, self.data_pipe, self.exit_pipe) 45 | os.close(self.data_pipe) 46 | os.close(self.exit_pipe) 47 | self.alive = False 48 | 49 | def stop(self): 50 | self.running = False 51 | 52 | 53 | class TerminalProcess: 54 | def __init__(self, cmd, raw_debug = None): 55 | if os.name == "nt": 56 | raise RuntimeError, "Windows does not support pseudo-terminal devices" 57 | 58 | if cmd: 59 | # Spawn the new process in a new PTY 60 | self.exit_pipe, child_pipe = os.pipe() 61 | self.exit_callback = None 62 | 63 | pid, fd = pty.fork() 64 | if pid == 0: 65 | try: 66 | os.environ["TERM"] = "xterm-256color" 67 | if "PYTHONPATH" in os.environ: 68 | del(os.environ["PYTHONPATH"]) 69 | if "LD_LIBRARY_PATH" in os.environ: 70 | del(os.environ["LD_LIBRARY_PATH"]) 71 | if sys.platform == "darwin": 72 | if "DYLD_LIBRARY_PATH" in os.environ: 73 | del(os.environ["DYLD_LIBRARY_PATH"]) 74 | 75 | retval = subprocess.call(cmd, close_fds=True) 76 | os.write(2, "\033[01;34mProcess has completed.\033[00m\n") 77 | os.write(child_pipe, "t") 78 | os._exit(retval) 79 | except: 80 | pass 81 | os.write(2, "\033[01;31mCommand '" + cmd[0] + "' failed to execute.\033[00m\n") 82 | os.write(child_pipe, "f") 83 | os._exit(1) 84 | 85 | os.close(child_pipe) 86 | 87 | self.pid = pid 88 | self.data_pipe = fd 89 | else: 90 | self.exit_pipe = -1 91 | self.pid = -1 92 | self.data_pipe = -1 93 | 94 | self.rows = 25 95 | self.cols = 80 96 | self.term = TerminalEmulator(self.rows, self.cols) 97 | self.raw_debug = raw_debug 98 | self.completed = False 99 | 100 | self.term.response_callback = self.send_input 101 | 102 | if cmd: 103 | # Initialize terminal settings 104 | fcntl.ioctl(self.data_pipe, termios.TIOCSWINSZ, struct.pack("hhhh", self.rows, self.cols, 0, 0)) 105 | attribute = termios.tcgetattr(self.data_pipe) 106 | termios.tcsetattr(self.data_pipe, termios.TCSAFLUSH, attribute) 107 | else: 108 | self.process_input("\033[01;33mEnter desired command arguments, then run again.\033[00m\r\n") 109 | self.completed = True 110 | 111 | def process_input(self, data): 112 | self.term.process(data) 113 | if self.raw_debug is not None: 114 | self.raw_debug.insert(len(self.raw_debug), data) 115 | 116 | def exit_notify(self, thread, ok): 117 | if thread == self.thread: 118 | # Process that is exiting was the active process, notify owner 119 | self.completed = True 120 | if self.exit_callback: 121 | run_on_gui_thread(lambda: self.exit_callback(ok)) 122 | thread.stop() 123 | 124 | def check_for_output(self, thread, data_pipe, exit_pipe): 125 | # Combine writes in 20ms intervals to avoid too many calls to run_on_gui_thread 126 | ready_data = "" 127 | timeout = 0.02 128 | end_time = time.time() + timeout 129 | 130 | while len(ready_data) < 8192: 131 | input_ready, output_ready, error = select.select([data_pipe, exit_pipe], [], [], timeout) 132 | if data_pipe in input_ready: 133 | try: 134 | data = os.read(data_pipe, 4096) 135 | except: 136 | data = "" 137 | if len(data) > 0: 138 | ready_data += data 139 | timeout = end_time - time.time() 140 | if timeout > 0: 141 | continue 142 | if exit_pipe in input_ready: 143 | result = os.read(exit_pipe, 1) 144 | if result == "t": 145 | ok = True 146 | else: 147 | ok = False 148 | run_on_gui_thread(lambda: self.exit_notify(thread, ok)) 149 | break 150 | 151 | if len(ready_data) > 0: 152 | run_on_gui_thread(lambda: self.process_input(ready_data)) 153 | return True 154 | return False 155 | 156 | def send_input(self, data): 157 | try: 158 | os.write(self.data_pipe, data) 159 | except: 160 | pass 161 | 162 | def resize(self, rows, cols): 163 | if (self.rows == rows) and (self.cols == cols): 164 | return 165 | 166 | self.rows = rows 167 | self.cols = cols 168 | self.term.resize(self.rows, self.cols) 169 | 170 | if not self.completed: 171 | fcntl.ioctl(self.data_pipe, termios.TIOCSWINSZ, struct.pack("hhhh", self.rows, self.cols, 0, 0)) 172 | 173 | def start_monitoring(self): 174 | if not self.completed: 175 | self.thread = TerminalUpdateThread(self, self.data_pipe, self.exit_pipe) 176 | self.thread.start() 177 | 178 | def kill(self): 179 | if not self.completed: 180 | os.kill(self.pid, signal.SIGHUP) 181 | thread = self.thread 182 | thread.stop() 183 | while thread.alive: 184 | QCoreApplication.processEvents() 185 | 186 | def restart(self, cmd): 187 | if not self.completed: 188 | self.process_input("\n\033[01;31mProcess killed.\033[00m\r\n") 189 | os.kill(self.pid, signal.SIGHUP) 190 | thread = self.thread 191 | thread.stop() 192 | while thread.alive: 193 | QCoreApplication.processEvents() 194 | 195 | self.exit_pipe, child_pipe = os.pipe() 196 | pid, fd = pty.fork() 197 | if pid == 0: 198 | try: 199 | os.environ["TERM"] = "xterm-256color" 200 | retval = subprocess.call(cmd, close_fds=True) 201 | os.write(2, "\033[01;34mProcess has completed.\033[00m\n") 202 | os.write(child_pipe, "t") 203 | os._exit(retval) 204 | except: 205 | pass 206 | os.write(2, "\033[01;31mCommand '" + cmd[0] + "' failed to execute.\033[00m\n") 207 | os.write(child_pipe, "f") 208 | os._exit(1) 209 | 210 | os.close(child_pipe) 211 | self.process_input("\033[01;34mStarted process with PID %d.\033[00m\r\n" % pid) 212 | 213 | self.pid = pid 214 | self.data_pipe = fd 215 | self.completed = False 216 | 217 | # Initialize terminal settings 218 | fcntl.ioctl(self.data_pipe, termios.TIOCSWINSZ, struct.pack("hhhh", self.rows, self.cols, 0, 0)) 219 | attribute = termios.tcgetattr(self.data_pipe) 220 | termios.tcsetattr(self.data_pipe, termios.TCSAFLUSH, attribute) 221 | 222 | self.thread = TerminalUpdateThread(self, self.data_pipe, self.exit_pipe) 223 | self.thread.start() 224 | 225 | def reinit(self): 226 | self.rows = 25 227 | self.cols = 80 228 | self.term = TerminalEmulator(self.rows, self.cols) 229 | self.term.response_callback = self.send_input 230 | self.process_input("\033[01;33mEnter desired command arguments, then run again.\033[00m\r\n") 231 | 232 | -------------------------------------------------------------------------------- /TextLines.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2012 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | HIGHLIGHT_NONE = 0 17 | HIGHLIGHT_KEYWORD = 1 18 | HIGHLIGHT_IDENTIFIER = 2 19 | HIGHLIGHT_STRING = 3 20 | HIGHLIGHT_VALUE = 4 21 | HIGHLIGHT_ESCAPE = 5 22 | HIGHLIGHT_COMMENT = 6 23 | HIGHLIGHT_DIRECTIVE = 7 24 | 25 | 26 | highlightTypes = {} 27 | 28 | 29 | class HighlightState: 30 | def __init__(self, style): 31 | self.style = style 32 | 33 | def __eq__(self, other): 34 | if other is None: 35 | return False 36 | return self.style == other.style 37 | 38 | 39 | class HighlightToken: 40 | def __init__(self, start, length, state): 41 | self.start = start 42 | self.length = length 43 | self.state = state 44 | 45 | 46 | class Highlight: 47 | def __init__(self): 48 | pass 49 | 50 | def simple_tokenize(self, text): 51 | tokens = [] 52 | cur_token = "" 53 | string_char = None 54 | backslash = False 55 | offset = 0 56 | for i in xrange(0, len(text)): 57 | ch = text[i] 58 | if string_char: 59 | if backslash: 60 | cur_token += ch 61 | backslash = False 62 | elif ch == '\\': 63 | cur_token += ch 64 | backslash = True 65 | elif ch == string_char: 66 | cur_token += ch 67 | tokens.append((offset, cur_token)) 68 | cur_token = "" 69 | offset = i + 1 70 | string_char = None 71 | else: 72 | cur_token += ch 73 | elif (ch == '\'') or (ch == '"'): 74 | if len(cur_token) > 0: 75 | tokens.append((offset, cur_token)) 76 | cur_token = "" 77 | offset = i 78 | cur_token = ch 79 | string_char = ch 80 | elif (ch == ' ') or (ch == '\t'): 81 | if len(cur_token) > 0: 82 | tokens.append((offset, cur_token)) 83 | cur_token = "" 84 | offset = i + 1 85 | elif ((ch >= '0') and (ch <= '9')) or ((ch >= 'A') and (ch <= 'Z')) or ((ch >= 'a') and (ch <= 'z')) or (ch == '_'): 86 | cur_token += ch 87 | else: 88 | if len(cur_token) > 0: 89 | tokens.append((offset, cur_token)) 90 | cur_token = "" 91 | tokens.append((offset, ch)) 92 | offset = i + 1 93 | if len(cur_token) > 0: 94 | tokens.append((offset, cur_token)) 95 | return tokens 96 | 97 | def append_escaped_string_tokens(self, line, offset, text): 98 | cur_token = "" 99 | token_start = offset 100 | i = 0 101 | while i < len(text): 102 | if text[i] == '\\': 103 | if len(cur_token) > 0: 104 | line.tokens.append(HighlightToken(token_start, len(cur_token), 105 | HighlightState(HIGHLIGHT_STRING))) 106 | cur_token = "" 107 | if text[i:i+2] == "\\x": 108 | line.tokens.append(HighlightToken(offset + i, len(text[i:i+4]), 109 | HighlightState(HIGHLIGHT_ESCAPE))) 110 | i += len(text[i:i+4]) 111 | elif text[i:i+2] == "\\u": 112 | line.tokens.append(HighlightToken(offset + i, len(text[i:i+6]), 113 | HighlightState(HIGHLIGHT_ESCAPE))) 114 | i += len(text[i:i+6]) 115 | else: 116 | line.tokens.append(HighlightToken(offset + i, len(text[i:i+2]), 117 | HighlightState(HIGHLIGHT_ESCAPE))) 118 | i += len(text[i:i+2]) 119 | token_start = offset + i 120 | else: 121 | cur_token += text[i] 122 | i += 1 123 | if len(cur_token) > 0: 124 | line.tokens.append(HighlightToken(token_start, len(cur_token), HighlightState(HIGHLIGHT_STRING))) 125 | 126 | 127 | class TextLine: 128 | def __init__(self, offset, length, width, offset_map, newline_length): 129 | self.offset = offset 130 | self.length = length 131 | self.width = width 132 | self.offset_map = offset_map 133 | self.newline_length = newline_length 134 | self.highlight_state = HighlightState(HIGHLIGHT_NONE) 135 | self.tokens = [] 136 | 137 | def offset_to_col(self, offset): 138 | if len(self.offset_map) == 0: 139 | return offset 140 | for i in xrange(0, len(self.offset_map)): 141 | if self.offset_map[i][0] > offset: 142 | if i == 0: 143 | return offset 144 | return self.offset_map[i - 1][1] + (offset - self.offset_map[i - 1][0]) 145 | return self.offset_map[len(self.offset_map) - 1][1] + (offset - self.offset_map[len(self.offset_map) - 1][0]) 146 | 147 | def col_to_offset(self, col): 148 | if len(self.offset_map) == 0: 149 | if col > self.length: 150 | return self.length 151 | return col 152 | for i in xrange(0, len(self.offset_map)): 153 | if self.offset_map[i][1] > col: 154 | if i == 0: 155 | if col > self.length: 156 | return self.length 157 | if col >= self.offset_map[i][0]: 158 | return self.offset_map[i][0] - 1 159 | return col 160 | offset = self.offset_map[i - 1][0] + (col - self.offset_map[i - 1][1]) 161 | if offset >= self.offset_map[i][0]: 162 | return self.offset_map[i][0] - 1 163 | return offset 164 | offset = self.offset_map[len(self.offset_map) - 1][0] + (col - self.offset_map[len(self.offset_map) - 1][1]) 165 | if offset > self.length: 166 | return self.length 167 | return offset 168 | 169 | def read(self, data): 170 | return data.read(data.start() + self.offset, self.length) 171 | 172 | def recompute(self, data, tab_width): 173 | contents = self.read(data) 174 | width = 0 175 | self.offset_map = [] 176 | for i in xrange(0, len(contents)): 177 | if contents[i] == '\t': 178 | width += tab_width - (width % tab_width) 179 | self.offset_map.append((i + 1, width)) 180 | else: 181 | width += 1 182 | self.width = width 183 | 184 | def leading_tab_width(self, data, tab_width): 185 | contents = self.read(data) 186 | width = 0 187 | for i in xrange(0, len(contents)): 188 | if contents[i] == '\t': 189 | width += tab_width 190 | else: 191 | break 192 | return width 193 | 194 | def leading_whitespace_width(self, data, tab_width): 195 | contents = self.read(data) 196 | width = 0 197 | for i in xrange(0, len(contents)): 198 | if contents[i] == '\t': 199 | width += tab_width - (width % tab_width) 200 | elif contents[i] == ' ': 201 | width += 1 202 | else: 203 | break 204 | return width 205 | 206 | 207 | class TextLines: 208 | def __init__(self, data, tab_width, highlight = None): 209 | self.data = data 210 | self.tab_width = tab_width 211 | self.highlight = highlight 212 | self.callbacks = [] 213 | 214 | self.data.add_callback(self) 215 | 216 | newline_count = {"\r": 0, "\n": 0, "\r\n": 0, "\n\r": 0} 217 | 218 | offset = 0 219 | width = 0 220 | line_start = offset 221 | offset_map = [] 222 | self.lines = [] 223 | 224 | # Break the file into lines 225 | while offset < len(self.data): 226 | ch = self.data.read(offset + self.data.start(), 1) 227 | if len(ch) == 0: 228 | break 229 | offset += 1 230 | 231 | if ch == '\r': 232 | if self.data.read(offset, 1) == '\n': 233 | offset += 1 234 | newline_count['\r\n'] += 1 235 | self.lines.append(TextLine(line_start, (offset - 2) - line_start, width, offset_map, 2)) 236 | line_start = offset 237 | width = 0 238 | offset_map = [] 239 | else: 240 | newline_count['\r'] += 1 241 | self.lines.append(TextLine(line_start, (offset - 1) - line_start, width, offset_map, 1)) 242 | line_start = offset 243 | width = 0 244 | offset_map = [] 245 | elif ch == '\n': 246 | if self.data.read(offset, 1) == '\r': 247 | offset += 1 248 | newline_count['\n\r'] += 1 249 | self.lines.append(TextLine(line_start, (offset - 2) - line_start, width, offset_map, 2)) 250 | line_start = offset 251 | width = 0 252 | offset_map = [] 253 | else: 254 | newline_count['\n'] += 1 255 | self.lines.append(TextLine(line_start, (offset - 1) - line_start, width, offset_map, 1)) 256 | line_start = offset 257 | width = 0 258 | offset_map = [] 259 | elif ch == '\t': 260 | width += self.tab_width - (width % self.tab_width) 261 | offset_map.append((offset - line_start, width)) 262 | else: 263 | width += 1 264 | 265 | if (line_start != offset) or (len(self.lines) == 0): 266 | self.lines.append(TextLine(line_start, offset - line_start, width, offset_map, 0)) 267 | 268 | # If there is a trailing newline, add the final blank line to ensure consistent editing 269 | if (len(self.lines) > 0) and (self.lines[len(self.lines) - 1].newline_length > 0): 270 | self.lines.append(TextLine(offset, 0, 0, [], 0)) 271 | 272 | # Determine what type of newline should be used for this file 273 | self.default_newline = '\n' 274 | default_newline_count = newline_count['\n'] 275 | for newline in newline_count.keys(): 276 | if newline_count[newline] > default_newline_count: 277 | self.default_newline = newline 278 | default_newline_count = newline_count[newline] 279 | 280 | # Compute maximum line width 281 | self.max_line_width = 0 282 | self.update_max_width() 283 | 284 | if self.highlight: 285 | # Run text through the syntax highlighter 286 | if hasattr(self.highlight, "default_state"): 287 | state = self.highlight.default_state 288 | else: 289 | state = HighlightState(HIGHLIGHT_NONE) 290 | for line in self.lines: 291 | line.highlight_state = state 292 | line.tokens = [] 293 | text = self.data.read(self.data.start() + line.offset, line.length) 294 | state = self.highlight.update_line(line, text) 295 | 296 | def close(self): 297 | self.data.remove_callback(self) 298 | 299 | def set_highlight(self, highlight): 300 | self.highlight = highlight 301 | 302 | # Reapply highlighting to entire file 303 | if self.highlight: 304 | # Run text through the syntax highlighter 305 | if hasattr(self.highlight, "default_state"): 306 | state = self.highlight.default_state 307 | else: 308 | state = HighlightState(HIGHLIGHT_NONE) 309 | for line in self.lines: 310 | line.highlight_state = state 311 | line.tokens = [] 312 | text = self.data.read(self.data.start() + line.offset, line.length) 313 | state = self.highlight.update_line(line, text) 314 | else: 315 | for line in self.lines: 316 | line.highlight_state = None 317 | line.tokens = [] 318 | 319 | # Notify callbacks that everything has updated 320 | for cb in self.callbacks: 321 | if hasattr(cb, "notify_update_lines"): 322 | cb.notify_update_lines(self, 0, len(self.lines)) 323 | 324 | def offset_to_line(self, offset): 325 | # Binary search for the correct line for speed 326 | min_line = 0 327 | max_line = len(self.lines) 328 | while min_line < max_line: 329 | i = int((min_line + max_line) / 2) 330 | if i < min_line: 331 | i = min_line 332 | if i >= max_line: 333 | i = max_line - 1 334 | 335 | if (offset >= self.lines[i].offset) and (offset < (self.lines[i].offset + self.lines[i].length + 336 | self.lines[i].newline_length)): 337 | return i 338 | 339 | if offset < self.lines[i].offset: 340 | max_line = i 341 | else: 342 | min_line = i + 1 343 | 344 | # If line not found, was past the end of the buffer 345 | return len(self.lines) - 1 346 | 347 | def rebase_lines(self, first, diff): 348 | for i in xrange(first, len(self.lines)): 349 | self.lines[i].offset += diff 350 | 351 | def rebase_lines_absolute(self, first, offset): 352 | self.rebase_lines(first, offset - self.lines[first].offset) 353 | 354 | def update_highlight(self, line, count): 355 | if self.highlight: 356 | # Run updated lines through the highlighter, and continue updating until the state is consistent 357 | # with what was already there 358 | final_count = 0 359 | state = self.lines[line].highlight_state 360 | while line < len(self.lines): 361 | self.lines[line].highlight_state = state 362 | self.lines[line].tokens = [] 363 | text = self.data.read(self.data.start() + self.lines[line].offset, self.lines[line].length) 364 | state = self.highlight.update_line(self.lines[line], text) 365 | 366 | line += 1 367 | final_count += 1 368 | 369 | # If we have processed the required lines, break out when highlighting is up to date 370 | if (line < len(self.lines)) and (state == self.lines[line].highlight_state) and (final_count >= count): 371 | # Starting state hasn't changed for this line, so the rest of the data won't change either 372 | break 373 | else: 374 | # No highlighting, only update the range specified 375 | final_count = count 376 | return final_count 377 | 378 | def update_max_width(self): 379 | max_line_width = 0 380 | for line in self.lines: 381 | if line.width > max_line_width: 382 | max_line_width = line.width 383 | 384 | changed = self.max_line_width != max_line_width 385 | self.max_line_width = max_line_width 386 | 387 | if changed: 388 | for cb in self.callbacks: 389 | if hasattr(cb, "notify_max_width_changed"): 390 | cb.notify_max_width_changed(self, max_line_width) 391 | 392 | def add_callback(self, cb): 393 | self.callbacks.append(cb) 394 | 395 | def remove_callback(self, cb): 396 | self.callbacks.remove(cb) 397 | 398 | def handle_delete(self, offset, size): 399 | # Figure out how lines are affected by the delete, process lines until deleted range is exhausted 400 | line = self.offset_to_line(offset) 401 | while size > 0: 402 | x = offset - self.lines[line].offset 403 | if size > (self.lines[line].length - x): 404 | line_remaining = (self.lines[line].length + self.lines[line].newline_length - x) 405 | if size >= line_remaining: 406 | # Removing the entire remaining part of the line, combine with next line 407 | to_remove = line_remaining 408 | if line + 1 < len(self.lines): 409 | self.lines[line].length = x + self.lines[line + 1].length 410 | self.lines[line].newline_length = self.lines[line + 1].newline_length 411 | del(self.lines[line + 1]) 412 | else: 413 | self.lines[line].length = x 414 | self.lines[line].newline_length = 0 415 | else: 416 | # Removing part of the newline 417 | self.newline_length = line_remaining - size 418 | to_remove = size 419 | self.rebase_lines(line + 1, -to_remove) 420 | size -= to_remove 421 | else: 422 | # Removing part of the line 423 | self.lines[line].length -= size 424 | self.rebase_lines(line + 1, -size) 425 | size = 0 426 | return line 427 | 428 | def handle_insert(self, offset, contents): 429 | # Figure out how lines are affected by the insertion 430 | line = self.offset_to_line(offset) 431 | first_line = line 432 | i = 0 433 | while i < len(contents): 434 | if (contents[i:i+2] == '\r\n') or (contents[i:i+2] == '\n\r'): 435 | # Inserting two character newline, split current line into two 436 | x = (offset + i) - self.lines[line].offset 437 | remaining = self.lines[line].length - x 438 | next_line = TextLine(offset + i + 2, remaining, 0, [], self.lines[line].newline_length) 439 | self.lines[line].length = x 440 | self.lines[line].newline_length = 2 441 | line += 1 442 | self.lines.insert(line, next_line) 443 | i += 2 444 | elif (contents[i] == '\r') or (contents[i] == '\n'): 445 | # Inserting one character newline, split current line into two 446 | x = (offset + i) - self.lines[line].offset 447 | remaining = self.lines[line].length - x 448 | next_line = TextLine(offset + i + 1, remaining, 0, [], self.lines[line].newline_length) 449 | self.lines[line].length = x 450 | self.lines[line].newline_length = 1 451 | line += 1 452 | self.lines.insert(line, next_line) 453 | i += 1 454 | else: 455 | # Normal character, make current line longer 456 | self.lines[line].length += 1 457 | i += 1 458 | 459 | # Rebase remaining lines to account for insertion 460 | if (line + 1) < len(self.lines): 461 | self.rebase_lines_absolute(line + 1, self.lines[line].offset + self.lines[line].length + 462 | self.lines[line].newline_length) 463 | 464 | return first_line, line 465 | 466 | def notify_data_write(self, data, offset, contents): 467 | # Handle a write by simulating a delete then an insert 468 | old_line_count = len(self.lines) 469 | self.handle_delete(offset, len(contents)) 470 | first_line, line = self.handle_insert(offset, contents) 471 | 472 | # Update width and offsets for affected lines 473 | lines_affected = (len(self.lines) - old_line_count) + 1 474 | for i in xrange(first_line, first_line + lines_affected): 475 | self.lines[i].recompute(self.data, self.tab_width) 476 | 477 | # Notify callbacks about any inserted or removed lines 478 | if len(self.lines) > old_line_count: 479 | for cb in self.callbacks: 480 | if hasattr(cb, "notify_insert_lines"): 481 | cb.notify_insert_lines(self, first_line, len(self.lines) - old_line_count) 482 | elif len(self.lines) < old_line_count: 483 | for cb in self.callbacks: 484 | if hasattr(cb, "notify_remove_lines"): 485 | cb.notify_remove_lines(self, line, old_line_count - len(self.lines)) 486 | 487 | # Update syntax highlighting and notify callbacks about updates 488 | count = self.update_highlight(first_line, lines_affected) 489 | 490 | for cb in self.callbacks: 491 | if hasattr(cb, "notify_update_lines"): 492 | cb.notify_update_lines(self, first_line, count) 493 | 494 | self.update_max_width() 495 | 496 | def notify_data_insert(self, data, offset, contents): 497 | old_line_count = len(self.lines) 498 | first_line, line = self.handle_insert(offset, contents) 499 | 500 | # Update width and offsets for affected lines 501 | lines_affected = (len(self.lines) - old_line_count) + 1 502 | for i in xrange(first_line, first_line + lines_affected): 503 | self.lines[i].recompute(self.data, self.tab_width) 504 | 505 | # Notify callbacks about any inserted lines 506 | if len(self.lines) != old_line_count: 507 | for cb in self.callbacks: 508 | if hasattr(cb, "notify_insert_lines"): 509 | cb.notify_insert_lines(self, first_line, len(self.lines) - old_line_count) 510 | 511 | # Update syntax highlighting and notify callbacks about updates 512 | count = self.update_highlight(first_line, lines_affected) 513 | 514 | for cb in self.callbacks: 515 | if hasattr(cb, "notify_update_lines"): 516 | cb.notify_update_lines(self, first_line, count) 517 | 518 | self.update_max_width() 519 | 520 | def notify_data_remove(self, data, offset, size): 521 | old_line_count = len(self.lines) 522 | line = self.handle_delete(offset, size) 523 | 524 | # Only one line's width (the current one) can be affected, which may have involved combining lines above 525 | self.lines[line].recompute(self.data, self.tab_width) 526 | 527 | # Notify callbacks about any removed lines 528 | if len(self.lines) != old_line_count: 529 | for cb in self.callbacks: 530 | if hasattr(cb, "notify_remove_lines"): 531 | cb.notify_remove_lines(self, line, old_line_count - len(self.lines)) 532 | 533 | # Update syntax highlighting and notify callbacks about updates 534 | count = self.update_highlight(line, 1) 535 | 536 | for cb in self.callbacks: 537 | if hasattr(cb, "notify_update_lines"): 538 | cb.notify_update_lines(self, line, count) 539 | 540 | self.update_max_width() 541 | 542 | -------------------------------------------------------------------------------- /Threads.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from PySide.QtCore import * 17 | from PySide.QtGui import * 18 | import thread 19 | import threading 20 | 21 | gui_thread = None 22 | main_window = None 23 | 24 | class RunCodeEvent(QEvent): 25 | def __init__(self, code): 26 | super(RunCodeEvent, self).__init__(QEvent.User) 27 | self.code = code 28 | self.event = threading.Event() 29 | self.result = None 30 | self.exception = None 31 | 32 | # Proxy to ensure that GUI calls run on the GUI thread 33 | # Code derived from: http://code.activestate.com/recipes/496741-object-proxying/ 34 | class GuiObjectProxy(object): 35 | __slots__ = ["_obj", "__weakref__"] 36 | def __init__(self, obj): 37 | object.__setattr__(self, "_obj", obj) 38 | 39 | # 40 | # proxying (special cases) 41 | # 42 | def __getattribute__(self, name): 43 | result = run_on_gui_thread(lambda: getattr(object.__getattribute__(self, "_obj"), name)) 44 | if result is None: 45 | return result 46 | if type(result) in [int, float, str, bool]: 47 | return result 48 | return GuiObjectProxy(result) 49 | def __delattr__(self, name): 50 | run_on_gui_thread(lambda: delattr(object.__getattribute__(self, "_obj"), name)) 51 | def __setattr__(self, name, value): 52 | run_on_gui_thread(lambda: setattr(object.__getattribute__(self, "_obj"), name, value)) 53 | 54 | def __nonzero__(self): 55 | return run_on_gui_thread(lambda: bool(object.__getattribute__(self, "_obj"))) 56 | def __str__(self): 57 | return run_on_gui_thread(lambda: str(object.__getattribute__(self, "_obj"))) 58 | def __repr__(self): 59 | return run_on_gui_thread(lambda: repr(object.__getattribute__(self, "_obj"))) 60 | 61 | # 62 | # factories 63 | # 64 | _special_names = [ 65 | '__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__', 66 | '__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__', 67 | '__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__', 68 | '__getslice__', '__gt__', '__hash__', '__hex__', '__iadd__', '__iand__', 69 | '__idiv__', '__idivmod__', '__ifloordiv__', '__ilshift__', '__imod__', 70 | '__imul__', '__int__', '__invert__', '__ior__', '__ipow__', '__irshift__', 71 | '__isub__', '__iter__', '__itruediv__', '__ixor__', '__le__', '__len__', 72 | '__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', 73 | '__neg__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', 74 | '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', 75 | '__repr__', '__reversed__', '__rfloorfiv__', '__rlshift__', '__rmod__', 76 | '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', 77 | '__rtruediv__', '__rxor__', '__setitem__', '__setslice__', '__sub__', 78 | '__truediv__', '__xor__', 'next', 79 | ] 80 | 81 | @classmethod 82 | def _create_class_proxy(cls, theclass): 83 | """creates a proxy for the given class""" 84 | 85 | def make_method(name): 86 | def method(self, *args, **kw): 87 | return run_on_gui_thread(lambda: getattr(object.__getattribute__(self, "_obj"), name)(*args, **kw)) 88 | return method 89 | 90 | namespace = {} 91 | for name in cls._special_names: 92 | if hasattr(theclass, name): 93 | namespace[name] = make_method(name) 94 | return type("%s(%s)" % (cls.__name__, theclass.__name__), (cls,), namespace) 95 | 96 | def __new__(cls, obj, *args, **kwargs): 97 | """ 98 | creates an proxy instance referencing `obj`. (obj, *args, **kwargs) are 99 | passed to this class' __init__, so deriving classes can define an 100 | __init__ method of their own. 101 | note: _class_proxy_cache is unique per deriving class (each deriving 102 | class must hold its own cache) 103 | """ 104 | try: 105 | cache = cls.__dict__["_class_proxy_cache"] 106 | except KeyError: 107 | cls._class_proxy_cache = cache = {} 108 | try: 109 | theclass = cache[obj.__class__] 110 | except KeyError: 111 | cache[obj.__class__] = theclass = cls._create_class_proxy(obj.__class__) 112 | ins = object.__new__(theclass) 113 | theclass.__init__(ins, obj, *args, **kwargs) 114 | return ins 115 | 116 | def is_gui_thread(): 117 | global gui_thread 118 | return thread.get_ident() == gui_thread 119 | 120 | def run_on_gui_thread(code): 121 | global main_window 122 | if is_gui_thread(): 123 | return code() 124 | event = RunCodeEvent(code) 125 | QCoreApplication.postEvent(main_window, event) 126 | event.event.wait() 127 | if event.exception is not None: 128 | raise event.exception[0], event.exception[1], event.exception[2] 129 | return event.result 130 | 131 | def create_file(data): 132 | global main_window 133 | main_window.create_tab_from_data(data) 134 | 135 | -------------------------------------------------------------------------------- /Transform.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | from PySide.QtCore import * 17 | from PySide.QtGui import * 18 | from Crypto.Cipher import AES 19 | from Crypto.Cipher import Blowfish 20 | from Crypto.Cipher import CAST 21 | from Crypto.Cipher import DES 22 | from Crypto.Cipher import DES3 23 | from Crypto.Cipher import ARC2 24 | from Crypto.Cipher import ARC4 25 | import HexEditor 26 | import View 27 | import BinaryData 28 | 29 | 30 | class KeyDialog(QDialog): 31 | def __init__(self, parent, iv = False): 32 | super(KeyDialog, self).__init__(parent) 33 | self.key = BinaryData.BinaryData("") 34 | if iv: 35 | self.iv = BinaryData.BinaryData("") 36 | 37 | self.setWindowTitle("Input Key") 38 | layout = QVBoxLayout() 39 | 40 | layout.addWidget(QLabel("Key:")) 41 | self.edit = View.ViewFrame(HexEditor.HexEditor, self.key, "", [HexEditor.HexEditor]) 42 | if iv: 43 | self.edit.setMinimumSize(600, 32) 44 | else: 45 | self.edit.setMinimumSize(600, 64) 46 | layout.addWidget(self.edit, 1) 47 | 48 | if iv: 49 | layout.addWidget(QLabel("Initialization vector:")) 50 | self.iv_edit = View.ViewFrame(HexEditor.HexEditor, self.iv, "", [HexEditor.HexEditor]) 51 | self.iv_edit.setMinimumSize(600, 32) 52 | layout.addWidget(self.iv_edit, 1) 53 | 54 | self.closeButton = QPushButton("Close") 55 | self.closeButton.clicked.connect(self.close) 56 | self.closeButton.setAutoDefault(False) 57 | 58 | self.assembleButton = QPushButton("OK") 59 | self.assembleButton.clicked.connect(self.accept) 60 | self.assembleButton.setAutoDefault(True) 61 | 62 | buttonLayout = QHBoxLayout() 63 | buttonLayout.setContentsMargins(0, 0, 0, 0) 64 | buttonLayout.addStretch(1) 65 | buttonLayout.addWidget(self.assembleButton) 66 | buttonLayout.addWidget(self.closeButton) 67 | layout.addLayout(buttonLayout) 68 | self.setLayout(layout) 69 | 70 | 71 | def xor_transform(data, key): 72 | if len(key) == 0: 73 | return data 74 | 75 | result = "" 76 | for i in xrange(0, len(data)): 77 | result += chr(ord(data[i]) ^ ord(key[i % len(key)])) 78 | return result 79 | 80 | def aes_encrypt_transform(data, key, mode, iv): 81 | aes = AES.new(key, mode, iv) 82 | return aes.encrypt(data) 83 | 84 | def aes_decrypt_transform(data, key, mode, iv): 85 | aes = AES.new(key, mode, iv) 86 | return aes.decrypt(data) 87 | 88 | def blowfish_encrypt_transform(data, key, mode, iv): 89 | blowfish = Blowfish.new(key, mode, iv) 90 | return blowfish.encrypt(data) 91 | 92 | def blowfish_decrypt_transform(data, key, mode, iv): 93 | blowfish = Blowfish.new(key, mode, iv) 94 | return blowfish.decrypt(data) 95 | 96 | def cast_encrypt_transform(data, key, mode, iv): 97 | cast = CAST.new(key, mode, iv) 98 | return cast.encrypt(data) 99 | 100 | def cast_decrypt_transform(data, key, mode, iv): 101 | cast = CAST.new(key, mode, iv) 102 | return cast.decrypt(data) 103 | 104 | def des_encrypt_transform(data, key, mode, iv): 105 | des = DES.new(key, mode, iv) 106 | return des.encrypt(data) 107 | 108 | def des_decrypt_transform(data, key, mode, iv): 109 | des = DES.new(key, mode, iv) 110 | return des.decrypt(data) 111 | 112 | def des3_encrypt_transform(data, key, mode, iv): 113 | des = DES3.new(key, mode, iv) 114 | return des.encrypt(data) 115 | 116 | def des3_decrypt_transform(data, key, mode, iv): 117 | des = DES3.new(key, mode, iv) 118 | return des.decrypt(data) 119 | 120 | def rc2_encrypt_transform(data, key, mode, iv): 121 | arc2 = ARC2.new(key, mode, iv) 122 | return arc2.encrypt(data) 123 | 124 | def rc2_decrypt_transform(data, key, mode, iv): 125 | arc2 = ARC2.new(key, mode, iv) 126 | return arc2.decrypt(data) 127 | 128 | def rc4_transform(data, key): 129 | arc4 = ARC4.new(key) 130 | return arc4.encrypt(data) 131 | 132 | 133 | def populate_transform_menu(menu, obj, action_table): 134 | aes_menu = menu.addMenu("AES") 135 | aes_ecb_menu = aes_menu.addMenu("ECB mode") 136 | aes_cbc_menu = aes_menu.addMenu("CBC mode") 137 | action_table[aes_ecb_menu.addAction("Encrypt")] = lambda: obj.transform_with_key(lambda data, key: aes_encrypt_transform(data, key, AES.MODE_ECB, "")) 138 | action_table[aes_ecb_menu.addAction("Decrypt")] = lambda: obj.transform_with_key(lambda data, key: aes_decrypt_transform(data, key, AES.MODE_ECB, "")) 139 | action_table[aes_cbc_menu.addAction("Encrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: aes_encrypt_transform(data, key, AES.MODE_CBC, iv)) 140 | action_table[aes_cbc_menu.addAction("Decrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: aes_decrypt_transform(data, key, AES.MODE_CBC, iv)) 141 | 142 | blowfish_menu = menu.addMenu("Blowfish") 143 | blowfish_ecb_menu = blowfish_menu.addMenu("ECB mode") 144 | blowfish_cbc_menu = blowfish_menu.addMenu("CBC mode") 145 | action_table[blowfish_ecb_menu.addAction("Encrypt")] = lambda: obj.transform_with_key(lambda data, key: blowfish_encrypt_transform(data, key, Blowfish.MODE_ECB, "")) 146 | action_table[blowfish_ecb_menu.addAction("Decrypt")] = lambda: obj.transform_with_key(lambda data, key: blowfish_decrypt_transform(data, key, Blowfish.MODE_ECB, "")) 147 | action_table[blowfish_cbc_menu.addAction("Encrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: blowfish_encrypt_transform(data, key, Blowfish.MODE_CBC, iv)) 148 | action_table[blowfish_cbc_menu.addAction("Decrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: blowfish_decrypt_transform(data, key, Blowfish.MODE_CBC, iv)) 149 | 150 | cast_menu = menu.addMenu("CAST") 151 | cast_ecb_menu = cast_menu.addMenu("ECB mode") 152 | cast_cbc_menu = cast_menu.addMenu("CBC mode") 153 | action_table[cast_ecb_menu.addAction("Encrypt")] = lambda: obj.transform_with_key(lambda data, key: cast_encrypt_transform(data, key, CAST.MODE_ECB, "")) 154 | action_table[cast_ecb_menu.addAction("Decrypt")] = lambda: obj.transform_with_key(lambda data, key: cast_decrypt_transform(data, key, CAST.MODE_ECB, "")) 155 | action_table[cast_cbc_menu.addAction("Encrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: cast_encrypt_transform(data, key, CAST.MODE_CBC, iv)) 156 | action_table[cast_cbc_menu.addAction("Decrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: cast_decrypt_transform(data, key, CAST.MODE_CBC, iv)) 157 | 158 | des_menu = menu.addMenu("DES") 159 | des_ecb_menu = des_menu.addMenu("ECB mode") 160 | des_cbc_menu = des_menu.addMenu("CBC mode") 161 | action_table[des_ecb_menu.addAction("Encrypt")] = lambda: obj.transform_with_key(lambda data, key: des_encrypt_transform(data, key, DES.MODE_ECB, "")) 162 | action_table[des_ecb_menu.addAction("Decrypt")] = lambda: obj.transform_with_key(lambda data, key: des_decrypt_transform(data, key, DES.MODE_ECB, "")) 163 | action_table[des_cbc_menu.addAction("Encrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: des_encrypt_transform(data, key, DES.MODE_CBC, iv)) 164 | action_table[des_cbc_menu.addAction("Decrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: des_decrypt_transform(data, key, DES.MODE_CBC, iv)) 165 | 166 | des3_menu = menu.addMenu("Triple DES") 167 | des3_ecb_menu = des3_menu.addMenu("ECB mode") 168 | des3_cbc_menu = des3_menu.addMenu("CBC mode") 169 | action_table[des3_ecb_menu.addAction("Encrypt")] = lambda: obj.transform_with_key(lambda data, key: des3_encrypt_transform(data, key, DES3.MODE_ECB, "")) 170 | action_table[des3_ecb_menu.addAction("Decrypt")] = lambda: obj.transform_with_key(lambda data, key: des3_decrypt_transform(data, key, DES3.MODE_ECB, "")) 171 | action_table[des3_cbc_menu.addAction("Encrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: des3_encrypt_transform(data, key, DES3.MODE_CBC, iv)) 172 | action_table[des3_cbc_menu.addAction("Decrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: des3_decrypt_transform(data, key, DES3.MODE_CBC, iv)) 173 | 174 | rc2_menu = menu.addMenu("RC2") 175 | rc2_ecb_menu = rc2_menu.addMenu("ECB mode") 176 | rc2_cbc_menu = rc2_menu.addMenu("CBC mode") 177 | action_table[rc2_ecb_menu.addAction("Encrypt")] = lambda: obj.transform_with_key(lambda data, key: rc2_encrypt_transform(data, key, ARC2.MODE_ECB, "")) 178 | action_table[rc2_ecb_menu.addAction("Decrypt")] = lambda: obj.transform_with_key(lambda data, key: rc2_decrypt_transform(data, key, ARC2.MODE_ECB, "")) 179 | action_table[rc2_cbc_menu.addAction("Encrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: rc2_encrypt_transform(data, key, ARC2.MODE_CBC, iv)) 180 | action_table[rc2_cbc_menu.addAction("Decrypt")] = lambda: obj.transform_with_key_and_iv(lambda data, key, iv: rc2_decrypt_transform(data, key, ARC2.MODE_CBC, iv)) 181 | 182 | action_table[menu.addAction("RC4")] = lambda: obj.transform_with_key(lambda data, key: rc4_transform(data, key)) 183 | action_table[menu.addAction("XOR")] = lambda: obj.transform_with_key(lambda data, key: xor_transform(data, key)) 184 | 185 | -------------------------------------------------------------------------------- /Util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011-2015 Rusty Wagner 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | import struct 17 | from PySide.QtCore import * 18 | from PySide.QtGui import * 19 | from Crypto.Hash import MD2 20 | from Crypto.Hash import MD4 21 | from Crypto.Hash import MD5 22 | from Crypto.Hash import SHA 23 | from Crypto.Hash import SHA256 24 | from Crypto.Hash import HMAC 25 | import Transform 26 | 27 | 28 | def hex_dump_encode(data): 29 | result = "" 30 | for i in range(0, len(data), 16): 31 | result += "%.8x:" % i 32 | hex = "" 33 | ascii = "" 34 | for j in range(0, 16): 35 | if (i + j) >= len(data): 36 | hex += " " 37 | else: 38 | hex += " %.2x" % ord(data[i + j]) 39 | if (data[i + j] < ' ') or (data[i + j] > '~'): 40 | ascii += "." 41 | else: 42 | ascii += data[i + j] 43 | result += hex + " " + ascii + "\n" 44 | return result 45 | 46 | def hex_dump_decode(data): 47 | result = "" 48 | lines = data.split("\n") 49 | for line in lines: 50 | # Hex dump lines follow the following format: 51 | # * An address, followed by any number of spaces 52 | # * The hex dump itself, 16 bytes per line 53 | # * Optionally two or more spaces, followed by the ASCII dump 54 | line.strip(" \t") 55 | if line.find(' ') == -1: 56 | continue 57 | hex = line[line.find(' '):].strip(" \t") 58 | if hex.find(" ") != -1: 59 | hex = hex[0:hex.find(" ")] 60 | hex = hex.replace(" ", "") 61 | hex = hex[0:32] 62 | result += hex.decode("hex") 63 | return result 64 | 65 | def encode_utf16_string(data, char_escape): 66 | if len(data) % 2: 67 | raise ValueError, "Odd number of bytes" 68 | result = "" 69 | for i in range(0, len(data), 2): 70 | value = struct.unpack("= ' ') and (value <= '~'): 72 | result += chr(value) 73 | else: 74 | result += char_escape + ("%.4x" % value) 75 | return result 76 | 77 | def encode_url(data): 78 | result = "" 79 | for i in range(0, len(data)): 80 | if data[i] in ['-', '_', '.', '~']: 81 | result += data[i] 82 | elif (data[i] >= '0') and (data[i] <= '9'): 83 | result += data[i] 84 | elif (data[i] >= 'a') and (data[i] <= 'z'): 85 | result += data[i] 86 | elif (data[i] >= 'A') and (data[i] <= 'Z'): 87 | result += data[i] 88 | else: 89 | result += "%%%.2x" % ord(data[i]) 90 | return result 91 | 92 | def decode_url(data): 93 | result = "" 94 | i = 0 95 | while i < len(data): 96 | if data[i] == '%': 97 | if data[i + 1] == 'u': 98 | result += unichr(int(data[i+2:i+6], 16)).encode("utf8") 99 | i += 6 100 | else: 101 | result += chr(int(data[i+1:i+3], 16)) 102 | i += 3 103 | else: 104 | result += data[i] 105 | i += 1 106 | return result 107 | 108 | def encode_c_array(data, element_size, element_struct, type_name, postfix): 109 | if len(data) % element_size: 110 | raise ValueError, "Data length is not a multiple of the element size" 111 | 112 | fmt = "0x%%.%dx%s" % (element_size * 2, postfix) 113 | result = "{\n" 114 | for i in range(0, len(data), 16): 115 | line = "" 116 | for j in range(0, 16, element_size): 117 | if (i + j) >= len(data): 118 | break 119 | if j > 0: 120 | line += ", " 121 | value = struct.unpack(element_struct, data[i+j:i+j+element_size])[0] 122 | line += fmt % value 123 | if (i + 16) < len(data): 124 | line += "," 125 | result += "\t" + line + "\n" 126 | return type_name + (" data[%d] = \n" % (len(data) / element_size)) + result + "};\n" 127 | 128 | def decode_int_list(data, signed, unsigned): 129 | result = "" 130 | list = data.split(",") 131 | for i in list: 132 | i = i.strip(" \t\r\n") 133 | value = int(i, 0) 134 | if value < 0: 135 | result += struct.pack(signed, value) 136 | else: 137 | result += struct.pack(unsigned, value) 138 | return result 139 | 140 | class CancelException(Exception): 141 | pass 142 | 143 | def request_key(obj): 144 | dlg = Transform.KeyDialog(obj) 145 | if dlg.exec_() == QDialog.Rejected: 146 | raise CancelException 147 | return dlg.key[:] 148 | 149 | def populate_copy_as_menu(menu, obj, action_table): 150 | string_menu = menu.addMenu("Escaped string") 151 | action_table[string_menu.addAction("ASCII")] = lambda : obj.copy_as(lambda data : data.encode("string_escape").replace("\"", "\\\""), False) 152 | action_table[string_menu.addAction("UTF-8 URL")] = lambda : obj.copy_as(encode_url, False) 153 | action_table[string_menu.addAction("UTF-8 IDNA")] = lambda : obj.copy_as(lambda data : data.decode("utf8").encode("idna"), False) 154 | action_table[string_menu.addAction("UTF-16 (\\u)")] = lambda : obj.copy_as(lambda data : encode_utf16_string(data, "\\u"), False) 155 | action_table[string_menu.addAction("UTF-16 (%u)")] = lambda : obj.copy_as(lambda data : encode_utf16_string(data, "%u"), False) 156 | action_table[string_menu.addAction("UTF-16 URL")] = lambda : obj.copy_as(lambda data : encode_url(data.decode("utf16").encode("utf8")), False) 157 | action_table[string_menu.addAction("UTF-16 IDNA")] = lambda : obj.copy_as(lambda data : data.decode("utf16").encode("idna"), False) 158 | unicode_menu = menu.addMenu("Unicode") 159 | action_table[unicode_menu.addAction("UTF-16")] = lambda : obj.copy_as(lambda data : data.decode("utf16"), False) 160 | action_table[unicode_menu.addAction("UTF-32")] = lambda : obj.copy_as(lambda data : data.decode("utf32"), False) 161 | menu.addSeparator() 162 | action_table[menu.addAction("Hex dump")] = lambda : obj.copy_as(hex_dump_encode, False) 163 | action_table[menu.addAction("Raw hex")] = lambda : obj.copy_as(lambda data : data.encode("hex"), False) 164 | action_table[menu.addAction("Base64")] = lambda : obj.copy_as(lambda data : data.encode("base64"), False) 165 | action_table[menu.addAction("UUEncode")] = lambda : obj.copy_as(lambda data : data.encode("uu_codec"), False) 166 | compress_menu = menu.addMenu("Compressed") 167 | action_table[compress_menu.addAction("zlib")] = lambda : obj.copy_as(lambda data : data.encode("zlib"), True) 168 | action_table[compress_menu.addAction("bz2")] = lambda : obj.copy_as(lambda data : data.encode("bz2"), True) 169 | menu.addSeparator() 170 | array_menu = menu.addMenu("C array") 171 | action_table[array_menu.addAction("8-bit elements")] = lambda : obj.copy_as(lambda data : encode_c_array(data, 1, "B", "unsigned char", ""), False) 172 | action_table[array_menu.addAction("16-bit elements")] = lambda : obj.copy_as(lambda data : encode_c_array(data, 2, ". 15 | 16 | import os 17 | from PySide.QtCore import * 18 | from PySide.QtGui import * 19 | from PythonConsole import * 20 | 21 | ViewTypes = [] 22 | 23 | from RunWindow import * 24 | 25 | 26 | class HistoryEntry: 27 | def __init__(self, type, data): 28 | self.type = type 29 | self.data = data 30 | 31 | class ViewFrame(QWidget): 32 | statusUpdated = Signal(QWidget) 33 | viewChanged = Signal(QWidget) 34 | closeRequest = Signal(QWidget) 35 | 36 | def __init__(self, type, data, filename, viewList): 37 | super(ViewFrame, self).__init__(None) 38 | 39 | self.navigation = {} 40 | self.back = [] 41 | self.forward = [] 42 | self.undo_buffer = [] 43 | self.redo_buffer = [] 44 | self.undo_location = None 45 | self.python_console = None 46 | self.terminal = None 47 | self.allow_title_change = False 48 | 49 | self.splitter = QSplitter(Qt.Vertical, self) 50 | self.main_area = QWidget() 51 | size_policy = self.main_area.sizePolicy() 52 | size_policy.setVerticalStretch(3) 53 | self.main_area.setSizePolicy(size_policy) 54 | self.splitter.addWidget(self.main_area) 55 | parent_layout = QVBoxLayout() 56 | parent_layout.setContentsMargins(0, 0, 0, 0) 57 | parent_layout.setSpacing(0) 58 | parent_layout.addWidget(self.splitter) 59 | self.setLayout(parent_layout) 60 | 61 | self.data = data 62 | self.exe = data 63 | self.view = type(data, filename, self, self.main_area) 64 | self.filename = filename 65 | self.new_filename = False 66 | self.custom_tab_name = None 67 | self.available = viewList 68 | self.splittable = True 69 | self.cache = {type : self.view} 70 | self.status = {self.view : ""} 71 | if hasattr(self.view, "statusUpdated"): 72 | self.status[self.view] = self.view.status 73 | self.view.statusUpdated.connect(self.viewStatusUpdated) 74 | 75 | self.layout = QVBoxLayout() 76 | self.layout.addWidget(self.view) 77 | self.layout.setContentsMargins(0, 0, 0, 0) 78 | self.layout.setSpacing(0) 79 | self.main_area.setLayout(self.layout) 80 | 81 | self.grabGesture(Qt.SwipeGesture) 82 | 83 | def createView(self, type): 84 | view = type(self.data, self.filename, self, self.main_area) 85 | view.setVisible(False) 86 | self.status[view] = "" 87 | if hasattr(view, "statusUpdated"): 88 | self.status[view] = view.status 89 | view.statusUpdated.connect(self.viewStatusUpdated) 90 | self.cache[type] = view 91 | self.layout.addWidget(view) 92 | return view 93 | 94 | def setViewType(self, type): 95 | self.view.setVisible(False) 96 | 97 | if self.cache.has_key(type): 98 | view = self.cache[type] 99 | else: 100 | view = self.createView(type) 101 | self.view = view 102 | self.view.setVisible(True) 103 | 104 | self.viewChanged.emit(self) 105 | self.statusUpdated.emit(self) 106 | 107 | self.view.setFocus(Qt.OtherFocusReason) 108 | 109 | def getTabName(self): 110 | if self.custom_tab_name: 111 | return self.custom_tab_name 112 | if self.filename == "": 113 | return "Untitled" + " (" + self.view.__class__.getShortViewName() + ")" 114 | return os.path.basename(self.filename) + " (" + self.view.__class__.getShortViewName() + ")" 115 | 116 | def setTabName(self, name): 117 | if not self.allow_title_change: 118 | return 119 | self.custom_tab_name = name 120 | self.statusUpdated.emit(self) 121 | 122 | def getShortFileName(self): 123 | if self.filename == "": 124 | return "Untitled" 125 | return os.path.basename(self.filename) 126 | 127 | def isUntitled(self): 128 | return self.filename == "" 129 | 130 | def isNewFileName(self): 131 | return self.new_filename 132 | 133 | def getStatus(self): 134 | return self.status[self.view] 135 | 136 | def viewStatusUpdated(self, view): 137 | self.status[view] = view.status 138 | if self.view == view: 139 | self.statusUpdated.emit(self) 140 | 141 | def register_navigate(self, name, view, func): 142 | self.navigation[name] = [view.__class__, func] 143 | 144 | def navigate(self, name, ofs): 145 | entry = self.get_history_entry() 146 | 147 | # If view is already open, navigate now 148 | if name in self.navigation: 149 | if not self.navigation[name][1](ofs): 150 | return False 151 | self.setViewType(self.navigation[name][0]) 152 | self.back += [entry] 153 | self.forward = [] 154 | self.view.setFocus(Qt.OtherFocusReason) 155 | return True 156 | 157 | # Look for a valid view type that handles this navigation 158 | for type in self.available: 159 | if type in self.cache: 160 | continue 161 | if hasattr(type, "handlesNavigationType"): 162 | if type.handlesNavigationType(name): 163 | view = self.createView(type) 164 | if not self.navigation[name][1](ofs): 165 | self.view.setFocus(Qt.OtherFocusReason) 166 | return False 167 | self.setViewType(type) 168 | self.back += [entry] 169 | self.forward = [] 170 | self.view.setFocus(Qt.OtherFocusReason) 171 | return True 172 | 173 | return False 174 | 175 | def get_history_entry(self): 176 | if hasattr(self.view, "get_history_entry"): 177 | data = self.view.get_history_entry() 178 | else: 179 | data = None 180 | return HistoryEntry(self.view.__class__, data) 181 | 182 | def add_history_entry(self): 183 | entry = self.get_history_entry() 184 | self.back += [entry] 185 | self.forward = [] 186 | 187 | def go_back(self): 188 | if len(self.back) > 0: 189 | entry = self.back.pop() 190 | self.forward += [self.get_history_entry()] 191 | self.setViewType(entry.type) 192 | if entry.data != None: 193 | self.view.navigate_to_history_entry(entry.data) 194 | 195 | def go_forward(self): 196 | if len(self.forward) > 0: 197 | entry = self.forward.pop() 198 | self.back += [self.get_history_entry()] 199 | self.setViewType(entry.type) 200 | if entry.data != None: 201 | self.view.navigate_to_history_entry(entry.data) 202 | 203 | def keyPressEvent(self, event): 204 | if event.key() == Qt.Key_Escape: 205 | self.go_back() 206 | elif event.key() == Qt.Key_Back: 207 | self.go_back() 208 | elif event.key() == Qt.Key_Forward: 209 | self.go_forward() 210 | else: 211 | super(ViewFrame, self).keyPressEvent(event) 212 | 213 | def event(self, event): 214 | if event.type() == QEvent.Gesture: 215 | gesture = event.gesture(Qt.SwipeGesture) 216 | if (gesture != None) and (gesture.state() == Qt.GestureFinished): 217 | if gesture.horizontalDirection() == QSwipeGesture.Left: 218 | self.go_back() 219 | return True 220 | elif gesture.horizontalDirection() == QSwipeGesture.Right: 221 | self.go_forward() 222 | return True 223 | return super(ViewFrame, self).event(event) 224 | 225 | def save(self, filename): 226 | try: 227 | self.data.save(filename) 228 | except IOError as (errno, msg): 229 | QMessageBox.critical(self, "Error", "Unable to save: " + msg) 230 | return False 231 | self.notify_save(filename) 232 | return True 233 | 234 | def notify_save(self, filename): 235 | self.filename = filename 236 | self.new_filename = True 237 | for view in self.cache.values(): 238 | if hasattr(view, "notify_save"): 239 | view.notify_save() 240 | 241 | def is_modified(self): 242 | return self.data.is_modified() 243 | 244 | def begin_undo(self): 245 | self.undo_location = self.get_history_entry() 246 | 247 | def commit_undo(self): 248 | self.data.commit_undo(self.undo_location, self.get_history_entry()) 249 | 250 | def undo(self): 251 | # Ensure any pending undo actions are accounted for 252 | self.commit_undo() 253 | 254 | entry = self.data.undo() 255 | if entry: 256 | self.setViewType(entry.type) 257 | if entry.data != None: 258 | self.view.navigate_to_history_entry(entry.data) 259 | 260 | def redo(self): 261 | # Ensure any pending undo actions are accounted for 262 | self.commit_undo() 263 | 264 | entry = self.data.redo() 265 | if entry: 266 | self.setViewType(entry.type) 267 | if entry.data != None: 268 | self.view.navigate_to_history_entry(entry.data) 269 | 270 | def toggle_python_console(self): 271 | if self.python_console: 272 | if self.python_console.isVisible(): 273 | self.python_console.hide() 274 | self.view.setFocus(Qt.OtherFocusReason) 275 | else: 276 | self.python_console.show() 277 | self.python_console.input.setFocus(Qt.OtherFocusReason) 278 | else: 279 | self.python_console = PythonConsole(self) 280 | self.splitter.addWidget(self.python_console) 281 | self.python_console.input.setFocus(Qt.OtherFocusReason) 282 | 283 | def run_in_terminal(self, cmd): 284 | if self.terminal: 285 | if not self.terminal.isVisible(): 286 | self.terminal.show() 287 | self.terminal.run() 288 | self.terminal.commandLine.setFocus(Qt.OtherFocusReason) 289 | else: 290 | self.terminal.run() 291 | self.terminal.term.setFocus(Qt.OtherFocusReason) 292 | else: 293 | self.terminal = RunWindow(self, self, cmd) 294 | self.splitter.addWidget(self.terminal) 295 | self.terminal.commandLine.setFocus(Qt.OtherFocusReason) 296 | 297 | def terminal_process_exit(self): 298 | if self.terminal: 299 | # Terminal process exited, set focus to view 300 | self.view.setFocus(Qt.OtherFocusReason) 301 | 302 | def terminal_closed(self): 303 | self.terminal.hide() 304 | self.view.setFocus(Qt.OtherFocusReason) 305 | 306 | def closing(self): 307 | if self.python_console: 308 | self.python_console.stop() 309 | if self.terminal: 310 | self.terminal.closeRequest() 311 | 312 | def force_close(self): 313 | self.closeRequest.emit(self) 314 | 315 | def font_changed(self): 316 | for view in self.cache.values(): 317 | if hasattr(view, "fontChanged"): 318 | view.fontChanged() 319 | 320 | -------------------------------------------------------------------------------- /docs/python_api.html: -------------------------------------------------------------------------------- 1 | 2 | Binary Ninja Python Console 3 | 4 |

Python console API

5 |

Global objects

6 |
    7 |
  • data
    8 |

    Raw binary representation of the active file. This object can be accessed or modified using standard 9 | Python [] operator (slices are supported). See below for a description of the methods 10 | supported by the data object.

    11 |
  • 12 |
  • exe
    13 |

    Like the data object, but instead represents the in memory view of an executable. If the 14 | file is not an executable, this object is the same as data. This object can also be 15 | accessed or modified using the standard Python [] operator. See below for a description 16 | of the methods supported by the data object.

    17 |
  • 18 |
  • view
    19 |

    Active view container object. Most functionality is exposed by the APIs listed below.

    20 |
  • 21 |
22 |

Data class methods

23 |
    24 |
  • data.read(address, size)
    25 |

    Reads the given number of bytes at the given address, and returns the result as a string object.

    26 |
  • 27 |
  • data.write(address, value)
    28 |

    Writes the given value at the given address. The given value should be a string object containing the 29 | data to be written. The write is an in-place overwrite that is the same length as the string object given. 30 | The function returns the number of bytes written.

    31 |
  • 32 |
  • data.insert(address, value)
    33 |

    Inserts the given value at the given address. The existing contents at the given address will be moved 34 | to be directly after the inserted data. The given value should be a string object containing the data to 35 | be written. Some data types may not support insertion, and will return zero from this function. The 36 | function returns the number of bytes inserted.

    37 |
  • 38 |
  • data.remove(address, size)
    39 |

    Removes the given number of bytes at the given address. Some data types may not support removal of 40 | bytes, and will return zero from this function. The function returns the number of bytes removed.

    41 |
  • 42 |
  • data.add_callback(object)
    43 |

    Registers an object to receive callbacks when this data object is updated. The following methods will 44 | be invoked when the data is updated:

    45 |
      46 |
    • callback.notify_data_write(data, address, value): Data was written with data.write.
    • 47 |
    • callback.notify_data_insert(data, address, value): Data was inserted with data.insert.
    • 48 |
    • callback.notify_data_remove(data, address, size): Data was removed with data.remove.
    • 49 |
    50 |
  • 51 |
  • data.remove_callback(object)
    52 |

    Unregisters an object that was receiving callbacks.

    53 |
  • 54 |
  • data.save(filename)
    55 |

    Saves the current file contents to disk with the given filename.

    56 |
  • 57 |
  • data.start()
    58 |

    Gets the starting address of the data. For raw data, this will be zero. For executables, this will be 59 | the base address of the module.

    60 |
  • 61 |
  • data.is_modified()
    62 |

    Determines if the data has been modified since the last time it was written to disk.

    63 |
  • 64 |
  • data.find(regex, address)
    65 |

    Find the first occurence of the given regular expression starting at the given address. Returns the address 66 | of the first match.

    67 |
  • 68 |
  • data.architecture()
    69 |

    Returns the target CPU architecture of the file. For raw data, this will default to 32-bit X86.

    70 |
  • 71 |
  • data.entry()
    72 |

    Gets the entry point of an executable. This method will not exist on raw data objects.

    73 |
  • 74 |
75 |

View functions

76 |
    77 |
  • current_view()
    78 |

    Get the currently active view.

    79 |
  • 80 |
  • change_view(view_type)
    81 |

    Changes the current view to the type passed. The type should be the class object of the desired view 82 | type (for example, DisassemblerView.DisassemblerView). 83 |

  • 84 |
  • navigate(action_type, address)
    85 |

    Navigates to the given address with the given action type string. The following action types are 86 | currently defined in the default install:

    87 |
      88 |
    • disassembler: Navigate to address in disassembly view.
    • 89 |
    • make_proc: Make new function at address in disassembly view.
    • 90 |
    • hex: Navigate to address in raw hex view.
    • 91 |
    • exe: Navigate to address in executable hex view.
    • 92 |
    93 |
  • 94 |
  • create_file(data)
    95 |

    Creates a new unnamed file with the given contents. The initial view type is chosen based on the content in the same 96 | manner as opening a file through the user interface.

    97 |
  • 98 |
99 |

Cursor and selection functions

100 |
    101 |
  • cursor()
    102 |

    Gets the address of the current cursor position.

    103 |
  • 104 |
  • set_cursor(pos)
    105 |

    Sets the cursor position to the given address. After this function call there will be no active selection.

    106 |
  • 107 |
  • selection_range()
    108 |

    Gets the address range of the current selection as a tuple. The first element is the start of the selection and 109 | the second element is the end of the selection. If there is no selection, the function returns the current cursor 110 | position in both of the elements. The second element minus the first element is the length of the selection.

    111 |
  • 112 |
  • set_selection_range(start, end)
    113 |

    Sets the current selection to the range given.

    114 |
  • 115 |
  • selection()
    116 |

    Returns the currently selected bytes as a string object. If there is no selection, this function returns the 117 | empty string.

    118 |
  • 119 |
  • replace_selection(value)
    120 |

    Replaces the selected bytes with the given value. The value should be a string object containing the new data. 121 | If there is no active selection, this function will insert the bytes at the cursor position.

    122 |
  • 123 |
  • write_at_cursor(value)
    124 |

    Overwrites the bytes at the cursor position with the given value. The value should be a string object 125 | containing the new data. This is an in-place overwrite and will perform a write of the same length as the 126 | string object passed to this function.

    127 |
128 |

Undo functions

129 |
    130 |
  • undo()
    131 |

    Undo the last action(s) performed since the last undo buffer commit.

    132 |
  • 133 |
  • redo()
    134 |

    Redo the actions that were last undone.

    135 |
  • 136 |
  • commit()
    137 |

    Commit the actions pending in the undo buffer. Each undo action will go to the previous buffer commit. Undo 138 | buffer commits should be performed after each logical action (i.e. replace, rebase image, create symbol, etc.). 139 | If an undo buffer commit is not performed, undo will still function, but may cause more actions to be undone 140 | than desired for a single step.

    141 |
  • 142 |
143 |

Clipboard functions

144 |
    145 |
  • copy(value)
    146 |

    Copies the given value to the clipboard. The value should be a string object containing the data to be copied.

    147 |

  • 148 |
  • paste()
    149 |

    Paste the contents of the clipboard into the current view. This performs the same function as the paste menu item.

    150 |
  • 151 |
  • clipboard()
    152 |

    Get the current clipboard contents as a string object.

    153 |
  • 154 |
155 | 156 | -------------------------------------------------------------------------------- /images/aboutback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated-binaryninja-python/83f59f7214bf8177c48224e7bee01fbe376a00f4/images/aboutback.png -------------------------------------------------------------------------------- /images/copy.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char * copy_xpm[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "16 15 23 1", 5 | "+ c #769CDA", 6 | ": c #DCE6F6", 7 | "X c #3365B7", 8 | "* c #FFFFFF", 9 | "o c #9AB6E4", 10 | "< c #EAF0FA", 11 | "# c #B1C7EB", 12 | ". c #6992D7", 13 | "3 c #F7F9FD", 14 | ", c #F0F5FC", 15 | "$ c #A8C0E8", 16 | " c None", 17 | "- c #FDFEFF", 18 | "& c #C4D5F0", 19 | "1 c #E2EAF8", 20 | "O c #89A9DF", 21 | "= c #D2DFF4", 22 | "4 c #FAFCFE", 23 | "2 c #F5F8FD", 24 | "; c #DFE8F7", 25 | "% c #B8CCEC", 26 | "> c #E5EDF9", 27 | "@ c #648FD6", 28 | /* pixels */ 29 | " .....XX ", 30 | " .oO+@X#X ", 31 | " .$oO+X##X ", 32 | " .%$o........ ", 33 | " .&%$.*=&#o.-. ", 34 | " .=&%.*;=&#.--. ", 35 | " .:=&.*>;=&.... ", 36 | " .>:=.*,>;=&#o. ", 37 | " .<1:.*2,>:=&#. ", 38 | " .2<1.*32,>:=&. ", 39 | " .32<.*432,>:=. ", 40 | " .32<.*-432,>:. ", 41 | " .....**-432,>. ", 42 | " .***-432,. ", 43 | " .......... " 44 | }; 45 | -------------------------------------------------------------------------------- /images/cut.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char * cut_xpm[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "16 15 25 1", 5 | "6 c #D8BDC0", 6 | ": c #C3C3C4", 7 | "- c #FFFFFF", 8 | ". c #6C6D70", 9 | "2 c #AD3A45", 10 | "o c #DBDBDB", 11 | "# c #939495", 12 | "< c #E42234", 13 | "& c #C3C5C8", 14 | "; c #C6CCD3", 15 | "% c #B7B7B8", 16 | " c None", 17 | "* c #DFE0E2", 18 | "5 c #B69596", 19 | "3 c #9C2A35", 20 | "1 c #CFCFD0", 21 | ", c #AB5C64", 22 | "+ c #D2D3D4", 23 | "$ c #BCBDBE", 24 | "@ c #C6C8CA", 25 | "> c #CDC0C1", 26 | "O c #826F72", 27 | "X c #979BA0", 28 | "4 c #9B8687", 29 | "= c #9FA0A0", 30 | /* pixels */ 31 | " .X .o ", 32 | " O.+ @. ", 33 | " O. .. ", 34 | " O#$ %.& ", 35 | " O.*.. ", 36 | " #%#.. ", 37 | " O=-.. ", 38 | " #%#;. ", 39 | " OO:=O ", 40 | " >,,<, ,<,,1 ", 41 | " ><23<1 1<32<1 ", 42 | " ,2 4< <5 2, ", 43 | " <, ,2 2, ,< ", 44 | " 23,<5 5<,32 ", 45 | " 6225 522> " 46 | }; 47 | -------------------------------------------------------------------------------- /images/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated-binaryninja-python/83f59f7214bf8177c48224e7bee01fbe376a00f4/images/icon.icns -------------------------------------------------------------------------------- /images/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated-binaryninja-python/83f59f7214bf8177c48224e7bee01fbe376a00f4/images/icon.ico -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector35/deprecated-binaryninja-python/83f59f7214bf8177c48224e7bee01fbe376a00f4/images/icon.png -------------------------------------------------------------------------------- /images/new.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char * new_xpm[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "16 15 31 1", 5 | ". c #7198D9", 6 | "2 c #DCE6F6", 7 | ", c #FFFFFF", 8 | "= c #9AB6E4", 9 | "6 c #EAF0FA", 10 | "w c #6992D7", 11 | "0 c #5886D2", 12 | "7 c #F7F9FD", 13 | "5 c #F0F5FC", 14 | "* c #A8C0E8", 15 | " c None", 16 | "8 c #FDFEFF", 17 | "% c #C4D5F0", 18 | "3 c #E2EAF8", 19 | "+ c #4377CD", 20 | "O c #487BCE", 21 | "; c #6B94D7", 22 | "- c #89A9DF", 23 | ": c #5584D1", 24 | "# c #3569BF", 25 | "@ c #3A70CA", 26 | "1 c #D2DFF4", 27 | "> c #3366BB", 28 | "$ c #2E5CA8", 29 | "9 c #FAFCFE", 30 | "4 c #F5F8FD", 31 | "q c #638ED5", 32 | "o c #5282D0", 33 | "& c #B8CCEC", 34 | "X c #376EC9", 35 | "< c #ACE95B", 36 | /* pixels */ 37 | " .XoO+@#$. ", 38 | " .%%&*=-O;: ", 39 | " >>>>%&*=O,=o ", 40 | " ><<>%%&*O,,=o ", 41 | ">>><<>>>%&OOo+@ ", 42 | "><<<<<<>1%&*=-@ ", 43 | "><<<<<<>21%&*=@ ", 44 | ">>><<>>>321%&*+ ", 45 | " ><<>456321%&O ", 46 | " >>>>7456321%o ", 47 | " .,8974563210 ", 48 | " .,,897456320 ", 49 | " .,,,8974563q ", 50 | " .,,,,897456w ", 51 | " ............ " 52 | }; 53 | -------------------------------------------------------------------------------- /images/open.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char * open_xpm[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "16 15 36 1", 5 | ", c #AAC1E8", 6 | "1 c #9AEA53", 7 | "q c #DCE6F6", 8 | ". c #295193", 9 | "; c #A9D066", 10 | "r c #C6D6F0", 11 | "$ c #4A7CCE", 12 | "9 c #779DDB", 13 | "8 c #EAF0FA", 14 | "O c #6E96D8", 15 | "X c #214279", 16 | "e c #BED0EE", 17 | "6 c #85A7DF", 18 | "5 c #F0F5FC", 19 | "t c #ADC4E9", 20 | ": c #3161B1", 21 | " c None", 22 | "u c #274D8B", 23 | "& c #BFDC9B", 24 | "> c #9FB9E5", 25 | "y c #5584D1", 26 | "w c #3569BF", 27 | "% c #3A70CA", 28 | "+ c #305FAC", 29 | "3 c #5D89D3", 30 | "0 c #D2DFF4", 31 | "@ c #CAE2AA", 32 | "= c #B2D58C", 33 | "2 c #FAFCFE", 34 | "# c #638ED5", 35 | "* c #CEDCF2", 36 | "4 c #90AFE2", 37 | "< c #B3C8EB", 38 | "7 c #E5EDF9", 39 | "- c #A2BCE6", 40 | "o c #DFF0D0", 41 | /* pixels */ 42 | " ", 43 | " .... ", 44 | "XXXXX .oo. ", 45 | "XOOOO+ .@o. ", 46 | "XOOOO#$%.&*XXX ", 47 | "XOOOOOOO.=*X-X ", 48 | "XOXXXX...;*XXXX:", 49 | "XOX>,<.11111*X2:", 50 | "X3X4>,<.111*X5-:", 51 | "XX#64>,,.1*X78: ", 52 | "XXO964>,,.X0q7: ", 53 | "Xw3O964>,,er0t: ", 54 | "X%y3O964>, c #3C78A6", 17 | ", c #FF00FF", 18 | "' c #F08B62", 19 | ") c #9B8687", 20 | "! c #B6D5EE", 21 | "~ c #F79586", 22 | "{ c #F8AA8F", 23 | "] c #FBBCA4", 24 | "^ c #FCCBB8", 25 | "/ c #CAE1F3", 26 | "( c #DAEAF7", 27 | "_ c #FDD8C9", 28 | ": c #FEE3D7", 29 | "< c #E9F3FA", 30 | "[ c #EFF6FC", 31 | "} c #FEECE4", 32 | "| c #FFF1ED", 33 | "1 c #F4F9FD", 34 | "2 c #F7FBFD", 35 | "3 c #FFF5F0", 36 | "4 c #FFF8F4", 37 | "5 c #FAFCFE", 38 | "6 c #FFFAF8", 39 | "7 c #FFFFFF", 40 | "8 c #000000", 41 | "9 c #000000", 42 | "0 c #000000", 43 | "a c #000000", 44 | "b c #000000", 45 | "c c #000000", 46 | "d c #000000", 47 | "e c #000000", 48 | "f c #000000", 49 | "g c #000000", 50 | "h c #000000", 51 | "i c #000000", 52 | "j c #000000", 53 | "k c #000000", 54 | "l c #000000", 55 | "m c #000000", 56 | "n c #000000", 57 | "o c #000000", 58 | "p c #000000", 59 | "q c #000000", 60 | "r c #000000", 61 | "s c #000000", 62 | "t c #000000", 63 | "u c #000000", 64 | "v c #000000", 65 | "w c #000000", 66 | "x c #000000", 67 | "y c #000000", 68 | "z c #000000", 69 | "A c #000000", 70 | "B c #000000", 71 | "C c #000000", 72 | "D c #000000", 73 | "E c #000000", 74 | "F c #000000", 75 | "G c #000000", 76 | "H c #000000", 77 | "I c #000000", 78 | "J c #000000", 79 | "K c #000000", 80 | "L c #000000", 81 | "M c #000000", 82 | "N c #000000", 83 | "O c #000000", 84 | "P c #000000", 85 | "Q c #000000", 86 | "R c #000000", 87 | "S c #000000", 88 | "T c #000000", 89 | "U c #000000", 90 | "V c #000000", 91 | "W c #000000", 92 | "X c #000000", 93 | "Y c #000000", 94 | "Z c #000000", 95 | "` c #000000", 96 | " . c #000000", 97 | ".. c #000000", 98 | "+. c #000000", 99 | "@. c #000000", 100 | "#. c #000000", 101 | "$. c #000000", 102 | "%. c #000000", 103 | "&. c #000000", 104 | "*. c #000000", 105 | "=. c #000000", 106 | "-. c #000000", 107 | ";. c #000000", 108 | ">. c #000000", 109 | ",. c #000000", 110 | "'. c #000000", 111 | "). c #000000", 112 | "!. c #000000", 113 | "~. c #000000", 114 | "{. c #000000", 115 | "]. c #000000", 116 | "^. c #000000", 117 | "/. c #000000", 118 | "(. c #000000", 119 | "_. c #000000", 120 | ":. c #000000", 121 | "<. c #000000", 122 | "[. c #000000", 123 | "}. c #000000", 124 | "|. c #000000", 125 | "1. c #000000", 126 | "2. c #000000", 127 | "3. c #000000", 128 | "4. c #000000", 129 | "5. c #000000", 130 | "6. c #000000", 131 | "7. c #000000", 132 | "8. c #000000", 133 | "9. c #000000", 134 | "0. c #000000", 135 | "a. c #000000", 136 | "b. c #000000", 137 | "c. c #000000", 138 | "d. c #000000", 139 | "e. c #000000", 140 | "f. c #000000", 141 | "g. c #000000", 142 | "h. c #000000", 143 | "i. c #000000", 144 | "j. c #000000", 145 | "k. c #000000", 146 | "l. c #000000", 147 | "m. c #000000", 148 | "n. c #000000", 149 | "o. c #000000", 150 | "p. c #000000", 151 | "q. c #000000", 152 | "r. c #000000", 153 | "s. c #000000", 154 | "t. c #000000", 155 | "u. c #000000", 156 | "v. c #000000", 157 | "w. c #000000", 158 | "x. c #000000", 159 | "y. c #000000", 160 | "z. c #000000", 161 | "A. c #000000", 162 | "B. c #000000", 163 | "C. c #000000", 164 | "D. c #000000", 165 | "E. c #000000", 166 | "F. c #000000", 167 | "G. c #000000", 168 | "H. c #000000", 169 | "I. c #000000", 170 | "J. c #000000", 171 | "K. c #000000", 172 | "L. c #000000", 173 | "M. c #000000", 174 | "N. c #000000", 175 | "O. c #000000", 176 | "P. c #000000", 177 | "Q. c #000000", 178 | "R. c #000000", 179 | "S. c #000000", 180 | "T. c #000000", 181 | "U. c #000000", 182 | "V. c #000000", 183 | "W. c #000000", 184 | "X. c #000000", 185 | "Y. c #000000", 186 | "Z. c #000000", 187 | "`. c #000000", 188 | " + c #000000", 189 | ".+ c #000000", 190 | "++ c #000000", 191 | "@+ c #000000", 192 | "#+ c #000000", 193 | "$+ c #000000", 194 | "%+ c #000000", 195 | "&+ c #000000", 196 | "*+ c #000000", 197 | "=+ c #000000", 198 | "-+ c #000000", 199 | ";+ c #000000", 200 | ">+ c #000000", 201 | ",+ c #000000", 202 | "'+ c #000000", 203 | ")+ c #000000", 204 | "!+ c #000000", 205 | "~+ c #000000", 206 | "{+ c #000000", 207 | "]+ c #000000", 208 | "^+ c #000000", 209 | "/+ c #000000", 210 | "(+ c #000000", 211 | "_+ c #000000", 212 | ":+ c #000000", 213 | "<+ c #000000", 214 | "[+ c #000000", 215 | "}+ c #000000", 216 | "|+ c #000000", 217 | "1+ c #000000", 218 | "2+ c #000000", 219 | "3+ c #000000", 220 | "4+ c #000000", 221 | "5+ c #000000", 222 | "6+ c #000000", 223 | "7+ c #000000", 224 | "8+ c #000000", 225 | "9+ c #000000", 226 | "0+ c #000000", 227 | "a+ c #000000", 228 | "b+ c #000000", 229 | "c+ c #000000", 230 | "d+ c #000000", 231 | "e+ c #000000", 232 | "f+ c #000000", 233 | "g+ c #000000", 234 | "h+ c #000000", 235 | "i+ c #000000", 236 | "j+ c #000000", 237 | "k+ c #000000", 238 | "l+ c #000000", 239 | "m+ c #000000", 240 | "n+ c #000000", 241 | "o+ c #000000", 242 | "p+ c #000000", 243 | "q+ c #000000", 244 | "r+ c #000000", 245 | "s+ c #000000", 246 | "t+ c #000000", 247 | "u+ c #000000", 248 | "v+ c #000000", 249 | "w+ c #000000", 250 | "x+ c #000000", 251 | "y+ c #FFFBF0", 252 | "z+ c #A0A0A4", 253 | "A+ c #808080", 254 | "B+ c #FF0000", 255 | "C+ c #00FF00", 256 | "D+ c #FFFF00", 257 | "E+ c #0000FF", 258 | "F+ c #FF00FF", 259 | "G+ c #00FFFF", 260 | "H+ c #FFFFFF", 261 | " > > > > > > ", 262 | " > ) ; 7 7 7 7 ; ; > ", 263 | "> ] ) 7 7 7 7 7 7 ) ' > ", 264 | "> ] ) ) ) ) ) ) ) ) ' > ", 265 | "> ^ ] ] { { ~ ' > > > > > ", 266 | "> ^ ^ ] ] { { ~ > / ! > 1 > ", 267 | "> _ ^ ^ ] ] { { > ( / > 1 1 > ", 268 | "> _ _ ^ ^ ] ] { > < ( > > > > ", 269 | "> : _ _ ^ ^ ] ] > [ < ( / ! > ", 270 | "> } : _ _ ^ ^ ] > 2 [ < ( / > ", 271 | "> } } : _ _ ^ ^ > 5 2 [ < ( > ", 272 | "> | } } : _ _ ^ > 7 5 2 1 < > ", 273 | "> 4 3 } } : _ _ > > > > > > > ", 274 | "> 6 4 3 | } : _ _ _ _ > ", 275 | "> > > > > > > > > > > > "}; 276 | -------------------------------------------------------------------------------- /images/save.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char * save_xpm[] = { 3 | /* columns rows colors chars-per-pixel */ 4 | "16 15 21 1", 5 | ": c #AAC1E8", 6 | "1 c #B9CDED", 7 | "O c #FFFFFF", 8 | " c #2C58A0", 9 | "* c #B0C6EA", 10 | "; c #2D59A3", 11 | "X c #1C3866", 12 | "= c #C3D4EF", 13 | "2 c #CBD9F1", 14 | "- c #DAE5F6", 15 | "# c #97B4E3", 16 | ". c None", 17 | "$ c #274D8B", 18 | "& c #9FB9E5", 19 | "@ c #5584D1", 20 | "% c #82A5DE", 21 | "o c #3A70CA", 22 | "< c #A5BEE7", 23 | ", c #D2DFF4", 24 | "+ c #3467BC", 25 | "> c #C0D1EE", 26 | /* pixels */ 27 | " .", 28 | " XoOOOOOOOOO+X .", 29 | " @oO#######O+@ .", 30 | " @oOOOOOOOOO+@ .", 31 | " @oO#######O+@ .", 32 | " @oOOOOOOOOO+@ .", 33 | " @@+++++++++@@ .", 34 | " @@@@@@@@@@@@@ .", 35 | " @@@$$$$$$$$@@ .", 36 | " @@$%%%&*=-O$@ .", 37 | " @@$%X;;*=-O$@ .", 38 | " @@$%X;;:>,O$@ .", 39 | " @@$%X;;<12O$@ .", 40 | " @@$<<2OOOOO$@ .", 41 | ". .." 42 | }; 43 | -------------------------------------------------------------------------------- /images/split.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char * split_xpm[] = { 3 | "16 15 15 1", 4 | " c None", 5 | ". c #6992D7", 6 | "+ c #FFFFFF", 7 | "@ c #D2DFF4", 8 | "# c #C4D5F0", 9 | "$ c #B1C7EB", 10 | "% c #9AB6E4", 11 | "& c #FDFEFF", 12 | "* c #DFE8F7", 13 | "= c #E5EDF9", 14 | "- c #F0F5FC", 15 | "; c #DCE6F6", 16 | "> c #F7F9FD", 17 | ", c #FAFCFE", 18 | "' c #F5F8FD", 19 | " ........ ", 20 | " .+@#$%.&. ", 21 | " .+*@#$.&&. ", 22 | " .+=*@#.... ", 23 | " .+-=*@#$%. ", 24 | " .+.-.;.#.. ", 25 | " . . . . . ", 26 | " ", 27 | " . . . . . ", 28 | " ..>.-.;.#. ", 29 | " .+,>'-=;@. ", 30 | " .+&,>'-=;. ", 31 | " .++&,>'-=. ", 32 | " .+++&,>'-. ", 33 | " .......... "}; 34 | -------------------------------------------------------------------------------- /nasm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import subprocess 5 | import tempfile 6 | import traceback 7 | 8 | import logging 9 | logger = logging.getLogger('framework') 10 | #logger.setLevel(logging.ERROR) 11 | 12 | class assemblyException(Exception): 13 | def __init__(self,message): 14 | super(assemblyException,self).__init__("Shellcode failed to assemble!\n" + message) 15 | 16 | class disassemblyException(Exception): 17 | def __init__(self,message): 18 | super(disassemblyException,self).__init__("Shellcode failed to disassemble!\n" + message) 19 | 20 | def disassemble(buff): 21 | infile = tempfile.NamedTemporaryFile(delete=False) 22 | try: 23 | infile.write(buff) 24 | infile.close() 25 | 26 | asm = "No output\n" 27 | asm = subprocess.check_output(["ndisasm", "-u", infile.name]) 28 | 29 | os.unlink(infile.name) 30 | except: 31 | #traceback.print_exc() 32 | os.unlink(infile.name) 33 | raise disassemblyException(asm) 34 | 35 | return asm 36 | 37 | def assemble(buff, includes='.', *args, **kwargs): 38 | header = kwargs.get("header", "BITS 32\n") 39 | infile = buff 40 | if type(buff) is not file: 41 | infile = tempfile.NamedTemporaryFile() 42 | infile.write(header) 43 | infile.write(buff) 44 | infile.flush() 45 | outfile = tempfile.NamedTemporaryFile() 46 | errors = "write file failed" 47 | 48 | define_args = [] 49 | try: 50 | del kwargs["header"] 51 | except: 52 | pass 53 | 54 | for item in args: 55 | define_args.append("-d"+str(item)) 56 | 57 | for k, v in kwargs.iteritems(): 58 | k = str(k).upper() 59 | define_args.append("-D %s=%s" % (k, str(v))) 60 | 61 | nasm_args = ["nasm"] 62 | nasm_args.extend(define_args) 63 | nasm_args.append('-I '+ includes) 64 | nasm_args.extend(["-fbin", "-o", outfile.name, infile.name]) 65 | 66 | try: 67 | logger.debug("executing: " + ' '.join(nasm_args)) 68 | popen = subprocess.Popen(nasm_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 69 | output, errors = popen.communicate() 70 | asm = outfile.read() 71 | if popen.returncode == 0: 72 | errors = None 73 | else: 74 | asm = None 75 | except OSError as e: 76 | asm = None 77 | errors = e.args[1] 78 | except: 79 | asm = None 80 | 81 | return (asm, errors) 82 | 83 | def main(): 84 | usage = "{0} [-d] [-b]\n\t-d - disassemble\n\t-b - bare (no opcodes or addresses)" 85 | 86 | dis = False 87 | bare = False 88 | if "-d" in sys.argv: 89 | dis = True 90 | if "-b" in sys.argv: 91 | bare = True 92 | 93 | asm = sys.stdin.read() 94 | out = "" 95 | if dis: 96 | out = "" 97 | try: 98 | out = disassemble(asm) 99 | except disassemblyException as de: 100 | logger.error(de.message) 101 | exit(1) 102 | 103 | if bare: 104 | tmp = "" 105 | for a in out.split("\n"): 106 | if a != "": 107 | tmp += a.split(None,2)[-1]+"\n" 108 | out = tmp 109 | else: 110 | try: 111 | out = assemble(asm) 112 | except assemblyException as ae: 113 | logger.error(ae.message) 114 | exit(1) 115 | sys.stdout.write(out) 116 | 117 | if __name__ == "__main__": 118 | logger.addHandler(logging.StreamHandler()) 119 | main() 120 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Binary Ninja (OBSOLETE PYTHON PROTOTYPE) 2 | This is the Binary Ninja prototype, written in Python. See [binary.ninja](https://binary.ninja) for information about the current version. 3 | 4 | This source code is released under the [GPLv2 license](https://www.gnu.org/licenses/gpl-2.0.html). The individual disassembler libraries (which include `X86.py`, `PPC.py`, and `ARM.py`) are released under the [MIT license](http://opensource.org/licenses/MIT). 5 | 6 | Binary Ninja and the Binary Ninja logo are trademarks of Vector 35 LLC. 7 | 8 | ## Running Binary Ninja 9 | Binary Ninja is cross-platform and can run on Linux, Mac OS X, Windows, and FreeBSD. In order to run Binary Ninja, you will need to install a few prerequisites: 10 | 11 | * [Python 2.7](https://www.python.org/downloads/) 12 | * [PySide](https://pypi.python.org/pypi/PySide#installing-prerequisites) for Qt Python bindings 13 | * The [pycrypto](https://www.dlitz.net/software/pycrypto/) library 14 | 15 | You can start Binary Ninja by running `binja.py` in the Python interpreter. 16 | 17 | ### Windows Step-by-step Instructions 18 | 19 | * Install the latest [Python 2.7](https://www.python.org/downloads/). 20 | * In a command-prompt, run: 21 | ``` 22 | cd \Python27\Scripts 23 | pip install PySide 24 | easy_install http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win32-py2.7.exe 25 | ``` 26 | * Install [SourceTree](http://www.sourcetreeapp.com/download/) or [GitHub for Windows](https://windows.github.com/) 27 | * Clone `https://github.com/Vector35/binaryninja-python` to a local folder using whichever tool you installed. 28 | * Run `binja.py` from the directory you cloned the source code into 29 | --------------------------------------------------------------------------------