Published on GitHub at https://github.com/ivision-research/dtu.

About 6 years ago I was tasked with answering a question: how can we perform in depth testing of Android devices quickly and without root access? That question resulted, over multiple iterations and a few complete rewrites, in the creation of dtu, our Android device testing utility. I’m really happy to finally be releasing this tool to the public, because I think it fills a major gap in the available open source tooling for testing Android devices and makes it straightforward for anyone to get set up to test any device, hopefully increasing the security of the Android operating system in the process. This is also not just a code dump, since I’ve been actively maintaining this tool in some form for over 6 years now. Using this tool we have found and disclosed multiple critical vulnerabilities in Android devices, and I’d like to explain how it was developed and how it works.

High level

dtu is a tool (and Rust crate) to help understand and test the Java based attack surface on an Android device. This means that it helps test system services implemented in Java and exported IPC interfaces in high privileged APKs. Of note, there are no tools currently in dtu for testing native code. This decision was made for two reasons: (1) we were required to work without rooted devices and SELinux generally prevents access to native system service implementations and (2) Java bytecode is simply easier to analyze and work with. I think it’s important to point out this limitation early in this introduction, because these considerations drove all design decisions. There are other methods that can be employed for working with native code, and dtu is hopefully just another tool in a tester’s arsenal. dtu is also not going to solve your problems for you: it’s going to provide you with tools to be more efficient and a framework to build automation on top of, but there is very little automation directly in the tool. Expect to still be reverse engineering Java code as either smali or decompiled Java, and expect to still have to do a fair amount of thinking.

With all of that in mind, what dtu does is:

  1. Identify the source of Java code running on the device. Think jar, apk, and apex files
  2. Convert all of that code to smali
  3. Perform some static analysis on the smali and create a graph database of inheritance and method calls
  4. Assist the tester in quickly identifying, and testing, interesting classes or methods

I’m going to walk through a typical test using dtu to give a feel for how to use it.

Testing setup

You’ll see a tl;dr in the dtu README with the following:

dtu pull
dtu graph full-setup
dtu db setup

These are commands that you’ll run for every device you test, and you’re going to want to take a nap while they run, because a lot is happening in these 3 commands. The first, dtu pull, attempts to identify and pull all files that may have Java code running on the device. It uses a few heuristics to do this, and we’re constantly updating this process as we learn more and Android evolves, but it generally gets enough to do some interesting testing. Behind the scenes this is just using tools like:

  • adb - Used to run shell commands on the device and pull files
  • apktool - Used for converting APK files to smali
  • baksmali - Used for converting dex files to smali
  • vdexExtractor - Not used for later versions of Android, but can find code in vdex files on older versions

Everything that is pulled is decompiled and placed into a dtu_out/smali directory. Files that are part of the framework are stored separately from individual APK files.

Once all of this data is gathered, dtu graph full-setup makes use of smalisa to parse all of the smali files and create an inheritance and call graph to store in a Cozo database. This graph is used throughout dtu to get answers to questions like “Which classes implement foobar.Interface, the interface for the foobar system service?” or “which functions, within 3 hops, call java.lang.Runtime.exec(...)?” and any number of other interesting things you can ask a call graph. You can directly interact with this Cozo database with dtu graph repl or send one off queries to dtu graph eval. There are some canned queries available at dtu graph canned.

After the Cozo database is set up, we next populate an sqlite database with dtu db setup. Some examples of data stored in this database are:

  • The output of getprop
  • All permissions that can be automatically discovered
  • All BroadcastReceivers, Services, ContentProviders, and Activitys in the pulled APKs and framework
  • All system services and their Java implementations if they exist
  • Whether the implementations of methods match the implementations on a chosen comparison (more on this later)

You can check the contents of the database yourself over in dtu_out/sqlite/device.db.

At this point you’re ready to start testing the device! Note that this whole process pulls and stores a ton of data: sorry about your hard drive space. Expect about 15-20GB for a device.

Diffing

In the previous section I alluded to the ability to compare devices, and this is a core feature of dtu that I highly recommend taking advantage of. We generally create a dtu device.db file for an Android emulator at every API level we’re interested in and use this as the base against which we should compare a device. In fact, this is expected, if you don’t want the diff to occur, you have to run dtu db setup --no-diff. Diffing helps cut down on noise and helps focus your testing on features that aren’t part of AOSP: we all know Google doesn’t write buggy code, so we want to find code specific to our device. You can diff against arbitrary devices too! For example, say you are testing the Pixel 9, and last year you tested the Pixel 8. Well, you determined that the Pixel 8 was perfect and has no bugs, so you want to find only things that are different in the Pixel 9. Add that Pixel 8’s device.db as a diff, dtu db diff-source add ..., and fire up the diff TUI dtu diff ui -S pixel8 to focus on the new features!

Diffing is a lot deeper than you may first expect. While at one level the diff just says “this exists only in the device under test” it also has the capability to say “this exists in both devices, but it looks different”. This is done for all system service methods that are discovered by walking through all of the smali for each method and trying to determine if they have the exact same code or not. While this is not perfect and can be a bit brittle, it has proven really helpful, and anything to help focus testing of such a huge attack surface is a plus.

Test application

dtu app setup and dtu app install will create and install a testing application. This application is used by various dtu commands to interact with the device as a “low privileged” application, and while not required, it is recommended to use and install it. The communication with the application happens via a TCP server that is accessible via an adb forwarded port. The test application provides some helpful tools to write and execute tests. Check out dtu app in general for the test application; I’m not going to talk about it much here. Commands that require the server up and running will generally error out and let you know that it should be installed.

Active testing

Now that you have all this data, you probably want to do something other than fill up your hard drive. For me, testing normally begins at dtu diff ui, and this should keep you busy for a while. I typically head straight to the Providers section first, because there is sometimes some funny business happening in ContentProviders and I like to check them out. My work flow here is typically to find one with an “interesting name”, navigate over to it, and hit O to open it up in vim, where I have a custom plugin that I use to automatically convert the file to Java with jadx. This is a kind of “hook” system provided by dtu if the dtu-open-file binary exists in your $PATH or $DTU_OPEN_EXECUTABLE is set. Then I’ll look through the implementation and if I see something potentially interesting, I’ll hit c to invoke the dtu-clipboard hook and copy a template dtu provider -a AUTHORITY ... call to my clipboard and paste it in a shell to try to interact with the ContentProvider via the dtu test application. If I don’t want to revisit the ContentProvider again, I’ll just hit h and hide it so I don’t have to see it anymore. This workflow is fast: I can do a high level test of every discovered ContentProvider on the device within a couple hours without ever writing a line of Kotlin code. This testing strategy gets results too: I’ve found dozens of ways to completely take over a device from an unprivileged application with dtu and even ended up rooting some devices!

dtu supports a ton more functionality than just the diff UI though. You can run canned queries against the graph with dtu graph canned to look for some standard suspicious behavior; you can check if a given permission you see is dangerous or not; you can list all system services and the classes that implement them; you can pull and compile the SELinux policy for analysis with apol, and much more. dtu can also work based on file system dumps instead of a live device. This is really useful when you are able to dump the flash memory of an Android based device for example. dtu has been evolving for years now and I’ve added a ton of functionality, but there is also so much more potential for this tool. On top of all of that, dtu is a Rust crate exposing all of its internals. I’m excited for this next step in dtus development where the public at large gets to contribute to and use it!