AssetHook: A Redirector for Android Asset Files Using Old Dogs and Modern Tricks

Summary

AssetHook is a tool that enables Android security researchers and pentesters to modify the asset portions of Android applications on the fly, without modifying the APK itself. Such modifications allow researchers to alter embedded data to better assess and test mobile applications. AssetHook is easier to use than existing methods and can be more effective than the traditional approaches used to modify asset resources.

Background

tl;dr React Native uses the native Android APIs for handling APK-embedded file reads and Java function hooking doesn't cut it.

At the end of last year, I started looking at the Android implementation of React Native — Facebook's new framework to unify cross-platform mobile development onto JavaScript, a language that needs no introduction. Before React Native, the primary way to build cross-platform mobile apps in JavaScript was to use PhoneGap (...or is it Cordova now? And don't even get me started on Ionic). These are all based on writing the main app logic in JavaScript that is loaded into a webview (UIWebView on iOS, WebView on Android). A developer may then write some platform specific code in the platform's "native" language and connect them together with spooky webview magic FFIs.

React Native kicks this design (and possibly reason) to the curb and inverts the paradigm. Instead of using webviews and HTML/CSS for rendering, it maps JavaScript-declared UIs to the platform's native UI toolkit and embeds WebKit's JavaScriptCore (JSC) library to run that JavaScript, entirely avoiding the platform's webview implementation. JSC supports interpreting JavaScript without JIT-ing, which is necessary on iOS, since its security model is based on not allowing anyone to generate executable memory (and people say the OpenBSD folks are Luddites for W^X...); and, the last I checked, V8 is JIT only (the new ignition interpreter doesn't seem to change this as "JIT code generation is still required for ICs and code stubs").

As part of my initial foray into React Native on Android, I sought to inject additional JavaScript code into apps to load a development console REPL to poke around with (and perform janky function hooking). So, I did what one normally does in this case and wrote an Xposed function hook for Android's android.content.res.AssetManager class. ...and I got nothing.

Android uses the AssetManager class as an interface for apps to access certain types of file resources that are embedded within their APKs (specifically those in the assets/ directory in the APK). Given that I knew (from cracking open a release build React Native application) that the main post-processing JavaScript bundle was stored as an asset, it was strange that the hook wasn't working. I then set forth on an adventure through React Native's Android codebase and soon found that the bundle file was loaded by C++ code using a C API for Android's asset manager, which explained why the Java function hooking wasn't working.

As I wanted to modify the contents of these bundles on the fly without creating and signing modified APKs (which would then require more hooking to spoof their signatures), I set to work building a tool to hook these asset loads. I wrote the initial proof-of-concept in (modern) C++ and then subsequently re-wrote it in Rust.

(A)AssetHook

AssetHook is an LD_PRELOAD-based function hooking library for Android applications. It hooks Android's internal asset management code and redirects asset file loads from legitimate (i.e. signed) APKs to a separate location on the device's local file system. The initial version of (A)AssetHook, "AAssetHook" (two "A"s), intercepted calls to Android's public AAssetManager C API, which is actually written in extern "C" C++. However, as this API is just a wrapper around Android's internal AssetManager class that is used directly by the Java android.content.res.AssetManager class, I rewrote it as "AssetHook" (one 'A') to hook the underlying C++ API instead.

Without something like AssetHook, replacing an Android asset would require unpacking the APK, replacing the asset file, re-packing the APK, possibly re-aligning it, signing the new APK file, and then installing it. Aside from this being a tedious and slow process (especially the APK copy to the device and install process for larger apps), one would also have to handle the fallout of the new signature breaking cross-application signature checks. For groups of apps that use custom permissions, shared user IDs, or custom IPC access controls, this new signature would prevent the modified app from interacting with the rest of the apps or from functioning at all. Additional hooking would then be necessary to spoof the signing certificate of the modified APK to match the original one. Furthermore, such hooks would likely not be possible on Android without a more ingrained and flexible function hooking infrastructure — such as Xposed, which modifies system partition binaries. A key issue with use of such frameworks is their delay in supporting new Android releases due to the porting effort needed.

C API Hooking With LD_PRELOAD

The C API hooking variant ("AAssetHook") is a fairly vanilla LD_PRELOAD hook implementation that declares a number of duplicate function symbols that — though the magic of dynamic linking — override calls to those functions when accessed from outside of their binary. It then uses a Rust wrapper library around dlopen(3)/dlsym(3) (on Unix) to proxy calls to the original functions as necessary. After that, it checks if a given file path within an APK matches an existing path on the device's file system, and spoofs the C API to return file contents for the on-disk file instead of the in-APK one.

C++ API Hooking With LD_PRELOAD

Polymorphism and the virtual Keyword

The C++ API hooking variant ("AssetHook") is slightly more complicated due to the fact that the C++ implementation of the asset manager is not a public API, relies heavily on polymorphism, and is subject to change across minor Android releases. When using polymorphism in C++, virtual method calls will generally be handled through dynamic dispatch. On most "sane" platforms this is implemented by having a virtual table (vtable) pointer as one of the first elements of a class' memory structure. This pointer is set during construction and points to a table filled with function pointers for the specific virtual methods that the class uses. LD_PRELOAD-based function hooking is very limited in this situation because it can only hook cross-binary function calls for exported symbols, and dynamic dispatch calls instead use direct function pointers. This prevents vtable-based calls from being directly hooked by LD_PRELOAD symbol overrides. Additionally, the order of these function pointers is highly likely to change on minor class definition changes. It is also inconsistent across different compilers, and in general can be tricky to infer across multiple versions of a binary.

Note: It's all up to the compiler, but this is mostly consistent across Clang and GCC on Unix (at least for x86/amd64 and ARM/AArch64, anyway).

For example, the following code snippet's objects may be laid out as shown in the following diagram:

#include <stdio.h>

struct Base {
  virtual void foo() {
    puts("base!");
  }
  size_t a = (size_t)-};

struct Derived : public Base {
  virtual void foo() {
    puts("derived!");
  }
  size_t b = (size_t)-};

struct DDerived : public Derived {
  void foo() final override {
    puts("dderived!");
  }
  size_t c = (size_t)-};

void call_foo(Base& br.foo();
}

int main() {
  Base b;
  Derived d;
  DDerived dd;

  call_foo(b);
  call_foo(d);
  call_foo(dd);

  return 0;
}

vtable layout

Function Searching and vtable Slot Mapping

One method to handle the volatility of vtable ordering would be to cross-reference a given class' vtable against the symbol table of its binary (parsed with things like binutils' GPL'd bfd.h or libelfin). With the address of a function in hand, it can be cross-referenced against the vtable to find its offset. However, this may not work as some virtual method functions might not have symbols, as is the case with the C++ implementation of the Android asset manager. Another method would be to scan for the raw bytes of such unsymboled functions, but this is unlikely to scale across multiple binary releases.

It is also entirely possible (using something like Capstone) to parse the assembly of well-symboled functions that call a target class' virtual methods and extract those methods' vtable offsets.

vtable Slot Knocking

The C++ API hooking implementation of AssetHook uses a variation of this last approach by relying on the fact that the C API that thinly wraps virtual method calls is itself is a public API with ABI stability guarantees. Instead of parsing the instructions for the C API functions to yank out vtable offsets, AssetHook creates a fake C++ object and uses it to profile the vtable. This object vtable pointer points to a spoofed vtable that contains a series of function pointers that report the order in which they are invoked. AssetHook passes this object as a pointer embedded within a void*-masked wrapper struct to the C API, which then invokes the associated virtual method on it via a compiler-supplied offset. When this call is performed, an embedded function pointer is fired, exposing the vtable offset for a given operation. This allows for the actual vtable order to be ascertained by calling through the known C API functions on this fake object one-by-one to obtain the vtable offsets for the C++ methods they wrap.

Note: For particularly "complicated" classes involving thunks, a more dynamic profiling of the vtable would be necessary, as the "normal" member function pointer in the vtable segment of the class alias is actually a wrapper function generated by the compiler. This function shifts the this pointer accordingly and invokes the "real" member function pointer from elsewhere in the greater vtable for the actual class.

Hooking Virtual Methods

After this, an LD_PRELOAD-style hook is made on the mangled symbol name for the "public" non-virtual method of the AssetManager class that returns an Asset object pointer. This hook determines if the file should be hooked and, if so, returns a pointer to a modified Asset object with a vtable full of function hooks.

Example

React Native

In this example, we'll use AssetHook to swap our own JavaScript file into the Tic-tac-toe example app (refer to the React Native documentation, I had to pass --dev false to react-native build to get it to minify most of the app). We'll assume you have installed it as described in the README.

  1. Install a "release" build of the app (for minification, AssetHook works with both release and debug Android APK builds) on a rooted test device (AssetHook is known to work with Android 5.x, 6.x, and 7.x on 32-bit and 64-bit ARM, and the Android O Developer Preview on 32-bit x86).

  2. Extract the assets/index.android.bundle file from the APK (sourced from the embedded Android app/build directory or from the device using adb shell pm list packages -f | grep <pkg> and adb pull).

  3. Modify the file to inject a JavaScript alert(...).

  4. Run the following commands:

    $ adb push path/to/index.android.bundle /data/local/tmp/assethook/<pkg>/assets/
    $ adb shell su -c 'setprop wrap.<pkg> LD_PRELOAD=/data/local/tmp/lib/libassethook_cppapi.so'
    

    Note: React Native uses 32-bit binaries, even on 64-bit Android. Due to this, the process loads in 32-bit mode and we need to use the 32-bit version of AssetHook.

  5. Start up the app (close it first if already open).

Hooked Tic-tac-toe app

Future Work

The main priority is to support Android when SEAndroid is fully enabled in enforcing mode. Right now, AssetHook requires that SELinux be put in permissive mode as the replacement files exist within a shared temporary directory that Google has sought to restrict access to in modern Android versions via SELinux. I'm looking to support loading replacement asset files from an application's own internal directory, but this may require additional tooling to ease file uploads onto devices.

After that, I'd like to focus on adding similar hooks for the native APIs used internally by Android to load Application resources.

Published date:  26 May 2017

Written by:  Jeff Dileo

Filter By Service

Filter By Date