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.
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 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:
- Add a “SHARED” library named “fooSo”, whose execution will be hooked.
- 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.
- Hooking user defined .so
- Hooking standard libc.so
- 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.
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 .
LikeLike
thanks!
LikeLike
Hello, can you tell me how to hook a third part app’s so. And I had create an issue in your github project. Thanks!
LikeLike
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.
LikeLike
> 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.
LikeLike