The Xposed Framework

Some developers may have noticed that Substrate bears many similarities to a framework called Xposed that was released at the end of March 2012. In the last year, the developer of this library has put a lot of good work into it, attempting to foster a community of developers who are beginning to use it to accomplish some interesting packages.

Now, to be clear, as I'm certain this is the first place where peoples' minds go when they see me suddenly release a similar library: I did not know that this framework existed until January of 2013, and had thereby been working on Substrate for Android for literally years before having discovered it. Please understand that Substrate is not a "rip-off" ;P.

That said, at some level the libraries are fairly similar: the core mechanism by which a method is hooked in Xposed (swapping it out for a JNI implementation) is actually the same one that I described in the talk I gave on Substrate at a conference called Android Open in 2011. The result is that there are some similarities in the functionality offered by the two frameworks.

Thankfully, Substrate's injection mechanism does not conflict with the one used by Xposed: therefore, if users or developers would like to have both frameworks installed, that is actually possible. There are some places where the two libraries "disagree" (such as Xposed totally removing the Java security model that Substrate preserves), but the resulting behavior is harmless.

I thereby hope that everyone involved, from the developer of Xposed to the people who may currently be developing extensions for it, are able to look at Substrate not as an evil incursion, but as a powerful option. I really wasn't attempting to ruffle any feathers here: I've just been working—for much longer than I would have liked ;P—to bring the functionality we have on iOS to Android.

The goal of this article is thereby to document the differences between these frameworks so that people can understand why I still chose to release Substrate. There are a few ways in which I hope developers will find the work I've put into Android Substrate over the last few years to be valuable enough to at least consider using Substrate instead of Xposed.

Security

One of the first questions on the minds of people opening their devices up to rampant code modifications is in what ways the library can ensure that applications are not secretly installing extensions that can then modify other software without the knowledge of the user... put differently, what keeps you from installing a game today and having your bank password stolen tomorrow.

Xposed does not offer any kind of security system for this: any application can register itself as a provider of Xposed modules. In comparison, Substrate integrates with the Android permission system, requiring applications that wish to modify the code of other applications to clearly and explicitly request that functionality as they are installed.

Additionally, Xposed neuters the Java access check system used by the verifier: all of the functions are replaced with "return true". Substrate is able to operate without making these changes; instead, if a developer actually needs such functionality, it is possible to explicitly "bless" a restricted classloader, limiting the scope of power to only classes distributed with the extension.

Safety

Having worked with Substrate on iOS now in an ecosystem with numerous unrelated developers for over four years, there are various situations and corner cases that I have learned to watch out for. One of these is that sometimes, an extension is just going to fail. Substrate on Android provides the same "hold volume-up to disable" functionality that is provided with iOS Substrate as a solution.

However, there are also some additional things of interest: in particular, due to how these devices handle things like upgrades (patch files with signatures), it is both easier and safer if you can arrange to never modify or replace even a single file on the device. Substrate manages to accomplish this goal: a few files are added, but none are changed: even OTA upgrades are not affected by Substrate.

Substrate's implementation also manages to avoid having to re-implement any functionality from the underlying system, allowing it to work on "virtually any environment" without preparation. In comparison, Xposed has to come with pre-compiled versions of its app_process replacement for each of the various SDK levels that it supports (and may need special variants for custom ROMs).

Even so, when new versions of Android come out, sometimes changes have to be made. I consider the maintenance of Substrate my primary job (even on Android, which is a secondary platform); so, when Android 4.3 came out I dropped everything else and had a fully-working build pushed to the Play Store within hours: this same process took Xposed almost two weeks.

Performance

The mechanism used for keeping track of hooks with Xposed involves multiple levels of indirect iteration. Functions are only hooked once, and they all must then come through the same code inside of Xposed to figure out which method was called and what code should be called instead. The first level is a linear search through a linked list, and the second level involves iterating a TreeSet.

Substrate solves both of these problems using a more advanced technique involving runtime code generation: it never has to do any lookups, and can thereby scale to arbitrarily large numbers of methods being hooked by arbitrarily large numbers of hooks; the only performance overhead comes from Java/native transitions (which can be removed in the future) and the code inside the hooks themselves.

The only advantage Xposed gets from its indirection is that hooks can specify a "priority"; however, taking advantage of this feature requires global coordination, which in a community with thousands of developers (as Substrate has on iOS) is unrealistic, and would require much more complex planning than just a simple priority: that feature simply does not scale to a distributed ecosystem.

(This "advantage", however, only applies to the storage in the TreeSet; the asymptotic increase in time complexity in the number of hooked methods comes at no advantage, and in fact contributed to a long-standing bug in Xposed that methods with similar signatures sometimes got mixed up: this was only finally fixed months after discovery, and in a way that makes the iteration process even slower.)

Comprehensiveness

The Xposed framework is only able to operate on Java code: if a program calls out using JNI to native code, you are no longer able to make modifications. As Substrate's heritage is as a fully-featured engine for code instrumentation in a native environment (many core libraries on iOS are written in C), Substrate provides the features required to hook C functions.

You also may find yourself needing to hook code that exists in one of the various daemons that comes with Android, such as adbd. As Xposed hooks only into Zygote by way of the app_process binary, it is unable to load your code into these other processes. Substrate's more pervasive code injection solution allows you to inject into every single process.

Substrate also offers the ability to get hooks loaded earlier: as it offers an API based entirely in JNI and native libraries, and can load early enough to simulate LD_PRELOAD support, you can use Substrate to modify any aspect of the process including its initialization. Xposed only has a Java-based API that must be loaded out of application contexts, which drastically delays its effects.

Simplicity

Substrate's API makes the kinds of changes that most developers need to make fairly short: there is no indirection required to modify arguments or call the original function. Substrate also encourages and enables a style where extensions can be written against native classes, even when those classes are loaded multiple times by different classloaders.

Additionally, Substrate is well documented (much of the time I've spent on this project in the last year has actually been on documentation) and has an SDK that is easy to obtain using the standard Android tools (such as the SDK Manager). Using the libraries also does not require messing around with your classpath: you can just include the jar.

Finally, as Substrate is distributed via the Play Store, it is easy for users to find and install. While people can download APKs and install them manually (and in some cases will need to, if the Play Store is not available), requiring everyone who wants to use your modification to install a third-party APK manually is a massive barrier to people using your extension.

Orthogonality

Due to some limitations of Xposed's implementation (which lacks native code modication techniques), it requires developers to carefully time when their hooks are applied: if you want to modify the code in a particular package, for example, you will need to wait until after that package is loaded. You then can get the class loader for the package, and use it to find classes and hook their methods.

To make this easier, Xposed provides a set of helpers for common use cases: you can hook when the VM starts, when Zygote takes control, when a particular package is loaded, or when a command line application is executed. You need to know which of these to use, and it is still unclear how you'd hook a class loaded via a dyamic runtime-created class loader (such as against downloaded code).

Substrate instead does away with all of this, thanks to MS.hookClassLoad, an API it provides that allows you to wait for particular classes to be loaded from any class loader at any time. This allows you to write hooks in a way that is less brittle to changes, less prone to simple mistakes, and less limited by how the developer of the target application decided to load their program's code.