Hooking and Patching Android Apps Using Cydia Substrate Extensions
Introduction:
In one of the previous articles, we have discussed how to exploit debuggable applications on Android. You can find that one here. Exploiting debuggable apps using JDB has some limitations as the app's debuggable flag must be set to true to do it on a real device. Moreover, we have seen that it runs on the command line where we need to set up break points and control the flow. Though this technique is useful in analyzing the apps, we need a solution that controls the flow of the application on the fly while the app is running. This is where the awesomeness of tools like Cydia Substrate, Xposed Framework, and Frida comes into the picture. Though we are going to use Xposed and Frida later in this series, writing Cydia Substrate extensions to control the application flow is what we are going to discuss in this article.
To make the things clear before you begin, the following is what is required to follow this article practically.
What should you learn next?
- Rooted Android Device with Cydia Substrate installed.
- Cydia Substrate app can be found here.
- Create a new Android Application (This is the Cydia Substrate extension) using your favorite IDE and add substrate-api.jar library into your libs folder. Substrate-api.jar can be found here.
- Target application - you can download it here:
[download]
Now, Let's start writing the Substrate extension. Like every other article of mine, we will have a vulnerable app here and then; we will exploit it using this Cydia Substrate extension. Following is our target app's first activity.
When the user enters invalid credentials, an error will be thrown as shown in the figure below.
Our goal is to bypass this login by writing a Cydia Substrate extension. Obviously, we first need to understand the application's logic before proceeding with the Substrate extension.
We can decompile the APK and get the Java version of the code to understand the logic. To keep the things simple, I am showing the original source as the idea here is to understand how to write Cydia Substrate extension assuming that we have access to the application's logic.
Following is the code snippet for Login.java
Login.java
package com.androidpentesting.targetapp;
if(isValidLogin(username,password))
{
Intent in=new Intent(getApplicationContext(),Welcome.class);
startActivity(in);
}
else {
Toast.makeText(getApplicationContext(), "Invalid Username or password", Toast.LENGTH_LONG).show();
}
public boolean isValidLogin(String username, String password)
{
String uname=pref.getString("username",null);
String pass=pref.getString("password",null);
if(username.contentEquals(uname) && password.contentEquals(pass))
{
return true;
}
else{
return false;
}
}
As you can see in the above code snippet, the app is getting the username and password from the user and then comparing it against the values stored in SharedPreferences. If the user entered credentials are matching, the app is returning a boolean value true and then redirecting the user to a private activity. That's a perfect test bed for us to write a Cydia extension in a way that the app always returns true regardless of the user input.
Details we got from the above Snippet:
Class name: com.androidpentesting.targetapp.Login
Method name: isValidLogin
Let's begin.
As shown in the Cydia Substrate documentation here, lets first set up our AndroidManifest.xml file.
We need to add two entries in AndroidManifest.xml file as highlighted in the code below.
<manifest xmlns_android="http://schemas.android.com/apk/res/android"
package="com.androidpentesting.cydia"
android_versionCode="1"
android_versionName="1.0" >
<uses-permission android_name="cydia.permission.SUBSTRATE" />
<uses-sdk
android_minSdkVersion="8"
android_targetSdkVersion="21" />
<application
android_allowBackup="true"
android_icon="@drawable/ic_launcher"
android_label="@string/app_name"
android_theme="@style/AppTheme" >
<meta-data android_name="com.saurik.substrate.main"
android_value=".BypassLogin"/>
</application>
</manifest>
- First, we need to request cydia.permission.SUBSTRATE permission.
- We need to add meta-data element within the application section.
Now, the fun part. We need to write the actual implementation to hook into our target method that is responsible for validating the user credentials and then modify its definition.
There are two important functions that we are going to use to achieve this.
MS.hookClassLoad
MS.hookMethod
MS.hookClassLoad can be used to detect the classes of our interested when loaded.
MS.hookMethod can be used to make desired changes to our target method.
For more details about what these methods do and how they work, please refer here and here.
Now, let's create a new class with the name BypassLogin. When this extension is loaded, initialize() method is going to be executed first.
So, the following is the skeleton code that we need to write within BypassLogin class.
Public class Main {
static void initialize() {
// code to run when extension is loaded
}
}
Now, let's write the code to detect when com.androidpentesting.targetapp.Login class is loaded. As mentioned earlier, we can do it using MS.hookClassLoad.
MS.hookClassLoad("com.androidpentesting.targetapp.Login", new MS.ClassLoadHook() {
public void classLoaded(Class<?> resources) {
// ... code to modify the class when loaded
}
});
When this class is loaded, following is what we are going to do.
- We are going to write a piece of code to check if our target method exists.
- If the method doesn't exist, log an entry into logcat
- If the method exists, change it's definition using MS.hookMethod.
That's all.
Following is the piece of code to implement the steps mentioned above.
Method methodToHook;
try{
methodToHook = resources.getMethod("isValidLogin", String.class, String.class);
}catch(NoSuchMethodException e){
methodToHook = null;
}
if (methodToHook == null) {
Log.v("cydia","No method found");
}
else{
MS.hookMethod(resources, methodToHook, new MS.MethodAlteration<Object, Boolean>() {
public Boolean invoked(Object _class, Object... args) throws Throwable
{
return true;
}
});
}
The pieces highlighted red in color are important to notice. From the source code, we know that the target app's isLoginMethod takes two string arguments and thus we are specifying String.class twice along with the method name in resources.getMethod();
When the method is detected, we are simply returning true value regardless of its actual implementation.
Following is the complete code we wrote.
package com.androidpentesting.cydia;
import java.lang.reflect.Method;
import android.util.Log;
import com.saurik.substrate.*;
public class BypassLogin {
public static void initialize() {
MS.hookClassLoad("com.androidpentesting.targetapp.Login", new MS.ClassLoadHook() {
@SuppressWarnings({ "unchecked", "rawtypes" })
public void classLoaded(Class<?> resources) {
Method methodToHook;
try{
methodToHook = resources.getMethod("isValidLogin", String.class, String.class);
}catch(NoSuchMethodException e){
methodToHook = null;
}
if (methodToHook == null) {
Log.v("cydia","No method found");
}
else{
MS.hookMethod(resources, methodToHook, new MS.MethodAlteration<Object, Boolean>() {
public Boolean invoked(Object _class, Object... args) throws Throwable
{
return true;
}
});
}
}
});
}
}
Now, install this extension just like how you install a normal app and make sure that you restart the device once to activate this extension. Launch the target app once again. When you click on the Login button, you will automatically be redirected to the login screen thus bypassing the authentication checks.
X`
Nice! We have bypassed the authentication. Cydia Substrate extensions are useful when you need to bypass client side controls on the fly. Examples include root detection bypass, SSL Pinning bypass, etc.
References:
What should you learn next?
http://www.cydiasubstrate.com/id/20cf4700-6379-4a14-9bc2-853fde8cc9d1/