Instrumenting Android Apps With Soot

Navid Salehnamadi
Jul 7, 2020 · 7 min read

In this blog post, I describe how to use Soot to read an Android APK (without the source code), change its methods and classes(even add a new class), and write the new code into a working APK. A few notes:

  • You can find the code in the SootTutorial repository.
  • This repository has a CLI to instrument APKs more conveniently.
  • For better understanding this post, I recommend to first take a look at the previous post “Know the basic tools in Soot” to be familiar with the Soot’s APIs that I used in the code.
  • This post is mostly adapted from this page in the Soot’s official wiki.

Intro

One way to analyze Android apps is by running them on a device (or an emulator) and observe the logs to capture some desired information. If you have the app’s source code, you can log whatever you want, e.g., record the execution of a method at its beginning. However, in the cases that the source code is not available, such as in security analysis where you are dealing with possible malicious apps, you need to instrument the APK, which is compiled in Dalvik byte code. And as you may have guessed, Soot is going to save the day.

The following figure shows an overview of how Soot reads/modifies/writes an APK. First, Soot uses Dexpler to convert the Dalvik byte code to Jimple bodies. Then it runs Whole Packs that can transform, optimize, and annotate the whole program (for example, call graph generation). Next, the Jimple Transformation Packs will run on each Jimple body (this is the part that we modify the code). Finally, Soot converts all Jimple bodies to Baf (a low-level intermediate representation in Soot), and using Dexpler the whole code will be compiled into an APK. The instrumented APK can be installed on an Android device (you just need to sign it first). Now, let’s instrument some apps!

An overview of instrumenting an Android APK by Soot (the circles are Soot packs, more info here)

Android Logger

We are going to add a simple statement() at the beginning of each APK method using a BodyTransfomer. Before reading further, please clone the SootTutorial repository and have AndroidLogger.java in front of you.

Setup

In order to analyze an Android APK with Soot, you need to install the Android SDK in your machine. You can either use the SootTutorial docker image () or follow this link.

Soot needs a special configuration for analyzing Android apps. The following code shows the options that I used for the instrumentation. Each option accompanies with a comment that describes it.

Soot Initialization

The last part of the setup code is not setting an option but resolving the required classes for the instrumentation. Recall that we want to add a new statement at the beginning of each APK method, which its Jimple representation is:

$r1 = <java.lang.System: java.io.PrintStream out>
virtualinvoke $r1.<java.io.PrintStream: void println(java.lang.String)>("<SOOT_TUTORIAL> Beginning of method METHOD_NAME")

Since these statements require classes and , we should resolve them in Soot, which is done in lines 20–21 in .

Body Transformation

Now, we’re ready to write a BodyTransformer to modify the code. The following code shows BodyTransformer. Note that the transformer is added to the pack and it will be applied to all methods’ bodies.

First of all, we need to filter out non-APK methods (lines 5–6), because Soot loads these methods and may not be aware that they belong to Android libraries (check). Then, we can create the content that we want to log (line 11, the method’s signature is ). We add a tag to the content so we can retrieve these logs later. Recall that Jimple statements are three-address code; so, in order to invoke we need to first create a local variable, and in the Jimple code, that points to (lines 13–16), then invoke the method using that local variable (lines 19–21).

For adding a local variable we input the body and the type of the local variable to a LocalGenerator (can be found here). To create a Jimple statement, we use the singleton . For example, line 16 creates an equivalent to (note that you need to pass the reference of a SootField or SootMethod). Similarly, line 21 creates an that has a virtual invoke expression equivalent to . Note that the parameter of this invocation must be a ; therefore, we use to create a String constant equal to the content.

So far, we have created two Jimple statements (lines 16 and 21). Note that, the IdentitiyStmts of Jimple bodies (that determines the parameters and pointer) must appear at the beginning; therefore, we need to find the first non-identity statements and insert our code before it (look at lines 17, 22, and 24). Finally, we have to validate the new modified body to make sure no problems exist (at least statically). The final step is to just run the packs () and write the output into an APK ()

Sign and Run!

That’s it! You add a Java statement at the beginning of all APK methods under 20 lines of code in Soot (actually it should be less than 10 if I didn’t expand all statements to write comments :) ). However, there is one more step before you can install the instrumented APK: you need to sign it. You can use the bash script sign.sh that basically first run zipalign (that aligns the APK) and then run apksigner using a keyset.

In summary, run , either in Intellij or CLI by running . It should create and you can sign and install it by running the following commands (don’t forget to connect your device to your machine or run an Android emulator):

cd ./demo/Android
./sign.sh Instrumented/calc.apk key "android"
adb install -r -t Instrumented/calc.apk

To see the logs, run and then use your device and open Numix Calculator. You should see something like this on your terminal:

07-07 11:41:26.570 32487 32487 I System.out: <SOOT_TUTORIAL> Beginning of method <com.numix.calculator.view.CalculatorDisplay: void onSizeChanged(int,int,int,int)>
07-07 11:41:26.571 32487 32487 I System.out: <SOOT_TUTORIAL> Beginning of method <com.numix.calculator.view.ScrollableDisplay: void onLayout(boolean,int,int,int,int)>
07-07 11:41:26.571 32487 32487 I System.out: <SOOT_TUTORIAL> Beginning of method <com.numix.calculator.view.ScrollableDisplay: com.numix.calculator.view.AdvancedDisplay getView()>
07-07 11:41:26.589 32487 32487 I System.out: <SOOT_TUTORIAL> Beginning of method <com.numix.calculator.view.ScrollableDisplay: void scrollTo(int,int)>

If you’re interested in some specific methods you can filter it by piping to

Android Class Injector

Let’s do some more existing things. Assume in the previous example, instead of just logging the name of the method, we wanted to keep track of the number of methods that have been called so far (or simply count the executed method). One way to do this is to have a static integer field and increase it by one when a method is executed. Now, I show how to create a new class, field, and method from scratch, add them to the APK, and more importantly, use them in other methods. The whole code can be found in AndroidClassInjector.java and InstrumentUtil.java and the following code shows creating a class:

The new class must be in the same package of APK in order that other methods could access it (line 3). Also, it should be public (Modifier at line 4), a subclass of Object (line 5), and a Soot Application class (line 6). At the end of the generated class has been created and added to the . Now let’s create a static integer field for this class:

As can be seen, it’s so simple: just instantiate a , provide its name, type, and its modifiers (note that the modifiers are aggregated by or, binary operator). Then we add this field to the class. Now, we are going to create a method that increments this field and prints it:

It’s a little bit more complex than creating the field. For instantiating a you need to pass name, parameters’ types, return type, and modifier (lines 3–5). In addition to that, a method needs to have a body (line 7) that represents the functionality of the method. First, we increment the field by assigning its value to a temporary local variable (recall Jimple has three-addressed statements), add the local by one, and reassign the field to the incremented number (lines 11–15). Next, we log this number similarly to AndroidLogger; however, since we have two things to print (the string and the local ) we need to contact them using StringBuilder (for more info look at the code in InstrumentUtil.java). The final statement must be a return statement (line 21). Next, we validate and make it the active body of (lines 23-24).

We’re done! A new class, field, and method have been created and added to the APK. To invoke incrementAndLog method we use a similar approach to AndroidLoggger and you can find the corresponding BodyTransformer here. Once you run AndroidClassInjector.java, sign the instrumented app, install and open it, you should see something like this in the adb logcat:

07-07 14:33:03.177  3967  3967 I <SOOT_TUTORIAL>: Beginning of method <com.numix.calculator.view.ScrollableDisplay: void onMeasure(int,int)>
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Counter's value: 3935
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Beginning of method <com.numix.calculator.view.ScrollableDisplay: void onMeasure(int,int)>
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Counter's value: 3936
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Beginning of method <com.numix.calculator.view.ScrollableDisplay: void onMeasure(int,int)>
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Counter's value: 3937
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Beginning of method <com.numix.calculator.view.ScrollableDisplay: void onMeasure(int,int)>
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Counter's value: 3938
07-07 14:33:03.177 3967 3967 I <SOOT_TUTORIAL>: Beginning of method <com.numix.calculator.view.ScrollableDisplay: void onMeasure(int,int)>

Conclusion

In this blog post, we reviewed how Soot can reads/modifies/writes an Android APK and get familiar with Soot packs, in particular, Jimple Transformer. Feel free and play with the code, especially AndroidClassInjector.java, to create more interesting applications. For example, you can log the running thread or count the execution of each method separately.

If you’re interested in this work and you want to use it in a real project, I suggest you take a look at Android Soot Instrumentor repository which is designed for CLI and is more configurable. I created this repository based on my experience in Software Engineering research projects that I was involved and it would be great if you can collaborate to make it more useful.

The Startup

Medium's largest active publication, followed by +753K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store