├── demoApp
├── .gitignore
├── src
│ └── main
│ │ ├── jni
│ │ ├── Application.mk
│ │ ├── Android.mk
│ │ └── hello.c
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── styles.xml
│ │ │ └── colors.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ └── layout
│ │ │ └── activity_main.xml
│ │ ├── java
│ │ └── lab
│ │ │ └── galaxy
│ │ │ └── yahfa
│ │ │ └── demoApp
│ │ │ ├── ClassWithJNIMethod.java
│ │ │ ├── ClassWithCtor.java
│ │ │ ├── ClassWithStaticMethod.java
│ │ │ ├── ClassWithVirtualMethod.java
│ │ │ ├── Hook_Log_e.java
│ │ │ ├── MainApp.java
│ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
├── build.gradle
└── README.md
├── demoPlugin
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── lab
│ │ └── galaxy
│ │ └── yahfa
│ │ ├── demoPlugin
│ │ ├── Hook_Log_e.java
│ │ ├── Hook_ClassWithCtor.java
│ │ ├── Hook_ClassWithJNIMethod_fromJNI.java
│ │ ├── Hook_String_startsWith.java
│ │ ├── Hook_ClassWithStaticMethod_tac.java
│ │ └── Hook_ClassWithVirtualMethod_tac.java
│ │ └── HookInfo.java
├── build.gradle
└── README.md
├── library
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ ├── jni
│ │ │ ├── trampoline.h
│ │ │ ├── common.h
│ │ │ ├── trampoline.c
│ │ │ ├── utils.c
│ │ │ └── HookMain.c
│ │ └── java
│ │ │ └── lab
│ │ │ └── galaxy
│ │ │ └── yahfa
│ │ │ ├── HookAnnotation.java
│ │ │ └── HookMain.java
│ └── androidTest
│ │ └── java
│ │ └── lab
│ │ └── galaxy
│ │ └── yahfa
│ │ └── HookingTest.java
├── build.gradle
└── CMakeLists.txt
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── .github
└── workflows
│ └── ci.yml
├── gradle.properties
├── gradlew.bat
├── README.md
├── gradlew
└── LICENSE
/demoApp/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/demoPlugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':demoApp', ':library', ':demoPlugin'
2 |
--------------------------------------------------------------------------------
/demoApp/src/main/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_ABI := arm64-v8a armeabi-v7a x86
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | YAHFA
3 |
4 |
--------------------------------------------------------------------------------
/demoApp/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | YAHFA Demo
3 |
4 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | YAHFA demoPlugin
3 |
4 |
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | *.swp
10 | *.aar
11 |
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demoApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PAGalaxyLab/YAHFA/HEAD/demoApp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/demoApp/src/main/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH:= $(call my-dir)
2 | include $(CLEAR_VARS)
3 |
4 | LOCAL_SRC_FILES:= hello.c
5 |
6 | LOCAL_LDLIBS := -llog
7 |
8 | LOCAL_MODULE:= hello
9 |
10 | include $(BUILD_SHARED_LIBRARY)
11 |
--------------------------------------------------------------------------------
/demoApp/src/main/jni/hello.c:
--------------------------------------------------------------------------------
1 | //
2 | // Created by lrk on 5/4/17.
3 | //
4 | #include
5 |
6 | jstring Java_lab_galaxy_yahfa_demoApp_ClassWithJNIMethod_fromJNI(JNIEnv *env, jclass clazz) {
7 | return (*env)->NewStringUTF(env, "hello from JNI");
8 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
6 |
--------------------------------------------------------------------------------
/demoApp/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demoApp/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/demoPlugin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "lab.galaxy.yahfa.demoPlugin"
7 | minSdkVersion 21
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 | }
--------------------------------------------------------------------------------
/demoApp/src/main/java/lab/galaxy/yahfa/demoApp/ClassWithJNIMethod.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoApp;
2 |
3 | /**
4 | * Created by lrk on 5/4/17.
5 | */
6 |
7 | public class ClassWithJNIMethod {
8 | static {
9 | System.loadLibrary("hello");
10 | }
11 |
12 | public native static String fromJNI();
13 | }
14 |
--------------------------------------------------------------------------------
/demoApp/src/main/java/lab/galaxy/yahfa/demoApp/ClassWithCtor.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoApp;
2 |
3 | public class ClassWithCtor {
4 | private String field;
5 |
6 | public ClassWithCtor(String param) {
7 | field = param;
8 | }
9 |
10 | public String getField() {
11 | return field;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v1
10 | - uses: actions/setup-java@v1
11 | with:
12 | java-version: 1.8
13 | - name: build
14 | run: ./gradlew :library:build --stacktrace
15 |
16 |
17 |
--------------------------------------------------------------------------------
/library/src/main/jni/trampoline.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by liuruikai756 on 05/07/2017.
3 | //
4 |
5 | #ifndef YAHFA_TAMPOLINE_H
6 | #define YAHFA_TAMPOLINE_H
7 |
8 | void setupTrampoline(unsigned char offset);
9 |
10 | void *genTrampoline(void *toMethod, void *entrypoint);
11 |
12 | #define TRAMPOLINE_SPACE_SIZE 4096 // 4k mem page size
13 |
14 | #endif //YAHFA_TAMPOLINE_H
15 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demoApp/src/main/java/lab/galaxy/yahfa/demoApp/ClassWithStaticMethod.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoApp;
2 |
3 | /**
4 | * Created by liuruikai756 on 30/03/2017.
5 | */
6 |
7 | public class ClassWithStaticMethod {
8 | public static String tac(String a, String b, String c, String d) {
9 | try {
10 | return d + c + b + a;
11 | } catch (Exception e) {
12 | e.printStackTrace();
13 | return "";
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/demoApp/src/main/java/lab/galaxy/yahfa/demoApp/ClassWithVirtualMethod.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoApp;
2 |
3 | /**
4 | * Created by liuruikai756 on 30/03/2017.
5 | */
6 |
7 | public class ClassWithVirtualMethod {
8 | final public String tac(String a, String b, String c, String d) {
9 | try {
10 | return d + c + b + a;
11 | } catch (Exception e) {
12 | e.printStackTrace();
13 | return "";
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/demoApp/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "lab.galaxy.yahfa.demoApp"
7 | minSdkVersion 21
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 | externalNativeBuild {
13 | ndkBuild {
14 | path 'src/main/jni/Android.mk'
15 | }
16 | }
17 | }
18 |
19 | dependencies {
20 | implementation project(':library')
21 | }
22 |
--------------------------------------------------------------------------------
/demoApp/README.md:
--------------------------------------------------------------------------------
1 | YAHFA demoApp
2 | -------------
3 |
4 | Here is a demo app which shows the basic usage of YAHFA.
5 |
6 | ## Prerequisite
7 |
8 | Please build and push the [demoPlugin](https://github.com/rk700/YAHFA/tree/master/demoPlugin) APK to sdcard before running the demo app.
9 |
10 | ## Usage
11 |
12 | Click the button and take a look at the app log.
13 |
14 | ## What happened
15 |
16 | The demo app loads and applies the plugin APK, which hooks the following methods:
17 |
18 | - Log.e()
19 | - String.startsWith()
20 | - ClassWithVirtualMethod.tac()
21 | - ClassWithStaticMethod.tac()
22 |
23 | After hooking, the arguments would be displayed and then the original method be called.
24 |
--------------------------------------------------------------------------------
/demoApp/src/main/java/lab/galaxy/yahfa/demoApp/Hook_Log_e.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoApp;
2 |
3 | import android.util.Log;
4 |
5 | public class Hook_Log_e {
6 | public static String className = "android.util.Log";
7 | public static String methodName = "e";
8 | public static String methodSig = "(Ljava/lang/String;Ljava/lang/String;)I";
9 |
10 | public static int hook(String tag, String msg) {
11 | Log.w("HookTest", "in Log.e(): " + tag + ", " + msg);
12 | return backup(tag, msg);
13 | }
14 |
15 | public static int backup(String tag, String msg) {
16 | Log.w("HookTest", "Log.e() should not be here");
17 | return 1;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_Log_e.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoPlugin;
2 |
3 | import android.util.Log;
4 |
5 | import static lab.galaxy.yahfa.HookInfo.TAG;
6 |
7 | /**
8 | * Created by liuruikai756 on 30/03/2017.
9 | */
10 |
11 | public class Hook_Log_e {
12 | public static String className = "android.util.Log";
13 | public static String methodName = "e";
14 | public static String methodSig = "(Ljava/lang/String;Ljava/lang/String;)I";
15 |
16 | public static int hook(String tag, String msg) {
17 | Log.w(TAG, "in Log.e(): " + tag + ", " + msg);
18 | return backup(tag, msg);
19 | }
20 |
21 | public static int backup(String tag, String msg) {
22 | Log.w(TAG, "Log.e() should not be here");
23 | return 1;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithCtor.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoPlugin;
2 |
3 | import android.util.Log;
4 |
5 | import static lab.galaxy.yahfa.HookInfo.TAG;
6 |
7 | public class Hook_ClassWithCtor {
8 | public static String className = "lab.galaxy.yahfa.demoApp.ClassWithCtor";
9 | public static String methodName = "";
10 | public static String methodSig =
11 | "(Ljava/lang/String;)V";
12 |
13 | public static void hook(Object thiz, String param) {
14 | Log.w(TAG, "in ClassWithCtor.: " + param);
15 | backup(thiz, "hooked " + param);
16 | return;
17 | }
18 |
19 | public static void backup(Object thiz, String param) {
20 | Log.w(TAG, "ClassWithVirtualMethod.tac() should not be here");
21 | return;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithJNIMethod_fromJNI.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoPlugin;
2 |
3 | import android.util.Log;
4 |
5 | import static lab.galaxy.yahfa.HookInfo.TAG;
6 |
7 | /**
8 | * Created by lrk on 5/4/17.
9 | */
10 |
11 | public class Hook_ClassWithJNIMethod_fromJNI {
12 | public static String className = "lab.galaxy.yahfa.demoApp.ClassWithJNIMethod";
13 | public static String methodName = "fromJNI";
14 | public static String methodSig = "()Ljava/lang/String;";
15 |
16 | // calling origin method is no longer available for JNI methods
17 | public static String hook() {
18 | Log.w(TAG, "calling fromJNI");
19 | return backup();
20 | }
21 |
22 | public static String backup() {
23 | Log.w(TAG, "calling fromJNI");
24 | return "1234";
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_String_startsWith.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoPlugin;
2 |
3 | import android.util.Log;
4 |
5 | import static lab.galaxy.yahfa.HookInfo.TAG;
6 |
7 | /**
8 | * Created by liuruikai756 on 30/03/2017.
9 | */
10 |
11 | public class Hook_String_startsWith {
12 | public static String className = "java.lang.String";
13 | public static String methodName = "startsWith";
14 | public static String methodSig = "(Ljava/lang/String;)Z";
15 |
16 | public static boolean hook(String thiz, String prefix) {
17 | Log.w(TAG, "in String.startsWith(): " + thiz + ", " + prefix);
18 | return backup(thiz, prefix);
19 | }
20 |
21 | public static boolean backup(String thiz, String prefix) {
22 | Log.w(TAG, "String.startsWith() should not be here");
23 | return false;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Wed Dec 06 09:59:46 CST 2017
16 | android.enableJetifier=true
17 | android.useAndroidX=true
18 | org.gradle.jvmargs=-Xmx1536m
19 |
--------------------------------------------------------------------------------
/demoApp/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
22 |
23 |
--------------------------------------------------------------------------------
/demoApp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/java/lab/galaxy/yahfa/HookInfo.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa;
2 |
3 | import lab.galaxy.yahfa.demoPlugin.Hook_ClassWithCtor;
4 | import lab.galaxy.yahfa.demoPlugin.Hook_ClassWithJNIMethod_fromJNI;
5 | import lab.galaxy.yahfa.demoPlugin.Hook_ClassWithStaticMethod_tac;
6 | import lab.galaxy.yahfa.demoPlugin.Hook_ClassWithVirtualMethod_tac;
7 | import lab.galaxy.yahfa.demoPlugin.Hook_Log_e;
8 | import lab.galaxy.yahfa.demoPlugin.Hook_String_startsWith;
9 |
10 | /**
11 | * Created by liuruikai756 on 30/03/2017.
12 | */
13 |
14 | public class HookInfo {
15 | public static final String TAG = "HookInfo";
16 | public static String[] hookItemNames = {
17 | Hook_Log_e.class.getName(),
18 | Hook_String_startsWith.class.getName(),
19 | Hook_ClassWithVirtualMethod_tac.class.getName(),
20 | Hook_ClassWithStaticMethod_tac.class.getName(),
21 | Hook_ClassWithJNIMethod_fromJNI.class.getName(),
22 | Hook_ClassWithCtor.class.getName(),
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithStaticMethod_tac.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoPlugin;
2 |
3 | import android.util.Log;
4 |
5 | import static lab.galaxy.yahfa.HookInfo.TAG;
6 |
7 | /**
8 | * Created by liuruikai756 on 30/03/2017.
9 | */
10 |
11 | public class Hook_ClassWithStaticMethod_tac {
12 | public static String className = "lab.galaxy.yahfa.demoApp.ClassWithStaticMethod";
13 | public static String methodName = "tac";
14 | public static String methodSig =
15 | "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
16 |
17 | public static String hook(String a, String b, String c, String d) {
18 | Log.w(TAG, "in ClassWithStaticMethod.tac(): " + a + ", " + b + ", " + c + ", " + d);
19 | return "test" + a;
20 | }
21 | /*
22 | public static String backup(String a, String b, String c, String d) {
23 | Log.w(TAG, "ClassWithStaticMethod.tac() should not be here");
24 | return "";
25 | }
26 | */
27 | }
28 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | }
4 |
5 | android {
6 | compileSdkVersion 28
7 | defaultConfig {
8 | minSdkVersion 21
9 | targetSdkVersion 28
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 |
15 | externalNativeBuild {
16 | cmake {
17 | arguments "-DANDROID_STL=none"
18 | }
19 | }
20 | }
21 | externalNativeBuild {
22 | cmake {
23 | path 'CMakeLists.txt'
24 | }
25 | }
26 | buildFeatures {
27 | prefab true
28 | }
29 |
30 | packagingOptions {
31 | exclude "**/libdlfunc.so"
32 | }
33 |
34 | // testOptions {
35 | // unitTests {
36 | // packagingOptions {
37 | // pickFirst "**/libdlfunc.so"
38 | // }
39 | // }
40 | // }
41 | }
42 |
43 | dependencies {
44 | implementation 'io.github.rk700:dlfunc:0.1.0'
45 | testImplementation 'junit:junit:4.12'
46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/demoApp/src/main/java/lab/galaxy/yahfa/demoApp/MainApp.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoApp;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.os.Environment;
6 |
7 | import java.io.File;
8 |
9 | import dalvik.system.DexClassLoader;
10 | import lab.galaxy.yahfa.HookMain;
11 |
12 | /**
13 | * Created by liuruikai756 on 30/03/2017.
14 | */
15 |
16 | public class MainApp extends Application {
17 | @Override
18 | protected void attachBaseContext(Context base) {
19 | super.attachBaseContext(base);
20 |
21 | try {
22 | /*
23 | Build and put the demoPlugin apk in sdcard before running the demoApp
24 | */
25 | ClassLoader classLoader = getClassLoader();
26 |
27 | DexClassLoader dexClassLoader = new DexClassLoader(
28 | new File(Environment.getExternalStorageDirectory(), "demoPlugin-debug.apk").getAbsolutePath(),
29 | getCodeCacheDir().getAbsolutePath(), null, classLoader);
30 | HookMain.doHookDefault(dexClassLoader, classLoader);
31 | } catch (Exception e) {
32 | e.printStackTrace();
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/demoPlugin/src/main/java/lab/galaxy/yahfa/demoPlugin/Hook_ClassWithVirtualMethod_tac.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoPlugin;
2 |
3 | import android.util.Log;
4 |
5 | import static lab.galaxy.yahfa.HookInfo.TAG;
6 |
7 | /**
8 | * Created by liuruikai756 on 30/03/2017.
9 | */
10 |
11 | public class Hook_ClassWithVirtualMethod_tac {
12 | public static String className = "lab.galaxy.yahfa.demoApp.ClassWithVirtualMethod";
13 | public static String methodName = "tac";
14 | public static String methodSig =
15 | "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
16 |
17 | public static String hook(Object thiz, String a, String b, String c, String d) {
18 | int uid = android.os.Process.myUid();
19 | Log.w(TAG, "in ClassWithVirtualMethod.tac(): " + a + ", " + b + ", " + c + ", " + d + ": " + uid);
20 | return backup(thiz, a, b, c, d);
21 | }
22 |
23 | public static String backup(Object thiz, String a, String b, String c, String d) {
24 | try {
25 | Log.w(TAG, "ClassWithVirtualMethod.tac() should not be here");
26 | }
27 | catch (Exception e) {
28 |
29 | }
30 | return "";
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/library/src/main/jni/common.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by liuruikai756 on 05/07/2017.
3 | //
4 | #ifndef YAHFA_COMMON_H
5 | #define YAHFA_COMMON_H
6 |
7 | #include
8 | #include
9 | #include
10 |
11 | // Android 13
12 | #ifndef __ANDROID_API_T__
13 | #define __ANDROID_API_T__ 33
14 | #endif
15 |
16 | // Android 12+
17 | #ifndef __ANDROID_API_S_L__
18 | #define __ANDROID_API_S_L__ 32
19 | #endif
20 |
21 | #ifndef __ANDROID_API_S__
22 | #define __ANDROID_API_S__ 31
23 | #endif
24 |
25 | extern int SDKVersion;
26 |
27 | #define LOG_TAG "YAHFA-Native"
28 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
29 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
30 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
31 |
32 | #define pointer_size sizeof(void*)
33 | #define roundUpToPtrSize(v) (v + pointer_size - 1 - ((v + pointer_size - 1) & (pointer_size - 1)))
34 |
35 | #define read32(addr) *((uint32_t *)(addr))
36 |
37 | #define write32(addr, value) *((uint32_t *)(addr)) = (value)
38 |
39 | #define readAddr(addr) *((void **)(addr))
40 |
41 | #define writeAddr(addr, value) *((void **)(addr)) = (value)
42 |
43 | #endif //YAHFA_COMMON_H
44 |
--------------------------------------------------------------------------------
/library/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # Sets the minimum version of CMake required to build your native library.
2 | # This ensures that a certain set of CMake features is available to
3 | # your build.
4 |
5 | cmake_minimum_required(VERSION 3.4.1)
6 |
7 | # Specifies a library name, specifies whether the library is STATIC or
8 | # SHARED, and provides relative paths to the source code. You can
9 | # define multiple libraries by adding multiple add_library() commands,
10 | # and CMake builds them for you. When you build your app, Gradle
11 | # automatically packages shared libraries with your APK.
12 |
13 | find_library( # Defines the name of the path variable that stores the
14 | # location of the NDK library.
15 | log-lib
16 |
17 | # Specifies the name of the NDK library that
18 | # CMake needs to locate.
19 | log )
20 |
21 |
22 | add_library( # Specifies the name of the library.
23 | yahfa
24 |
25 | # Sets the library as a shared library.
26 | SHARED
27 |
28 | # Provides a relative path to your source file(s).
29 | src/main/jni/HookMain.c
30 | src/main/jni/trampoline.c
31 | src/main/jni/utils.c
32 | )
33 |
34 | # class visibly init only needed after Android R for arm
35 | if(${ANDROID_ABI} MATCHES "arm")
36 | find_package(dlfunc REQUIRED CONFIG)
37 | target_link_libraries( # Specifies the target library.
38 | yahfa
39 |
40 | # Links the log library to the target library.
41 | ${log-lib}
42 | dlfunc::dlfunc
43 | )
44 | else()
45 | target_link_libraries( # Specifies the target library.
46 | yahfa
47 |
48 | # Links the log library to the target library.
49 | ${log-lib}
50 | )
51 | endif()
52 |
53 |
54 |
--------------------------------------------------------------------------------
/demoPlugin/README.md:
--------------------------------------------------------------------------------
1 | YAHFA demoPlugin
2 | ----------------
3 |
4 | Here is a demo plugin which contains the hook info.
5 |
6 | ## Usage
7 |
8 | Build the plugin and you'll get an APK file. Push the APK to `/sdcard/demoPlugin-debug.apk` before running the [demoApp](https://github.com/rk700/YAHFA/tree/master/demoApp).
9 |
10 | ## How to write a plugin
11 |
12 | To hook a method, please create a `Class` which has the following fields:
13 |
14 | - A static `String` field named `className`. This is the name of the class where the target method belongs to
15 | - A static `String` field named `methodName`. This is the name of the target method
16 | - A static `String` field named `methodSig`. This is the signature string of the target method
17 |
18 | The above fields are used for finding target method. Besides, the class should have the following methods:
19 |
20 | - A __static__ method named `hook`. This is the hook method that would replace the target method. Please make sure that the arguments do match. For example, if you hook a virtual method, then the first argument of the `hook()` should be _this_ `Object`.
21 | - A __static__ method named `backup`. This is the placeholder where the target method would be backed up before hooking. You can invoke `backup()` in `hook()` wherever you like. Since `backup()` is a placeholder, how the method is implemented doesn't matter. Also, `backup()` is optional, which means it's not necessary if the original method would not be called.
22 |
23 | Now you have a class which contains all the information for hooking a method. We call this class _hook item_. You can create as many hook items as you like.
24 |
25 | After creating all the hook items, put their names in the field `hookItemNames` of class `lab.galaxy.yahfa.HookInfo`. YAHFA would read this field for hook items when loading the plugin.
26 |
27 |
--------------------------------------------------------------------------------
/demoApp/src/main/java/lab/galaxy/yahfa/demoApp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa.demoApp;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.util.Log;
6 | import android.view.View;
7 | import android.widget.Button;
8 |
9 | import java.lang.reflect.Method;
10 |
11 | import lab.galaxy.yahfa.HookMain;
12 |
13 | public class MainActivity extends Activity {
14 | private static final String TAG = "origin";
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_main);
20 |
21 | findViewById(R.id.hookBtn).setOnClickListener(new View.OnClickListener() {
22 | @Override
23 | public void onClick(View v) {
24 | try {
25 | Method hook = Hook_Log_e.class.getDeclaredMethod("hook", String.class, String.class);
26 | Method backup = Hook_Log_e.class.getDeclaredMethod("backup", String.class, String.class);
27 | HookMain.findAndBackupAndHook(Log.class, Hook_Log_e.methodName, Hook_Log_e.methodSig, hook, backup);
28 | } catch (NoSuchMethodException e) {
29 | e.printStackTrace();
30 | }
31 | }
32 | });
33 |
34 | Button button = (Button) findViewById(R.id.button);
35 | button.setOnClickListener(new View.OnClickListener() {
36 | @Override
37 | public void onClick(View v) {
38 | for (int i = 0; i < 200; i++) {
39 | doWork();
40 | }
41 | }
42 | });
43 | }
44 |
45 | void doWork() {
46 | // Log.e() should be hooked
47 | Log.e(TAG, "call Log.e()");
48 | // String.startsWith() should be hooked
49 | Log.w(TAG, "foo startsWith f is " + "foo".startsWith("f"));
50 | // ClassWithVirtualMethod.tac() should be hooked
51 | Log.w(TAG, "virtual tac a,b,c,d, got " +
52 | new ClassWithVirtualMethod().tac("a", "b", "c", "d"));
53 | // ClassWithStaticMethod.tac() should be hooked
54 | Log.w(TAG, "static tac a,b,c,d, got " +
55 | ClassWithStaticMethod.tac("a", "b", "c", "d"));
56 | Log.w(TAG, "JNI method return string: " + ClassWithJNIMethod.fromJNI());
57 |
58 | ClassWithCtor classWithCtor = new ClassWithCtor("param");
59 | Log.w(TAG, "class ctor and get field: " + classWithCtor.getField());
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | YAHFA
2 | ----------------
3 |
4 | [](https://github.com/PAGalaxyLab/YAHFA/actions)
5 | [](https://github.com/PAGalaxyLab/YAHFA/releases/latest/download/library-release.aar)
6 | [](https://repo1.maven.org/maven2/io/github/pagalaxylab/yahfa/)
7 |
8 | ## Introduction
9 |
10 | YAHFA is a hook framework for Android ART. It provides an efficient way for Java method hooking or replacement. Currently it supports:
11 |
12 | - ~~Android 5.0(API 21)~~
13 | - ~~Android 5.1(API 22)~~
14 | - ~~Android 6.0(API 23)~~
15 | - Android 7.0(API 24)
16 | - Android 7.1(API 25)
17 | - Android 8.0(API 26)
18 | - Android 8.1(API 27)
19 | - Android 9(API 28)
20 | - Android 10(API 29)
21 | - Android 11(API 30)
22 | - Android 12(DP1)
23 |
24 | (Support for version <= 6.0 is broken after commit [9824bdd](https://github.com/PAGalaxyLab/YAHFA/commit/9824bdd9d958fd0eca43537b6288bb04da191036).)
25 |
26 | with ABI:
27 |
28 | - x86
29 | - x86_64
30 | - armeabi-v7a
31 | - arm64-v8a
32 |
33 | YAHFA is utilized by [VirtualHook](https://github.com/rk700/VirtualHook) so that applications can be hooked without root permission.
34 |
35 | Please take a look at this [article](http://rk700.github.io/2017/03/30/YAHFA-introduction/) and this [one](http://rk700.github.io/2017/06/30/hook-on-android-n/) for a detailed introduction.
36 |
37 | [更新说明](https://github.com/rk700/YAHFA/wiki/%E6%9B%B4%E6%96%B0%E8%AF%B4%E6%98%8E)
38 |
39 | ## Setup
40 |
41 | Add Maven central repo in `build.gradle`:
42 |
43 | ```
44 | buildscript {
45 | repositories {
46 | mavenCentral()
47 | }
48 | }
49 |
50 | allprojects {
51 | repositories {
52 | mavenCentral()
53 | }
54 | }
55 | ```
56 |
57 | Then add YAHFA as a dependency:
58 |
59 | ```
60 | dependencies {
61 | implementation 'io.github.pagalaxylab:yahfa:0.10.0'
62 | }
63 | ```
64 |
65 | YAHFA depends on [dlfunc](https://github.com/rk700/dlfunc) after commit [5b60df8](https://github.com/PAGalaxyLab/YAHFA/commit/5b60df8af85fab2b4901cf881c7e9362010c0472) for calling `MakeInitializedClassesVisiblyInitialized` explicitly on Android R, and Android Gradle Plugin version 4.1+ is required for that native library dependency.
66 |
67 | ## Usage
68 |
69 | To hook a method:
70 |
71 | ```java
72 | HookMain.backupAndHook(Method target, Method hook, Method backup);
73 | ```
74 |
75 | where `backup` would be a placeholder for invoking the target method. Set `backup` to null or just use `HookMain.hook(Method target, Method hook)` if the original code is not needed.
76 |
77 | Both `hook` and `backup` are static methods, and their parameters should match the ones of `target`. Please take a look at [demoPlugin](https://github.com/rk700/YAHFA/tree/master/demoPlugin) on how these methods are defined.
78 |
79 | ## Workaround for Method Inlining
80 |
81 | Hooking would fail for methods that are compiled to be inlined. For example:
82 |
83 | ```
84 | 0x00004d5a: f24a7e81 movw lr, #42881
85 | 0x00004d5e: f2c73e11 movt lr, #29457
86 | 0x00004d62: f6495040 movw r0, #40256
87 | 0x00004d66: f2c70033 movt r0, #28723
88 | 0x00004d6a: 4641 mov r1, r8
89 | 0x00004d6c: 1c32 mov r2, r6
90 | 0x00004d6e: 47f0 blx lr
91 | ```
92 |
93 | Here the value of register `lr` is hardcoded instead of reading from entry point field of `ArtMethod`.
94 |
95 | A simple workaround is to build the APP with debuggable option on, in which case the inlining optimization will not apply. However the option `--debuggable` of `dex2oat` is not available until API 23. So please take a look at machine instructions of the target when the hook doesn't work.
96 |
97 | ## License
98 |
99 | YAHFA is distributed under GNU GPL V3.
100 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/library/src/main/jni/trampoline.c:
--------------------------------------------------------------------------------
1 | //
2 | // Created by liuruikai756 on 05/07/2017.
3 | //
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "common.h"
10 | #include "trampoline.h"
11 |
12 | static unsigned char *currentTrampolineOff = 0;
13 | static unsigned char *trampolineSpaceEnd = 0;
14 |
15 | static void *allocTrampolineSpace();
16 |
17 | // trampoline:
18 | // 1. set eax/rdi/r0/x0 to the hook ArtMethod addr
19 | // 2. jump into its entry point
20 |
21 | // trampoline for backup:
22 | // 1. set eax/rdi/r0/x0 to the target ArtMethod addr
23 | // 2. ret to the hardcoded original entry point
24 |
25 | #if defined(__i386__)
26 | // b8 78 56 34 12 ; mov eax, 0x12345678 (addr of the hook method)
27 | // ff 70 20 ; push DWORD PTR [eax + 0x20]
28 | // c3 ; ret
29 | unsigned char trampoline[] = {
30 | 0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
31 | 0xb8, 0x78, 0x56, 0x34, 0x12,
32 | 0xff, 0x70, 0x20,
33 | 0xc3
34 | };
35 |
36 | // b8 78 56 34 12 ; mov eax, 0x12345678 (addr of the target method)
37 | // 68 78 56 34 12 ; push 0x12345678 (original entry point of the target method)
38 | // c3 ; ret
39 | unsigned char trampolineForBackup[] = {
40 | 0xb8, 0x78, 0x56, 0x34, 0x12,
41 | 0x68, 0x78, 0x56, 0x34, 0x12,
42 | 0xc3
43 | };
44 |
45 | #elif defined(__x86_64__)
46 | // 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678
47 | // ff 77 20 ; push QWORD PTR [rdi + 0x20]
48 | // c3 ; ret
49 | unsigned char trampoline[] = {
50 | 0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
51 | 0x48, 0xbf, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
52 | 0xff, 0x77, 0x20,
53 | 0xc3
54 | };
55 |
56 | // 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678
57 | // 57 ; push rdi (original entry point of the target method)
58 | // 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678 (addr of the target method)
59 | // c3 ; ret
60 | unsigned char trampolineForBackup[] = {
61 | 0x48, 0xbf, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
62 | 0x57,
63 | 0x48, 0xbf, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12,
64 | 0xc3
65 | };
66 |
67 | #elif defined(__arm__)
68 | // 00 00 9F E5 ; ldr r0, [pc, #0]
69 | // 20 F0 90 E5 ; ldr pc, [r0, 0x20]
70 | // 78 56 34 12 ; 0x12345678 (addr of the hook method)
71 | unsigned char trampoline[] = {
72 | 0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
73 | 0x00, 0x00, 0x9f, 0xe5,
74 | 0x20, 0xf0, 0x90, 0xe5,
75 | 0x78, 0x56, 0x34, 0x12
76 | };
77 |
78 | // 0c 00 9F E5 ; ldr r0, [pc, #12]
79 | // 01 00 2d e9 ; push {r0}
80 | // 00 00 9F E5 ; ldr r0, [pc, #0]
81 | // 00 80 bd e8 ; pop {pc}
82 | // 78 56 34 12 ; 0x12345678 (addr of the hook method)
83 | // 78 56 34 12 ; 0x12345678 (original entry point of the target method)
84 | unsigned char trampolineForBackup[] = {
85 | 0x0c, 0x00, 0x9f, 0xe5,
86 | 0x01, 0x00, 0x2d, 0xe9,
87 | 0x00, 0x00, 0x9f, 0xe5,
88 | 0x00, 0x80, 0xbd, 0xe8,
89 | 0x78, 0x56, 0x34, 0x12,
90 | 0x78, 0x56, 0x34, 0x12
91 | };
92 |
93 | #elif defined(__aarch64__)
94 | // 60 00 00 58 ; ldr x0, 12
95 | // 10 00 40 F8 ; ldr x16, [x0, #0x00]
96 | // 00 02 1f d6 ; br x16
97 | // 78 56 34 12
98 | // 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
99 | unsigned char trampoline[] = {
100 | 0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
101 | 0x60, 0x00, 0x00, 0x58,
102 | 0x10, 0x00, 0x40, 0xf8,
103 | 0x00, 0x02, 0x1f, 0xd6,
104 | 0x78, 0x56, 0x34, 0x12,
105 | 0x89, 0x67, 0x45, 0x23
106 | };
107 |
108 | // 60 00 00 58 ; ldr x0, 12
109 | // 90 00 00 58 ; ldr x16, 16
110 | // 00 02 1f d6 ; br x16
111 | // 78 56 34 12
112 | // 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
113 | // 78 56 34 12
114 | // 89 67 45 23 ; 0x2345678912345678 (original entry point of the target method)
115 | unsigned char trampolineForBackup[] = {
116 | 0x60, 0x00, 0x00, 0x58,
117 | 0x90, 0x00, 0x00, 0x58,
118 | 0x00, 0x02, 0x1f, 0xd6,
119 | 0x78, 0x56, 0x34, 0x12,
120 | 0x89, 0x67, 0x45, 0x23,
121 | 0x78, 0x56, 0x34, 0x12,
122 | 0x89, 0x67, 0x45, 0x23
123 | };
124 | #endif
125 |
126 | void *genTrampoline(void *toMethod, void *entrypoint) {
127 | size_t trampolineSize = entrypoint != NULL ? sizeof(trampolineForBackup) : sizeof(trampoline);
128 |
129 | // check available space for new trampoline
130 | if(currentTrampolineOff+trampolineSize > trampolineSpaceEnd) {
131 | currentTrampolineOff = allocTrampolineSpace();
132 | if (currentTrampolineOff == NULL) {
133 | return NULL;
134 | } else {
135 | trampolineSpaceEnd = currentTrampolineOff + TRAMPOLINE_SPACE_SIZE;
136 | }
137 | }
138 |
139 | unsigned char *targetAddr = currentTrampolineOff;
140 |
141 | if(entrypoint != NULL) {
142 | memcpy(targetAddr, trampolineForBackup, sizeof(trampolineForBackup));
143 | }
144 | else {
145 | memcpy(targetAddr, trampoline,
146 | sizeof(trampoline)); // do not use trampolineSize since it's a rounded size
147 | }
148 |
149 | // replace with the actual ArtMethod addr
150 | #if defined(__i386__)
151 | if(entrypoint) {
152 | memcpy(targetAddr + 1, &toMethod, pointer_size);
153 | memcpy(targetAddr + 6, &entrypoint, pointer_size);
154 | }
155 | else {
156 | memcpy(targetAddr + 5, &toMethod, pointer_size);
157 | }
158 |
159 | #elif defined(__x86_64__)
160 | if(entrypoint) {
161 | memcpy(targetAddr + 2, &entrypoint, pointer_size);
162 | memcpy(targetAddr + 13, &toMethod, pointer_size);
163 | }
164 | else {
165 | memcpy(targetAddr + 6, &toMethod, pointer_size);
166 | }
167 |
168 | #elif defined(__arm__)
169 | if(entrypoint) {
170 | memcpy(targetAddr + 20, &entrypoint, pointer_size);
171 | memcpy(targetAddr + 16, &toMethod, pointer_size);
172 | }
173 | else {
174 | memcpy(targetAddr + 12, &toMethod, pointer_size);
175 | }
176 |
177 | #elif defined(__aarch64__)
178 | if(entrypoint) {
179 | memcpy(targetAddr + 20, &entrypoint, pointer_size);
180 | memcpy(targetAddr + 12, &toMethod, pointer_size);
181 | }
182 | else {
183 | memcpy(targetAddr + 16, &toMethod, pointer_size);
184 | }
185 | #else
186 | #error Unsupported architecture
187 | #endif
188 |
189 | // skip 4 bytes of code_size_
190 | if(entrypoint == NULL) {
191 | targetAddr += 4;
192 | }
193 |
194 | // keep each trampoline aligned
195 | currentTrampolineOff += roundUpToPtrSize(trampolineSize);
196 |
197 | return targetAddr;
198 | }
199 |
200 | void setupTrampoline(uint8_t offset) {
201 | #if defined(__i386__)
202 | trampoline[11] = offset;
203 | #elif defined(__x86_64__)
204 | trampoline[16] = offset;
205 | #elif defined(__arm__)
206 | trampoline[8] = offset;
207 | #elif defined(__aarch64__)
208 | trampoline[9] |= offset << 4;
209 | trampoline[10] |= offset >> 4;
210 | #else
211 | #error Unsupported architecture
212 | #endif
213 | }
214 |
215 | static void *allocTrampolineSpace() {
216 | unsigned char *buf = mmap(NULL, TRAMPOLINE_SPACE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
217 | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
218 | if (buf == MAP_FAILED) {
219 | LOGE("mmap failed, errno = %s", strerror(errno));
220 | return NULL;
221 | }
222 | else {
223 | LOGI("allocating space for trampoline code at %p", buf);
224 | return buf;
225 | }
226 |
227 | }
--------------------------------------------------------------------------------
/library/src/androidTest/java/lab/galaxy/yahfa/HookingTest.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa;
2 |
3 | import android.os.Build;
4 | import androidx.test.runner.AndroidJUnit4;
5 | import android.util.Log;
6 |
7 | import org.junit.Assert;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 |
12 | @RunWith(AndroidJUnit4.class)
13 | public class HookingTest {
14 | private static final String TAG = HookingTest.class.getSimpleName();
15 |
16 | @Before
17 | public void setup() {
18 | Log.i(TAG, "ABI=" + Build.CPU_ABI);
19 | StaticHook.targetCount = 0;
20 | StaticHook.hookCount = 0;
21 | StaticHook.backupCount = 0;
22 |
23 | InstanceHook.targetCount = 0;
24 | InstanceHook.hookCount = 0;
25 | InstanceHook.backupCount = 0;
26 |
27 | CtorHook.targetCount = 0;
28 | CtorHook.hookCount = 0;
29 | CtorHook.backupCount = 0;
30 | }
31 |
32 | @Test
33 | public void hookInstanceMethod() throws Exception {
34 | InstanceHook hookedInstance = new InstanceHook();
35 |
36 | Assert.assertEquals(3, hookedInstance.target(3));
37 | Assert.assertEquals(1, InstanceHook.targetCount);
38 | Assert.assertEquals(0, InstanceHook.hookCount);
39 | Assert.assertEquals(0, InstanceHook.backupCount);
40 |
41 | try {
42 | InstanceHook.hook(hookedInstance, 4);
43 | Assert.fail("Backup should have failed");
44 | } catch (UnsupportedOperationException e) {
45 | //Ok
46 | }
47 | Assert.assertEquals(1, InstanceHook.targetCount);
48 | Assert.assertEquals(1, InstanceHook.hookCount);
49 | Assert.assertEquals(1, InstanceHook.backupCount);
50 |
51 |
52 | //------------------------ AFTER HOOKING --------------------------------
53 | HookAnnotation.hookClass(InstanceHook.class);
54 |
55 | Assert.assertEquals(5, hookedInstance.target(5));
56 | Assert.assertEquals(2, InstanceHook.targetCount);
57 | Assert.assertEquals(2, InstanceHook.hookCount);
58 | Assert.assertEquals(1, InstanceHook.backupCount);
59 |
60 | Assert.assertEquals(6, InstanceHook.hook(hookedInstance, 6));
61 | Assert.assertEquals(3, InstanceHook.targetCount);
62 | Assert.assertEquals(3, InstanceHook.hookCount);
63 | Assert.assertEquals(1, InstanceHook.backupCount);
64 |
65 | Assert.assertEquals(7, InstanceHook.backup(hookedInstance, 7));
66 | Assert.assertEquals(4, InstanceHook.targetCount);
67 | Assert.assertEquals(3, InstanceHook.hookCount);
68 | Assert.assertEquals(1, InstanceHook.backupCount);
69 | }
70 |
71 | @Test
72 | public void hookStaticMethod() throws Exception {
73 | Assert.assertEquals(3, StaticHook.target(3));
74 | Assert.assertEquals(1, StaticHook.targetCount);
75 | Assert.assertEquals(0, StaticHook.hookCount);
76 | Assert.assertEquals(0, StaticHook.backupCount);
77 |
78 | try {
79 | StaticHook.hook(4);
80 | Assert.fail("Backup should have failed");
81 | } catch (UnsupportedOperationException e) {
82 | //Ok
83 | }
84 | Assert.assertEquals(1, StaticHook.targetCount);
85 | Assert.assertEquals(1, StaticHook.hookCount);
86 | Assert.assertEquals(1, StaticHook.backupCount);
87 |
88 |
89 | //------------------------ AFTER HOOKING --------------------------------
90 | HookAnnotation.hookClass(StaticHook.class);
91 |
92 | Assert.assertEquals(5, StaticHook.target(5));
93 | Assert.assertEquals(2, StaticHook.targetCount);
94 | Assert.assertEquals(2, StaticHook.hookCount);
95 | Assert.assertEquals(1, StaticHook.backupCount);
96 |
97 | Assert.assertEquals(6, StaticHook.hook(6));
98 | Assert.assertEquals(3, StaticHook.targetCount);
99 | Assert.assertEquals(3, StaticHook.hookCount);
100 | Assert.assertEquals(1, StaticHook.backupCount);
101 |
102 | Assert.assertEquals(7, StaticHook.backup(7));
103 | Assert.assertEquals(4, StaticHook.targetCount);
104 | Assert.assertEquals(3, StaticHook.hookCount);
105 | Assert.assertEquals(1, StaticHook.backupCount);
106 | }
107 |
108 | @Test
109 | public void hookCtorMethod() throws Exception {
110 | CtorHook ctorHook = new CtorHook(0);
111 | Assert.assertEquals(1, CtorHook.targetCount);
112 | Assert.assertEquals(0, CtorHook.hookCount);
113 | Assert.assertEquals(0, CtorHook.backupCount);
114 |
115 | try {
116 | CtorHook.hook(ctorHook, 4);
117 | Assert.fail("Backup should have failed");
118 | } catch (UnsupportedOperationException e) {
119 | //Ok
120 | }
121 | Assert.assertEquals(1, CtorHook.targetCount);
122 | Assert.assertEquals(1, CtorHook.hookCount);
123 | Assert.assertEquals(1, CtorHook.backupCount);
124 |
125 |
126 | //------------------------ AFTER HOOKING --------------------------------
127 | HookAnnotation.hookClass(CtorHook.class);
128 |
129 | ctorHook = new CtorHook(0);
130 |
131 | Assert.assertEquals(2, CtorHook.targetCount);
132 | Assert.assertEquals(2, CtorHook.hookCount);
133 | Assert.assertEquals(1, CtorHook.backupCount);
134 |
135 | ctorHook = new CtorHook(0);
136 | Assert.assertEquals(3, CtorHook.targetCount);
137 | Assert.assertEquals(3, CtorHook.hookCount);
138 | Assert.assertEquals(1, CtorHook.backupCount);
139 |
140 | CtorHook.backup(ctorHook, 0);
141 | Assert.assertEquals(4, CtorHook.targetCount);
142 | Assert.assertEquals(3, CtorHook.hookCount);
143 | Assert.assertEquals(1, CtorHook.backupCount);
144 | }
145 |
146 | static class CtorHook {
147 | static int targetCount = 0;
148 | static int hookCount = 0;
149 | static int backupCount = 0;
150 |
151 | public CtorHook(int arg) {
152 | targetCount++;
153 | }
154 |
155 | @HookAnnotation.ConstructorHook
156 | public static void hook(CtorHook thiz, int arg) {
157 | hookCount++;
158 | backup(thiz, arg);
159 | }
160 |
161 | @HookAnnotation.ConstructorBackup
162 | public static void backup(CtorHook thiz, int arg) {
163 | backupCount++;
164 | throw new UnsupportedOperationException("Stub!");
165 | }
166 | }
167 |
168 | static class StaticHook {
169 | static int targetCount = 0;
170 | static int hookCount = 0;
171 | static int backupCount = 0;
172 |
173 | public static int target(int arg) {
174 | targetCount++;
175 | return arg;
176 | }
177 |
178 | @HookAnnotation.StaticMethodHook(targetClass = StaticHook.class, methodName = "target")
179 | public static int hook(int arg) {
180 | hookCount++;
181 | return backup(arg);
182 | }
183 |
184 | @HookAnnotation.StaticMethodBackup(targetClass = StaticHook.class, methodName = "target")
185 | public static int backup(int arg) {
186 | backupCount++;
187 | throw new UnsupportedOperationException("Stub!");
188 | }
189 | }
190 |
191 | static class InstanceHook {
192 | static int targetCount;
193 | static int hookCount;
194 | static int backupCount;
195 |
196 | @HookAnnotation.MethodHook(methodName = "target")
197 | public static int hook(InstanceHook thiz, int arg) {
198 | hookCount++;
199 | return backup(thiz, arg);
200 | }
201 |
202 | @HookAnnotation.MethodBackup(methodName = "target")
203 | public static int backup(InstanceHook thiz, int arg) {
204 | backupCount++;
205 | throw new UnsupportedOperationException("Stub!");
206 | }
207 |
208 | public int target(int arg) {
209 | targetCount++;
210 | return arg;
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/library/src/main/jni/utils.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #include "common.h"
8 |
9 | #if defined(__aarch64__)
10 | #include
11 | #include "dlfunc.h"
12 | #define NEED_CLASS_VISIBLY_INITIALIZED
13 | #endif
14 |
15 | static char *classLinker = NULL;
16 | typedef void (*InitClassFunc)(void *, void *, int);
17 | static InitClassFunc MakeInitializedClassesVisiblyInitialized = NULL;
18 | static int shouldVisiblyInit();
19 | static int findInitClassSymbols(JNIEnv *env);
20 |
21 | static const size_t kPointerSize = sizeof(void *);
22 | static const size_t kVTablePosition = 2;
23 |
24 | static int isValidAddress(const void *p) {
25 | if (!p) {
26 | return 0;
27 | }
28 |
29 | int ret = 1;
30 | int fd = open("/dev/random", O_WRONLY);
31 | size_t len = sizeof(int32_t);
32 | if (fd != -1) {
33 | if (write(fd, p, len) < 0) {
34 | ret = 0;
35 | }
36 | close(fd);
37 | } else {
38 | ret = 0;
39 | }
40 | return ret;
41 | }
42 |
43 | static int
44 | commonFindOffset(void *start, size_t max_count, size_t step, void *value, int start_search_offset) {
45 | if (NULL == start) {
46 | return -1;
47 | }
48 | if (start_search_offset > max_count) {
49 | return -1;
50 | }
51 |
52 | for (int i = start_search_offset; i <= max_count; i += step) {
53 | void *current_value = *(void **) ((size_t) start + i);
54 | if (value == current_value) {
55 | return i;
56 | }
57 | }
58 | return -1;
59 | }
60 |
61 | static int searchClassLinkerOffset(JavaVM *vm, void *runtime_instance, JNIEnv *env, void *libart_handle) {
62 | #ifndef NEED_CLASS_VISIBLY_INITIALIZED
63 | return -1;
64 | #else
65 | void *class_linker_vtable = dlfunc_dlsym(env, libart_handle, "_ZTVN3art11ClassLinkerE");
66 | if (class_linker_vtable != NULL) {
67 | // Before Android 9, class_liner do not hava virtual table, so class_linker_vtable is null.
68 | class_linker_vtable = (char *) class_linker_vtable + kPointerSize * kVTablePosition;
69 | }
70 |
71 | //Need to search jvm offset from 200, if not, on xiaomi android7.1.2 we would get jvm_offset 184, but in fact it is 440
72 | int jvm_offset_in_runtime = commonFindOffset(runtime_instance, 2000, 4, (void *) vm, 200);
73 |
74 | if (jvm_offset_in_runtime < 0) {
75 | LOGE("failed to find JavaVM in Runtime");
76 | return -1;
77 | }
78 |
79 | int step = 4;
80 | int class_linker_offset_value = -1;
81 | for (int i = jvm_offset_in_runtime - step; i > 0; i -= step) {
82 | void *class_linker_addr = *(void **) ((uintptr_t) runtime_instance + i);
83 |
84 | if (class_linker_addr == NULL || !isValidAddress(class_linker_addr)) {
85 | continue;
86 | }
87 | if (class_linker_vtable != NULL) {
88 | if (*(void **) class_linker_addr == class_linker_vtable) {
89 | class_linker_offset_value = i;
90 | break;
91 | }
92 | } else {
93 | // in runtime.h, the struct is:
94 | // ThreadList* thread_list_;
95 | // InternTable* intern_table_;
96 | // ClassLinker* class_linker_;
97 | // these objects list as this kind of order.
98 | // And the InternTable pointer is also saved in ClassLinker struct,
99 | // So, we can search ClassLinker struct to verify the intern_table_ address.
100 | void *intern_table_addr = *(void **) (
101 | (uintptr_t) runtime_instance +
102 | i -
103 | kPointerSize);
104 | if (isValidAddress(intern_table_addr)) {
105 | for (int j = 200; j < 500; j += step) {
106 | void *intern_table = *(void **) (
107 | (uintptr_t) class_linker_addr + j);
108 | if (intern_table_addr == intern_table) {
109 | class_linker_offset_value = i;
110 | break;
111 | }
112 | }
113 | }
114 | }
115 | if (class_linker_offset_value > 0) {
116 | break;
117 | }
118 | }
119 | return class_linker_offset_value;
120 | #endif
121 | }
122 |
123 | static int findInitClassSymbols(JNIEnv *env) {
124 | #ifndef NEED_CLASS_VISIBLY_INITIALIZED
125 | return 1;
126 | #else
127 | JavaVM *jvm = NULL;
128 | (*env)->GetJavaVM(env, &jvm);
129 | LOGI("JavaVM is %p", jvm);
130 |
131 | if(dlfunc_init(env) != JNI_OK) {
132 | LOGE("dlfunc init failed");
133 | return 1;
134 | }
135 | void *handle = dlfunc_dlopen(env, "libart.so", RTLD_LAZY);
136 | if(handle == NULL) {
137 | LOGE("failed to find libart.so handle");
138 | return 1;
139 | }
140 | else {
141 | void *runtime_bss = dlfunc_dlsym(env, handle, "_ZN3art7Runtime9instance_E");
142 | if(!runtime_bss) {
143 | LOGE("failed to find Runtime::instance symbol");
144 | return 1;
145 | }
146 | char *runtime = readAddr(runtime_bss);
147 | if(!runtime) {
148 | LOGE("Runtime::instance value is NULL");
149 | return 1;
150 | }
151 | LOGI("runtime bss is at %p, runtime instance is at %p", runtime_bss, runtime);
152 |
153 | int class_linker_offset_in_Runtime = searchClassLinkerOffset(jvm, runtime, env, handle);
154 | LOGI("find class_linker offset in_Runtime --> %d ", class_linker_offset_in_Runtime);
155 |
156 | if(class_linker_offset_in_Runtime < 0) {
157 | LOGE("failed to find class_linker offset in Runtime");
158 | return 1;
159 | }
160 |
161 | classLinker = readAddr(runtime + class_linker_offset_in_Runtime);
162 | MakeInitializedClassesVisiblyInitialized = dlfunc_dlsym(env, handle,
163 | "_ZN3art11ClassLinker40MakeInitializedClassesVisiblyInitializedEPNS_6ThreadEb");
164 | // "_ZN3art11ClassLinker12AllocIfTableEPNS_6ThreadEm"); // for test
165 | if(!MakeInitializedClassesVisiblyInitialized) {
166 | LOGE("failed to find MakeInitializedClassesVisiblyInitialized symbol");
167 | return 1;
168 | }
169 | LOGI("MakeInitializedClassesVisiblyInitialized is at %p",
170 | MakeInitializedClassesVisiblyInitialized);
171 | }
172 | return 0;
173 | #endif
174 | }
175 |
176 | jlong __attribute__((naked)) Java_lab_galaxy_yahfa_HookMain_00024Utils_getThread(JNIEnv *env, jclass clazz) {
177 | #if defined(__aarch64__)
178 | __asm__(
179 | "mov x0, x19\n"
180 | "ret\n"
181 | );
182 | #elif defined(__arm__)
183 | __asm__(
184 | "mov r0, r9\n"
185 | "bx lr\n"
186 | );
187 | #elif defined(__x86_64__)
188 | __asm__(
189 | "mov %gs:0xe8, %rax\n" // offset on Android R
190 | "ret\n"
191 | );
192 | #elif defined(__i386__)
193 | __asm__(
194 | "mov %fs:0xc4, %eax\n" // offset on Android R
195 | "ret\n"
196 | );
197 | #endif
198 | }
199 |
200 | static int shouldVisiblyInit() {
201 | #if defined(__i386__) || defined(__x86_64__)
202 | return 0;
203 | #else
204 | if(SDKVersion < __ANDROID_API_R__) {
205 | return 0;
206 | }
207 | else return 1;
208 | #endif
209 | }
210 |
211 | jboolean Java_lab_galaxy_yahfa_HookMain_00024Utils_shouldVisiblyInit(JNIEnv *env, jclass clazz) {
212 | return shouldVisiblyInit() != 0;
213 | }
214 |
215 | jint Java_lab_galaxy_yahfa_HookMain_00024Utils_visiblyInit(JNIEnv *env, jclass clazz, jlong thread) {
216 | if(!shouldVisiblyInit()) {
217 | return 0;
218 | }
219 |
220 | if(!classLinker || !MakeInitializedClassesVisiblyInitialized) {
221 | if(findInitClassSymbols(env) != 0) {
222 | LOGE("failed to find symbols: classLinker %p, MakeInitializedClassesVisiblyInitialized %p",
223 | classLinker, MakeInitializedClassesVisiblyInitialized);
224 | return 1;
225 | }
226 | }
227 |
228 | LOGI("thread is at %p", thread);
229 | MakeInitializedClassesVisiblyInitialized(classLinker, (void *)thread, 1);
230 | return 0;
231 | }
232 |
--------------------------------------------------------------------------------
/library/src/main/java/lab/galaxy/yahfa/HookAnnotation.java:
--------------------------------------------------------------------------------
1 | package lab.galaxy.yahfa;
2 |
3 | import android.util.Log;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 | import java.lang.reflect.Constructor;
10 | import java.lang.reflect.Method;
11 | import java.lang.reflect.Modifier;
12 | import java.util.Arrays;
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | /**
17 | * Defines hooks using annotated methods.
18 | *
19 | * Target class, method name and signature are automatically inferred from the hook when possible
20 | */
21 | public class HookAnnotation {
22 | public static final String TAG = HookAnnotation.class.getSimpleName();
23 |
24 | public static final String HOOK_SUFFIX = "Hook";
25 | public static final String BACKUP_SUFFIX = "Backup";
26 |
27 | @Retention(RetentionPolicy.RUNTIME)
28 | @Target(ElementType.METHOD)
29 | public @interface ConstructorHook {
30 | }
31 |
32 | @Retention(RetentionPolicy.RUNTIME)
33 | @Target(ElementType.METHOD)
34 | public @interface MethodHook {
35 | String methodName() default "";
36 | }
37 |
38 | @Retention(RetentionPolicy.RUNTIME)
39 | @Target(ElementType.METHOD)
40 | public @interface StaticMethodHook {
41 | Class> targetClass();
42 | String methodName() default "";
43 | }
44 |
45 | @Retention(RetentionPolicy.RUNTIME)
46 | @Target(ElementType.METHOD)
47 | public @interface ConstructorBackup {
48 | }
49 |
50 | @Retention(RetentionPolicy.RUNTIME)
51 | @Target(ElementType.METHOD)
52 | public @interface MethodBackup {
53 | String methodName() default "";
54 | }
55 |
56 | @Retention(RetentionPolicy.RUNTIME)
57 | @Target(ElementType.METHOD)
58 | public @interface StaticMethodBackup {
59 | Class> targetClass();
60 | String methodName() default "";
61 | }
62 |
63 | public static void hookClass(Class cls) {
64 | Map