Skip to content
LaurieWired edited this page Aug 8, 2023 · 15 revisions

ARTful Overview

ARTful is an open-source tool that allows users to manipulate the Android runtime. It works by hooking two specified Android methods "targetArtMethod" and "newArtMethod". ARTful then overwrites the associated method data of "targetArtMethod" with the contents of "newArtMethod" causing the new method to unexpectedly execute instead each time the target is invoked. This tool was written and tested on Android13, but will also work on multiple other versions.

Requirements and Capabilities

The following is a list of important notes for using ARTful and understanding suitable target methods:

  • Method signatures must match
  • Methods must be static
  • Replacement of methods occurs within the application context only
  • Target methods can be public or private
  • No root required!

Using the Example ARTful Application

The "Source" folder of the ARTful repository contains an example Android application. Simply import this project into Android Studio and begin manipulating the Android Runtime! The ARTful library source is written in native C++ and can be found at native-lib.cpp. Example use and invocations of the library from Java are inside MainActivity.java. The default behavior is to swap two methods within the same class, but native declarations of all ARTful native methods are declared inside the MainActivity. See below for details on implementing each method.

Using ARTful as a Library

Add libartful.so to your project and load the library from Java code:

System.loadLibrary("artful");

Then you can declare any native methods that you would like to use in your Java application. The full list of native methods can be found below.

public native void replaceAppMethodByObject(Object targetObject, Object newObject);
public native void replaceAppMethodBySignature(String targetClassName, String targetMethodName, String newClassName, String newMethodName, String methodSignature);
public native void replaceGetRadioVersionByObject(Object newObject);
public native void replaceGetRadioVersionBySignature(String newClassName, String newMethodName);

Hooking Via Object

Passing objects is easier syntactically, but must also catch and handle runtime exceptions. Example:

try {
    replaceAppMethodByObject(MainActivity.class.getDeclaredMethod("benignMethod"), MainActivity.class.getDeclaredMethod("newMethod"));
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
}

Hooking Via Method Signature

JNI Signature Reference

Example:

replaceAppMethodBySignature("com/app/artful/MainActivity", "benignMethod", "com/app/artful/MainActivity", "newMethod", "()Ljava/lang/String;");

Replacing Android Framework Methods

You can replace any static method located inside the Android Framework with your own method inside of your app. A pre-coded list of multiple Android APIs that ARTful can automatically replace can be found below. If you would like to replace one not already added to ARTful, you could instead call the replaceAppMethodBySignature or replaceAppMethodByObject methods and simply pass in the references to the Android Framework method.

try {
    replaceGetRadioVersionByObject(MainActivity.class.getDeclaredMethod("newMethod"));
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
}

Once the replacement has occurred, every time Build.getRadioVersion() is invoked, the code from newMethod will be run.

Creating a Replacement Method

If you want to replace any method in the Android Framework, simply write a new static method in your Java code with a matching signature. Then you can call the replace method and your new code will execute. For example, the declaration of the Build.getRadioVersion() method is defined as follows:

public static String getRadioVersion();

This means that we could replace this with our own method with a matching signature like the following:

public static String newMethod() {
    Log.d("ARTful", "I should not execute >:)");
    return "lol";
}

Replacing Log.e(tag, msg)

Implement a replacement method with a matching signature:

public static int newMethod(String tag, String msg) {
    Log.d("ARTful", "I should not execute >:)");

    // Your code goes here!


    return 1;
}

From Java code, declare and call the replace method from the ARTful library:

// Declare methods in your class
public native void replaceLogEByObject(Object newObject);
public native void replaceLogEBySignature(String newClassName, String newMethodName);

// Call by object or signature
try {
    replaceLogEByObject(MainActivity.class.getDeclaredMethod("newMethod", String.class, String.class));
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
}

// New invocations below will actually trigger newMethod
Log.e("ARTful", "I'm benign");

Replacing Build.getRadioVersion()

Implement a replacement method with a matching signature:

public static String newMethod() {
    Log.d("ARTful", "I should not execute >:)");

    // Your code goes here!

    return "lol";
}

From Java code, declare and call the replace method from the ARTful library:

// Declare methods in your class
public native void replaceGetRadioVersionByObject(Object newObject);
public native void replaceGetRadioVersionBySignature(String newClassName, String newMethodName);

// Call by object or signature
try {
    replaceGetRadioVersionByObject(MainActivity.class.getDeclaredMethod("newMethod"));
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
}

// New invocations below will actually trigger newMethod
Build.getRadioVersion();

Replacing Toast.makeText()

Implement a replacement method with a matching signature:

public static Toast newMethod(Context context, CharSequence text, int duration) {
    Log.d("ARTful", "I should not execute >:)");

    // Your code goes here!


    return new Toast(context);
}

From Java code, declare and call the replace method from the ARTful library:

// Declare methods in your class
public native void replaceToastMakeTextByObject(Object newObject);
public native void replaceToastMakeTextBySignature(String newClassName, String newMethodName);

// Call by object or signature
try {
    replaceToastMakeTextByObject(MainActivity.class.getDeclaredMethod("newMethod", Context.class, CharSequence.class, int.class));
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
}

// New invocations below will actually trigger newMethod
Toast.makeText(getApplicationContext(), "This will not print", Toast.LENGTH_LONG);

Replacing Pattern.matches()

Implement a replacement method with a matching signature:

public static boolean newMethod(String regex, CharSequence input) {
    Log.d("ARTful", "I should not execute >:)");

    // Your code goes here!


    return true;
}

From Java code, declare and call the replace method from the ARTful library:

// Declare methods in your class
public native void replacePatternMatchesByObject(Object newObject);
public native void replacePatternMatchesBySignature(String newClassName, String newMethodName);

// Call by object or signature
try {
    replacePatternMatchesByObject(MainActivity.class.getDeclaredMethod("newMethod", String.class, CharSequence.class));
} catch (NoSuchMethodException e) {
    throw new RuntimeException(e);
}

// New invocations below will actually trigger newMethod
Pattern.matches("MyRegex", "Not actually tested");

Printing ArtMethod Offsets

Dummy classes are included to allow ARTful to print the member variable offsets of the ArtMethod class in Android13. These offsets can change between versions of Android. They are defined inside of art_method.h. Print offsets from Java code by calling:

printArtMethodOffsets();

logcat_offsets

Clone this wiki locally