def _create_gui(self): self.t_fakefast_addr = QtWidgets.QLineEdit() self.t_fakefast_addr.setFixedWidth(150) self.tbl_fakefast = TTable( ['fast_id', 'fast_size', 'bytes to target', 'chunk address']) self.tbl_fakefast.customContextMenuRequested.connect(self.context_menu) self.t_fakefast_info = QtWidgets.QTextEdit() self.t_fakefast_info.setReadOnly(True) self.btn_find_fakefast = QtWidgets.QPushButton("Find") self.btn_find_fakefast.clicked.connect(self.find_fakefast_on_click) hbox_fakefast = QtWidgets.QHBoxLayout() hbox_fakefast.addWidget(QtWidgets.QLabel('Target address')) hbox_fakefast.addWidget(self.t_fakefast_addr) hbox_fakefast.addWidget(self.btn_find_fakefast) hbox_fakefast.addStretch(1) vbox_fakefast = QtWidgets.QVBoxLayout() vbox_fakefast.addLayout(hbox_fakefast) vbox_fakefast.addWidget(self.tbl_fakefast) vbox_fakefast.addStretch(1) vbox_fakefast.setContentsMargins(0, 0, 0, 0) self.setLayout(vbox_fakefast)
def _create_gui(self): self.tbl_offsets_vars = TTable(['name', 'offset']) self.tbl_offsets_vars.resize_columns([150, 100]) self.tbl_offsets_vars.cellDoubleClicked.connect( self.tbl_offsets_double_clicked) self.tbl_offsets_vars.customContextMenuRequested.connect( self.context_menu) self.tbl_offsets_funcs = TTable(['name', 'offset']) self.tbl_offsets_funcs.resize_columns([150, 100]) self.tbl_offsets_funcs.cellDoubleClicked.connect( self.tbl_offsets_double_clicked) self.tbl_offsets_funcs.customContextMenuRequested.connect( self.context_menu) vbox_offsets_vars = QtWidgets.QVBoxLayout() vbox_offsets_vars.addWidget(QtWidgets.QLabel('Variables')) vbox_offsets_vars.addWidget(self.tbl_offsets_vars) vbox_offsets_funcs = QtWidgets.QVBoxLayout() vbox_offsets_funcs.addWidget(QtWidgets.QLabel('Functions')) vbox_offsets_funcs.addWidget(self.tbl_offsets_funcs) hbox_libc_offsets = QtWidgets.QHBoxLayout() hbox_libc_offsets.addLayout(vbox_offsets_vars) hbox_libc_offsets.addLayout(vbox_offsets_funcs) hbox_libc_offsets.setContentsMargins(0, 0, 0, 0) self.setLayout(hbox_libc_offsets)
def _create_table(self): self.tbl_parsed_heap = TTable( ['address', 'prev', 'size', 'status', 'fd', 'bk']) self.tbl_parsed_heap.resize_columns([155, 40, 100, 120, 155, 155]) self.tbl_parsed_heap.customContextMenuRequested.connect( self.context_menu) self.tbl_parsed_heap.itemSelectionChanged.connect( self.view_selected_chunk)
def _create_table(self): self.tbl_traced_chunks = TTable(['#','User-address', 'Action', 'Arg 1', 'Arg 2', \ 'Thread', 'Caller', 'Info']) self.tbl_traced_chunks.setRowCount(0) self.tbl_traced_chunks.resize_columns([40, 155, 80, 80, 80, 80, 200, 200]) self.tbl_traced_chunks.customContextMenuRequested.connect(self.context_menu) self.tbl_traced_chunks.itemSelectionChanged.connect(self.view_selected_chunk) self.tbl_traced_chunks.cellDoubleClicked.connect(self.traced_double_clicked)
def _create_table(self): # ----------------------------------------------------------------------- # Tcache table self.tbl_tcache = TTable(['#', 'size', 'counts', 'next']) self.tbl_tcache.resize_columns([50, 100, 50, 155]) self.tbl_tcache.customContextMenuRequested.connect(self.context_menu) self.tbl_tcache.cellDoubleClicked.connect(self.show_selected_chunk) self.tbl_tcache.itemSelectionChanged.connect( self.table_on_change_index)
def _create_tables(self): # --- Fastbins table self.tbl_fastbins = TTable(['#', 'size', 'fd']) self.tbl_fastbins.resize_columns([50, 100, 155]) self.tbl_fastbins.customContextMenuRequested.connect(self.context_menu) self.tbl_fastbins.cellDoubleClicked.connect(self.table_on_change) self.tbl_fastbins.itemSelectionChanged.connect(self.table_on_change) # --- Unsortedbin self.tbl_unsortedbin = TTable(['#', 'fd', 'bk', 'base']) self.tbl_unsortedbin.resize_columns([50, 155, 155, 155]) self.tbl_unsortedbin.customContextMenuRequested.connect( self.context_menu) self.tbl_unsortedbin.cellDoubleClicked.connect(self.table_on_change) self.tbl_unsortedbin.itemClicked.connect(self.table_on_change) # --- Smallbins self.tbl_smallbins = TTable(['#', 'size', 'fd', 'bk', 'base']) self.tbl_smallbins.resize_columns([50, 100, 155, 155, 155]) self.tbl_smallbins.customContextMenuRequested.connect( self.context_menu) self.tbl_smallbins.cellDoubleClicked.connect(self.table_on_change) self.tbl_smallbins.itemSelectionChanged.connect(self.table_on_change) # --- Largebins self.tbl_largebins = TTable(['#', 'size', 'fd', 'bk', 'base']) self.tbl_largebins.resize_columns([50, 100, 155, 155, 155]) self.tbl_largebins.customContextMenuRequested.connect( self.context_menu) self.tbl_largebins.cellDoubleClicked.connect(self.table_on_change) self.tbl_largebins.itemSelectionChanged.connect(self.table_on_change) self.bin_tables = { self.tbl_fastbins: { 'title': 'fastbins', 'address': 2, 'size': 1, 'base': None }, self.tbl_unsortedbin: { 'title': 'unsortedbin', 'address': 1, 'size': None, 'base': 3 }, self.tbl_smallbins: { 'title': 'smallbins', 'address': 2, 'size': 1, 'base': 4 }, self.tbl_largebins: { 'title': 'largebins', 'address': 2, 'size': 1, 'base': 4 } }
class FakefastWidget(CustomWidget): def __init__(self, parent=None): super(FakefastWidget, self).__init__(parent) self._create_gui() def _create_gui(self): self.t_fakefast_addr = QtWidgets.QLineEdit() self.t_fakefast_addr.setFixedWidth(150) self.tbl_fakefast = TTable( ['fast_id', 'fast_size', 'bytes to target', 'chunk address']) self.tbl_fakefast.customContextMenuRequested.connect(self.context_menu) self.t_fakefast_info = QtWidgets.QTextEdit() self.t_fakefast_info.setReadOnly(True) self.btn_find_fakefast = QtWidgets.QPushButton("Find") self.btn_find_fakefast.clicked.connect(self.find_fakefast_on_click) hbox_fakefast = QtWidgets.QHBoxLayout() hbox_fakefast.addWidget(QtWidgets.QLabel('Target address')) hbox_fakefast.addWidget(self.t_fakefast_addr) hbox_fakefast.addWidget(self.btn_find_fakefast) hbox_fakefast.addStretch(1) vbox_fakefast = QtWidgets.QVBoxLayout() vbox_fakefast.addLayout(hbox_fakefast) vbox_fakefast.addWidget(self.tbl_fakefast) vbox_fakefast.addStretch(1) vbox_fakefast.setContentsMargins(0, 0, 0, 0) self.setLayout(vbox_fakefast) def context_menu(self, position): sender = self.sender() menu = QtWidgets.QMenu() copy_action = menu.addAction("Copy value") copy_row = menu.addAction("Copy row") view_chunk = menu.addAction("View chunk") jump_to = menu.addAction("Jump to chunk") chunk_addr = int(sender.item(sender.currentRow(), 3).text(), 16) action = menu.exec_(sender.mapToGlobal(position)) if action == copy_action: sender.copy_selected_value() if action == copy_row: sender.copy_selected_row() elif action == jump_to: idc.jumpto(chunk_addr) elif action == view_chunk: self.parent.parent.show_chunk_info(chunk_addr) def find_fakefast_on_click(self): start_addr = int(self.t_fakefast_addr.text(), 16) fake_chunks = self.heap.find_fakefast(start_addr) if len(fake_chunks) == 0: idaapi.info("Fakefast: 0 results") return self.tbl_fakefast.clearContents() self.tbl_fakefast.setRowCount(0) self.tbl_fakefast.setSortingEnabled(False) for idx, chunk in enumerate(fake_chunks): self.tbl_fakefast.insertRow(idx) self.tbl_fakefast.setItem( idx, 0, QtWidgets.QTableWidgetItem("%d" % chunk['fast_id'])) self.tbl_fakefast.setItem( idx, 1, QtWidgets.QTableWidgetItem("0x%x" % chunk['size'])) self.tbl_fakefast.setItem( idx, 2, QtWidgets.QTableWidgetItem("%d" % chunk['bytes_to'])) self.tbl_fakefast.setItem( idx, 3, QtWidgets.QTableWidgetItem("0x%x" % chunk['address'])) self.tbl_fakefast.resizeRowsToContents() self.tbl_fakefast.resizeColumnsToContents() self.tbl_fakefast.setSortingEnabled(True)
class LibcOffsetsWidget(CustomWidget): def __init__(self, parent=None): super(LibcOffsetsWidget, self).__init__(parent) self.libc_base = None self._create_gui() self.populate_table() def _create_gui(self): self.tbl_offsets_vars = TTable(['name', 'offset']) self.tbl_offsets_vars.resize_columns([150, 100]) self.tbl_offsets_vars.cellDoubleClicked.connect( self.tbl_offsets_double_clicked) self.tbl_offsets_vars.customContextMenuRequested.connect( self.context_menu) self.tbl_offsets_funcs = TTable(['name', 'offset']) self.tbl_offsets_funcs.resize_columns([150, 100]) self.tbl_offsets_funcs.cellDoubleClicked.connect( self.tbl_offsets_double_clicked) self.tbl_offsets_funcs.customContextMenuRequested.connect( self.context_menu) vbox_offsets_vars = QtWidgets.QVBoxLayout() vbox_offsets_vars.addWidget(QtWidgets.QLabel('Variables')) vbox_offsets_vars.addWidget(self.tbl_offsets_vars) vbox_offsets_funcs = QtWidgets.QVBoxLayout() vbox_offsets_funcs.addWidget(QtWidgets.QLabel('Functions')) vbox_offsets_funcs.addWidget(self.tbl_offsets_funcs) hbox_libc_offsets = QtWidgets.QHBoxLayout() hbox_libc_offsets.addLayout(vbox_offsets_vars) hbox_libc_offsets.addLayout(vbox_offsets_funcs) hbox_libc_offsets.setContentsMargins(0, 0, 0, 0) self.setLayout(hbox_libc_offsets) def context_menu(self, position): sender = self.sender() menu = QtWidgets.QMenu() action_copy = menu.addAction("Copy value") action_copy_row = menu.addAction("Copy row") action_jump_to = menu.addAction("Jump to address") action = menu.exec_(sender.mapToGlobal(position)) if action == action_copy: sender.copy_selected_value() elif action == action_copy_row: sender.copy_selected_row() elif action == action_jump_to: offset = int(sender.item(sender.currentRow(), 1).text(), 16) address = self.libc_base + offset idc.jumpto(address) def tbl_offsets_double_clicked(self): sender = self.sender() offset = int(sender.item(sender.currentRow(), 1).text(), 16) address = self.libc_base + offset idc.jumpto(address) def populate_table(self): self.tbl_offsets_vars.clearContents() self.tbl_offsets_funcs.clearContents() self.tbl_offsets_vars.setRowCount(0) self.tbl_offsets_funcs.setRowCount(0) self.tbl_offsets_vars.setSortingEnabled(False) self.tbl_offsets_funcs.setSortingEnabled(False) offsets = self.get_libc_offsets() variables = offsets['variables'] functions = offsets['functions'] for idx, (name, offset) in enumerate(variables.items()): self.tbl_offsets_vars.insertRow(idx) self.tbl_offsets_vars.setItem(idx, 0, QtWidgets.QTableWidgetItem(name)) self.tbl_offsets_vars.setItem( idx, 1, QtWidgets.QTableWidgetItem("0x%x" % offset)) for idx, (name, offset) in enumerate(functions.items()): self.tbl_offsets_funcs.insertRow(idx) self.tbl_offsets_funcs.setItem(idx, 0, QtWidgets.QTableWidgetItem(name)) self.tbl_offsets_funcs.setItem( idx, 1, QtWidgets.QTableWidgetItem("0x%x" % offset)) self.tbl_offsets_vars.resizeRowsToContents() self.tbl_offsets_funcs.resizeRowsToContents() self.tbl_offsets_vars.setSortingEnabled(True) self.tbl_offsets_funcs.setSortingEnabled(True) def get_libc_offsets(self): libc_symbols = { 'variables': [ 'environ', '__environ', '__free_hook', '__malloc_hook', '__realloc_hook', '_IO_list_all', '_IO_2_1_stdin_', '_IO_2_1_stdout_', '_IO_2_1_stderr_', ], 'functions': [ 'system', '__libc_system', 'execve', 'open', '__open64', 'read', 'write', '__write', '_IO_gets', 'gets', 'setcontext+0x35', ] } result = { 'variables': OrderedDict(), 'functions': OrderedDict(), } self.libc_base = get_libc_base() if not self.libc_base: return result libc_names = get_libc_names() if not libc_names: idaapi.warning("Unable to get glibc symbols") return result for s_type, symbols in libc_symbols.items(): for sym in symbols: name_expr = parse_name_expr(sym) if not name_expr: continue name, offset = name_expr addr = libc_names.get(name) if addr: addr += offset offset = addr - self.libc_base result[s_type][sym] = offset return result
class TcacheWidget(CustomWidget): def __init__(self, parent=None): super(TcacheWidget, self).__init__(parent) self._create_gui() def _create_gui(self): self._create_table() self.te_tcache_chain = QtWidgets.QTextEdit() self.te_tcache_chain.setFixedHeight(100) self.te_tcache_chain.setReadOnly(True) vbox_tcache_chain = QtWidgets.QVBoxLayout() vbox_tcache_chain.addWidget(QtWidgets.QLabel('Chain info')) vbox_tcache_chain.addWidget(self.te_tcache_chain) vbox_tcache = QtWidgets.QVBoxLayout() vbox_tcache.addWidget(QtWidgets.QLabel('Tcache entries')) vbox_tcache.addWidget(self.tbl_tcache) vbox_tcache.addLayout(vbox_tcache_chain) vbox_tcache.addStretch(1) self.setLayout(vbox_tcache) def _create_table(self): # ----------------------------------------------------------------------- # Tcache table self.tbl_tcache = TTable(['#', 'size', 'counts', 'next']) self.tbl_tcache.resize_columns([50, 100, 50, 155]) self.tbl_tcache.customContextMenuRequested.connect(self.context_menu) self.tbl_tcache.cellDoubleClicked.connect(self.show_selected_chunk) self.tbl_tcache.itemSelectionChanged.connect( self.table_on_change_index) def context_menu(self, position): sender = self.sender() menu = QtWidgets.QMenu() copy_action = menu.addAction("Copy value") copy_row = menu.addAction("Copy row") view_chunk = menu.addAction("View chunk") jump_to = menu.addAction("Jump to") graphview_action = menu.addAction("GraphView") action = menu.exec_(sender.mapToGlobal(position)) fd_addr = int(sender.item(sender.currentRow(), 3).text(), 16) if action == copy_action: sender.copy_selected_value() if action == copy_row: sender.copy_selected_row() elif action == jump_to: idc.Jump(fd_addr) elif action == view_chunk: self.show_selected_chunk() elif action == graphview_action: idx = int(sender.item(sender.currentRow(), 0).text()) size = int(sender.item(sender.currentRow(), 1).text(), 16) fd = int(sender.item(sender.currentRow(), 3).text(), 16) graph = BinGraph(self, info={ 'type': 'tcache', 'bin_id': idx, 'size': size, }) graph.Show() def populate_table(self): self.tbl_tcache.clearContents() self.tbl_tcache.setRowCount(0) self.tbl_tcache.setSortingEnabled(False) tcache = self.heap.get_tcache(self.cur_arena) if not tcache: return idx = 0 for i, (size, entry) in enumerate(tcache.iteritems()): if entry['counts'] == 0 and entry['next'] == 0: continue self.tbl_tcache.insertRow(idx) it_entry_id = QtWidgets.QTableWidgetItem("%d" % i) it_size = QtWidgets.QTableWidgetItem("%#x" % size) it_counts = QtWidgets.QTableWidgetItem("%d" % entry['counts']) it_address = QtWidgets.QTableWidgetItem("%#x" % entry['next']) self.tbl_tcache.setItem(idx, 0, it_entry_id) self.tbl_tcache.setItem(idx, 1, it_size) self.tbl_tcache.setItem(idx, 2, it_counts) self.tbl_tcache.setItem(idx, 3, it_address) idx += 1 self.tbl_tcache.resizeRowsToContents() self.tbl_tcache.resizeColumnsToContents() self.tbl_tcache.setSortingEnabled(True) def table_on_change_index(self): sender = self.sender() items = sender.selectedItems() if items and len(items): entry_addr = int(items[3].text(), 16) entry_size = int(items[1].text(), 16) self.show_chain(entry_addr, entry_size) def show_selected_chunk(self): items = self.sender().selectedItems() entry_addr = int(items[3].text(), 16) norm_address = "0x%x-0x%x" % (entry_addr, config.ptr_size * 2) self.parent.show_chunk_info(norm_address) def show_chain(self, address, size): title = 'Tcache[0x%02x]' % size chain, b_error = self.heap.tcache_chain(address) html_chain = make_html_chain(title, chain, b_error) self.te_tcache_chain.clear() self.te_tcache_chain.insertHtml(html_chain)
class BinsWidget(CustomWidget): def __init__(self, parent=None): super(BinsWidget, self).__init__(parent) self.show_bases = False self._create_gui() def _create_gui(self): self._create_tables() vbox_fastbins = QtWidgets.QVBoxLayout() vbox_fastbins.addWidget(QtWidgets.QLabel("Fastbins")) vbox_fastbins.addWidget(self.tbl_fastbins) vbox_unsortedbin = QtWidgets.QVBoxLayout() vbox_unsortedbin.addWidget(QtWidgets.QLabel("Unsorted")) vbox_unsortedbin.addWidget(self.tbl_unsortedbin) vbox_smallbins = QtWidgets.QVBoxLayout() vbox_smallbins.addWidget(QtWidgets.QLabel("Small bins")) vbox_smallbins.addWidget(self.tbl_smallbins) vbox_largebins = QtWidgets.QVBoxLayout() vbox_largebins.addWidget(QtWidgets.QLabel("Large bins")) vbox_largebins.addWidget(self.tbl_largebins) self.te_chain_info = QtWidgets.QTextEdit() self.te_chain_info.setReadOnly(True) vbox_chain = QtWidgets.QVBoxLayout() vbox_chain.addWidget(QtWidgets.QLabel("Chain info")) vbox_chain.addWidget(self.te_chain_info) grid_bins = QtWidgets.QGridLayout() grid_bins.addLayout(vbox_fastbins, 0, 0) grid_bins.addLayout(vbox_unsortedbin, 0, 1) grid_bins.addLayout(vbox_smallbins, 1, 0) grid_bins.addLayout(vbox_largebins, 1, 1) grid_bins.addLayout(vbox_chain, 2, 0, 2, 2) self.setLayout(grid_bins) def _create_tables(self): # --- Fastbins table self.tbl_fastbins = TTable(['#', 'size', 'fd']) self.tbl_fastbins.resize_columns([50, 100, 155]) self.tbl_fastbins.customContextMenuRequested.connect(self.context_menu) self.tbl_fastbins.cellDoubleClicked.connect(self.table_on_change) self.tbl_fastbins.itemSelectionChanged.connect(self.table_on_change) # --- Unsortedbin self.tbl_unsortedbin = TTable(['#', 'fd', 'bk', 'base']) self.tbl_unsortedbin.resize_columns([50, 155, 155, 155]) self.tbl_unsortedbin.customContextMenuRequested.connect( self.context_menu) self.tbl_unsortedbin.cellDoubleClicked.connect(self.table_on_change) self.tbl_unsortedbin.itemClicked.connect(self.table_on_change) # --- Smallbins self.tbl_smallbins = TTable(['#', 'size', 'fd', 'bk', 'base']) self.tbl_smallbins.resize_columns([50, 100, 155, 155, 155]) self.tbl_smallbins.customContextMenuRequested.connect( self.context_menu) self.tbl_smallbins.cellDoubleClicked.connect(self.table_on_change) self.tbl_smallbins.itemSelectionChanged.connect(self.table_on_change) # --- Largebins self.tbl_largebins = TTable(['#', 'size', 'fd', 'bk', 'base']) self.tbl_largebins.resize_columns([50, 100, 155, 155, 155]) self.tbl_largebins.customContextMenuRequested.connect( self.context_menu) self.tbl_largebins.cellDoubleClicked.connect(self.table_on_change) self.tbl_largebins.itemSelectionChanged.connect(self.table_on_change) self.bin_tables = { self.tbl_fastbins: { 'title': 'fastbins', 'address': 2, 'size': 1, 'base': None }, self.tbl_unsortedbin: { 'title': 'unsortedbin', 'address': 1, 'size': None, 'base': 3 }, self.tbl_smallbins: { 'title': 'smallbins', 'address': 2, 'size': 1, 'base': 4 }, self.tbl_largebins: { 'title': 'largebins', 'address': 2, 'size': 1, 'base': 4 } } def context_menu(self, position): sender = self.sender() menu = QtWidgets.QMenu() copy_action = menu.addAction("Copy value") copy_row = menu.addAction("Copy row") view_chunk = menu.addAction("View chunk") jump_to = menu.addAction("Jump to") graphview_action = menu.addAction("GraphView") self.show_uninit = menu.addAction("Show uninitialized bins (bases)") self.show_uninit.setCheckable(True) self.show_uninit.setChecked(self.show_bases) action = menu.exec_(sender.mapToGlobal(position)) if action == copy_action: sender.copy_selected_value() if action == copy_row: sender.copy_selected_row() elif action == jump_to: self.jmp_to_selected_chunk() elif action == view_chunk: self.show_selected_chunk() elif action == self.show_uninit: self.show_bases = self.show_uninit.isChecked() self.populate_tables() elif action == graphview_action: if sender is self.tbl_fastbins: idx = int(sender.item(sender.currentRow(), 0).text()) size = int(sender.item(sender.currentRow(), 1).text(), 16) graph = BinGraph(self, info={ 'type': 'fastbin', 'fastbin_id': idx, 'size': size }) graph.Show() elif sender is self.tbl_unsortedbin: idx = sender.item(sender.currentRow(), 0).text() base = int(sender.item(sender.currentRow(), 3).text(), 16) graph = BinGraph(self, info={ 'type': 'unsortedbin', 'bin_id': idx, 'bin_base': base }) graph.Show() elif sender is self.tbl_smallbins: idx = sender.item(sender.currentRow(), 0).text() size = int(sender.item(sender.currentRow(), 1).text(), 16) base = int(sender.item(sender.currentRow(), 4).text(), 16) graph = BinGraph(self, info={ 'type': 'smallbin', 'bin_id': idx, 'size': size, 'bin_base': base }) graph.Show() elif sender is self.tbl_largebins: idx = sender.item(sender.currentRow(), 0).text() size = int(sender.item(sender.currentRow(), 1).text(), 16) base = int(sender.item(sender.currentRow(), 4).text(), 16) graph = BinGraph(self, info={ 'type': 'largebin', 'bin_id': idx, 'size': size, 'bin_base': base }) graph.Show() def show_bin_chain(self): sender = self.sender() stop = 0 size = None row = sender.selectedItems() if not len(row): return bin_cols = self.bin_tables[sender] address = int(row[bin_cols['address']].text(), 16) if bin_cols['size']: size = int(row[bin_cols['size']].text(), 16) if bin_cols['base']: stop = int(row[bin_cols['base']].text(), 16) self.show_chain(bin_cols['title'], address, size, stop) def show_selected_chunk(self): chunk_addr = self.get_selected_chunk_addr() if chunk_addr: self.parent.show_chunk_info(chunk_addr) def jmp_to_selected_chunk(self): chunk_addr = self.get_selected_chunk_addr() if chunk_addr: idc.Jump(chunk_addr) def get_selected_chunk_addr(self): sender = self.sender() items = sender.selectedItems() if len(items): col_id = self.bin_tables[sender]['address'] address = int(items[col_id].text(), 16) return address return None def table_on_change(self): self.show_selected_chunk() self.show_bin_chain() def show_chain(self, title, address, size, stop=0): if size: title = '%s[0x%02x]' % (title, size) if address != 0 and address != idc.BADADDR: chain, b_error = self.heap.chunk_chain(address, stop) html_chain = make_html_chain(title, chain, b_error) self.te_chain_info.clear() self.te_chain_info.insertHtml(html_chain) def populate_tbl_fastbins(self): self.tbl_fastbins.clearContents() self.tbl_fastbins.setRowCount(0) self.tbl_fastbins.setSortingEnabled(False) idx = 0 fastbins = self.heap.get_fastbins(self.cur_arena) for id_fast, (size, fast_chunk) in enumerate(fastbins.iteritems()): if not self.show_bases and fast_chunk == 0: continue self.tbl_fastbins.insertRow(idx) it_bin_num = QtWidgets.QTableWidgetItem("%d" % id_fast) it_size = QtWidgets.QTableWidgetItem("%#x" % size) it_fastchunk = QtWidgets.QTableWidgetItem("%#x" % fast_chunk) self.tbl_fastbins.setItem(idx, 0, it_bin_num) self.tbl_fastbins.setItem(idx, 1, it_size) self.tbl_fastbins.setItem(idx, 2, it_fastchunk) idx += 1 if idx: self.tbl_fastbins.resize_to_contents() self.tbl_fastbins.setSortingEnabled(True) def populate_tbl_unsortedbin(self): base, fd, bk = self.heap.get_unsortedbin(self.cur_arena) self.tbl_unsortedbin.clearContents() self.tbl_unsortedbin.setRowCount(0) # points to current base if not self.show_bases and fd == base: return self.tbl_unsortedbin.setSortingEnabled(False) self.tbl_unsortedbin.insertRow(0) it_bin_num = QtWidgets.QTableWidgetItem("1") it_fd = QtWidgets.QTableWidgetItem("%#x" % fd) it_bk = QtWidgets.QTableWidgetItem("%#x" % bk) it_base = QtWidgets.QTableWidgetItem("%#x" % base) self.tbl_unsortedbin.setItem(0, 0, it_bin_num) self.tbl_unsortedbin.setItem(0, 1, it_fd) self.tbl_unsortedbin.setItem(0, 2, it_bk) self.tbl_unsortedbin.setItem(0, 3, it_base) self.tbl_unsortedbin.resizeRowsToContents() self.tbl_unsortedbin.resizeColumnsToContents() self.tbl_unsortedbin.setSortingEnabled(True) def populate_tbl_smallbins(self): self.tbl_smallbins.clearContents() self.tbl_smallbins.setRowCount(0) self.tbl_smallbins.setSortingEnabled(False) smallbins = self.heap.get_smallbins(self.cur_arena) idx = 0 for bin_id, smallbin in smallbins.iteritems(): # point to himself if not self.show_bases and smallbin['base'] == smallbin['fd']: continue self.tbl_smallbins.insertRow(idx) it_bin_num = QtWidgets.QTableWidgetItem("%d" % bin_id) it_size = QtWidgets.QTableWidgetItem("%#x" % smallbin['size']) it_fd = QtWidgets.QTableWidgetItem("%#x" % smallbin['fd']) it_bk = QtWidgets.QTableWidgetItem("%#x" % smallbin['bk']) it_base = QtWidgets.QTableWidgetItem("%#x" % smallbin['base']) self.tbl_smallbins.setItem(idx, 0, it_bin_num) self.tbl_smallbins.setItem(idx, 1, it_size) self.tbl_smallbins.setItem(idx, 2, it_fd) self.tbl_smallbins.setItem(idx, 3, it_bk) self.tbl_smallbins.setItem(idx, 4, it_base) idx += 1 self.tbl_smallbins.resizeRowsToContents() self.tbl_smallbins.resizeColumnsToContents() self.tbl_smallbins.setSortingEnabled(True) def populate_tbl_largebins(self): self.tbl_largebins.clearContents() self.tbl_largebins.setRowCount(0) self.tbl_largebins.setSortingEnabled(False) largebins = self.heap.get_largebins(self.cur_arena) idx = 0 for bin_id, largebin in largebins.iteritems(): # point to himself if not self.show_bases and largebin['base'] == largebin['fd']: continue self.tbl_largebins.insertRow(idx) it_bin_num = QtWidgets.QTableWidgetItem("%d" % bin_id) it_size = QtWidgets.QTableWidgetItem("%#x" % largebin['size']) it_fd = QtWidgets.QTableWidgetItem("%#x" % largebin['fd']) it_bk = QtWidgets.QTableWidgetItem("%#x" % largebin['bk']) it_base = QtWidgets.QTableWidgetItem("%#x" % largebin['base']) self.tbl_largebins.setItem(idx, 0, it_bin_num) self.tbl_largebins.setItem(idx, 1, it_size) self.tbl_largebins.setItem(idx, 2, it_fd) self.tbl_largebins.setItem(idx, 3, it_bk) self.tbl_largebins.setItem(idx, 4, it_base) idx += 1 self.tbl_largebins.resizeRowsToContents() self.tbl_largebins.resizeColumnsToContents() self.tbl_largebins.setSortingEnabled(True) def populate_tables(self): self.populate_tbl_fastbins() self.populate_tbl_unsortedbin() self.populate_tbl_smallbins() self.populate_tbl_largebins()
class ArenaWidget(CustomWidget): def __init__(self, parent=None): super(ArenaWidget, self).__init__(parent) self._create_gui() def _create_gui(self): self._create_table() self._create_menu() def _create_menu(self): self.t_top_addr = QtWidgets.QLineEdit() self.t_top_addr.setFixedWidth(130) self.t_top_addr.setReadOnly(True) self.t_last_remainder = QtWidgets.QLineEdit() self.t_last_remainder.setFixedWidth(150) self.t_last_remainder.setReadOnly(True) self.lbl_top_warning = QtWidgets.QLabel() self.lbl_top_warning.setStyleSheet('color: red') self.lbl_top_warning.setVisible(False) self.t_attached_threads = QtWidgets.QLineEdit() self.t_attached_threads.setFixedWidth(130) self.t_attached_threads.setReadOnly(True) hbox_arena_top = QtWidgets.QHBoxLayout() hbox_arena_top.addWidget(QtWidgets.QLabel('Top:')) hbox_arena_top.addWidget(self.t_top_addr) hbox_arena_top.addWidget(QtWidgets.QLabel('Last remainder:')) hbox_arena_top.addWidget(self.t_last_remainder) btn_malloc_par = QtWidgets.QPushButton("Struct") btn_malloc_par.clicked.connect(self.btn_struct_on_click) hbox_arena_top.addWidget(btn_malloc_par) hbox_arena_top.addStretch(1) hbox_arena_others = QtWidgets.QHBoxLayout() hbox_arena_others.addWidget(self.lbl_top_warning) self.bold_font = QtGui.QFont() self.bold_font.setBold(True) grid_arenas = QtWidgets.QGridLayout() grid_arenas.addLayout(hbox_arena_top, 0, 0) grid_arenas.addLayout(hbox_arena_others, 1, 0) grid_arenas.addWidget(self.tbl_parsed_heap, 2, 0) self.setLayout(grid_arenas) def _create_table(self): self.tbl_parsed_heap = TTable( ['address', 'prev', 'size', 'status', 'fd', 'bk']) self.tbl_parsed_heap.resize_columns([155, 40, 100, 120, 155, 155]) self.tbl_parsed_heap.customContextMenuRequested.connect( self.context_menu) self.tbl_parsed_heap.itemSelectionChanged.connect( self.view_selected_chunk) def context_menu(self, position): sender = self.sender() menu = QtWidgets.QMenu() copy_action = menu.addAction("Copy value") copy_row = menu.addAction("Copy row") view_chunk = menu.addAction("View chunk") jump_to = menu.addAction("Jump to chunk") jump_to_u = menu.addAction("Jump to user-data") check_freaable = menu.addAction("Check freeable") chunk_addr = int(sender.item(sender.currentRow(), 0).text(), 16) action = menu.exec_(sender.mapToGlobal(position)) if action == copy_action: sender.copy_selected_value() if action == copy_row: sender.copy_selected_row() elif action == jump_to: idc.jumpto(chunk_addr) elif action == jump_to_u: idc.jumpto(chunk_addr + (config.ptr_size * 2)) elif action == view_chunk: self.view_selected_chunk() elif action == check_freaable: self.parent.check_freeable(chunk_addr) def view_selected_chunk(self): items = self.tbl_parsed_heap.selectedItems() if len(items) > 0: chunk_addr = int(items[0].text(), 16) self.parent.show_chunk_info(chunk_addr) def populate_table(self): cur_arena = self.cur_arena arena = self.heap.get_arena(cur_arena) self.t_top_addr.setText("%#x" % arena.top) self.t_last_remainder.setText("%#x" % arena.last_remainder) self.t_attached_threads.setText("%d" % arena.attached_threads) top_segname = idc.get_segm_name(arena.top) if not any(s in top_segname for s in ['heap', 'debug']): self.lbl_top_warning.setVisible(True) self.lbl_top_warning.setText("Top points to '%s' segment" % top_segname) else: self.lbl_top_warning.setVisible(False) self.tbl_parsed_heap.clearContents() self.tbl_parsed_heap.setRowCount(0) self.tbl_parsed_heap.setSortingEnabled(False) parsed_heap = self.heap.parse_heap(cur_arena) for idx, chunk in enumerate(parsed_heap): self.tbl_parsed_heap.insertRow(idx) it_address = QtWidgets.QTableWidgetItem("%#x" % chunk['address']) it_prev = QtWidgets.QTableWidgetItem("%#x" % chunk['prev']) it_size = QtWidgets.QTableWidgetItem("%#x" % chunk['size']) it_status = QtWidgets.QTableWidgetItem("%s" % chunk['status']) it_fd = QtWidgets.QTableWidgetItem("%#x" % chunk['fd']) it_bk = QtWidgets.QTableWidgetItem("%#x" % chunk['bk']) if 'Freed' in chunk['status']: it_status.setForeground(QtGui.QColor('red')) elif chunk['status'] == 'Corrupt': it_status.setForeground(QtGui.QColor.fromRgb(213, 94, 0)) it_status.setFont(self.bold_font) else: it_status.setForeground(QtGui.QColor('blue')) self.tbl_parsed_heap.setItem(idx, 0, it_address) self.tbl_parsed_heap.setItem(idx, 1, it_prev) self.tbl_parsed_heap.setItem(idx, 2, it_size) self.tbl_parsed_heap.setItem(idx, 3, it_status) self.tbl_parsed_heap.setItem(idx, 4, it_fd) self.tbl_parsed_heap.setItem(idx, 5, it_bk) self.tbl_parsed_heap.resizeRowsToContents() self.tbl_parsed_heap.resizeColumnsToContents() self.tbl_parsed_heap.setSortingEnabled(True) self.tbl_parsed_heap.sortByColumn(0, QtCore.Qt.DescendingOrder) def btn_struct_on_click(self): self.parent.show_malloc_state()
class TracerWidget(CustomWidget): def __init__(self, parent=None): super(TracerWidget, self).__init__(parent) self.tracer = None self.free_chunks = {} self.allocated_chunks = {} self.row_info = {} self._create_gui() def _create_gui(self): self._create_table() self._create_menu() def _create_table(self): self.tbl_traced_chunks = TTable(['#','User-address', 'Action', 'Arg 1', 'Arg 2', \ 'Thread', 'Caller', 'Info']) self.tbl_traced_chunks.setRowCount(0) self.tbl_traced_chunks.resize_columns( [40, 155, 80, 80, 80, 80, 200, 200]) self.tbl_traced_chunks.customContextMenuRequested.connect( self.context_menu) self.tbl_traced_chunks.itemSelectionChanged.connect( self.view_selected_chunk) self.tbl_traced_chunks.cellDoubleClicked.connect( self.traced_double_clicked) def _create_menu(self): cb_enable_trace = QtWidgets.QCheckBox() cb_enable_trace.stateChanged.connect(self.cb_tracing_changed) self.cb_stop_during_tracing = QtWidgets.QCheckBox() btn_dump_trace = QtWidgets.QPushButton("Dump trace") btn_dump_trace.clicked.connect(self.btn_dump_trace_on_click) btn_villoc_trace = QtWidgets.QPushButton("Villoc") btn_villoc_trace.clicked.connect(self.btn_villoc_on_click) btn_clear_trace = QtWidgets.QPushButton("Clear") btn_clear_trace.clicked.connect(self.btn_clear_on_click) hbox_enable_trace = QtWidgets.QHBoxLayout() hbox_enable_trace.addWidget(QtWidgets.QLabel("Enable tracing")) hbox_enable_trace.addWidget(cb_enable_trace) hbox_enable_trace.addWidget(QtWidgets.QLabel("Stop during tracing")) hbox_enable_trace.addWidget(self.cb_stop_during_tracing) hbox_enable_trace.addWidget(btn_dump_trace) hbox_enable_trace.addWidget(btn_villoc_trace) hbox_enable_trace.addWidget(btn_clear_trace) hbox_enable_trace.addStretch(1) hbox_trace = QtWidgets.QVBoxLayout() hbox_trace.addLayout(hbox_enable_trace) hbox_trace.addWidget(QtWidgets.QLabel("Traced chunks")) hbox_trace.addWidget(self.tbl_traced_chunks) self.cb_stop_during_tracing.setChecked(config.stop_during_tracing) cb_enable_trace.setChecked(config.start_tracing_at_startup) self.setLayout(hbox_trace) def context_menu(self, position): sender = self.sender() menu = QtWidgets.QMenu() show_warn_info = None copy_action = menu.addAction("Copy value") copy_row = menu.addAction("Copy row") view_chunk = menu.addAction("View chunk") jump_to = menu.addAction("Jump to chunk") jump_to_u = menu.addAction("Jump to user-data") goto_caller = menu.addAction("Jump to caller") current_row = self.sender().currentRow() if current_row in self.row_info: show_warn_info = menu.addAction("Show warning info") chunk_addr = int(sender.item(sender.currentRow(), 1).text(), 16) action = menu.exec_(sender.mapToGlobal(position)) if action == copy_action: sender.copy_selected_value() if action == copy_row: sender.copy_selected_row() elif action == jump_to: idc.Jump(chunk_addr - (config.ptr_size * 2)) elif action == jump_to_u: idc.Jump(chunk_addr) elif action == goto_caller: caller_str = str(sender.item(sender.currentRow(), 6).text()) if caller_str: idc.Jump(str2ea(caller_str)) elif action == view_chunk: self.view_selected_chunk() elif show_warn_info and action == show_warn_info: self.traced_double_clicked() def enable_tracing(self): stop_during_tracing = not self.cb_stop_during_tracing.isChecked() self.tracer = HeapTracer(self.append_chunk, stop_during_tracing) self.tracer.hook() self.parent.tracer = self.tracer self.free_chunks = {} self.allocated_chunks = {} log("Tracer enabled") def disable_tracing(self): if self.tracer: self.tracer.remove_bps() self.tracer.unhook() self.parent.tracer = None log("Trace disabled") def cb_tracing_changed(self, state): if state == QtCore.Qt.Checked: self.cb_stop_during_tracing.setEnabled(False) self.enable_tracing() else: self.cb_stop_during_tracing.setEnabled(True) self.disable_tracing() def get_value(self, row, name): cols = { 'address': 1, 'action': 2, 'arg1': 3, 'arg2': 4, 'thread': 5, 'caller': 6, 'info': 7 } return self.tbl_traced_chunks.item(row, cols[name]).text() def dump_table_for_villoc(self): result = '' for i in range(self.tbl_traced_chunks.rowCount()): action = self.get_value(i, "action") info = self.get_value(i, "info") if info != "N/A": result += "@villoc(%s) = <void>\n" % info if action == "malloc": arg1 = self.get_value(i, "arg1") ret = self.get_value(i, "address") result += "malloc(%d) = " % int(arg1, 16) result += ret if ret.startswith("0x") else '<error>' elif action == "free": address = self.get_value(i, "address") result += "free(%s) = <void>" % address elif action == "realloc": address = self.get_value(i, "address") arg1 = self.get_value(i, "arg1") arg2 = self.get_value(i, "arg2") result += "realloc(%s, %d) = %s" % (arg1, int(arg2, 16), address) elif action == "calloc": address = self.get_value(i, "address") arg1 = self.get_value(i, "arg1") arg2 = self.get_value(i, "arg2") result += "calloc(%d, %d) = %s" % (int(arg1, 16), int( arg2, 16), address) result += '\n' return result def btn_villoc_on_click(self): if self.tbl_traced_chunks.rowCount() == 0: idaapi.warning("Empty table") return try: villoc.Block.header = config.ptr_size * 2 villoc.Block.round = self.parent.heap.malloc_alignment villoc.Block.minsz = self.parent.heap.min_chunk_size result = self.dump_table_for_villoc() html = villoc.build_html(result) h, filename = tempfile.mkstemp(suffix='.html') with open(filename, 'wb') as f: f.write(html) url = QtCore.QUrl.fromLocalFile(filename) QtGui.QDesktopServices.openUrl(url) except Exception as e: idaapi.warning(traceback.format_exc()) def btn_dump_trace_on_click(self): if self.tbl_traced_chunks.rowCount() == 0: idaapi.warning("Empty table") return filename = AskFile(1, "*.csv", "Select the file to store tracing results") if not filename: return try: result = self.tbl_traced_chunks.dump_table_as_csv() with open(filename, 'wb') as f: f.write(result) except Exception as e: idaapi.warning(traceback.format_exc()) def btn_clear_on_click(self): self.tbl_traced_chunks.clear_table() def view_selected_chunk(self): items = self.tbl_traced_chunks.selectedItems() if len(items) > 0: chunk_addr = int(items[1].text(), 16) norm_address = "0x%x-0x%x" % (chunk_addr, config.ptr_size * 2) self.parent.show_chunk_info(norm_address) def traced_double_clicked(self): current_row = self.sender().currentRow() if current_row in self.row_info: idaapi.info(self.row_info[current_row]) def update_allocated_chunks(self): for start, info in self.allocated_chunks.iteritems(): try: chunk = self.heap.get_chunk(start) if chunk.norm_size != info['size']: info['size'] = chunk.norm_size info['end'] = start + chunk.norm_size except: continue def malloc_consolidate(self): fastbins = self.heap.get_all_fastbins_chunks(self.cur_arena) if len(fastbins) == 0: return False self.free_chunks = {} for addr in fastbins: chunk = self.get_chunk(chunk) self.free_chunks[addr] = { 'size': chunk.norm_size, 'end': addr + chunk.norm_size } return True def append_chunk(self, addr, action, arg1, arg2, thread_id, caller, from_ret=True): warn_msg = None row_msg = None row_color = None warn_types = { 'overlap': ['Overlap detected', QtGui.QColor(255, 204, 0)], 'double_free': ['Double free detected', QtGui.QColor(255, 153, 102)] } if addr == 0: return if action == "free" and from_ret: self.parent.reload_gui_info() return if config.detect_double_frees_and_overlaps: if action == "malloc": chunk_addr = self.heap.mem2chunk(addr) chunk_size = self.heap.get_chunk(chunk_addr).norm_size chunk_end = chunk_addr + chunk_size chunk = {'size': chunk_size, 'end': chunk_end} overlap = check_overlap(chunk_addr, chunk_size, self.allocated_chunks) if overlap is not None: overlap_size = self.allocated_chunks[overlap]['size'] row_msg = "<h3>Heap overlap detected</h3>" row_msg += "<ul><li>malloc(<b>%d</b>) = <b>%#x</b> (<b>%#x</b>) of size %#x</li>\n" % \ (arg1, addr, chunk_addr, chunk_size) row_msg += "<li><b>%#x</b> overlaps in-used chunk (<b>%#x</b>) " % ( chunk_addr, overlap) row_msg += "of size <b>%#x</b></li></ul>\n" % overlap_size warn_msg, row_color = warn_types['overlap'] del self.allocated_chunks[overlap] self.allocated_chunks[chunk_addr] = chunk if chunk_addr in self.free_chunks: free_chunk = self.free_chunks[chunk_addr] del self.free_chunks[chunk_addr] if chunk['size'] != free_chunk['size']: split_addr = chunk_addr + chunk_size split_size = free_chunk['size'] - chunk['size'] split_end = split_addr + split_size self.free_chunks[split_addr] = { 'size': split_size, 'end': split_end } if arg1 >= self.heap.min_large_size: """ If this is a large request, consolidate fastbins """ self.malloc_consolidate() elif action == "free": chunk_addr = self.heap.mem2chunk(addr) chunk = self.heap.get_chunk(chunk_addr) chunk_size = chunk.norm_size prev_inuse = chunk.prev_inuse chunk_end = chunk_addr + chunk_size overlap = check_overlap(chunk_addr, chunk_size, self.free_chunks) if overlap is not None: row_msg = "<h3>Double free detected</h3>" row_msg += "<ul><li>free (<b>%#x</b>) of size <b>%#x</b></li>" % \ (chunk_addr, chunk_size) row_msg += "<li>Double free: <b>%#x</b> of size <b>%#x</b></li></ul>\n" % \ (overlap, self.free_chunks[overlap]['size']) warn_msg, row_color = warn_types['double_free'] del self.free_chunks[overlap] in_tcache = False tc_idx = self.heap.csize2tidx(chunk_size) if self.heap.tcache_enabled and tc_idx < TCACHE_BINS: tcache = self.heap.get_tcache_struct(self.cur_arena) tc_idx = self.heap.csize2tidx(chunk_size) if tcache.counts[tc_idx] < TCACHE_COUNT: self.free_chunks[chunk_addr] = { 'size': chunk_size, 'end': chunk_end } in_tcache = True if chunk_addr in self.allocated_chunks: del self.allocated_chunks[chunk_addr] if not in_tcache: # fastbins if chunk_size <= config.ptr_size * 16: self.free_chunks[chunk_addr] = { 'size': chunk_size, 'end': chunk_end } if chunk_addr in self.allocated_chunks: del self.allocated_chunks[chunk_addr] # > global_max_fast else: if prev_inuse == 0: prev_size = chunk.prev_size prev_addr = chunk_addr - prev_size if prev_addr in self.free_chunks: prev_size += chunk_size del self.free_chunks[prev_addr] next_chunk = {} av = self.heap.get_arena(self.cur_arena) next_addr = chunk_addr + chunk_size if next_addr == av.top: if chunk_addr in self.allocated_chunks: del self.allocated_chunks[chunk_addr] else: next_chunk = self.heap.get_chunk(next_addr) next_size = next_chunk.norm_size next_inuse = self.heap.get_chunk( next_addr + next_size).prev_inuse if next_inuse == 0: if prev_inuse: if next_addr in self.free_chunks: chunk_size += next_size del self.free_chunks[next_addr] else: if next_addr in self.free_chunks: prev_size += next_size del self.free_chunks[next_addr] # prev freed if prev_inuse == 0: if chunk_addr in self.allocated_chunks: del self.allocated_chunks[chunk_addr] chunk_addr = prev_addr chunk_size = prev_size chunk_end = prev_addr + prev_size self.free_chunks[chunk_addr] = { 'size': chunk_size, 'end': chunk_end } if chunk_addr in self.allocated_chunks: del self.allocated_chunks[chunk_addr] if chunk_size > 0x10000: self.malloc_consolidate() elif action == "realloc": self.update_allocated_chunks() num_rows = self.tbl_traced_chunks.rowCount() if row_msg is not None: self.row_info[num_rows] = row_msg self.tbl_traced_chunks.setSortingEnabled(False) self.tbl_traced_chunks.insertRow(num_rows) caller_name = get_func_name_offset(caller) if caller else '' arg1 = "%#x" % arg1 if arg1 is not None else 'N/A' arg2 = "%#x" % arg2 if arg2 is not None else 'N/A' warn_msg_s = warn_msg if warn_msg else 'N/A' it_count = QtWidgets.QTableWidgetItem("%d" % num_rows) it_chunk = QtWidgets.QTableWidgetItem("%#x" % addr) it_action = QtWidgets.QTableWidgetItem(action) it_arg1 = QtWidgets.QTableWidgetItem(arg1) it_arg2 = QtWidgets.QTableWidgetItem(arg2) it_thread = QtWidgets.QTableWidgetItem("%d" % thread_id) it_caller = QtWidgets.QTableWidgetItem(caller_name) it_info = QtWidgets.QTableWidgetItem(warn_msg_s) self.tbl_traced_chunks.setItem(num_rows, 0, it_count) self.tbl_traced_chunks.setItem(num_rows, 1, it_chunk) self.tbl_traced_chunks.setItem(num_rows, 2, it_action) self.tbl_traced_chunks.setItem(num_rows, 3, it_arg1) self.tbl_traced_chunks.setItem(num_rows, 4, it_arg2) self.tbl_traced_chunks.setItem(num_rows, 5, it_thread) self.tbl_traced_chunks.setItem(num_rows, 6, it_caller) self.tbl_traced_chunks.setItem(num_rows, 7, it_info) self.tbl_traced_chunks.resizeRowsToContents() self.tbl_traced_chunks.resizeColumnsToContents() self.tbl_traced_chunks.setSortingEnabled(True) if warn_msg: self.tbl_traced_chunks.set_row_color(num_rows, row_color) # only reload when the function returns if from_ret: self.parent.reload_gui_info()