Tuesday, December 16, 2014 At 3:30PM
One day, you decide to put on the security ninja gloves, open your laptop, plug your android phone and take apart your dear Android application using Part 1 of our tutorial on Android Hooking using Mobile Substrate; and you realize there is something wrong – something is not working. Then you come to the realization that your hook can’t access non-public methods. This post will discuss an option for hooking non-public methods by utilizing Mobile Substrate from NDK code.
Getting Started
The application we will be using in this demonstration is a demo application called SuperMath and can be downloaded here. This application will perform various mathematical operations within private functions and display the results to the user. The focus of this post will be hooking into private functions of a class, accessing and modifying the function parameters, and viewing and modifying the function’s return values.
Note: This guide assumes you have a rooted device, have already installed Mobile Substrate onto the device, and have already downloaded the Mobile Substrate API.
In case you haven’t done so yet, you can download the Mobile Substrate APK here: http://www.cydiasubstrate.com/
You can also read the following documentation on how to download the Mobile Substrate APIs here: http://www.cydiasubstrate.com/id/73e45fe5-4525-4de7-ac14-6016652cc1b8/
Preparing NDK/JNI Support
After following steps “Setting Up the IDE” and “Adding Permissions to AndroidManifest.xml” from Part 1 of our tutorial, we need to make sure that we have installed and enabled NDK support for the ADT plugin and that the environment variables are set properly. To ensure that we have NDK support available, we will navigate to the ‘About Eclipse’ menu item, click the ‘Installation Details’ button and check for ‘Android Native Development Tools’ under ‘Installed Software’. If an entry for ‘Android Native Development Tools’ is missing then we are missing NDK support and NDK support can be installed from the ADT Plugin Update Site.
Once the plugin has been installed successfully, ensure that SDK and NDK paths have been properly set in Eclipse Properties. This can be done as follows:
- Navigate to the ‘Preferences’ menu item, select ‘Android’ preferences, and set SDK path
-
Navigate to the ‘Preferences’ menu item, expand ‘Android’ preferences, select ‘NDK’ preferences, and set NDK path
Adding NDK/JNI Support
Next, we will need to add support for NDK/JNI to our project. This will allow us to use NDK functions to interact with the Android system and applications. In order to add NDK/JNI support, Right Click on project, navigate to the ‘Android Tools’ context menu, and click ‘Add Native Support…’. Once you click the menu item, Eclipse will show you a dialog box and will ask you for the name of your library. You can specify whatever name that pleases you. However, it is a good practice to append suffix ‘.cy’ to help one identify it is a Cydia extension.
Once you click Finish, the ADT plugin will process the provided information and generate a new directory ‘jni’ under the project directory and create following two new files –
- <libName>.cpp: This file will contain the code for the Substrate hook
-
Android.mk: This is a Makefile file which contains make commands used to build our hook
Including Substrate Libraries
Next, we need to include the Substrate libraries. The easiest way to do this is to navigate to the ‘/sdk/extras/saurikit/cydia_substrate/’ directory and copy ‘substrate.h’ and navigate to the ‘/sdk/extras/saurikit/cydia_substrate/lib/<ARCH>/’ directory and copy ‘libsubstrate-dvm.so’ file into the jni folder of the project.
Finding Target Functions
The ‘SuperMath.apk’ contains a private method to perform multiplication (This can be identified by decompiling the application using a utility like Dex2Jar) :
private String mul(int a, int b){ return "" + (a * b); }
If we recall, the steps outlined in previous tutorial will not allow us to hook into non-public members of a class. By creating our hooks using NDK code we will be able to overcome this limitation.
Coding a Substrate NDK Hook
We will start by including appropriate header files in the ’.cpp’ file, and calling the ‘MSConfig’ Substrate macro.
#include "substrate.h" #include <android/log.h> #define TAG "NDK_HOOK" /* * Decide where we would like to hook */ MSConfig(MSFilterExecutable, "/system/bin/app_process")
The first import statement imports substrate headers into the current program to provide us all Substrate functionality. The second import statement imports Android logging headers into the current program to provide us logging features, which will be used later in the program to log information to LogCat.
MSConfig is a Substrate macro that allows developers to configure and ask the Substrate runtime to allow our extension to hook into the application or the library of our choice. MSConfig takes two string constant arguments: the name of a configuration option and a string value.
Macro Signature:
MSConfig(configOption, configOptionValueAsString)
Configuration Option |
Description |
MSFilterExecutable |
This tells the Substrate runtime that we would like to hook into an application. If this configuration option is specified as the first argument to the MSConfig macro, the second argument is a string value containing the fully qualified path to the process that we are trying to hook. Usually this argument is set to “/system/bin/app_process” as this process points to zygote but can be other processes as well. For more information on why we wish to hook zygote, refer to the blogpost by David Ehringer on The Dalvik Virtual Machine Architecture. For this blogpost, we will hook into zygote. |
MSFilterLibrary |
This tells the Substrate runtime that we would like to hook into a library. If this configuration option is specified as the first argument to the MSConfig macro, the second argument is a string value containing the library that we are trying to hook. If we are trying to hook __android_log, the value of this argument will be “liblog.so”. |
Then we will call the MSInitialize macro in order for us to write the function definition used to initialize the hook. This is the primary entry point into the application and where we begin to write our hooks.
/* * Substrate entry point */ MSInitialize { // Let the user know that the extension has been // extension has been registered __android_log_print(ANDROID_LOG_ERROR, TAG, "Substrate initialized."); // Hook into specified class and call OnClazzLoad // method when hooked MSJavaHookClassLoad(NULL, "me/rahilparikh/SuperMath/ShowMathOps", &OnClazzLoad); }
A call is made to the MSJavaHookClassLoad function, which is used to hook into the class specified by the second argument to the function. A callback function is passed as the third argument which will be called once the ‘ShowMathOps’ object is loaded.
Method Signature: MSJavaHookClassLoad( jniPtr, classNameAsString, referenceToCallbackMethod)
The class name passed to MSJavaHookClassLoad follows the JNI naming convention. So, me.rahilparikh.SuperMath.ShowMathOps is written as me/rahilparikh/SuperMath/ShowMathOps. The OnClazzLoad callback function will now need to be defined as follows:
/* * This method should be used to perform various operations * within the class */ static void OnClazzLoad(JNIEnv *jniPtr, jclass clazz, void *data) { // Let user know that the class has been hooked __android_log_print(ANDROID_LOG_ERROR, TAG, "Hooked into the application."); // Search for method jmethodID methodMul = jniPtr->GetMethodID(clazz, "mul", "(II)Ljava/lang/String;"); if (methodMul != NULL) { // Let user know that we have found the method __android_log_print(ANDROID_LOG_ERROR, TAG, "mul called."); // Hook into method MSJavaHookMethod(jniPtr, clazz, methodMul, &newMulMethod, &oldMulMethod); } }
The onClazzLoad callback function is invoked once the specified object is loaded by the Android device. The first parameter to this function is a JNI Pointer, which will provide us access to various JNI methods. The second parameter to this function is an instance of jclass, which will provide us access to the members of the class.
In onClazzLoad, we call the GetMethodId method to get a reference to the method that we are trying to hook into. Refer to JNI and JNI Types and Data Structures documents to for more information on how to use GetMethodId and other JNI methods. On the next line, we check if an instance of the returned methodId is null or not. If the instance is not null, a Java method that matches our signature was found and we will use MSJavaHookMethod to hook into our desired method.
The MSJavaHookMethod function is used to replace the implementation of the function at runtime. The third parameter contains the reference to the ‘methodId’ that was previously returned by the GetMethodID function. The fourth parameter contains the reference to the new implementation of the ‘mul’ method that will need to be defined within our Substrate code. The fifth parameter will contain a callback reference to the original ‘mul’ method. We will still need to the define a prototype for the function passed as the fifth parameter. Substrate will handle setting the appropriate values once the ‘mul’ method is hooked.
The following walks through how to create the function prototype to the ‘oldMulMethod’ and a definition for the new ‘newMulMethod’ method to include the code we want to execute within the hook.
/* * Original method template */ static jstring (*oldMulMethod)(JNIEnv *jni, jobject _this, ...); /* * Modified method */ static jstring newMulMethod(JNIEnv *jni, jobject _this, jint param1, jint param2) { // Let user know that the method has been hooked __android_log_print(ANDROID_LOG_ERROR, TAG, "Hooked into mul."); // Print all original parameters __android_log_print(ANDROID_LOG_ERROR, TAG, "Param1 : %d", param1); __android_log_print(ANDROID_LOG_ERROR, TAG, "Param2 : %d", param2); // Call original method jstring originalRetVal = (*oldMulMethod)(jni, _this, param1, param2); __android_log_print(ANDROID_LOG_ERROR, TAG, "Original Answer : %s", jni->GetStringUTFChars(originalRetVal, 0)); // Modify return value of original method char *modifiedRetValC = (char*) malloc(3); strcpy(modifiedRetValC, "10"); __android_log_print(ANDROID_LOG_ERROR, TAG, "Modified Answer : %s", modifiedRetValC); jstring modifiedRetValS = jni->NewStringUTF( modifiedRetValC); // Be a nice kid, return what you've got free(modifiedRetValC); // Return modified return value return modifiedRetValS; }
The hook will simply log the values of the parameters that were passed by the application to the ‘mul’ method. This allows us to gain visbility into the values being passed at runtime without requiring a debugger. We then call the ‘oldMulMethod’ function which causes the original ‘mul’ method that was defined by the hooked application to be invoked. We could have modified the values for the param1 and param2 variables if we would have liked, but in this scenario we simply pass them to the original method. The return value of the original mul function is then logged as well. Lastly, we then decide to modify the expected return value of the ‘mul’ funtion so that the value of “10” will always be returned.
Configuring Android.mk
Once the hook has been created we need to update the Android.mk Makefile to help the Android build system understand what we are trying to achieve and how it should compile our application. The Android system creates a Android.mk make file with the following as default content –
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := SuperMathHook.cy LOCAL_SRC_FILES := SuperMathHook.cy.cpp include $(BUILD_SHARED_LIBRARY)
This would have allowed us to compile a simple ‘Hello World’ application but we will need to make some modifications to account for the Substrate library dependencies. The resulting Makefile should look similar to the following:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= substrate-dvm LOCAL_SRC_FILES := libsubstrate-dvm.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := SuperMathHook.cy LOCAL_SRC_FILES := SuperMathHook.cy.cpp LOCAL_LDLIBS := -llog LOCAL_LDLIBS += -L$(LOCAL_PATH) -lsubstrate-dvm include $(BUILD_SHARED_LIBRARY)
For more information on how to configure the Android.mk file, refer to the NDK Programmer’s Guide, which is shipped with the NDK SDK.
Running our Substrate Hook
With the skeleton of the Substrate hook in place, running this code should allow us to hook into the desired class and method. The created Substrate hook can then be run using the instructions found in Part I – Using Mobile Substrate With Android Applications After the screen flashes, we can run the SuperMath application and monitor the output of Logcat for the tag “NDKHook”.
Conclusion
JNI does not suffer from the access restrictions that are imposed by Java to access various fields, methods and other members of an application. Therefore, writing hooks using the NDK Substrate APIs can give us low-level access to the application code and allows us to make changes to the existing code at runtime.
One should also remember that some of the method signatures provided here are simplified, minimal and essential for writing a hook. There are instances where these methods can take more arguments but are not specified here as they have been omitted to improve the understanding of the hook writing process and/or they are not required in most cases.
All of the code discussed within this blog post can be found on our Github page here.
References/Further Reading
Ehringer, D. (2010, March). The Dalvik Virtual Machine Architecture. Retrieved October 2014, from http://davidehringer.com/software/android/The_Dalvik_Virtual_Machine.pdf
Java Programming Tutorial – Java Native Interface (JNI) . (2014, February). Retrieved October 2014, from https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
JNI Functions. (n.d.). (Oracle Corp.) Retrieved Oct 2014, from Java SE Documentation: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html
JNI Types and Data Structures. (n.d.). (Oracle Corp.) Retrieved October 2014, from Java SE Documentation: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html
Kumar, A. (2012, October). Understanding Android: Zygote and DalvikVM. Retrieved October 2014, from StackOverflow: http://stackoverflow.com/a/12703292
NDK Programmer’s Guide. (n.d.). Retrieved October 2014, from Android.mk: /ndk/docs/Programmers_Guide/html/md_3__key__topics__building__chapter_1-section_8__android_8mk.html
Author: Rahil Parikh
©Aon plc 2023