Thursday, June 19, 2014 At 2:17PM
Android application assessments are a frequent occurrence here at GDS. When our clients do not provide us with source code, we take a black box testing approach which includes intercepting and/or modifying function calls. This can range from parameters passed to a function, function return values, or even logic within a function.
Recently we’ve been utilizing Mobile Substrate as an alternative to the tried and true APK decompile, modify, and recompile methodology. A key advantage being you don’t have to work with smali.
Aside from the documentation on Mobile Substrate’s home site, there aren’t many posts on the web on how to use it and we’ve encountered a few errors and problems along the way. That being said, this post aims to provide a more in depth walkthrough for those interested in using Mobile Substrate in the most common scenarios.
Post Outline
The outline below illustrates the major topics that will be covered in this post:
- Hooking functions
- Viewing function parameters
- Modifying function parameters
- Viewing return values
- Modifying return values
Getting Started
The application we will be using in this demonstration is a demo application called ListLock. This application allows users to password protect a super secret list from prowling eyes.
The techniques in this post will be focused on bypassing ListLock’s lock screen.
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/
Setting Up the IDE
Note: I will be using the Android Development Toolkit (Eclipse) IDE that is packaged with the Android SDK for creating our Substrate hook.
In order to start building our Substrate hooks, we need to create a new Android application project. The name of the class will be ‘ListBypass’. Uncheck the “Create custom launcher icon” and “Create activity” options and click “Finish”.
Adding Permissions to AndroidManifest.xml
The first thing we like to do before coding the actual Substrate hook is adding permissions to the AndroidManifiest.xml file. This is usually the step that we tend to forget whenever we’re developing a Substrate hook, so we prefer to take care of it first.
To do this, we can add the following tag towards the top of the manifest file:
<uses-permission android:name="cydia.permission.SUBSTRATE"/>
Including Substrate Libraries
Next, we need to include the Substrate libraries. The easiest way to do this is to navigate to the ‘android/adt/sdk/extras/saurikit/cydia_substrate/’ directory and drag-and-drop the ‘substrate-api.jar’ file into the libs folder of your project.
Note: One of the biggest pitfalls we see people make is to select the ‘Link to files’ option. While this will stop your IDE from complaining about missing dependencies, this approach will not work when you attempt to load your Substrate hook on the device. This is because the ‘Link to files’ option points to the jar on your local computer. Since the Substrate hook will be running on the device, it will be unable to locate the jar.
Finding Target Function
Before we can start hooking, we need to know what to hook. To do this, we can decompile the ListLock.apk using a tool like Dex2Jar and look through the code until we find the appropriate function. Instructions for decompiling an APK will not be discussed in this post.
Let’s target the validatePassword() method found in the com.historypeats.listlock.classes.Authenticate class. By looking at the source-code, this method looks to be responsible for allowing a user to log into the ListLock application.
Below is a code snippet of the validatePassword() method:
public boolean validatePassword(String password, String storedPassword){ if(storedPassword.equals(password)){ return true; } return false; }
Let’s see if we can bypass this using a Substrate hook. We have two attack vectors for accomplishing this:
- Print out the storedPassword argument
- Modify the return value
Coding a Substrate Hook
Let’s start off our code by adding some variables and making sure we import our dependencies:
import java.lang.reflect.Method; import android.util.Log; import com.saurik.substrate.MS; public class ListBypass { private static String TAG = “ListBypass”; private static String className = “com.historypeats.listlock.classes.Authenticate”; }
The ‘TAG’ variable identifies the logging statements that pertain to this Substrate hook. The ‘className’ variable represents the fully qualified class name that contains the method we want to hook. In this case, ‘com.historypeats.listlock.classes.Authenticate’ is the class that contains the ‘validatePassword’ method that we are targeting.
Next we’ll dive into the meat of our Substrate hook. We’ll go ahead and add the essential layout to get started with the hook.
private static String className = "com.historypeats.listlock.classes.Authenticate"; public static void initialize() { Log.i(TAG, "Substrate Initialized."); MS.hookClassLoad(className, new MS.ClassLoadHook() { @SuppressWarnings({ "unchecked", "rawtypes" }) public void classLoaded(Class<?> _class) { Method method; final String methodName = "validatePassword"; Log.i(TAG, "Class Loaded."); try{ method = _class.getMethod(methodName, String.class, String.class); // public boolean validatePassword(String password, String storedPassword) }catch(NoSuchMethodException e){ method = null; Log.i(TAG, "No such method: " + methodName); } if (method != null) { Log.i(TAG, "Method Hooked."); } } }); // End MS.hookClassLoad }
Our IDE tells us that the MS.hookClassLoad() method is looking for a class name, and a hook object. Let’s go over the following essential code segments.
The variable ‘methodName’ contains the name of the method that we want to hook, which in this case is ‘validatePassword’. Next, we assign a value to the method variable by returning a method object from ‘_class.getMethod()’. The first argument to ‘_class.getMethod’ represents the target method to hook (‘validatePassword’). The subsequent arguments represent the target method’s parameter types (parameters accepted by ‘validatePassword’). The ‘validatePassword’ method accepts two string parameters, specifically a password and storedPassword. Note, the names of the parameters are irrelevant for hooking; only the types are important.
//public boolean validatePassword(String password, String storedPassword)
Some other commonly encountered parameter types you can pass to getMethod()include:
- Byte.class
- Integer.class
- Boolean.class
- Double.class
- Float.class
Note: The primitive array equivalents work as well. For example, Integer[].class for an array of integers.
Running our Substrate Hook
With the skeleton of the Substrate hook in place, running this code should yield the following debug message in Logcat: “Method Hooked”. In order to test this out, install and run the ListBypass hook from ADT by clicking the ‘Run’ icon in the toolbar or by navigating to the ‘Run’ option at the top of the window and then selecting the ‘Run’ button in the dropdown menu.
This should result in an alert from the Mobile Substrate application about new/updated extensions. We can either click on this alert or open the Mobile Substrate application directly.There will be several options to choose from. Select the “Restart System (Soft)” option.The device will perform a quick restart, which will result in the loading of our Substrate hook. After the screen flashes, we can run the ListLock application and monitor the Logcat output for our debug message.
04-26 22:15:21.536: I/ListBypasss(11604): Substrate Initialized. 04-26 22:16:22.085: I/ListBypasss(12596): Class Loaded. 04-26 22:16:22.085: I/ListBypasss(12596): Method Hooked.
Perfect! Based on the output, we have successfully hooked the method we had originally targeted. Now we can start performing the actions that we want.
First Attack: Print out method arguments
If you recall, the first goal was to discover the argument passed into the method. In this case, we want to find the value of the storedPassword parameter that is being passed into the validatePassword() method. Let’s start by writing the skeleton code with stub placeholders.
public static void initialize() { Log.i(TAG, "Substrate Initialized."); MS.hookClassLoad(className, new MS.ClassLoadHook() { @SuppressWarnings({ "unchecked", "rawtypes" }) public void classLoaded(Class<?> _class) { Method method; final String methodName = "validatePassword"; Log.i(TAG, "Class Loaded."); try{ method = _class.getMethod(methodName, String.class, String.class); // public boolean validatePassword(String password, String storedPassword) }catch(NoSuchMethodException e){ method = null; Log.i(TAG, "No such method: " + methodName); } if (method != null) { Log.i(TAG, "Method Hooked."); MS.hookMethod(_class, method, new MS.MethodAlteration<Object, Boolean>() { public Boolean invoked(Object _class, Object... args) throws Throwable { //Do Something return invoke(_class, args); } });// End MS.hookMethod } } }); // End MS.hookClassLoad }
In the code above, there is a call to the ‘MS.hookMethod()’ from the Substrate API. This method requires the following parameters: the class we hooked, the method we want to modify, and the altered method. What is an altered method? See below for an excerpt from the Substrate documentation:
“An instance of MS.MethodAlteration whose boxed invoked method will be called instead of member. This instance will also be filled in using information from the original implementation, allowing you to use invoke to call the original method implementation.”
This means that we can build a method named ‘invoked’ that will be called in place of the original method. Additionally, our invoked() method will have access to the parameters passed into the original method, allowing us to print out the values of the parameters. We also have the ability to call the original method (with the original parameters) from within our own method to allow the application to function as normal.
The items highlighted mark what the return type of the method we are hooking has. In this case, the ‘validatePassword()’ method that we are hooking has a return type of Boolean. In future hooks, you’ll want to ensure you have the proper return types that match the method you are hooking.
Finally, we need to invoke the original method to allow the application to function as normal. This is accomplished by calling ‘invoke()’, passing in the class and arguments that were originally provided. In this case, we immediately return the value returned by ‘invoke()’. As we’ll see later on, we can also store this value in a variable, modify its contents, etc.
Argument Tampering
Now that we have our own method that will be called in place of the original ‘validatePassword()’, let’s write the code to print out the juicy password arguments.
MS.hookMethod(_class, method, new MS.MethodAlteration<Object, Boolean>() { public Boolean invoked(Object _class, Object… args) throws Throwable { //Do Something String password = (String)args[0]; String storedPassword = (String)args[1]; Log.i(TAG, “Arg0 (password): ” + password); Log.i(TAG, “Arg1 (storedPassword): ” + storedPassword); return invoke(_class, args); // Call original validatePassword() method and pass it the original arguments then return the value to the caller } });// End MS.hookMethod
In the code above, the first argument represents the password the user entered into the application. This value is stored in the variable ‘password’. The second argument represents the stored password, which is the correct password to login to the list. This value is stored in the variable ‘storedPassword’. Since the args variable is of type Object, we need to remember to cast it to the proper type before we store it. In this case, we cast the values to String objects and store the values in String variables.
Viewing Arguments
We can now run our new Substrate hook following the steps previously outlined. The use of the Log() function should result in the values being printed out to Logcat.
04-26 23:12:39.621: I/ListBypasss(16551): Arg0 (password): wrongPassw0rd 04-26 23:12:39.621: I/ListBypasss(16551): Arg1 (storedPassword): C@ntSeeMyList!
Here we can see that the password we entered, wrongPassw0rd, does not match the correct password, which is C@ntSeeMyList!. Now that we know the correct password, we can simply log into the application!
Second Attack: Modify Return Values
Thus far, we have written a Substrate hook that will print out the stored password, thereby allowing us to login to the application. What if we didn’t want to type in the correct password every single time, but still be able to login? To accomplish this, we can tweak our Substrate hook to modify the return value of the ‘validatePassword’ method.
Let’s begin by printing out the return value of the ‘validatePassword’ method.
MS.hookMethod(_class, method, new MS.MethodAlteration<Object, Boolean>() { public Boolean invoked(Object _class, Object... args) throws Throwable { //Do Something Boolean originalRetValue = invoke(_class, args); Log.i(TAG, "Original Return Value: " + originalRetValue); return originalRetValue; } });// End MS.hookMethod
In the code segment above, we use the ‘invoke()’ method to call the original ‘validatePassword()’ method with the original arguments. We then store the return value from ‘validatePassword()’ in a Boolean variable named ‘originalRetValue’, print this value to Logcat, and return it to the caller.
04-26 23:59:18.421: I/ListBypasss(22365): Original Return Value: false
As seen above, the ‘validatePassword()’ method returns ‘false’ when an incorrect password is entered. The next logical step is to modify our Substrate hook to return ‘true’ to the caller instead of the original return value.
MS.hookMethod(_class, method, new MS.MethodAlteration<Object, Boolean>() { public Boolean invoked(Object _class, Object... args) throws Throwable { //Do Something Boolean originalRetValue = invoke(_class, args); Boolean newRetValue = true; Log.i(TAG, "Original Return Value: " + originalRetValue); Log.i(TAG, "New Return Value: " + newRetValue); return newRetValue; } });// End MS.hookMethod
Note that instead of returning ‘originalRetValue’, we return ‘newRetValue’, which is always true. This means that regardless of the password entered, we will always gain access to the List. It should be noted that we did not need to run the original method. So there is no need to call invoke() in this situation. We could have easily written: “return true”.
What if you want to hook multiple methods or classes? Well, if you want to create a single Substrate hook that hooks into multiple classes, all you need to do is create multiple MS.hookClassLoad methods, one after another. Sure, you can engineer a better object-oriented design, but for one or two classes, it may be simpler to just call multiple MS.hookClassLoad methods. Similarly for hooking multiple methods in a single class, you just call getMethod() multiple times with the proper arguments.
In conclusion, we learned the basics of hooking with Mobile Substrate. From setting up our Substrate code in an IDE to extracting and modifying the data that we want. If you want to practice this, the Substrate code and demo APK can be found the Github link below:
https://github.com/GDSSecurity/SubstrateDemo
Author: Mike Santillana
©Aon plc 2023