PlumExample Android JNI

To demonstrate how to use Substrate to build an extension, we will now walk through the implementation of a concrete example: an extension that modifies the color of numerous interface components to become shades of plum. This extension is useless, but has the advantage of being immediately noticeable.

Step 1: Manifest Configuration

For our code to be loaded, our package must have the cydia.permission.SUBSTRATE permission.

Additionally, we set android:hasCode to "false" and android:installLocation to "internalOnly".

See Also:

Code Deployment: Android Native

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="internalOnly"
>
    <application android:hasCode="false">
    </application>

    <uses-permission android:name="cydia.permission.SUBSTRATE"/>
</manifest>

Step 2: Library Skeleton

Before we develop our modifications, we first must get our own code loaded. We must both specify a filter describing where to load, as well as declare a function for our library initialization code.

See Also:

Code Deployment: Android Native
Guide: Process Loading

#include <substrate.h>

MSConfig(MSFilterExecutable, "/system/bin/app_process")

// this is a macro that uses __attribute__((__constructor__))
MSInitialize {
    // ... code to run when extension is loaded
}

Step 3: Wait for Class

In order to change the code for a class, we first need a reference to that class. Substrate allows us to provide a callback that will be executed when classes with specific descriptors are loaded.

See Also:

C API: MSJavaHookClassLoad

static void OnResources(JNIEnv *jni, jclass resources, void *data) {
    // ... code to modify the class when loaded
}

MSInitialize {
    MSJavaHookClassLoad(NULL, "android/content/res/Resources", &OnResources);
}

Step 4: Modify Implementation

The implementation of the method is replaced at the JNI level. We thereby specify our replacement implementation using JNI's calling convention.

To call the previous implementation, we use the function pointer provided when we hooked.

Here, we call through to the original and modify the result, removing all green from the color and jacking up the red (yielding shades of plum).

See Also:

C API: MSJavaHookMethod

static jint (*_Resources$getColor)(JNIEnv *jni, jobject _this, ...);

static jint $Resources$getColor(JNIEnv *jni, jobject _this, jint rid) {
    jint color = _Resources$getColor(jni, _this, rid);
    return color & ~0x0000ff00 | 0x00ff0000;
}

static void OnResources(JNIEnv *jni, jclass resources, void *data) {
    jmethodID method = jni->GetMethodID(resources, "getColor", "(I)I");
    if (method != NULL)
        MSJavaHookMethod(jni, resources, method,
            &$Resources$getColor, &_Resources$getColor);
}