Exposing the Core (part 2)

The release date of the upcoming 0.9.3 version is drawing nearer. Several format classes have already been exposed to Python and in this post I’m going to show you some code snippets. Since it’s impossible to demonstrate all format classes (12 have already been exposed) and all their methods (a single class may contain dozens of methods), the purpose of the snippets below is only to give the reader an idea of what can be achieved.

The SDK organization has changed a bit: because of its increasing size it made sense to subdivide it into modules. Thus, there’s now the Pro.Core module, the Pro.UI one and one module for each format (e.g. Pro.PE).

PDF

This is how we can output to text the raw stream of a PDF:

from Pro.Core import *
from Pro.PDF import *

c = createContainerFromFile(fname)
pdf = PDFObject()
pdf.Load(c)
objtable = pdf.BuildObjectTable()
pdf.SetObjectTable(objtable)
oid = PDFObject.OBJID(3, 0)
ret, dict, content, info = pdf.ParseObject(objtable, oid)
out = NTTextBuffer()
out.printHex(content)
print(out.buffer)

Output:

         0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F    Ascii

0000   48 89 24 8D CD 0A 83 30  10 84 EF 81 BC C3 1C 93    H.$....0........
0010   8B 4D 52 63 E3 B5 D0 0A  42 A1 D0 DC C4 83 D4 F8    .MRc....B.......
0020   D3 D6 0A 2A F5 F5 BB B6  B0 CC 2E C3 37 3B 1A 2D    ...*........7;.-
0030   67 2A B2 C8 38 D3 C8 A1  F0 80 C6 8A 18 17 14 7B    g*..8..........{
0040   94 0A 35 67 BB EC 66 D0  CE 1B D1 83 34 75 48 92    ..5g..f.....4uH.
0050   04 46 C7 B0 0E 53 E0 EC  48 E3 09 3C 1B 4A FB 86    .F...S..H..<.J..
0060   18 43 AF 14 68 19 7D 88  1C 05 52 05 3F 50 D7 DF    .C..h.}...R.?P..
0070   C7 73 3B FD FD A7 2B 67  85 B8 CA 58 89 6A 5E 02    .s;...+g...X.j^.
0080   96 2E A0 E9 C3 AB 46 F5  AE 31 8C 52 5B F1 91 C6    ......F..1.R[...
0090   8A 20 95 C0 32 A2 0B 53  D8 CC 48 96 3E E7 EC 44    . ..2..S..H.>..D
00A0   CD 5F 01 06 00 88 1E 2A  AA 0D 0A                   ._.....*...    

Streams in PDFs are usually compressed. Here’s how we can decode the same stream:

from Pro.Core import *
from Pro.PDF import *

c = createContainerFromFile(fname)
pdf = PDFObject()
pdf.Load(c)
objtable = pdf.BuildObjectTable()
pdf.SetObjectTable(objtable)
oid = PDFObject.OBJID(3, 0)
ret, dict, content, info = pdf.ParseObject(objtable, oid)
content = pdf.DecodeObjectStream(content, dict, oid)
out = NTTextBuffer()
out.printHex(content)
print(out.buffer)

Output:

        0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F    Ascii

0000   31 20 67 0D 0A 30 2E 35  20 47 0D 0A 31 20 4A 20    1 g..0.5 G..1 J 
0010   30 20 6A 20 31 20 77 20  34 20 4D 20 5B 33 20 5D    0 j 1 w 4 M [3 ]
0020   30 20 64 0D 0A 2F 47 53  32 20 67 73 0D 0A 31 20    0 d../GS2 gs..1 
0030   69 20 0D 0A 31 39 38 20  36 36 36 20 32 31 34 20    i ..198 666 214 
0040   35 38 20 72 65 0D 0A 42  0D 0A 42 54 0D 0A 2F 46    58 re..B..BT../F
0050   32 20 31 20 54 66 0D 0A  31 32 20 30 20 30 20 31    2 1 Tf..12 0 0 1
0060   32 20 32 31 37 2E 38 38  20 36 39 30 20 54 6D 0D    2 217.88 690 Tm.
0070   0A 30 20 30 20 30 20 31  20 6B 0D 0A 30 20 54 63    .0 0 0 1 k..0 Tc
0080   0D 0A 30 20 54 77 0D 0A  5B 28 50 29 34 30 28 61    ..0 Tw..[(P)40(a
0090   73 74 65 20 74 68 65 20  66 69 65 6C 64 20 61 6E    ste the field an
00A0   64 20 6D 6F 29 31 35 28  76 29 32 35 28 65 29 30    d mo)15(v)25(e)0
00B0   28 20 74 6F 20 68 65 72  65 29 31 35 28 2E 29 5D    ( to here)15(.)]
00C0   54 4A 0D 0A 45 54 0D 0A                             TJ..ET..        

We might also want to iterate through the key/value pairs of a PDF dictionary. Thus, iterators have been implemented everywhere they could be applied. While they don’t yet support the standard Python syntax they are very easy to use:

from Pro.Core import *
from Pro.PDF import *

c = createContainerFromFile(fname)
pdf = PDFObject()
pdf.Load(c)
objtable = pdf.BuildObjectTable()
pdf.SetObjectTable(objtable)
oid = PDFObject.OBJID(3, 0)
ret, dict, content, info = pdf.ParseObject(objtable, oid)
it = dict.iterator()
while it.hasNext():
    k, v = it.next()
    print(k + " - " + v)

Output:

/Length - 171
/Filter - /FlateDecode

Iterating through the objects of a PDF amounts to the same logic:

from Pro.Core import *
from Pro.PDF import *

c = createContainerFromFile(fname)
pdf = PDFObject()
pdf.Load(c)
objtable = pdf.BuildObjectTable()
it = objtable.iterator()
while it.hasNext():
    k, v = it.next()
    # print out the object id
    print(str(k >> 32))

CFBF (DOC, XLS, PPT, MSI, etc.)

Iterating through the directories of a CFBF can be as simple as:

from Pro.Core import *
from Pro.CFBF import *

def visitor(obj, ud, dir_id, children):
    name = obj.DirectoryName(dir_id)
    print(name)
    return 0

c = createContainerFromFile(fname)
cfb = CFBObject()
cfb.Load(c)
dirs = cfb.BuildDirectoryTree()
cfb.SetDirectoryTree(dirs)
cfb.VisitDirectories(dirs, visitor, None)

Output:

Root Entry
\1CompObj
\1Ole
1Table
\5SummaryInformation
WordDocument
\5DocumentSummaryInformation

Retrieving a stream is equally easy:

from Pro.Core import *
from Pro.CFBF import *

c = createContainerFromFile(fname)
cfb = CFBObject()
cfb.Load(c)
dirs = cfb.BuildDirectoryTree()
cfb.SetDirectoryTree(dirs)
s = cfb.Stream(1)
b = s.read(0, s.size()) # read bytes
t = NTTextBuffer()
t.printHex(b)
print(t.buffer)

Output:

        0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F    Ascii

0000   01 00 FE FF 03 0A 00 00  FF FF FF FF 06 09 02 00    ................
0010   00 00 00 00 C0 00 00 00  00 00 00 46 18 00 00 00    ...........F....
0020   4D 69 63 72 6F 73 6F 66  74 20 57 6F 72 64 2D 44    Microsoft Word-D
0030   6F 6B 75 6D 65 6E 74 00  0A 00 00 00 4D 53 57 6F    okument.....MSWo
0040   72 64 44 6F 63 00 10 00  00 00 57 6F 72 64 2E 44    rdDoc.....Word.D
0050   6F 63 75 6D 65 6E 74 2E  38 00 F4 39 B2 71 00 00    ocument.8..9.q..
0060   00 00 00 00 00 00 00 00  00 00                      ..........      

SWF

Here’s how to output the disasm of an ActionScript2 Flash file:

from Pro.Core import *
from Pro.SWF import *

c = createContainerFromFile(fname)
swf = SWFObject()
swf.Load(c)
if swf.IsCompressed():
    swf.Decompress()
tl = swf.EnumerateTags()
swf.SetStoredTags(tl)
out = NTTextBuffer()
swf.AS2Disassemble(out)
print(out.buffer)

The same can be done for ActionScript3 using the ABCFileObject class.

Class

This is how to disassemble a Java Class file:

from Pro.Core import *
from Pro.Class import *

c = createContainerFromFile(fname)
cl = ClassObject()
cl.Load(c)
cl.ProcessClass()
out = NTTextBuffer()
cl.Disassemble(out)
print(out.buffer)

DEX

This is how to disassemble an Android DEX file class:

from Pro.Core import *
from Pro.DEX import *

c = createContainerFromFile(fname)
dex = DEXObject()
dex.Load(c)
# disassemble the last class
classes = dex.Classes()
token = classes.Count() - 1
out = NTTextBuffer()
dex.Disassemble(out, token)
print(out.buffer)

In the upcoming post(s) I’m going to put it all together and do some very interesting things.
So stay tuned as the best has yet to come!

Exposing the Core (part 1)

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.

Python SDK improvements

The upcoming 0.8.9 release of the Profiler improves integration with Python and the SDK exposes new functionality. Moreover, it lays down the groundwork needed to expand the SDK in the next releases. Documentation of the SDK has been included in the docs directory and also a Python command line has been added to the workspace.

To offer a glimpse of the capabilities here is a small code snippet to create a custom plot which could be used, for instance, to display entropy.

import random

ctx = proContext()
v = ctx.createView(ProView.Type_Plot, "Test") 
v.setPlotTitle("Random")
v.setAxisScale(ProPlotView.xBottom, 0, 100)
v.setAxisScale(ProPlotView.yLeft, 0, 50)
x = NTDoubleVector()
y = NTDoubleVector()
i = 0
while i < 100:
    x.append(i)
    y.append(random.randrange(50))
    i = i + 1
v.addCurve(x, y)
ctx.addView(v)

And the screenshot of the created plot.

Custom plot

While this doesn't sound too exciting at first, the on-going SDK expansion will allow to do some very interesting things. Stay tuned as some more technical posts are coming.

Parameters & Settings

In the upcoming release of the Profiler (0.7.4) actions and scripts have a way to ask the user for parameters and settings: a new set of APIs featuring a property editor dialog.

Properties

Here’s the complete code of the sample followed by explanations.

import ProUI

xml = """


  <section label="General">
    <property id="0" label="Name" type="edit" value="object" />
    <property id="1" label="Size" type="static" value="(0,0)">
        <property id="2" label="Width" type="integer" value="0" signed="false" radix="10" align="0" maxbits="16" />
        <property id="3" label="Height" type="integer" value="0" signed="false" radix="10" align="0" maxbits="16" />
    </property>
  </section>
  
  <section label="Options">
    <property id="4" label="Word wrap" type="check" value="false" />
    <property id="5" label="Syntax" type="combo" value="1">
      <list>
        <i>JavaScript</i>
        <i>C++</i>
        <i>Pascal</i>
      </list>
    </property>
  </section>
  
  <section label="Files">
    <property id="6" label="Open file" type="open-file" value="C:\\test.txt" />
    <property id="7" label="Save file" type="save-file" value="C:\\test2.txt" />
    <property id="8" label="Select directory" type="open-directory" value="C:\\temp" />
  </section>
  
  <section label="Content">
    <property id="9" label="Text" type="text">
        This \tis a\nsample text
    </property>
  </section>
  
  <section id="20" label="Numbers">
    <property id="10" label="Base address" type="integer" value="401000" signed="false" radix="16" align="8" />
    <property id="11" label="Char" type="integer" value="127" signed="true" radix="10" align="0" maxbits="8" />
  </section>
  
"""

def UpdateSize(pe):
    sz = "(" + str(pe.getValue(2)) + "," + str(pe.getValue(3)) + ")"
    pe.setValue(1, sz)

def ParamsCallback(pe, id, userdata):
    print("changed: " + str(id) + " value: " + str(pe.getValue(id)))
    if id == ProUI.ProPropertyEditor.Init:
        UpdateSize(pe)
    elif id == 2 or id == 3:
        UpdateSize(pe)
        pe.setValue(0, "test2")
        pe.setValue(5, 2)
        pe.setValue(10, 0x2000)
    elif id == 4:
        b = pe.isVisible(20) == False
        pe.setVisible(20, b)
        if b == False:
            pe.setErrors([6, 7])
        else:
            pe.clearErrors()
    return True

def TestAction():
    context = ProUI.getContext()
    params = context.askParams(xml, "Test", ParamsCallback, None)
    print(params)
    return 0

Let’s start with the action code.

def TestAction():
    context = ProUI.getContext()
    params = context.askParams(xml, "Test", ParamsCallback, None)
    print(params)
    return 0

This code shows a property editor dialog specifying an XML string to create the dialog, a settings key (optional) and a callback (optional). The return value is a dictionary with the values of the properties with their id as key or None when the dialog is rejected.

{0: 'test', 1: '(4,0)', 2: 4, 3: 0, 4: False, 5: 1, 6: 'C:\\test.txt', 7: 'C:\\test2.txt', 8: 'C:\\temp', 9: 'This \tis a\nsample text', 10: 8192, 11: 127}

I’ll talk later about what the settings key means. Let’s first understand the XML syntax.

The XML root tells the function to create a property editor with the (optional) title “Settings”.

<section label="General">

A section is created. Properties do not need a section as parent, but it might be visually more appealing to specify one. Child nodes of sections are properties. Properties can have other properties as child nodes, but not sections.

<property id="0" label="Name" type="edit" value="object" />

The first property being created is a single line edit field with the id of 0 and a value of “object”. id and type attributes are mandatory for properties. Sections may optionally specify an id as we’ll see later.

    <property id="1" label="Size" type="static" value="(0,0)">
        <property id="2" label="Width" type="integer" value="0" signed="false" radix="10" align="0" maxbits="16" />
        <property id="3" label="Height" type="integer" value="0" signed="false" radix="10" align="0" maxbits="16" />
    </property>

Here we have one static property with two integer child properties. A static property is a non-editable text which can only be set programmatically.

An integer property can specify various things, although the only mandatory attributes remain id and type. Most of the attributes are self-explanatory. align specifies the 0s which may prefix the number to obtain the desired alignment. For example, the number 1 with an alignment of 4 will be displayed as 0001. maxbits specifies the maximum number of bits the integer can measure (at the time it defaults to 10000).

  <section label="Options">
    <property id="4" label="Word wrap" type="check" value="false" />

Inside a new section a check property is specified.

    <property id="5" label="Syntax" type="combo" value="1">
      <list>
        <i>JavaScript</i>
        <i>C++</i>
        <i>Pascal</i>
      </list>
    </property>

Following there’s a combo property. The list of the combo is specified as the child node list and the default index is specified as the value attribute.

  <section label="Files">
    <property id="6" label="Open file" type="open-file" value="C:\test.txt" />
    <property id="7" label="Save file" type="save-file" value="C:\test2.txt" />
    <property id="8" label="Select directory" type="open-directory" value="C:\temp" />
  </section>

These three properties are related to file operations. When the user activates one, he will be able to open a file dialog to perform the requested operation.

  <section label="Content">
    <property id="9" label="Text" type="text">
        This \tis a\nsample text
    </property>
  </section>

The text property specifies a multi-line text field. When the user activates this property, a multi-line text input dialog is displayed in order to change the value.

  <section id="20" label="Numbers">
    <property id="10" label="Base address" type="integer" value="401000" signed="false" radix="16" align="8" />
    <property id="11" label="Char" type="integer" value="127" signed="true" radix="10" align="0" maxbits="8" />
  </section>

The properties in this section are not any different than those seen before, but it should be noted that in this case the section has an id attribute. Giving an id to a section makes it possible to set the visibility or the enabled/disabled state of the section and its children.

Let’s analyze the callback.

def UpdateSize(pe):
    sz = "(" + str(pe.getValue(2)) + "," + str(pe.getValue(3)) + ")"
    pe.setValue(1, sz)

def ParamsCallback(pe, id, userdata):
    print("changed: " + str(id) + " value: " + str(pe.getValue(id)))
    if id == ProUI.ProPropertyEditor.Init:
        UpdateSize(pe)
    elif id == 2 or id == 3:
        UpdateSize(pe)
        pe.setValue(0, "test2")
        pe.setValue(5, 2)
        pe.setValue(10, 0x2000)
    elif id == 4:
        b = pe.isVisible(20) == False
        pe.setVisible(20, b)
        if b == False:
            pe.setErrors([6, 7])
        else:
            pe.clearErrors()
    return True

ParamsCallback has three arguments. pe is the ProPropertyEditor class instance. id is the property being modified or the notification code (Init, Accept). userdata is the custom data specified in the askParams method, which in this case is None.

The UpdateSize function updates the value of the static property when one of its children has been changed. Other fields are changed for the purpose of demonstration.

        pe.setVisible(20, b)

This line sets the visibility of the last section.

        if b == False:
            pe.setErrors([6, 7])
        else:
            pe.clearErrors()

The setErrors method allows to highlight properties in red. The idea is that a callback might perform some checks when being notified with the ProPropertyEditor.Accept code, highlight properties which are not accepted and return False to ask the user to enter correct values. Calling clearErrors or setErrors with an empty list will achieve the same result.

Here’s a screenshot with two highlighted properties and the last section hidden.

Properties 2

Let’s go back to askParams method. I haven’t yet explained the settings key (“Test”). This is an optional argument: it specifies if and where the values of the properties should be stored in case the dialog is accepted. The specified key name should be similar or equal to the name of the action to avoid conflicts. If the property dialog changes and the old settings must be discarded, it can be achieved by specifying a version at the end of the key name: “Test#1”. When the version number is omitted, it defaults to 0.

It should be noted that static and multi-line text properties are not saved automatically. The latter to avoid too large values being stored. However, it is still possible to save and restore these values through the Init and Accept notification codes by using the settings API.

# restore
ProUI.ProSettings.getValue("Test/mytext")
# save
ProUI.ProSettings.setValue("Test/mytext", text)

Key names starting with “_” are reserved and shouldn’t be used.

Finally, let’s see how actions can now optionally specify a configuration function.

Configure action

[TestAction]
label = Test
file = testfile.py
context = any
config = TestConfig

config specifies the name of a function to be called inside of file.

This new set of APIs opens the door to many interesting customizations for actions, scripts and other components, and we will soon show you some of them. 🙂

Python 3 SDK: actions & custom scripts

The new version 0.7.3 of Cerbero Suite features a powerful Python 3 SDK, which enables to run custom scripts and actions. Let’s first take a look at a simple script. Just press Ctrl+R (or “Execute action…” in the context menu of a view) and go to “Custom”:

Simple script

As it is easy to guess, this basic script shows a message box. Message boxes can be used to notify things to the user or to ask him a question. Most of the time they won’t be necessary and the standard output can be used instead. All the output produced by Python will be visible in the output console. In fact, the console will become visible when something is printed to it (this behavior can be changed from the options).

Output console

The SDK can be used to retrieve data from views, set their data, create new views and so on. But before looking at a more advanced script, let’s talk about a new feature of Cerbero Suite: actions. For the purpose of demonstration let’s take a malware with obfuscated JavaScript.

Obfuscated JS

And now let’s again press Ctrl+R in the context of the obfuscated JavaScript.

Actions

By activating the “Beautify JavaScript” action we will get a beautified version (jsbeautifier.org) of the previously obfuscated JavaScript.

Beautified JS

Python actions are defined in the config/actions.cfg file.

[JSBeautify]
category = JavaScript
label = Beautify JavaScript
file = javascript.py
context = text

The section name (JSBeautify) specifies the id of the action and is also the name of the function to be called in file. The file field supports absolute paths as well, otherwise the script will be loaded from plugins/python. The category and label specify in which category inside the execute action dialog the action should be grouped and its description. When the category field is omitted, it will default to “Other”.

The context field is very important as it specifies when the action should be available for use. In this specific case, the action can be used in any text view. An action can also be available in more than one context.

; available both in text and hex views
context = text|hex

; available in text and hex views only when text or data is selected
context = text|hex|sel

; always available even when not in a view
context = any

Now let’s see how to create an action which decodes some selected text from base64 and shows the decoded bytes in a new hex view. First it is necessary to define the action.

[Base64Decode]
category = Samples
label = Base64 decoder
file = samples.py
context = text|sel

And here’s the Python code.

from Pro.UI import *

def Base64Decode():
    context = proContext()
    view = context.getCurrentView()
    if view.isValid() and view.hasSelection():
        text = view.getSelectedText()
        decview = context.createView(ProView.Type_Hex, "Base64 decoded data")
        import base64
        decview.setBytes(base64.b64decode(text.encode("utf-8")))
        context.addView(decview)
    return 0

Let’s see it in action with a PGP public key.

PGP Public Key

And the decoded data.

PGP decoded key

Although the SDK is brand new, you will see very soon some new useful actions implemented. 🙂