Android Substrate Hooking C Code

Introduction

This article is to provide a tutorial about Android Substrate native hooking using the latest version v3.0.1 of Android Studio at the time of writing.
Typically, you already have a target Android app to be hooked and what you are trying to do is creating the native hooking module to tamper the code execution of the target app. Android Substrate can help with such kind of work not only on Java layer but also on native C layer. This article will focus on the latter.

Project setup

Host PC: MacOS Sierra 10.12.4

Android Studio: 3.0.1

Android OS: 4.4 and below (Note that Android Substate only works for Dalvik vm)

Android Substrate SDK r2: cydia_substrate-r2.zip , extract the substrate.h file and the dynamic library directories.

NDK: https://dl.google.com/android/repository/android-ndk-r15c-darwin-x86_64.zip

Steps

Create target app

We will use Android Studio project setup panel to create a simple app that has a button to trigger one native method “testPassword” which will just compare the parameter with the real hardcoded password and then try to hook this native method making it always return “equal” result.

Also, we will add one more button trying to trigger the hooking of standard libc method “strncmp()”.

Your target app should be something looks like below screenshot.

Screen Shot 2017-11-30 at 1.12.25 PM

Include Substrate native libs and header file

The two Substrate dependencies libsubstrate-dvm.so and libsubstrate.so need to be put in correct directory, i.e. under app/src/main/jniLibs for different ABIs. Note that it only support x86 and armeabi. The rest of ABIs came onto stage from Android API 21. But, it looks that Android Substrate is no longer maintained and updated Since 2013.

Add shared libs
Screen Shot 2017-11-30 at 10.01.37 AM
Add header file

Add code to load these shared libs

Open the MainActivity.java file and add below code snippet to load the necessary shared native libs.

static {
    // The target .so file to be hooked.
    System.loadLibrary("fooSo");
    // The hooking module, note that the .cy is necessary for CydiaSubstrate to recognize it correctly.
    System.loadLibrary("hook.cy");
    // CydiaSubstrate dependencies.
    System.loadLibrary("substrate");
    System.loadLibrary("substrate-dvm");
}

Modify the onCreate() method to let your buttons link to your methods to be hooked.

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    testPassword.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String resultString = testPassword("faked_password") == 0 ? "password success":"password failed";
            Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
        }
    });
    ...
    testStrncmp.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            String resultString = testStrncmp("apple", "banana", 5) == 0 ? "strncmp success":"strncmp failed";
            Toast.makeText(MainActivity.this, resultString, Toast.LENGTH_LONG).show();
        }
    });
}

Declare your JNI methods which will invoke the native implementation.

public native int testPassword(String password);
public native int testStrncmp(String s1, String s2, int size);

 

Modify build.gradle

Configure the native build process as below

android {
    compileSdkVersion 26
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions"
                abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                targets "fooSo", "hook.cy"
            }
        }
    }
    buildTypes {
        ...
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

 

Add the CMakeLists.txt

This file usually is under directory app/ for Android native build. What does this cmake file do are:

  1. Add a “SHARED” library named “fooSo”, whose execution will be hooked.
  2. Add a “SHARED” library named “hook.cy”, which is the Substrate hooking module, the “.cy” string is necessary for Substrate to recognise it correctly.
cmake_minimum_required(VERSION 3.4.1)

set(MODULE_NAME fooSo)
include_directories(src/main/cpp)
add_library( ${MODULE_NAME}
             SHARED
             src/main/jni/fooSo.c
             src/main/cpp/hook_example.c )
find_library( log-lib log )
target_link_libraries( ${MODULE_NAME}
                       ${log-lib})

set(HOOK_MODULE hook.cy)
include_directories(src/main/cpp)
add_library( ${HOOK_MODULE}
             SHARED
             src/main/cpp/hook.cpp )
target_link_libraries( ${HOOK_MODULE}
                       ${log-lib}
                       ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libsubstrate-dvm.so
                       ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libsubstrate.so)

Add the JNI implementation

const int testPassword(const char *myString)
{
    const char *mySecret = "my_secret_pass";
    int cmpResult = strncmp(myString, mySecret, strlen(mySecret));
    return cmpResult;
}

JNIEXPORT jint JNICALL
Java_com_example_shizzhan_sampleapp_MainActivity_testPassword(JNIEnv *env, jobject instance, jstring myString) {
    const char *password = (*env)->GetStringUTFChars(env, myString, 0);
    int ret = testPassword(password);
    LOGI("In JNI method, password is: %s", password);
    return ret;
}

JNIEXPORT jint JNICALL
Java_com_example_shizzhan_sampleapp_MainActivity_testStrncmp(JNIEnv *env, jobject instance,
                                                             jstring s1_, jstring s2_, jint size) {
    const char *s1 = (*env)->GetStringUTFChars(env, s1_, 0);
    const char *s2 = (*env)->GetStringUTFChars(env, s2_, 0);
    int cmpResult = strncmp(s1, s2, (size_t)size);
    (*env)->ReleaseStringUTFChars(env, s1_, s1);
    (*env)->ReleaseStringUTFChars(env, s2_, s2);
    return cmpResult;
}

Add the hooking module

I will provide examples for hooking 3 different shared libs.

  1. Hooking user defined .so
  2. Hooking standard libc.so
  3. Hooking the Android dalvik vm lib.
#include <jni.h>
#include "substrate.h"
#include <android/log.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define TAG "SHIZHEN_HOOKTEST"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

MSConfig(MSFilterLibrary, "libfooSo.so");
MSConfig(MSFilterLibrary, "libc.so");
MSConfig(MSFilterLibrary, "libdvm.so");

void performHook(const char *targetSo, const char *symbol, void *replace, void **original);

bool (*_dvmLoadNativeCode)(char* pathName, void* classLoader, char** detail);
bool fake_dvmLoadNativeCode(char* soPath, void* classLoader, char** detail)
{
    LOGD("fake_dvmLoadNativeCode soPath:%s", soPath);
    return _dvmLoadNativeCode(soPath,classLoader,detail);
}

const int (*original_testPassword)(const char *myString);
const int faked_testPassword(const char *myString)
{
    LOGE("testPassword() is hooked!!!!!!, will always return ZERO !!!!!!");
    return 0;
}

static int (*original_strncmp)(const char*, const char*, size_t);
int faked_strncmp(const char* a, const char * b, size_t n)
{
    LOGE("strncmp() is hooked!!!!!!");
    if(strcmp("apple", a) == 0) {
        LOGE("strncmp() is hooked!!!!!!, will always return ZERO is the string is \"apple\" !!!!!!");
        return 0;
    }
    return original_strncmp(a, b, n);
}

void* find_symbol(const char* libraryname, const char* symbolname)
{
    void *imagehandle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);
    if (imagehandle != NULL) {
        void * sym = dlsym(imagehandle, symbolname);
        if (sym != NULL) {
            LOGE("symbol (%s) is found at address %p (%p) in lib %s", symbolname, sym, &sym, libraryname);
            return sym;
        } else {
            LOGE("find_symbol() can't find symblo (%s).", symbolname);
            return NULL;
        }
    } else {
        LOGE("dlopen error: %s, when opening lib %s",dlerror(), libraryname);
        return NULL;
    }
}

// Substrate entry point
MSInitialize{
    // Example 1: hook user defined .so
    // Hook testPassword
    const char *fooSoPath = "/data/data/com.example.shizzhan.sampleapp/lib/libfooSo.so";
    performHook(fooSoPath, "testPassword", (void*)&faked_testPassword, (void**)&original_testPassword);
    // Example 2: hook libc.so. Hooking libc functions may cause the system hang. So, don't play around libc hookings.
    // Hook strncmp
    performHook("/system/lib/libc.so", "strncmp", (void*)&faked_strncmp, (void**)&original_strncmp);
    // Example 3: hook libdvm.so
    // Hook dvmLoadNativeCode
    performHook("/system/lib/libdvm.so", "_Z17dvmLoadNativeCodePKcP6ObjectPPc", (void*)&fake_dvmLoadNativeCode, (void**)&_dvmLoadNativeCode);
}

void performHook(const char *targetSoPath, const char *symbol, void *replace, void **original) {
    // MSGetImageByName only work when the .so has already been loaded.
    MSImageRef image = MSGetImageByName(targetSoPath);
    void *symAddress;
    if (image != NULL) {
        LOGE("===>>>>>> MSGetImageByName (%s) succeed, symbol address: %p", targetSoPath, image);
        symAddress = MSFindSymbol(image, symbol);
        LOGE("===>>>>>> MSFindSymbol (%s) succeed, symbol address: %p", symbol, symAddress);
        MSHookFunction(symAddress, replace, original);
    } else { // When the .so has not been loaded, need to use below code to find the symbol.
        LOGE("Image not found, trying with find_symbol");
        // The following will work, as it will forcefully load given library.
        symAddress = find_symbol(targetSoPath, symbol);
        if (symAddress != NULL) {
            LOGE("===>>>>>> find_symbol (%s) succeed, symbol address: %p", symbol, symAddress);
            MSHookFunction(symAddress, replace, original);
            LOGE("===>>>>>> find_symbol (%s) succeed, symbol address: %p", symbol, symAddress);
        } else {
            LOGE("===>>>>>> find_symbol failed");
        }
    }
}

Substrate Permission

Last but not least, add the Substrate permission in AndroidManifest.xml

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

Caveats

To hook the system libc.so, you need to know the absolute path on your device. Sometimes, the path of system libs may be different from different kind of devices.

You can use cat command to check the absolute paths of all the loaded components of your app.

adb shell
cat /proc/<pid>/maps

Then you should be able to see something like below, for example, the pid is 4276. Then feed the proper .so path to your hooking module.

root@generic_x86:/ # cat /proc/4276/maps
…
b4d19000-b4edb000 r-xp 00000000 fd:00 696 /system/lib/libdvm.so
b622f000-b6230000 r — p 00050000 fd:00 651 /system/lib/libRS.so
b6494000-b6496000 r — p 00056000 fd:00 778 /system/lib/libssl.so
b6ba8000-b6bf1000 r-xp 00000000 fd:00 738 /system/lib/libjpeg.so
…
b6c95000-b6c97000 r — p 0007a000 fd:00 711 /system/lib/libft2.so
…
b6c98000-b6c99000 r — p 00000000 00:00 0
b6c99000-b70df000 r-xp 00000000 fd:00 770 /system/lib/libskia.so
b742f000-b744b000 r-xp 00000000 fd:00 830 /system/lib/libz.so
b76a7000-b76a8000 rw-p 00002000 fd:00 806 /system/lib/libstdc++.so
b76a8000-b7741000 r-xp 00000000 fd:00 673 /system/lib/libc.so
b7756000-b775c000 r-xp 00000000 fd:00 741 /system/lib/liblog.so
b7771000-b7772000 rw-p 00012000 fd:00 690 /system/lib/libcutils.so
b7798000-b779a000 r-xp 00000000 fd:00 95 /system/bin/app_process
b779a000-b779b000 r — p 00001000 fd:00 95 /system/bin/app_process
root@generic_x86:/ #

If hooking success, then by clicking on the respective buttons, you should be able to see a Toast message saying “password success” or “strncmp success“.

Source code download

All the source code can be downloaded from my github page at AndroidCydiaHook.

References

http://www.cydiasubstrate.com

5 thoughts on “Android Substrate Hooking C Code

  1. Hi my friend! I want to say that this post is awesome, great
    written and come with approximately all important infos. I’d like
    to look extra posts like this .

    Like

  2. You need to understand the exported symbols and functions of that particular .so from the target application using some reverse engineering tools, e.g. IDA pro. Then apply the hooking approach as demonstrated in this example project. But, for your information that cydia substrate has not updated since Android ART come on to stage meaning this example project is not applicable to later Android versions.

    Like

  3. starch

    > Task :app:externalNativeBuildDebug FAILED
    Build fooSo_x86
    [1/3] Building C object CMakeFiles/fooSo.dir/src/main/cpp/hook_example.c.o
    [2/3] Building C object CMakeFiles/fooSo.dir/src/main/jni/fooSo.c.o
    [3/3] Linking C shared library ..\..\..\..\build\intermediates\cmake\debug\obj\x86\libfooSo.so
    Build hook.cy_x86
    [1/2] Building CXX object CMakeFiles/hook.cy.dir/src/main/cpp/hook.cpp.o
    [2/2] Linking CXX shared library ..\..\..\..\build\intermediates\cmake\debug\obj\x86\libhook.cy.so
    FAILED: cmd.exe /C “cd . && D:\AndroidSDK\ndk\22.0.7026061\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe –target=i686-none-linux-android16 –gcc-toolchain=D:/AndroidSDK/ndk/22.0.7026061/toolchains/llvm/prebuilt/windows-x86_64 –sysroot=D:/AndroidSDK/ndk/22.0.7026061/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -mstackrealign -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -frtti -fexceptions -O0 -fno-limit-debug-info -Wl,–exclude-libs,libgcc.a -Wl,–exclude-libs,libgcc_real.a -Wl,–exclude-libs,libatomic.a -static-libstdc++ -Wl,–build-id=sha1 -Wl,–no-rosegment -Wl,–fatal-warnings -Wl,–no-undefined -Qunused-arguments -shared -Wl,-soname,libhook.cy.so -o ..\..\..\..\build\intermediates\cmake\debug\obj\x86\libhook.cy.so CMakeFiles/hook.cy.dir/src/main/cpp/hook.cpp.o -LD:/Projects/AndroidStudioProjects/StudyProjects/AndroidCydiaHook-master/app/src/main/jniLibs/x86 -llog -lsubstrate-dvm -lsubstrate -latomic -lm && cd .”
    ld: error: found local symbol ‘__INIT_ARRAY__’ in global part of symbol table in file D:/Projects/AndroidStudioProjects/StudyProjects/AndroidCydiaHook-master/app/src/main/jniLibs/x86\libsubstrate-dvm.so
    ld: error: found local symbol ‘__FINI_ARRAY__’ in global part of symbol table in file D:/Projects/AndroidStudioProjects/StudyProjects/AndroidCydiaHook-master/app/src/main/jniLibs/x86\libsubstrate-dvm.so
    clang++: error: linker command failed with exit code 1 (use -v to see invocation)
    ninja: build stopped: subcommand failed.

    FAILURE: Build failed with an exception.

    * What went wrong:
    Execution failed for task ‘:app:externalNativeBuildDebug’.
    > Build command failed.
    Error while executing process D:\AndroidSDK\cmake\3.6.4111459\bin\cmake.exe with arguments {–build D:\Projects\AndroidStudioProjects\StudyProjects\AndroidCydiaHook-master\app\.cxx\cmake\debug\x86 –target hook.cy}
    [1/2] Building CXX object CMakeFiles/hook.cy.dir/src/main/cpp/hook.cpp.o
    [2/2] Linking CXX shared library ..\..\..\..\build\intermediates\cmake\debug\obj\x86\libhook.cy.so
    FAILED: cmd.exe /C “cd . && D:\AndroidSDK\ndk\22.0.7026061\toolchains\llvm\prebuilt\windows-x86_64\bin\clang++.exe –target=i686-none-linux-android16 –gcc-toolchain=D:/AndroidSDK/ndk/22.0.7026061/toolchains/llvm/prebuilt/windows-x86_64 –sysroot=D:/AndroidSDK/ndk/22.0.7026061/toolchains/llvm/prebuilt/windows-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -mstackrealign -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -frtti -fexceptions -O0 -fno-limit-debug-info -Wl,–exclude-libs,libgcc.a -Wl,–exclude-libs,libgcc_real.a -Wl,–exclude-libs,libatomic.a -static-libstdc++ -Wl,–build-id=sha1 -Wl,–no-rosegment -Wl,–fatal-warnings -Wl,–no-undefined -Qunused-arguments -shared -Wl,-soname,libhook.cy.so -o ..\..\..\..\build\intermediates\cmake\debug\obj\x86\libhook.cy.so CMakeFiles/hook.cy.dir/src/main/cpp/hook.cpp.o -LD:/Projects/AndroidStudioProjects/StudyProjects/AndroidCydiaHook-master/app/src/main/jniLibs/x86 -llog -lsubstrate-dvm -lsubstrate -latomic -lm && cd .”
    ld: error: found local symbol ‘__INIT_ARRAY__’ in global part of symbol table in file D:/Projects/AndroidStudioProjects/StudyProjects/AndroidCydiaHook-master/app/src/main/jniLibs/x86\libsubstrate-dvm.so

    ld: error: found local symbol ‘__FINI_ARRAY__’ in global part of symbol table in file D:/Projects/AndroidStudioProjects/StudyProjects/AndroidCydiaHook-master/app/src/main/jniLibs/x86\libsubstrate-dvm.so

    clang++: error: linker command failed with exit code 1 (use -v to see invocation)

    ninja: build stopped: subcommand failed.

    Like

Leave a comment