/pine

Dynamic java method hook framework on ART. Allowing you to change almost all java methods' behavior dynamically.

Primary LanguageJava

Pine LICENSE

中文版本

Introduction

Pine is a dynamic java method hook framework on ART runtime, which can intercept almost all java method calls in the current process.

Currently it supports Android 4.4(ART only) ~ 14 with thumb-2/arm64 architecture.

About its working principle, you can refer to this Chinese article.

Note: For Android 6.0 devices with arm32/thumb-2 architectures, the arguments may be wrong; and for Android 9.0+, pine will disable the hidden api restriction policy.

The name, Pine, represents a class of antipsychotic drugs represented by Quetiapine and Clozapine. It is also an acronym for "Pine Is Not Epic".

Usage

Basic Usage

Download

Add dependencies in build.gradle (like this):

dependencies {
    implementation 'top.canyie.pine:core:<version>'
}

Basic configuration:

PineConfig.debug = true; // Do we need to print more detailed logs?
PineConfig.debuggable = BuildConfig.DEBUG; // Is this process debuggable?

Example 1: monitor the creation of activities

Pine.hook(Activity.class.getDeclaredMethod("onCreate", Bundle.class), new MethodHook() {
    @Override public void beforeCall(Pine.CallFrame callFrame) {
        Log.i(TAG, "Before " + callFrame.thisObject + " onCreate()");
    }

    @Override public void afterCall(Pine.CallFrame callFrame) {
        Log.i(TAG, "After " + callFrame.thisObject + " onCreate()");
    }
});

Example 2: monitor the creation and destroy of all java threads

final MethodHook runHook = new MethodHook() {
    @Override public void beforeCall(Pine.CallFrame callFrame) throws Throwable {
        Log.i(TAG, "Thread " + callFrame.thisObject + " started...");
    }

    @Override public void afterCall(Pine.CallFrame callFrame) throws Throwable {
        Log.i(TAG, "Thread " + callFrame.thisObject + " exit...");
    }
};

Pine.hook(Thread.class.getDeclaredMethod("start"), new MethodHook() {
    @Override public void beforeCall(Pine.CallFrame callFrame) {
        Pine.hook(ReflectionHelper.getMethod(callFrame.thisObject.getClass(), "run"), runHook);
    }
});

Example 3: force allow any threads to modify ui:

Method checkThread = Class.forName("android.view.ViewRootImpl").getDeclaredMethod("checkThread");
Pine.hook(checkThread, MethodReplacement.DO_NOTHING);

Xposed Support

Download

Pine supports hooking methods in Xposed-style and loading Xposed modules. (Only java method hooking is supported. Modules using unsupported features like Resource-hooking won't work.)

implementation 'top.canyie.pine:xposed:<version>'

Directly hook methods in Xposed-style:

XposedHelpers.findAndHookMethod(TextView.class, "setText",
                CharSequence.class, TextView.BufferType.class, boolean.class, int.class,
                new XC_MethodHook() {
                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        Log.e(TAG, "Before TextView.setText");
                        param.args[0] = "hooked";
                    }

                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        Log.e(TAG, "After TextView.setText");
                    }
                });

or like this:

XposedBridge.hookMethod(target, callback);

and you can load xposed modules (resources hook is not supported now):

// 1. load modules
PineXposed.loadModule(new File(modulePath));

// 2. call all 'IXposedHookLoadPackage' callback
PineXposed.onPackageLoad(packageName, processName, appInfo, isFirstApp, classLoader);

Enhanced Features

Download

With Dobby, you can use some enhanced features:

implementation 'top.canyie.pine:enhances:<version>'
  • Delay hook (aka pending hook) support, hooking static methods without initializing its declaring class immediately:
PineEnhances.enableDelayHook();

ProGuard

If you are using enhanced features:

# Pine Enhances
-keep class top.canyie.pine.enhances.PineEnhances {
    private static void onClassInit(long);
}

If you use Xposed features and Xposed APIs need to be called outside your module (e.g. you call PineXposed.loadModule() to load external modules):

# Keep Xposed APIs
-keep class de.robv.android.xposed.** { *; }
-keep class android.** { *; }

Known issues

  • May not be compatible with some devices/systems.

  • Due to #11, we recommend hooking methods with less concurrency as much as possible, for example:

public static void method() {
    synchronized (sLock) {
        methodLocked();
    }
}

private static void methodLocked() {
    // ...
}

In the example, we recommend you to hook methodLocked instead of method.

  • Pine will disable hidden api policy on initialization by default. Due to an ART bug, if a thread changes hidden api policy while another thread is calling a API that lists members of a class, a out-of-bounds write may occur and causes crashes. We have no way to fix system bugs, so the only way is, initialize our library before other threads is started to avoid the race condition. For more info, see tiann/FreeReflection#60.

  • For more, see issues.

Discussion

QQ Group:949888394 Telegram Group: @DreamlandFramework

Credits

License

Pine Copyright (c) canyie

AndroidELF Copyright (c) Swift Gan

Dobby Copyright (c) jmpews

Licensed under the Anti 996 License, Version 1.0 (the "License");

you may not use this "Pine" project except in compliance with the License.

You may obtain a copy of the License at

https://github.com/996icu/996.ICU/blob/master/LICENSE