MindshaRE is our periodic look at some simple reverse engineering tips and tricks. The goal is to keep things small and discuss every day aspects of reversing. You can view previous entries by going through our blog history or querying a search engine for dvlabs mindshare.
In a prior post I showed how if you compile a newer version of PySide you can get access to the QCoreApplication instance object within IDA. In that entry, I was using that ability to get access to a specific QAction object in order to hook one of IDA's keyboard shortcuts. After publishing the post I received a bunch of suggestions from people about how that could be accomplished without using PySide at all. However, I wanted to show that there is a lot more you can do with the ability to introspect PySide objects inside IDA... so, here's one example. Hopefully, you will get some ideas from this simple example and start thinking of more powerful things you can accomplish with these techniques.
In IDA, the "Output window" allows you to execute arbitary Python statements within IDA. This is indeed useful, but the one feature, in my opinion, the output window is lacking is the ability to apply syntax highlighting to the code written inside of it. In this post I will show how to remedy that.
First off, we need to get access to the text document object IDA uses inside the output window. After playing around by crawling through PySide objects in IDA I found a reliable way to do so:
from PySide import QtCore
qapp = QtCore.QCoreApplication.instance()
all_widgets = qapp.allWidgets()
def getoutputwindow(widgets):
for w in widgets:
y = w.objectName()
if y:
if y == "Output window":
return w
res = getoutputwindow(all_widgets)
x = res.children()[2].children()[1]
textdocument = x.children()[1].children()[0]
At this point, we've got a reference to the QTextDocument object IDA uses. In order to apply syntax highlighting to the text of this object, we can make use of PySide's QSyntaxHighlighter class. Luckily, I found some code online that, after some minimal modifications, worked quite well.
As the code linked above was written for PyQt (not PySide), I had to make the following change, as QString objects are not present in PySide and thus the object returned by a QRegExp is an actual string and has no .length() method:
#length = expression.cap(nth).length() length = len(expression.cap(nth))
Additionally, the code for handling multi-line strings caused IDA to infinite loop, and I figured I could live without multiline strings colored, I made the following change:
def match_multiline(self, text, delimiter, in_state, style):
return False
With those two changes made, I was able to take the QTextDocument object I found and apply the syntax highlighting to it:
h = MyHighlighter(textdocument)
This worked quite well:
For your reference (or use), the entire code needed is below, sorry it's not syntax colored here ;) :
from PySide import QtCore
qapp = QtCore.QCoreApplication.instance()
all_widgets = qapp.allWidgets()
def getoutputwindow(widgets):
for w in widgets:
y = w.objectName()
if y:
if y == "Output window":
return w
res = getoutputwindow(all_widgets)
textdocument = res.children()[2].children()[1].children()[1].children()[0]
def format(color, style=''):
"""Return a QTextCharFormat with the given attributes.
"""
_color = QtGui.QColor()
_color.setNamedColor(color)
_format = QtGui.QTextCharFormat()
_format.setForeground(_color)
if 'bold' in style:
_format.setFontWeight(QtGui.QFont.Bold)
if 'italic' in style:
_format.setFontItalic(True)
return _format
# Syntax styles that can be shared by all languages
STYLES = {
'keyword': format('blue'),
'operator': format('red'),
'brace': format('maroon'),
'defclass': format('black', 'bold'),
'string': format('magenta'),
'string2': format('darkMagenta'),
'comment': format('darkGreen', 'italic'),
'self': format('black', 'italic'),
'numbers': format('brown'),
}
class MyHighlighter(QtGui.QSyntaxHighlighter):
"""Syntax highlighter for the Python language.
"""
# Python keywords
keywords = [
'and', 'assert', 'break', 'class', 'continue', 'def',
'del', 'elif', 'else', 'except', 'exec', 'finally',
'for', 'from', 'global', 'if', 'import', 'in',
'is', 'lambda', 'not', 'or', 'pass', 'print',
'raise', 'return', 'try', 'while', 'yield',
'None', 'True', 'False',
]
# Python operators
operators = [
'=',
# Comparison
'==', '!=', '<', '<=', '>', '>=',
# Arithmetic
'\+', '-', '\*', '/', '//', '\%', '\*\*',
# In-place
'\+=', '-=', '\*=', '/=', '\%=',
# Bitwise
'\^', '\|', '\&', '\~', '>>', '<<',
]
# Python braces
braces = [
'\{', '\}', '\(', '\)', '\[', '\]',
]
def __init__(self, document):
QtGui.QSyntaxHighlighter.__init__(self, document)
self.tri_single = (QtCore.QRegExp("'''"), 1, STYLES['string2'])
self.tri_double = (QtCore.QRegExp('"""'), 2, STYLES['string2'])
rules = []
# Keyword, operator, and brace rules
rules += [(r'\b%s\b' % w, 0, STYLES['keyword'])
for w in MyHighlighter.keywords]
rules += [(r'%s' % o, 0, STYLES['operator'])
for o in MyHighlighter.operators]
rules += [(r'%s' % b, 0, STYLES['brace'])
for b in MyHighlighter.braces]
# All other rules
rules += [
# 'self'
(r'\bself\b', 0, STYLES['self']),
# Double-quoted string, possibly containing escape sequences
(r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']),
# Single-quoted string, possibly containing escape sequences
(r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']),
# 'def' followed by an identifier
(r'\bdef\b\s*(\w+)', 1, STYLES['defclass']),
# 'class' followed by an identifier
(r'\bclass\b\s*(\w+)', 1, STYLES['defclass']),
# From '#' until a newline
(r'#[^\n]*', 0, STYLES['comment']),
# Numeric literals
(r'\b[+-]?[0-9]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, STYLES['numbers']),
(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']),
]
# Build a QRegExp for each pattern
self.rules = [(QtCore.QRegExp(pat), index, fmt)
for (pat, index, fmt) in rules]
def highlightBlock(self, text):
"""Apply syntax highlighting to the given block of text.
"""
# Do other syntax formatting
for expression, nth, format in self.rules:
index = expression.indexIn(text, 0)
while index >= 0:
# We actually want the index of the nth match
index = expression.pos(nth)
#length = expression.cap(nth).length()
length = len(expression.cap(nth))
self.setFormat(index, length, format)
index = expression.indexIn(text, index + length)
self.setCurrentBlockState(0)
# Do multi-line strings
in_multiline = self.match_multiline(text, *self.tri_single)
if not in_multiline:
in_multiline = self.match_multiline(text, *self.tri_double)
def match_multiline(self, text, delimiter, in_state, style):
return False
h = MyHighlighter(textdocument)
That's all, hope someone finds this useful.
--
Aaron
@aaronportnoy
