Skip to content

Part 3: Documents and Delegation

Warning

This tutorial is deprecated, and will be rewritten to account for JEB 4 API changes.

JEB Plugin Development Tutorial part 3/8

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

  • Clone this repo: git clone https://github.com/pnfsoftware/jeb2-plugindemo-js.git
  • Switch to the tutorial3 branch: git checkout tutorial3

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:

  • When opening a new file, all the IPlugin.canIdentify methods are called.
  • If identified, the method IPlugin.prepare is called, which will create a new unit.
  • When opening the unit, if the unit was not previously processed, the method IUnit.process method will be called.
  • At last, IUnit.getFormatter is used to display the result of processing.

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: JEB 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.

Info

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.

Assignment#

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: