Skip to main content

JEB Unchained

SA
Space & Airborne Systems
Nov 14, 2023 | 5+ Minute Read

By Antonio Fuerte

Note: This article is one in a technical series by Trenchant of L3Harris Technologies.

What is JEB?

If you are asking this question, this may not be a post for you :). However, let's start from the beginning.
JEB is a reverse engineering tool that can analyze several file formats, e.g. Siemens Simatic PLC software, Ethereum smart contracts, as well as native code, and Android. You can see a detailed description of all supported formats here
 

We are going to focus on Android, since JEB is the standard de facto for this platform.

The need to automate

In a nutshell, the answer to "why do I need automation in JEB?" is the same generic answer as in any other reverse engineering endeavour: the sheer amount of code is mind numbing. Take as an example the excerpt below, taken from a session where a common Android APK has been loaded into JEB:
 

# [Dalvik Disassembly] 
187319 classes: 
423757 fields, 
712946 methods (incl. 5129 native)

Those are more than 700K methods!

Let's imagine a simple case, for example, you want to look at all the methods that are calling Android APIs accesing the camera. A (very) naive approach would be to locate all the relevant APIs, e.g. android.hardware.camera2 or alike in JEB (by using the Bytecode/Hierarchy view) and search for all the cross references to your app's code.

No offense, but this is insanity. Before long, you will even forget which pieces of code you already analyzed. A more efficient way to work on this (and less prone to cause burn out) is to spend a fraction of that time writing a script, which has the additional benefit of being reusable. In the case of our imaginary script, the workflow would be something along these lines:

  1. find all DEX classes in your APK
  2. get all references between them (essentially a massive graph)
  3. find the DEX classes with specific keywords in their signatures
  4. get all their callers (by querying the graph created in step 2)
     

All these "bricks" you need to develop your script can be more or less easily implemented by calling a few JEB APIs.

The scripting engine

JEB exposes a rich API to query all contents of your JEB database by using Python. One important caveat is you can only use Python 2.7, but more on that later. Note that there is another way to automate JEB, by writing plugins in Java, but this is out of the scope for this blog post.
Below we show a very bare-bones script, kind of JEB scripting 101 if you like. However, this showcases all the essential elements, and serves as an skeleton that you can build upon.
 

#?description=Dummy script
#?shortcut=Ctrl+Alt+x
#?version=1.0
#?author=El Trenchanto
from com.pnfsoftware.jeb.core import RuntimeProjectUtil
from com.pnfsoftware.jeb.core.units.code.android import IDexUnit
from com.pnfsoftware.jeb.client.api import IScript, IGraphicalClientContext

class TrenchantoFirst(IScript):
   def __init__(self):
       self.current_project = None
       self.current_dex_unit = None


   def run(self, ctx):
       # Sanity checks
       if not isinstance(ctx, IGraphicalClientContext):
           print("[-] This script must be run within a graphical client!")
           return


       self.current_project = ctx.getMainProject()
       dex_units = RuntimeProjectUtil.findUnitsByType(
           self.current_project,
           IDexUnit,
           False)
       
# NOTE: if an APK contains several DEX units,
       # the default behaviour is to combine them into one
       self.current_dex_unit = dex_units[0]
       all_classes = self.current_dex_unit.getClasses()
       for c in all_classes:
           print(c.getSignature())
       
print("[+] Done")
 

A JEB script is essentially a class inheriting from the IScript interface. Its entry-point is the mandatory run method, that you have to override. The only argument for the run method is the client context (don't worry, JEB will populate this for you). The context is the glue between your Python script, and the JEB API. Through the context you will be able to ultimately access all elements of your database.

Very important gotcha (that got me many times!)

Remember that the class name must match the file name, e.g. Template.py must implement the class Template It appears that numerical suffixes are OK, e.g. 00_Template.py
 

Now that you have your new shiny script ready, how do you actually use it? The most convenient way to do it is this:

  • Drop your python file(s) into <jeb install dir>/scripts
  • Press F2 to display the Script Selector and select your script from the list
  • Press "Execute". Easy peasy.


NOTE: it's possible to save the script(s) somewhere else on your disk, and browse to it, if you really need to…

Pro-tip: If you want to re-run your last script, you can simply press Ctrl-F2.

Another very convenient way is to define a keyboard shortcut. You may have noticed the following line on the above script:

#?shortcut=Ctrl+Alt+x

This registers the shortcut Ctrl+Alt+x to execute your script; just be sure that it does not conflict with any other system or application shortcut and you're good to go.
 

Neat trick

Pro-tip for rapid prototyping! You can even try short snippets of code directly on the JEB "terminal" (the tab besides the Logger on the lower pane). When you access the Python interpreter via this UI, one of the globals() is the current context, accessible via a variable aptly named jeb. This is same object passed as an argument to the run method in a normal script. See the following excerpt of a session inside JEB's "terminal" for an example:

> help
help              : help about the master interpreter
list              : display a list of available console ports
use <interpreter> : set the active console associated with this display; -1 for master
exit              : exit the current interpreter and return to the master interpreter
> list
1 interpreter available
(0) py: Python Interpreter (built on Jython 2.7)
> use 0
py> prj = jeb.getMainProject()
py> prj
Project:{base.apk.jdb2}
py> for dex in prj.findUnits(IDexUnit): print(dex)
Unit:name={Bytecode},type={dex}
py> dex
Unit:name={Bytecode},type={dex}
py> classez = dex.getClasses()
py> len(classez)
153591
py> print(classez[0].getSignature())
LSome/Class_Signature;
py>

This is very useful for quickly testing some code, but there is a significant caveat: it does not accept multi-line entries. It is however very convenient, and more often than not a considerable time-saver when developing.

How does scripting work? Are you a wizard?

JEB achieves Python scripting via Jython, which comes preinstalled under <jeb install dir>/bin/app (jython-standalone-2.7.3.jar at the time of writing). Your script is invoked by JEB and runs within a Jython VM.

Jython itself is essentially just another Python interpreter. As you can see in the following excerpt, it can be executed directly from the command line, entirely outside of JEB:

┌──(user💣ubuntu)-[~/Software/jeb-pro/bin/app]
└─$ java -jar jython-standalone-2.7.3.jar 
Jython 2.7.3 (tags/v2.7.3:5f29801fe, Sep 10 2022, 18:52:49)
[OpenJDK 64-Bit Server VM (Amazon.com Inc.)] on java1.8.0_372
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello from Jython")
Hello from Jython
>>> quit()


Logically, when ran this way, we cannot access the contents of our JEB database. Actually, at this point, we can't even use JEB's API from this vanilla Python interpreter. For that we need to add the JAR file containing the JEB API to Jython's python.path, i.e. invoking the interpreter this way:
 

┌──(user💀ubuntu)-[~]
└─$ cd Software/jeb-pro/bin/app
┌──(user💀ubuntu)-[~/Software/jeb-pro/bin/app]
└─$ java -Dpython.path=jeb.jar -jar jython-standalone-2.7.3.jar
Jython 2.7.3 (tags/v2.7.3:5f29801fe, Sep 10 2022, 18:52:49)
[OpenJDK 64-Bit Server VM (Amazon.com Inc.)] on java1.8.0_372
Type "help", "copyright", "credits" or "license" for more information.
>>> from com.pnfsoftware.jeb.core.util import DecompilerHelper
>>> DecompilerHelper
<type 'com.pnfsoftware.jeb.core.util.DecompilerHelper'>
>>>


Nice, we have access to the JEB API now… but does this mean we can start running our scripts from a regular terminal? No, since our interpreter is completely decoupled from our JEB instance, which contains all information about our analyzed APK.
 

The missing part here is the client context that JEB makes available to Jython, via a global named jeb. (See Neat trick section)

How much Python can I use?

Besides its own API, JEB has access to the Python standard library, located inside Jython's JAR file. To verify this you can simply unzip the JAR file, and look under the Lib directory:
 

┌──(user💀ubuntu)-[~/Software/jeb-pro/bin/app]
└─$ unzip -Z jython-standalone-2.7.3.jar | grep "Lib/"
<snip>
-rw-r--r--  2.0 unx    12956 b- defN 22-Sep-10 18:54 Lib/unittest/util$py.class
-rw-r--r--  2.0 unx     4606 b- defN 22-Sep-10 18:53 Lib/unittest/util.py
-rw-r--r--  2.0 unx    98107 b- defN 22-Sep-10 18:54 Lib/urllib$py.class
-rw-r--r--  2.0 unx    60358 b- defN 22-Sep-10 18:53 Lib/urllib.py
-rw-r--r--  2.0 unx    86102 b- defN 22-Sep-10 18:54 Lib/urllib2$py.class
-rw-r--r--  2.0 unx    52513 b- defN 22-Sep-10 18:53 Lib/urllib2.py
-rw-r--r--  2.0 unx    29237 b- defN 22-Sep-10 18:54 Lib/urlparse$py.class
-rw-r--r--  2.0 unx    14620 b- defN 22-Sep-10 18:53 Lib/urlparse.py
<snip>
 

That's OK for some basic scripts, but if you want to do something more advanced…

We are gonna need a bigger sys.path
 

The default Jython syspath is shown below:


┌──(user💀ubuntu)-[~/Software/jeb-pro/bin/app]      
└─$ java -jar jython-standalone-2.7.3.jar
Jython 2.7.3 (tags/v2.7.3:5f29801fe, Sep 10 2022, 18:52:49)
[OpenJDK 64-Bit Server VM (Amazon.com Inc.)] on java1.8.0_372
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/home/user/Software/jeb-pro/bin/app/jython-standalone-2.7.3.jar/Lib', 
'__classpath__', 
'__pyclasspath__/', ...]
>>>
 

As you can see, the most relevant place where Jython will look for additional modules is that Lib directory within its own JAR file.
 

I need moar power

What if you need other Python modules, e.g. NetworkX to use some kick-ass graph algorithms? The easiest way to add Python modules is to install pip:
 

$ cd <jeb install dir>/bin/app
$ java -jar jython-standalone-2.7.3.jar -m ensurepip
 

IMPORTANT: Do NOT update pip >= 20.2, it's buggy!
 

When pip is installed, you should see two new directories under bin/app:

  • Lib, contains site-packages and such
  • bin, contains pip itself, and related binaries


Apart from those changes, take a look at the updated sys.path for Jython:


┌──(user💀ubuntu)-[~/Software/jeb-pro/bin/app]      
└─$ java -jar jython-standalone-2.7.3.jar
Jython 2.7.3 (tags/v2.7.3:5f29801fe, Sep 10 2022, 18:52:49)
[OpenJDK 64-Bit Server VM (Amazon.com Inc.)] on java1.8.0_372
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/home/user/Software/jeb-pro/bin/app/Lib', 
'/home/user/Software/jeb-pro/bin/app/jython-standalone-2.7.3.jar/Lib', 
'__classpath__', 
'__pyclasspath__/', 
'/home/user/Software/jeb-pro/bin/app/Lib/site-packages', ...]
>>>
 

The Lib directory is now included in the sys.path, meaning your scripts will search under its site-packages for additional modules!
 

Now you can install your favorite Python packages, e.g.
 

$ java -jar jython-standalone-2.7.3.jar -m pip install pyvis
$ java -jar jython-standalone-2.7.3.jar -m pip install networkx
 

Note that some Python modules are distributed with native code that needs to be compiled during the installation process. To do this you have to indicate Jython where to find the system's CPython. You can do this adding the following switch: -D python.cpython2=python2.7, e.g.
 

$ java -Dpython.cpython2=python2.7 -jar jython-standalone-2.7.3.jar -m pip install networkx
 

As far as I know, this option is not intrusive and could be used all the time, perhaps by defining an alias for convenience?
 

Working on an offline world

In case your work is sensitive, and you need to perform all your tasks on an offline machine you can still get access to all those additional python packages. The easiest way is to use the standalone Jython interpreter to install the modules you need (exactly as shown in the previous section) on another online machine, and then copy the entire Lib directory (and containing site-packages, etc.) to your offline one.

Using the JAVAz

JEB comes with many JAVA libraries preinstalled, see <jeb install dir>/bin/app. This is where Jython is installed as well.
┌──(user💀ubuntu)-[~/Software/jeb-pro/bin/app]                       
└─$ ls -l *.jar                                     
-rw-rw-r-- 1 user user   336803 Jun 21  2018 antlr4-runtime-4.7.1.jar
-rw-rw-r-- 1 user user   392703 Jan  6  2022 apksig-7.0.3.jar
-rw-rw-r-- 1 user user  3612688 Jan  6  2022 byte-buddy-1.11.15.jar
-rw-rw-r-- 1 user user   246174 Jun 21  2018 commons-beanutils-1.9.3.jar
-rw-rw-r-- 1 user user   335042 Jun 21  2018 commons-codec-1.11.jar
<snip>
-rw-rw-r-- 1 user user  4871283 May 31 11:54 d8.jar
-rw-rw-r-- 1 user user  1039859 Dec 27 00:04 dx.jar
-rw-rw-r-- 1 user user  2738171 Jun 21  2018 guava-25.0-jre.jar
-rw-rw-r-- 1 user user  3014739 Jun 14 10:17 jebc.jar
-rw-rw-r-- 1 user user    49513 Jun 14 10:17 jebi.jar
-rw-rw-r-- 1 user user 59853777 Jun 14 10:17 jeb.jar


It is possible to reach the code inside through Jython. See the following excerpt of a JEB script showcasing how to use Google's Guava graph API:
f

rom com.google.common.graph import GraphBuilder, Traverser
 

def my_code(futurama_instance):
   """Example showing several graph manipulation
   and visualization libraries
   """
   f = futurama_instance


   # Google's Guava
 

   # --------------------
   root = Node("root")
   graph = GraphBuilder.directed().build()
   a = Node("a")
   b = Node("b")
   c = Node("c")


   graph.putEdge(root, a)
   graph.putEdge(root, b)
   graph.putEdge(a, b)
   graph.putEdge(a, c)
   graph.putEdge(b, c)
   for x in Traverser.forGraph(graph).depthFirstPostOrder(root):
       print(x)


To use the Java APIs in Python requires a bit of experience and/or an educated quess. It requires looking at the JAVA API, and being able to translate its syntax to Python. Luckily, this is not difficult in most cases. As an example, this Java code:


Traverser.forGraph(graph).depthFirstPostOrder(root)
       .forEach(x->System.out.println(x));
 

becomes the following in Python:
for x in Traverser.forGraph(graph).depthFirstPostOrder(root):
   print(x)
 

Altought syntactically different, semantically both snippets are equivalent.
Finally, if we call our JEB script we get the output shown below:


c
b
a
root

Wow! So algo… Much graph… I know, I know, this is a simple and underwhelming example. However, it clearly shows that you can use this Java library (and many others!) from your scripts right off the bat.