The main feature of the upcoming 0.9.3 version of the Profiler is the expansion of the public SDK. This basically means that a consistent subset of the internal classes will be exposed. Although it’s a subset, there’s no way to document all methods and functions. Fortunately, many of them should be quite intuitive.
Some of the most common important classes are:
- NTContainer: this is a generic container which is used to encapsulate data such as files and memory. It’s an extremely important class, since it’s used extensively. Containers can for the time being be created through SDK functions such as: createContainerFromFile/newContainer.
- NTBuffer/NTContainerBuffer/CFFBuffer/etc.: used to efficiently read iteratively small amounts of data from a source.
- NTTextStream/NTTextBuffer/NTTextStringBuffer: used to output text. Indentation can be specified.
- NTXml: used to parse XML. Fast and secure. This class is based on RapidXML.
- CFFObject: the class from which every format class inherits (ZipObject, PEObject, etc). A very small subset of this class is exposed for now. This will change in the future.
- CFFStruct: representation of a file format structure.
- CFFFlags: representation of flags in a CFFStruct.
One of the new additions is that Python can now use filters as well. Do you remember the post about Widget and Views? Let’s use the same code base and change just a few lines:
from Pro import *
from PySide import QtCore, QtGui
class MixedWidget(QtGui.QSplitter):
def __init__(self, parent=None):
super(MixedWidget, self).__init__(parent)
self.setWindowTitle("Mixed widget")
self.setOrientation(QtCore.Qt.Vertical)
self.model = QtGui.QDirModel()
tree = QtGui.QTreeView()
tree.setModel(self.model)
self.addWidget(tree)
ctx = proContext()
self.hex = ctx.createView(ProView.Type_Hex, "")
self.addWidget(self.hex.toWidget())
tree.activated.connect(self.updateFile)
def updateFile(self, idx):
if self.model.isDir(idx) == True:
self.hex.clear()
else:
# modified lines
name = self.model.filePath(idx)
c = createContainerFromFile(name)
fstr = " "
c = applyFilters(c, fstr)
self.hex.setData(c)
# end
ctx = proContext()
w = MixedWidget()
v = ctx.createViewFromWidget(w)
ctx.addView(v)
With just three of the modified lines we are xoring all opened files with the value 0xCC and then show the resulting data in the hex view. The Profiler provides a huge number of filters for any kind of operation and they can be chained, so we could easily compress and then encrypt a file with AES by just replacing one line in the sample above. The function applyFilters displays an optional default wait dialog to the user to interrupt the operation (if it is executing in the main thread). Please remember that the easiest way to obtain the needed filters XML string is to use the UI view and use the export command from the list (context menu->Export…).
NTBuffer generates an exception when a read operations fail. Thus, it should be used as follows:
ctx = proContext()
v = ctx.getCurrentView()
d = v.getData()
b = NTContainerBuffer(d, ENDIANNESS_LITTLE, 0)
print(str(hex(b.u8())))
try:
b.read(10) # or b.u8(), b.u16(), etc.
except IndexError as e:
print(str(e))
A small snippet to show how to use NTXml:
x = NTXml()
ret = x.parse(" ")
if ret == NTXml_ErrNone:
n = x.findChild(None, "r")
if n != None:
n = x.findChild(n, "e")
if n != None:
a = x.findAttribute(n, "t")
if a != None:
print(x.value(a))
Along with the core, several of the file objects will be exposed. A text dump of a structure could be as easy as:
c = createContainerFromFile(fname)
pe = PEObject()
pe.Load(c)
out = NTTextStringBuffer()
pe.DosHeader().Dump(out) # CFFStruct::Dump
print(out.buffer)
Please notice that the code above misses several checks. We need to make sure that c is valid and Load succeds. I’ll omit these checks here to keep the code minimal.
You might say that printing out a single structure is an easy task. So let’s take a look at another cooler sample:
c = createContainerFromFile(fname)
pe = PEObject()
pe.Load(c)
out = NTTextStringBuffer()
tables = pe.MDTables("#~") # 'tables' references all .NET metadata tables
pe.DisassembleMSIL(out, 0x06000001) # .NET token (MethodDef | index)
print(out.buffer)
These few lines output an entire .NET method such as:
private static void Main(string [] args)
{
locals: int local_0,
int local_1
ldc_i4_2
stloc_0 // int local_0
ldloc_0 // int local_0
stloc_1 // int local_1
ldloc_1 // int local_1
ldc_i4_1
sub
switch
goto loc_22
goto loc_60
br_s loc_71
loc_22:
try
{
ldstr "h"
call System.Console::WriteLine(string) // returns void
leave_s loc_81
}
catch (System.ArgumentNullException)
{
pop
ldstr "null"
call System.Console::WriteLine(string) // returns void
leave_s loc_81
}
catch (System.ArgumentException)
{
pop
ldstr "error"
call System.Console::WriteLine(string) // returns void
leave_s loc_81
}
loc_60:
ldstr "k"
call System.Console::WriteLine(string) // returns void
ret
loc_71:
ldstr "c"
call System.Console::WriteLine(string) // returns void
loc_81:
ret
}
Nice, isn’t it? Remember we can change the indentation programmatically.
Of course, it will also be possible to get the object currently being analyzed and similar stuff. But we’ll see how to do that in another post.
If you’re wondering why the case convention for methods is not always the same, the reason is simple. CFFObject/CFFStruct/etc are based on older code which followed the Win32-like convention. Consequently all derived classes like PEObject follow this convention. All other classes use the camel-case convention.