JEB Plugin Development Tutorial part 3/8

Documents and Delegation

The source code for part 3 of this sample plugin is located on GitHub:

Process the input

In the previous part, we built a single document that displays the whole JavaScript file.

Imagine we have large functions that we would like to split using the following simple rule: one function per document.

Let's detail the flow of execution around IUnit:

The process method is where the heavy-lifting of your parser should be located. If you need to pre-process data, you may call the process method in IUnitIdentifier.prepare. Since we don't need it for now, we won't do it.

So let's start with implementing process(). First, we need to extract the JavaScript and split this test block into two functions. For the sake of this tutorial, we will use Mozilla Rhino to deal with JavaScript lexing and parsing. You can download it here.

First, add rhino-*.jar to your eclipse project (you can attach the javadoc to this .jar, the process is like adding javadoc to jeb.jar). Then, use the following snippet to extract the Functions from Javascript:

private static final ILogger logger = GlobalLog.getLogger(SampleUnit.class);

private AstRoot root = null;
private List<FunctionNode> functions = new ArrayList<FunctionNode>();

@Override
public boolean process() {
    // parse the javascript
    IRFactory factory = new IRFactory();
    try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(getInput().getStream()));
        reader.readLine(); // ignore first line with #Javascript
        root = factory.parse(reader, null, 0);
    }
    catch(IOException e) {
        logger.catching(e);
        return false;
    }

    // save functions
    List<AstNode> statements = root.getStatements();
    for(AstNode statement: statements) {
        if(statement.getType() == Token.FUNCTION) {
            functions.add((FunctionNode)statement);
        }
    }

    // done
    setProcessed(true);
    return true;
}

As you can see here, process() is only extracting data: FunctionNode objects are saved in the functions list.

Note: JEB2 provides global logging facility that you can use anytime to send messages in the Logger panel. Do not hesitate to use it to investigate your code. We also recommend you enable the Development Mode in the Options panel: this will allow logging of debug and trace messages.

import com.pnfsoftware.jeb.util.logging.GlobalLog;
import com.pnfsoftware.jeb.util.logging.ILogger;

public class Xxx {
    private static final ILogger logger = GlobalLog.getLogger(Xxx.class);
    ...

Add a Document per function

Now, let's modify the getFormatter method to change rendering, adding as many tabs (documents) as defined functions.

@Override
public IUnitFormatter getFormatter() {
    UnitFormatterAdapter adapter = new UnitFormatterAdapter(new AbstractUnitRepresentation("javascript raw code", true) {
        @Override
        public IGenericDocument getDocument() {
                return new AsciiDocument(getInput());
        }
    });
    if(functions != null) {
        for(final FunctionNode function: functions) {
            adapter.addDocumentPresentation(new AbstractUnitRepresentation(function.getName()) {
                @Override
                public IGenericDocument getDocument() {
                    return new AsciiDocument(new BytesInput(function.toSource().getBytes()));
                }
            });
        }
    }
    return adapter;
}

We called the method UnitFormatterAdapter.addDocumentPresentation to add more documents.

Note: since AsciiDocument requires an IInput as entry, we used the most appropriate implementation:

Now, start JEB.

Note: You will probably see this exception: java.lang.NoClassDefFoundError: org/mozilla/javascript/IRFactory. Remember that js.jar was not added to JEB classpath. Add it to your plugin classpath (in the Options panel - refer to Part 1 of this tutorial).

If all works well, you shall to see two additional tabs:

Delegate to another Unit

These new documents are great but what if we have lots of functions? We will have lots of tabs and it will become unreadable. One solution would be to add these FunctionNodes as children unit of the test.js unit node. We can delegate the creation of those units to the unit processor.

Here is a short explanation of delegation: assume have a zip file containing pdf, apk, js... You do not want to write a single parser that manages all those file types. A better approach is to have a Zip parser that will delegate parsing of its contents. That puts the Unit Processor in charge of finding the best Unit that match the file type: DEX Unit, Pdf Unit... and even our new Javascript Unit.

More on delegation here.

So how to do it? You need to slightly modify process():

// save functions
List<AstNode> statements = root.getStatements();
for(AstNode statement: statements) {
        if(statement.getType() == Token.FUNCTION) {
            FunctionNode function = (FunctionNode)statement;
            functions.add(function);
            IUnit jsUnit = getUnitProcessor().process(function.getName(), new BytesInput(function.toSource().getBytes()), this);
            if(jsUnit != null) {
                addChildUnit(jsUnit);
            }
        }
}

You should be able to see a generic binary output for a and b.

Exercice

Build a simple JavascriptPlugin that takes advantage of the delegation. The solution lies in branch tutorial3 of this sample plugin repository.

Do not forget to register the parser plugin's classname in the options panel. You should obtain something like this:

Next: Part 4