├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.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
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_empty.xml
│ │ │ │ ├── activity_test_thread_hook.xml
│ │ │ │ ├── activity_test_on_click.xml
│ │ │ │ ├── activity_test_notification.xml
│ │ │ │ ├── activity_target.xml
│ │ │ │ ├── activity_target_app_compat.xml
│ │ │ │ ├── activity_test_start.xml
│ │ │ │ ├── activity_test_application_start.xml
│ │ │ │ ├── activity_test_imanager_start.xml
│ │ │ │ ├── activity_rx_java_hook.xml
│ │ │ │ ├── activity_test_clipboard.xml
│ │ │ │ ├── activity_test_start_no_register.xml
│ │ │ │ ├── activity_test_hook_start.xml
│ │ │ │ └── activity_main.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── xj
│ │ │ │ └── hookdemo
│ │ │ │ ├── activityhook
│ │ │ │ ├── EmptyActivity.java
│ │ │ │ ├── TargetActivity.java
│ │ │ │ ├── TestActivityStart.java
│ │ │ │ ├── TargetAppCompatActivity.java
│ │ │ │ ├── TestApplicationStart.java
│ │ │ │ ├── TestIActivityManagerStart.java
│ │ │ │ ├── TestStartActivityNoRegister.java
│ │ │ │ ├── TestHookStartActivity.java
│ │ │ │ └── TestClipboardActivity.java
│ │ │ │ ├── TestNotificationActivity.java
│ │ │ │ ├── hook
│ │ │ │ ├── HookedClickListenerProxy.java
│ │ │ │ ├── activity
│ │ │ │ │ ├── PMSHandler.java
│ │ │ │ │ ├── AMSInvocationHandler.java
│ │ │ │ │ ├── IActivityManagerHandler.java
│ │ │ │ │ ├── ActivityThreadHandlerCallback.java
│ │ │ │ │ ├── AMSHookInvocationHandler.java
│ │ │ │ │ ├── ActivityProxyInstrumentation.java
│ │ │ │ │ ├── ApplicationInstrumentation.java
│ │ │ │ │ └── AMSHookUtil.java
│ │ │ │ ├── clipboard
│ │ │ │ │ ├── ClipboardHookRemoteBinderHandler.java
│ │ │ │ │ ├── ClipboardHookLocalBinderHandler.java
│ │ │ │ │ └── ClipboardHelper.java
│ │ │ │ ├── notification
│ │ │ │ │ └── NotificationHookHelper.java
│ │ │ │ ├── HookResetUtils.java
│ │ │ │ └── HookHelper.java
│ │ │ │ ├── thread
│ │ │ │ ├── TestThreadHookActivity.kt
│ │ │ │ ├── ThreadMethodHook.java
│ │ │ │ └── ThreadHookUtils.java
│ │ │ │ ├── rxjava
│ │ │ │ ├── IOMain.java
│ │ │ │ ├── Rx2Utils.kt
│ │ │ │ └── RxJavaHookActivity.kt
│ │ │ │ ├── App.java
│ │ │ │ ├── TestOnClickActivity.java
│ │ │ │ ├── utils
│ │ │ │ └── BinderHook.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── NotificationHelper.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── administrator
│ │ │ └── hookdemo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── administrator
│ │ └── hookdemo
│ │ └── ExampleInstrumentedTest.java
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .idea
├── caches
│ └── build_file_checksums.ser
├── encodings.xml
├── compiler.xml
├── runConfigurations.xml
├── gradle.xml
├── jarRepositories.xml
├── misc.xml
└── codeStyles
│ └── Project.xml
├── .gitignore
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':demo',"Demo2"
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HookDemo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | /demo/
10 | /Demo2/
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gdutxiaoxu/HookDemo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 03 17:14:46 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/EmptyActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | public class EmptyActivity extends AppCompatActivity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | // setContentView(R.layout.activity_empty);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/TestNotificationActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | public class TestNotificationActivity extends AppCompatActivity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.activity_test_notification);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TargetActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | import com.xj.hookdemo.R;
7 |
8 | public class TargetActivity extends Activity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_target);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_empty.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TestActivityStart.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.xj.hookdemo.R;
7 |
8 | public class TestActivityStart extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_test_start);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/administrator/hookdemo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TargetAppCompatActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.xj.hookdemo.R;
7 |
8 | public class TargetAppCompatActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_target_app_compat);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TestApplicationStart.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.xj.hookdemo.R;
7 |
8 | public class TestApplicationStart extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_test_application_start);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TestIActivityManagerStart.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | import com.xj.hookdemo.R;
7 |
8 | public class TestIActivityManagerStart extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_test_imanager_start);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_thread_hook.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_on_click.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_notification.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/HookedClickListenerProxy.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook;
2 |
3 | import android.view.View;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @author xujun on 16/7/2018.
8 | */
9 | public class HookedClickListenerProxy implements View.OnClickListener {
10 |
11 | private View.OnClickListener origin;
12 |
13 | public HookedClickListenerProxy(View.OnClickListener origin) {
14 | this.origin = origin;
15 | }
16 |
17 | @Override
18 | public void onClick(View v) {
19 | Toast.makeText(v.getContext(), "Hook Click Listener", Toast.LENGTH_SHORT).show();
20 | if (origin != null) {
21 | origin.onClick(v);
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/thread/TestThreadHookActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.thread
2 |
3 | import android.support.v7.app.AppCompatActivity
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.View
7 | import com.xj.hookdemo.R
8 |
9 | class TestThreadHookActivity : AppCompatActivity() {
10 |
11 | private val TAG = "TestThreadHookActivity"
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_test_thread_hook)
16 | ThreadHookUtils.hook()
17 | findViewById(R.id.btn_start_new_thread).setOnClickListener {
18 | Log.i(TAG, "onCreate: ")
19 | }
20 | }
21 |
22 |
23 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_target.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_target_app_compat.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/PMSHandler.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.util.Log;
4 |
5 | import java.lang.reflect.InvocationHandler;
6 | import java.lang.reflect.Method;
7 |
8 | /**
9 | * @author xujun on 17/7/2018.
10 | */
11 | public class PMSHandler implements InvocationHandler {
12 |
13 | private final Object mOrigin;
14 |
15 | private static final String TAG = "PMSHandler";
16 |
17 | public PMSHandler(Object origin) {
18 | mOrigin = origin;
19 | }
20 |
21 | @Override
22 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
23 | Log.i(TAG, "invoke: method=" +method);
24 | return method.invoke(mOrigin, args);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_start.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_application_start.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_imanager_start.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
18 |
19 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/administrator/hookdemo/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.xj.hookdemo", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/thread/ThreadMethodHook.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.thread;
2 |
3 | import android.support.v7.widget.DialogTitle;
4 | import android.util.Log;
5 |
6 | import de.robv.android.xposed.XC_MethodHook;
7 |
8 | class ThreadMethodHook extends XC_MethodHook {
9 |
10 | private static final String TAG = "Hook.ThreadMethodHook";
11 |
12 | @Override
13 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
14 | super.beforeHookedMethod(param);
15 | Thread t = (Thread) param.thisObject;
16 | Log.i(TAG, "thread:" + t + ", started..");
17 | }
18 |
19 | @Override
20 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
21 | super.afterHookedMethod(param);
22 | Thread t = (Thread) param.thisObject;
23 | Log.i(TAG, "thread:" + t + ", exit..");
24 | }
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_rx_java_hook.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
16 |
17 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/rxjava/IOMain.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.rxjava;
2 |
3 | import org.reactivestreams.Publisher;
4 |
5 | import io.reactivex.Completable;
6 | import io.reactivex.CompletableSource;
7 | import io.reactivex.Flowable;
8 | import io.reactivex.Maybe;
9 | import io.reactivex.MaybeSource;
10 | import io.reactivex.Observable;
11 | import io.reactivex.ObservableSource;
12 | import io.reactivex.ObservableTransformer;
13 | import io.reactivex.Single;
14 | import io.reactivex.SingleSource;
15 | import io.reactivex.android.schedulers.AndroidSchedulers;
16 | import io.reactivex.schedulers.Schedulers;
17 |
18 | /**
19 | * @author by zhengxinwei@N3072 on 2018/5/21.
20 | */
21 | class IOMain implements ObservableTransformer {
22 |
23 |
24 | @Override
25 | public ObservableSource apply(Observable upstream) {
26 | return upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
27 | }
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/App.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.os.Handler;
6 |
7 | import com.xj.hookdemo.hook.HookResetUtils;
8 |
9 | /**
10 | * @author xujun on 17/7/2018.
11 | */
12 | public class App extends Application {
13 |
14 | private static Object mObject;
15 | private static Handler mHandler;
16 |
17 | @Override
18 | protected void attachBaseContext(Context base) {
19 | super.attachBaseContext(base);
20 | try {
21 | mObject = HookResetUtils.storeAms();
22 | mHandler = HookResetUtils.storeActivityLaunch();
23 | } catch (Exception e) {
24 | e.printStackTrace();
25 | }
26 |
27 |
28 | }
29 |
30 | public static void reset(){
31 | try {
32 | HookResetUtils.resetAms(mObject);
33 | HookResetUtils.resetActivityLaunch(mHandler);
34 | } catch (Exception e) {
35 | e.printStackTrace();
36 | }
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/AMSInvocationHandler.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.util.Log;
4 |
5 | import java.lang.reflect.InvocationHandler;
6 | import java.lang.reflect.Method;
7 |
8 | /**
9 | * @author xujun on 16/7/2018.
10 | */
11 | public class AMSInvocationHandler implements InvocationHandler {
12 |
13 | private static final String TAG = "AMSInvocationHandler";
14 |
15 | Object iamObject;
16 |
17 | public AMSInvocationHandler(Object iamObject) {
18 | this.iamObject = iamObject;
19 | }
20 |
21 | @Override
22 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
23 | // Log.e(TAG, method.getName());
24 | if ("startActivity".equals(method.getName())) {
25 | Log.i(TAG, "ready to startActivity");
26 | for (Object object : args) {
27 | Log.d(TAG, "invoke: object=" + object);
28 | }
29 | }
30 | return method.invoke(iamObject, args);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_clipboard.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
18 |
24 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_start_no_register.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
16 |
22 |
23 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/TestOnClickActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.util.Log;
6 | import android.view.View;
7 | import android.widget.Button;
8 |
9 | import com.xj.hookdemo.hook.HookHelper;
10 |
11 | public class TestOnClickActivity extends AppCompatActivity implements View.OnClickListener {
12 |
13 | private static final String TAG = "TestOnClickActivity";
14 | private Button mBtn1;
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_test_on_click);
20 | initView();
21 | }
22 |
23 |
24 | private void initView() {
25 | mBtn1 = (Button) findViewById(R.id.btn_1);
26 | mBtn1.setOnClickListener(this);
27 | try {
28 | HookHelper.hookOnClickListener(mBtn1);
29 | } catch (Exception e) {
30 | e.printStackTrace();
31 | }
32 | }
33 |
34 | @Override
35 | public void onClick(View v) {
36 | switch (v.getId()) {
37 | default:
38 | break;
39 | case R.id.btn_1:
40 | Log.i(TAG, "onClick: btn_1");
41 | break;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/thread/ThreadHookUtils.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.thread;
2 |
3 | import android.util.Log;
4 |
5 | import de.robv.android.xposed.DexposedBridge;
6 | import de.robv.android.xposed.XC_MethodHook;
7 | import de.robv.android.xposed.XposedBridge;
8 | import de.robv.android.xposed.XposedHelpers;
9 |
10 | public class ThreadHookUtils {
11 |
12 | private static final String TAG = "hook.ThreadHookUtils";
13 |
14 | public static void hook(){
15 | DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
16 | @Override
17 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
18 | super.afterHookedMethod(param);
19 | Thread thread = (Thread) param.thisObject;
20 | Class> clazz = thread.getClass();
21 | if (clazz != Thread.class) {
22 | Log.d(TAG, "found class extend Thread:" + clazz);
23 | DexposedBridge.findAndHookMethod(clazz, "run", new ThreadMethodHook());
24 | }
25 | Log.d(TAG, "Thread: " + thread.getName() + " class:" + thread.getClass() + " is created.");
26 | }
27 | });
28 | DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());
29 | }
30 |
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/utils/BinderHook.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.utils;
2 |
3 | import android.content.Context;
4 | import android.os.IBinder;
5 |
6 | import com.xj.hookdemo.hook.clipboard.ClipboardHookRemoteBinderHandler;
7 |
8 | import java.lang.reflect.Field;
9 | import java.lang.reflect.Method;
10 | import java.lang.reflect.Proxy;
11 | import java.util.Map;
12 |
13 | /**
14 | * Created by xujun on 24/7/2018$ 20:00$.
15 | */
16 | public class BinderHook {
17 |
18 | public static void hookClipboardService(String serviceName) throws Exception{
19 |
20 | //通过反射获取剪切板服务的远程Binder对象
21 | Class serviceManager = Class.forName("android.os.ServiceManager");
22 | Method getServiceMethod = serviceManager.getMethod("getService", String.class);
23 | IBinder remoteBinder = (IBinder) getServiceMethod.invoke(null,serviceName);
24 |
25 | //新建一个我们需要的Binder,动态代理原来的Binder对象
26 | IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
27 | new Class[]{IBinder.class}, new ClipboardHookRemoteBinderHandler(remoteBinder));
28 |
29 | //通过反射获取ServiceManger存储Binder对象的缓存集合,把我们新建的代理Binder放进缓存
30 | Field sCacheField = serviceManager.getDeclaredField("sCache");
31 | sCacheField.setAccessible(true);
32 | Map sCache = (Map) sCacheField.get(null);
33 | sCache.put(Context.CLIPBOARD_SERVICE, hookBinder);
34 |
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion 30
6 | defaultConfig {
7 | applicationId "com.xj.hookdemo"
8 | minSdkVersion 21
9 | targetSdkVersion 30
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | compileOptions {
21 | sourceCompatibility JavaVersion.VERSION_1_8
22 | targetCompatibility JavaVersion.VERSION_1_8
23 | }
24 | }
25 |
26 | dependencies {
27 | implementation fileTree(dir: 'libs', include: ['*.jar'])
28 | implementation 'com.android.support:appcompat-v7:26.+'
29 | implementation 'com.android.support.constraint:constraint-layout:1.1.2'
30 | testImplementation 'junit:junit:4.12'
31 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
33 | // implementation 'com.github.tiann:epic:0.11.2'
34 | implementation 'me.weishu:epic:0.11.1'
35 |
36 | def rxjava2Version = "2.2.14"
37 | def rxandroid2Version = "2.1.1"
38 | implementation "io.reactivex.rxjava2:rxjava:$rxjava2Version"
39 | implementation "io.reactivex.rxjava2:rxandroid:$rxandroid2Version"
40 | implementation "com.github.akarnokd:rxjava2-extensions:0.20.10"
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/clipboard/ClipboardHookRemoteBinderHandler.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.clipboard;
2 |
3 | import android.os.IBinder;
4 | import android.util.Log;
5 |
6 | import java.lang.reflect.InvocationHandler;
7 | import java.lang.reflect.Method;
8 | import java.lang.reflect.Proxy;
9 |
10 | /**
11 | * @author xujun on 16/7/2018.
12 | */
13 | public class ClipboardHookRemoteBinderHandler implements InvocationHandler {
14 |
15 | private IBinder remoteBinder;
16 | private Class iInterface;
17 | private Class stubClass;
18 |
19 | public ClipboardHookRemoteBinderHandler(IBinder remoteBinder) {
20 | this.remoteBinder = remoteBinder;
21 | try {
22 | this.iInterface = Class.forName("android.content.IClipboard");
23 | this.stubClass = Class.forName("android.content.IClipboard$Stub");
24 | } catch (Exception e) {
25 | e.printStackTrace();
26 | }
27 | }
28 |
29 | @Override
30 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
31 | Log.d("RemoteBinderHandler", method.getName() + "() is invoked");
32 | if ("queryLocalInterface".equals(method.getName())) {
33 | //这里不能拦截具体的服务的方法,因为这是一个远程的Binder,还没有转化为本地Binder对象
34 | //所以先拦截我们所知的queryLocalInterface方法,返回一个本地Binder对象的代理
35 | return Proxy.newProxyInstance(remoteBinder.getClass().getClassLoader(),
36 | new Class[]{this.iInterface},
37 | new ClipboardHookLocalBinderHandler(remoteBinder, stubClass));
38 | }
39 |
40 | return method.invoke(remoteBinder, args);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/clipboard/ClipboardHookLocalBinderHandler.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.clipboard;
2 |
3 | import android.content.ClipData;
4 | import android.os.IBinder;
5 | import android.util.Log;
6 |
7 | import java.lang.reflect.InvocationHandler;
8 | import java.lang.reflect.Method;
9 |
10 | /**
11 | * @author xujun on 16/7/2018.
12 | */
13 | public class ClipboardHookLocalBinderHandler implements InvocationHandler{
14 |
15 | private Object localProxyBinder;
16 |
17 | public ClipboardHookLocalBinderHandler(IBinder remoteBinder, Class> stubClass) {
18 | try {
19 | Method asInterfaceMethod = stubClass.getMethod("asInterface", IBinder.class);
20 | localProxyBinder = asInterfaceMethod.invoke(null, remoteBinder);
21 | } catch (Exception e) {
22 | e.printStackTrace();
23 | }
24 | }
25 |
26 | @Override
27 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
28 | Log.d("LocalBinderHandler", method.getName() + "() is invoked");
29 | String methodName = method.getName();
30 | if ("setPrimaryClip".equals(methodName)) {
31 | //这里对setPrimaryClip()进行了拦截
32 | int argsLength = args.length;
33 | if (argsLength >= 2 && args[0] instanceof ClipData) {
34 | ClipData data = (ClipData) args[0];
35 | String text = data.getItemAt(0).getText().toString();
36 | text += " -- Hooked by me";
37 | args[0] = ClipData.newPlainText(data.getDescription().getLabel(), text);
38 | }
39 | }
40 |
41 | return method.invoke(localProxyBinder, args);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_test_hook_start.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
16 |
22 |
23 |
29 |
30 |
36 |
37 |
42 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/IActivityManagerHandler.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Intent;
5 | import android.util.Log;
6 |
7 | import com.xj.hookdemo.BuildConfig;
8 | import com.xj.hookdemo.activityhook.EmptyActivity;
9 | import com.xj.hookdemo.hook.HookHelper;
10 |
11 | import java.lang.reflect.InvocationHandler;
12 | import java.lang.reflect.Method;
13 |
14 | /**
15 | * @author xujun on 17/7/2018.
16 | */
17 | public class IActivityManagerHandler implements InvocationHandler {
18 |
19 | private static final String TAG = "IActivityManagerHandler";
20 | Object mBase;
21 |
22 | public IActivityManagerHandler(Object base) {
23 | mBase = base;
24 | }
25 |
26 | @Override
27 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
28 | if ("startActivity".equals(method.getName())) {
29 | Intent raw;
30 | int index = 0;
31 | for (int i = 0; i < args.length; i++) {
32 | if (args[i] instanceof Intent) {
33 | index = i;
34 | break;
35 | }
36 | }
37 | raw = (Intent) args[index];
38 | Intent newIntent = new Intent();
39 | String stubPackage = BuildConfig.APPLICATION_ID;
40 | ComponentName componentName = new ComponentName(stubPackage, EmptyActivity.class.getName());
41 | newIntent.setComponent(componentName);
42 | newIntent.putExtra(HookHelper.EXTRA_TARGET_INTENT, raw);
43 | args[index] = newIntent;
44 | Log.d(TAG, "hook success");
45 | return method.invoke(mBase, args);
46 |
47 | }
48 | return method.invoke(mBase, args);
49 | }
50 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
23 |
24 |
30 |
37 |
38 |
45 |
46 |
47 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/ActivityThreadHandlerCallback.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Intent;
5 | import android.os.Handler;
6 | import android.os.Message;
7 | import android.util.Log;
8 |
9 | import com.xj.hookdemo.hook.HookHelper;
10 |
11 | import java.lang.reflect.Field;
12 |
13 | /**
14 | * @author xujun on 17/7/2018.
15 | */
16 | public class ActivityThreadHandlerCallback implements Handler.Callback {
17 |
18 | private static final String TAG = "ActivityThreadHandlerCa";
19 | Handler mBase;
20 |
21 | public ActivityThreadHandlerCallback(Handler base) {
22 | mBase = base;
23 | }
24 | @Override
25 | public boolean handleMessage(Message msg) {
26 | Log.d(TAG, "handleMessage: msg=" +msg);
27 | switch (msg.what) {
28 | // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
29 | case 100:
30 | handleLaunchActivity(msg);
31 | Log.d(TAG, "handleMessage: msg=" +msg);
32 | break;
33 | }
34 |
35 | // mBase.handleMessage(msg);
36 | return false;
37 | }
38 |
39 | private void handleLaunchActivity(Message msg) {
40 | Object obj = msg.obj;
41 | try {
42 | // 把替身恢复成真身
43 | Field intent = obj.getClass().getDeclaredField("intent");
44 | intent.setAccessible(true);
45 | Intent raw = (Intent) intent.get(obj);
46 | Intent target = raw.getParcelableExtra(HookHelper.EXTRA_TARGET_INTENT);
47 | if(target!=null){
48 | ComponentName component = target.getComponent();
49 | raw.setComponent(component);
50 | Log.i(TAG, "handleLaunchActivity: component=" +component);
51 | }
52 |
53 | } catch (NoSuchFieldException e) {
54 | e.printStackTrace();
55 | } catch (IllegalAccessException e) {
56 | e.printStackTrace();
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TestStartActivityNoRegister.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.View;
8 |
9 | import com.xj.hookdemo.App;
10 | import com.xj.hookdemo.R;
11 | import com.xj.hookdemo.hook.activity.AMSHookUtil;
12 |
13 | public class TestStartActivityNoRegister extends AppCompatActivity {
14 |
15 | @Override
16 | protected void attachBaseContext(Context newBase) {
17 | super.attachBaseContext(newBase);
18 |
19 |
20 | }
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_test_start_no_register);
26 | }
27 |
28 | public void onButtonClick(View v) {
29 | switch (v.getId()) {
30 | case R.id.btn_1:
31 | App.reset();
32 | try {
33 | AMSHookUtil.hookActivity(this, false);
34 | } catch (Exception e) {
35 | e.printStackTrace();
36 | }
37 | startActivity(new Intent(this, TargetAppCompatActivity.class));
38 | break;
39 | case R.id.btn_2:
40 | App.reset();
41 | try {
42 | // HookHelper.hookActivityThreadHandler();
43 | // HookHelper.hookActivityManagerNative();
44 | AMSHookUtil.hookActivity(this, false);
45 | } catch (Exception e) {
46 | e.printStackTrace();
47 | }
48 | startActivity(new Intent(this, TargetActivity.class));
49 | break;
50 | case R.id.btn_3:
51 | App.reset();
52 | startActivity(new Intent(this, TargetAppCompatActivity.class));
53 | break;
54 |
55 | default:
56 | break;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/AMSHookInvocationHandler.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Intent;
5 | import android.util.Log;
6 |
7 | import java.lang.reflect.InvocationHandler;
8 | import java.lang.reflect.Method;
9 |
10 | /**
11 | * @author xujun
12 | * @time 4/8/2018 10:29.
13 | */
14 | public class AMSHookInvocationHandler implements InvocationHandler {
15 |
16 | public static final String ORIGINALLY_INTENT = "originallyIntent";
17 | private Object mAmsObj;
18 | private String mPackageName;
19 | private String cls;
20 |
21 | public AMSHookInvocationHandler(Object amsObj, String packageName, String cls) {
22 | this.mAmsObj = amsObj;
23 | this.mPackageName = packageName;
24 | this.cls = cls;
25 | }
26 |
27 | @Override
28 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
29 | // 对 startActivity进行Hook
30 | if (method.getName().equals("startActivity")) {
31 | int index = 0;
32 | // 找到我们启动时的intent
33 | for (int i = 0; i < args.length; i++) {
34 | if (args[i] instanceof Intent) {
35 | index = i;
36 | break;
37 | }
38 | }
39 |
40 | // 取出在真实的Intent
41 | Intent originallyIntent = (Intent) args[index];
42 | Log.i("AMSHookUtil", "AMSHookInvocationHandler:" + originallyIntent.getComponent()
43 | .getClassName());
44 | // 自己伪造一个配置文件已注册过的Activity Intent
45 | Intent proxyIntent = new Intent();
46 | // 因为我们调用的Activity没有注册,所以这里我们先偷偷换成已注册。使用一个假的Intent
47 | ComponentName componentName = new ComponentName(mPackageName, cls);
48 | proxyIntent.setComponent(componentName);
49 | // 在这里把未注册的Intent先存起来 一会儿我们需要在Handle里取出来用
50 | proxyIntent.putExtra(ORIGINALLY_INTENT, originallyIntent);
51 | args[index] = proxyIntent;
52 | }
53 | return method.invoke(mAmsObj, args);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/ActivityProxyInstrumentation.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.app.Activity;
4 | import android.app.Instrumentation;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.os.IBinder;
9 | import android.util.Log;
10 |
11 | import java.lang.reflect.Method;
12 |
13 | /**
14 | * @author xujun on 16/7/2018.
15 | */
16 | public class ActivityProxyInstrumentation extends Instrumentation {
17 |
18 | private static final String TAG = "ActivityProxyInstrumentation";
19 |
20 | // ActivityThread中原始的对象, 保存起来
21 | Instrumentation mBase;
22 |
23 | public ActivityProxyInstrumentation(Instrumentation base) {
24 | mBase = base;
25 | }
26 |
27 | public ActivityResult execStartActivity(
28 | Context who, IBinder contextThread, IBinder token, Activity target,
29 | Intent intent, int requestCode, Bundle options) {
30 |
31 | // Hook之前, 可以输出你想要的!
32 | Log.d(TAG,"xxxx: 执行了startActivity, 参数如下: " + "who = [" + who + "], " +
33 | "contextThread = [" + contextThread + "], token = [" + token + "], " +
34 | "target = [" + target + "], intent = [" + intent +
35 | "], requestCode = [" + requestCode + "], options = [" + options + "]");
36 |
37 | // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
38 | // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
39 | try {
40 | Method execStartActivity = Instrumentation.class.getDeclaredMethod(
41 | "execStartActivity",
42 | Context.class, IBinder.class, IBinder.class, Activity.class,
43 | Intent.class, int.class, Bundle.class);
44 | execStartActivity.setAccessible(true);
45 | return (ActivityResult) execStartActivity.invoke(mBase, who,
46 | contextThread, token, target, intent, requestCode, options);
47 | } catch (Exception e) {
48 | // rom修改了 需要手动适配
49 | throw new RuntimeException("do not support!!! pls adapt it");
50 | }
51 | }
52 |
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/ApplicationInstrumentation.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.app.Activity;
4 | import android.app.Instrumentation;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.os.IBinder;
9 | import android.util.Log;
10 |
11 | import java.lang.reflect.Method;
12 |
13 | /**
14 | * @author xujun on 16/7/2018.
15 | */
16 | public class ApplicationInstrumentation extends Instrumentation {
17 |
18 | private static final String TAG = "ApplicationInstrumentation";
19 |
20 | // ActivityThread中原始的对象, 保存起来
21 | Instrumentation mBase;
22 |
23 | public ApplicationInstrumentation(Instrumentation base) {
24 | mBase = base;
25 | }
26 |
27 | public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token,
28 | Activity target, Intent intent, int requestCode,
29 | Bundle options) {
30 |
31 | // Hook之前, 可以输出你想要的!
32 | Log.d(TAG, "xxxx: 执行了startActivity, 参数如下: " + "who = [" + who + "], " + "contextThread = " +
33 | "" + "" + "[" + contextThread + "], token = [" + token + "], " + "target = [" +
34 | target + "], intent = [" + intent + "], requestCode = [" + requestCode + "], " +
35 | "options = " + "[" + options + "]");
36 |
37 | // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了.
38 | // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
39 | try {
40 | Method execStartActivity = Instrumentation.class.getDeclaredMethod
41 | ("execStartActivity", Context.class, IBinder.class, IBinder.class, Activity
42 | .class, Intent.class, int.class, Bundle.class);
43 | execStartActivity.setAccessible(true);
44 | return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token,
45 | target, intent, requestCode, options);
46 | } catch (Exception e) {
47 | // rom修改了 需要手动适配
48 | throw new RuntimeException("do not support!!! pls adapt it");
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TestHookStartActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.view.View;
8 |
9 | import com.xj.hookdemo.App;
10 | import com.xj.hookdemo.R;
11 | import com.xj.hookdemo.hook.HookHelper;
12 |
13 | public class TestHookStartActivity extends AppCompatActivity {
14 |
15 | @Override
16 | protected void attachBaseContext(Context newBase) {
17 | super.attachBaseContext(newBase);
18 | }
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 |
24 | setContentView(R.layout.activity_test_hook_start);
25 | }
26 |
27 | public void onButtonClick(View view){
28 | switch (view.getId()){
29 | case R.id.btn_1:
30 | App.reset();
31 | try {
32 | HookHelper.replaceInstrumentation(this);
33 | } catch (Exception e) {
34 | e.printStackTrace();
35 | }
36 | startActivity(new Intent(this,TestActivityStart.class));
37 | break;
38 | case R.id.btn_2:
39 | App.reset();
40 | try {
41 | HookHelper.attachContext();
42 | } catch (Exception e) {
43 | e.printStackTrace();
44 | }
45 | Intent intent = new Intent(this, TestActivityStart.class);
46 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
47 | getApplicationContext().startActivity(intent);
48 | break;
49 | case R.id.btn_3:
50 | App.reset();
51 | try {
52 | HookHelper.hookAMS();
53 | } catch (Exception e) {
54 | e.printStackTrace();
55 | }
56 | startActivity(new Intent(this,TestActivityStart.class));
57 | break;
58 |
59 | case R.id.btn_4:
60 | App.reset();
61 | startActivity(new Intent(this,TestStartActivityNoRegister.class));
62 | break;
63 |
64 |
65 | case R.id.btn_resetHook:
66 | App.reset();
67 | break;
68 |
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/rxjava/Rx2Utils.kt:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.rxjava
2 |
3 | import android.util.Log
4 | import hu.akarnokd.rxjava2.debug.RxJavaAssemblyException
5 | import io.reactivex.plugins.RxJavaPlugins
6 |
7 |
8 | object Rx2Utils {
9 |
10 | private const val TAG = "Rx2Utils"
11 |
12 | /**
13 | * 设置全局的 onErrorHandler。
14 | */
15 | fun setRxOnErrorHandler() {
16 | RxJavaPlugins.setErrorHandler { throwable: Throwable ->
17 | val assembled = RxJavaAssemblyException.find(throwable)
18 | if (assembled != null) {
19 | Log.e(TAG, assembled.stacktrace())
20 | } else {
21 | throwable.printStackTrace()
22 | }
23 | }
24 | }
25 |
26 | fun buildStackTrace(): String? {
27 | val b = StringBuilder()
28 | val es = Thread.currentThread().stackTrace
29 | b.append("RxJavaAssemblyException: assembled\r\n")
30 | for (e in es) {
31 | if (filter(e)) {
32 | b.append("at ").append(e).append("\r\n")
33 | }
34 | }
35 | return b.toString()
36 | }
37 |
38 | /**
39 | * Filters out irrelevant stacktrace entries.
40 | * @param e the stacktrace element
41 | * @return true if the element may pass
42 | */
43 | private fun filter(e: StackTraceElement): Boolean {
44 | // ignore bridge methods
45 | if (e.lineNumber == 1) {
46 | return false
47 | }
48 | val cn = e.className
49 | if (cn.contains("java.lang.Thread")) {
50 | return false
51 | }
52 |
53 | // ignore JUnit elements
54 | if (cn.contains("junit.runner")
55 | || cn.contains("org.junit.internal")
56 | || cn.contains("junit4.runner")) {
57 | return false
58 | }
59 |
60 | // ignore reflective accessors
61 | if (cn.contains("java.lang.reflect")
62 | || cn.contains("sun.reflect")) {
63 | return false
64 | }
65 |
66 | // ignore RxJavaAssemblyException itself
67 | if (cn.contains(".RxJavaAssemblyException")) {
68 | return false
69 | }
70 |
71 | // the shims injecting the error
72 | return if (cn.contains("OnAssembly")
73 | || cn.contains("RxJavaAssemblyTracking")
74 | || cn.contains("RxJavaPlugins")) {
75 | false
76 | } else true
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/notification/NotificationHookHelper.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.notification;
2 |
3 | import android.app.NotificationManager;
4 | import android.content.Context;
5 | import android.util.Log;
6 | import android.widget.Toast;
7 |
8 | import java.lang.reflect.Field;
9 | import java.lang.reflect.InvocationHandler;
10 | import java.lang.reflect.Method;
11 | import java.lang.reflect.Proxy;
12 |
13 | /**
14 | * Created by xujun on 4/8/2018$ 10:28$.
15 | */
16 | public class NotificationHookHelper {
17 |
18 | private static final String TAG = "NotificationHookHelper";
19 |
20 | public static void hookNotificationManager(final Context context) throws Exception {
21 | NotificationManager notificationManager = (NotificationManager) context.getSystemService
22 | (Context.NOTIFICATION_SERVICE);
23 |
24 | Method getService = NotificationManager.class.getDeclaredMethod("getService");
25 | getService.setAccessible(true);
26 | // 第一步:得到系统的 sService
27 | final Object sOriginService = getService.invoke(notificationManager);
28 |
29 | Class iNotiMngClz = Class.forName("android.app.INotificationManager");
30 | // 第二步:得到我们的动态代理对象
31 | Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
32 | Class[]{iNotiMngClz}, new InvocationHandler() {
33 |
34 | @Override
35 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
36 | Log.d(TAG, "invoke(). method:" + method);
37 | String name = method.getName();
38 | Log.d(TAG, "invoke: name=" + name);
39 | if (args != null && args.length > 0) {
40 | for (Object arg : args) {
41 | Log.d(TAG, "invoke: arg=" + arg);
42 | }
43 | }
44 | Toast.makeText(context.getApplicationContext(), "检测到有人发通知了", Toast.LENGTH_SHORT)
45 | .show();
46 | // 操作交由 sOriginService 处理,不拦截通知
47 | return method.invoke(sOriginService, args);
48 | // 拦截通知,什么也不做
49 | // return null;
50 | // 或者是根据通知的 Tag 和 ID 进行筛选
51 | }
52 | });
53 | // 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的sService
54 | Field sServiceField = NotificationManager.class.getDeclaredField("sService");
55 | sServiceField.setAccessible(true);
56 | sServiceField.set(notificationManager, proxyNotiMng);
57 |
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo;
2 |
3 | import android.app.Activity;
4 | import android.app.PendingIntent;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.graphics.Bitmap;
8 | import android.graphics.BitmapFactory;
9 | import android.os.Bundle;
10 | import android.support.v7.app.AppCompatActivity;
11 | import android.view.View;
12 |
13 | import com.xj.hookdemo.activityhook.TestClipboardActivity;
14 | import com.xj.hookdemo.activityhook.TestHookStartActivity;
15 | import com.xj.hookdemo.hook.notification.NotificationHookHelper;
16 | import com.xj.hookdemo.rxjava.RxJavaHookActivity;
17 | import com.xj.hookdemo.thread.TestThreadHookActivity;
18 |
19 | import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
20 |
21 | import hu.akarnokd.rxjava2.debug.RxJavaAssemblyTracking;
22 |
23 | public class MainActivity extends AppCompatActivity {
24 |
25 | @Override
26 | protected void attachBaseContext(Context newBase) {
27 | super.attachBaseContext(newBase);
28 |
29 | }
30 |
31 | @Override
32 | protected void onCreate(Bundle savedInstanceState) {
33 | super.onCreate(savedInstanceState);
34 | setContentView(R.layout.activity_main);
35 | RxJavaAssemblyTracking.enable();
36 | }
37 |
38 | public void onButtonClick(View view) {
39 | switch (view.getId()) {
40 | case R.id.btn_1:
41 | jump(this, TestOnClickActivity.class);
42 | break;
43 | case R.id.btn_2:
44 | jump(this, TestHookStartActivity.class);
45 |
46 | break;
47 | case R.id.btn_3:
48 | try {
49 | NotificationHookHelper.hookNotificationManager(this);
50 | } catch (Exception e) {
51 | e.printStackTrace();
52 | }
53 | testNotification();
54 | break;
55 | case R.id.btn_4:
56 | jump(this, TestClipboardActivity.class);
57 | break;
58 | case R.id.btn_5:
59 | jump(this, TestThreadHookActivity.class);
60 | break;
61 |
62 | case R.id.btn_rx:
63 | jump(this, RxJavaHookActivity.class);
64 | break;
65 |
66 | }
67 |
68 |
69 | }
70 |
71 | private void testNotification() {
72 | Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable
73 | .ic_launcher_background);
74 | Intent intent = new Intent(MainActivity.this, TestNotificationActivity.class);
75 | PendingIntent pendingIntent = PendingIntent.getActivity(MainActivity.this, 0, intent,
76 | FLAG_UPDATE_CURRENT);
77 | NotificationHelper.notification(MainActivity.this, bitmap, R.mipmap.ic_launcher, "title",
78 | "content", "subText", 1, pendingIntent);
79 |
80 | }
81 |
82 | public static void jump(Context context, Class clz) {
83 | Intent intent = new Intent(context, clz);
84 | if (false == (context instanceof Activity)) {
85 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
86 | }
87 | context.startActivity(intent);
88 |
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/HookResetUtils.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook;
2 |
3 | import android.os.Build;
4 | import android.os.Handler;
5 |
6 | import java.lang.reflect.Field;
7 |
8 | /**
9 | * Created by xujun on 6/8/2018$ 11:04$.
10 | */
11 | public class HookResetUtils {
12 |
13 | public static void resetAms(Object amsObj) throws Exception {
14 | Field gDefaultField = null;
15 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
16 | Class> activityManager = Class.forName("android.app.ActivityManager");
17 | gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
18 | } else {
19 | Class> activityManagerNativeClass = Class.forName("android.app" +
20 | ".ActivityManagerNative");
21 | gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
22 | }
23 | gDefaultField.setAccessible(true);
24 | Object gDefaultObj = gDefaultField.get(null); //所有静态对象的反射可以通过传null获取。如果是实列必须传实例
25 | Class> singletonClazz = Class.forName("android.util.Singleton");
26 | Field amsField = singletonClazz.getDeclaredField("mInstance");
27 | amsField.setAccessible(true);
28 | amsField.set(gDefaultObj, amsObj);
29 | }
30 |
31 | public static void resetActivityLaunch(Object mH) throws Exception {
32 | Class> activityThreadClazz = Class.forName("android.app.ActivityThread");
33 | Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField
34 | ("sCurrentActivityThread");
35 | sCurrentActivityThreadField.setAccessible(true);
36 | Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null);
37 | Field mHField = activityThreadClazz.getDeclaredField("mH");
38 | mHField.setAccessible(true);
39 | Field callBackField = Handler.class.getDeclaredField("mCallback");
40 | callBackField.setAccessible(true);
41 | callBackField.set(sCurrentActivityThreadObj,mH);
42 | }
43 |
44 | public static Object storeAms() throws Exception {
45 | Field gDefaultField = null;
46 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
47 | Class> activityManager = Class.forName("android.app.ActivityManager");
48 | gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
49 | } else {
50 | Class> activityManagerNativeClass = Class.forName("android.app" +
51 | ".ActivityManagerNative");
52 | gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
53 | }
54 | gDefaultField.setAccessible(true);
55 | Object gDefaultObj = gDefaultField.get(null); //所有静态对象的反射可以通过传null获取。如果是实列必须传实例
56 | Class> singletonClazz = Class.forName("android.util.Singleton");
57 | Field amsField = singletonClazz.getDeclaredField("mInstance");
58 | amsField.setAccessible(true);
59 | return amsField.get(gDefaultObj);
60 |
61 |
62 | }
63 |
64 | public static Handler storeActivityLaunch() throws Exception {
65 | Class> activityThreadClazz = Class.forName("android.app.ActivityThread");
66 | Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField
67 | ("sCurrentActivityThread");
68 | sCurrentActivityThreadField.setAccessible(true);
69 | Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null);
70 | Field mHField = activityThreadClazz.getDeclaredField("mH");
71 | mHField.setAccessible(true);
72 | return (Handler) mHField.get(sCurrentActivityThreadObj);
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/NotificationHelper.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.NotificationChannel;
5 | import android.app.NotificationManager;
6 | import android.app.PendingIntent;
7 | import android.content.Context;
8 | import android.graphics.Bitmap;
9 | import android.os.Build;
10 | import android.support.v4.app.NotificationCompat;
11 | import android.util.Log;
12 |
13 | /**
14 | * @author xujun on 16/7/2018.
15 | */
16 | public class NotificationHelper {
17 |
18 | private static final String TAG = "NotificationHelper";
19 |
20 | public static void notification(Context context, Bitmap bitmap, int smallId,CharSequence title, CharSequence content,
21 | CharSequence SubText, int id, PendingIntent pendingIntent) {
22 | String channelId = "subscribe";
23 | NotificationCompat.Builder builder=null;
24 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
25 | builder = new NotificationCompat.Builder(context,channelId);
26 | }else{
27 | builder = new NotificationCompat.Builder(context);
28 | }
29 |
30 |
31 | //设置小图标
32 | builder.setSmallIcon(smallId);
33 | //设置大图标
34 | builder.setLargeIcon(bitmap);
35 | //设置标题
36 | builder.setContentTitle(title);
37 | //设置通知正文
38 | builder.setContentText("这是正文,当前ID是:" + content);
39 | //设置摘要
40 | builder.setSubText("这是摘要"+SubText);
41 | //设置是否点击消息后自动clean
42 | builder.setAutoCancel(true);
43 | //显示指定文本
44 | builder.setContentInfo("Info");
45 | //与setContentInfo类似,但如果设置了setContentInfo则无效果
46 | //用于当显示了多个相同ID的Notification时,显示消息总数
47 | builder.setNumber(2);
48 | //通知在状态栏显示时的文本
49 | builder.setTicker("在状态栏上显示的文本");
50 | //设置优先级
51 | builder.setPriority(NotificationCompat.PRIORITY_MAX);
52 | //自定义消息时间,以毫秒为单位,当前设置为系统时间
53 | builder.setWhen(System.currentTimeMillis());
54 | //设置为一个正在进行的通知,此时用户无法清除通知
55 | builder.setOngoing(true);
56 | //设置消息的提醒方式,震动提醒:DEFAULT_VIBRATE 声音提醒:NotificationCompat.DEFAULT_SOUND
57 | //三色灯提醒NotificationCompat.DEFAULT_LIGHTS 以上三种方式一起:DEFAULT_ALL
58 | builder.setDefaults(NotificationCompat.DEFAULT_SOUND);
59 | //设置震动方式,延迟零秒,震动一秒,延迟一秒、震动一秒
60 | builder.setVibrate(new long[]{0, 1000, 1000, 1000});
61 |
62 | // Intent intent = new Intent(this, ResultActivity.class);
63 | // PendingIntent pIntent = PendingIntent.getActivity(context, 0, intent, 0);
64 | builder.setContentIntent(pendingIntent);
65 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
66 |
67 |
68 | String channelName = "订阅消息";
69 | int importance = NotificationManager.IMPORTANCE_DEFAULT;
70 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
71 | createNotificationChannel(notificationManager,channelId,channelName,importance);
72 | }
73 | Log.i(TAG, "notification: ");
74 | notificationManager.notify(id, builder.build());
75 | }
76 |
77 |
78 |
79 | @TargetApi(Build.VERSION_CODES.O)
80 | private static void createNotificationChannel(NotificationManager notificationManager,String channelId, String channelName, int importance) {
81 | NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
82 | // NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
83 | notificationManager.createNotificationChannel(channel);
84 | }
85 |
86 |
87 |
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/activityhook/TestClipboardActivity.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.activityhook;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.os.Bundle;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.text.Editable;
9 | import android.text.TextWatcher;
10 | import android.util.Log;
11 | import android.view.View;
12 | import android.view.inputmethod.InputMethodManager;
13 | import android.widget.Button;
14 | import android.widget.EditText;
15 | import android.widget.Toast;
16 |
17 | import com.xj.hookdemo.R;
18 | import com.xj.hookdemo.hook.clipboard.ClipboardHelper;
19 |
20 | public class TestClipboardActivity extends AppCompatActivity implements View.OnClickListener {
21 |
22 | private static final String TAG = "TestClipboardActivity";
23 |
24 | private ClipboardManager mClipboardManager;
25 | /**
26 | * 英俊潇洒
27 | */
28 | private EditText mEt;
29 | /**
30 | * copy
31 | */
32 | private Button mBtn1;
33 | /**
34 | * show
35 | */
36 | private Button mBtn2;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_test_clipboard);
42 | try {
43 | ClipboardHelper.hookClipboardService(this);
44 | } catch (Exception e) {
45 | e.printStackTrace();
46 | }
47 | initView();
48 | mClipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
49 | }
50 |
51 | public void onButtonClick(View view) {
52 | ClipData clip = null;
53 | switch (view.getId()) {
54 | case R.id.btn_1:
55 | String text = mEt.getText().toString().trim();
56 | clip = ClipData.newPlainText("simple text", text);
57 | mClipboardManager.setPrimaryClip(clip);
58 | break;
59 | case R.id.btn_2:
60 |
61 | clip = mClipboardManager.getPrimaryClip();
62 | Toast.makeText(TestClipboardActivity.this, clip.getItemAt(0).getText(), Toast
63 | .LENGTH_SHORT).show();
64 | break;
65 | }
66 | }
67 |
68 | private void initView() {
69 | mEt = (EditText) findViewById(R.id.et);
70 | mBtn1 = (Button) findViewById(R.id.btn_1);
71 | mBtn2 = (Button) findViewById(R.id.btn_2);
72 | mEt.setOnClickListener(new View.OnClickListener() {
73 | @Override
74 | public void onClick(View v) {
75 | InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
76 | boolean isOpen=imm.isActive();
77 | if(!isOpen){
78 | imm.showSoftInput(mEt,0);
79 | }
80 | Log.i(TAG, "beforeTextChanged: isOpen=" +isOpen);
81 | }
82 | });
83 | mEt.addTextChangedListener(new TextWatcher() {
84 | @Override
85 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
86 |
87 | }
88 |
89 | @Override
90 | public void onTextChanged(CharSequence s, int start, int before, int count) {
91 |
92 | }
93 |
94 | @Override
95 | public void afterTextChanged(Editable s) {
96 |
97 | }
98 | });
99 | }
100 |
101 | @Override
102 | public void onClick(View v) {
103 | switch (v.getId()) {
104 | default:
105 | break;
106 | case R.id.btn_1:
107 | break;
108 | case R.id.btn_2:
109 | break;
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/clipboard/ClipboardHelper.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.clipboard;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.os.IBinder;
7 | import android.util.Log;
8 | import android.widget.Toast;
9 |
10 | import java.lang.reflect.Field;
11 | import java.lang.reflect.InvocationHandler;
12 | import java.lang.reflect.Method;
13 | import java.lang.reflect.Proxy;
14 | import java.util.Map;
15 |
16 | /**
17 | * Created by xujun on 4/8/2018$ 10:25$.
18 | */
19 | public class ClipboardHelper {
20 |
21 | private static final String TAG = "ClipboardHelper";
22 |
23 | public static void hookClipboardService(final Context context) throws Exception {
24 | ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
25 | Field mServiceFiled = ClipboardManager.class.getDeclaredField("mService");
26 | mServiceFiled.setAccessible(true);
27 | // 第一步:得到系统的 mService
28 | final Object mService = mServiceFiled.get(clipboardManager);
29 |
30 | // 第二步:初始化动态代理对象
31 | Class aClass = Class.forName("android.content.IClipboard");
32 | Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
33 | Class[]{aClass}, new InvocationHandler() {
34 |
35 | @Override
36 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
37 | Log.d(TAG, "invoke(). method:" + method);
38 | String name = method.getName();
39 | if (args != null && args.length > 0) {
40 | for (Object arg : args) {
41 | Log.d(TAG, "invoke: arg=" + arg);
42 | }
43 | }
44 | if ("setPrimaryClip".equals(name)) {
45 | Object arg = args[0];
46 | if (arg instanceof ClipData) {
47 | ClipData clipData = (ClipData) arg;
48 | int itemCount = clipData.getItemCount();
49 | for (int i = 0; i < itemCount; i++) {
50 | ClipData.Item item = clipData.getItemAt(i);
51 | Log.i(TAG, "invoke: item=" + item);
52 | }
53 | }
54 | Toast.makeText(context, "检测到有人设置粘贴板内容", Toast.LENGTH_SHORT).show();
55 | } else if ("getPrimaryClip".equals(name)) {
56 | Toast.makeText(context, "检测到有人要获取粘贴板的内容", Toast.LENGTH_SHORT).show();
57 | }
58 | // 操作交由 sOriginService 处理,不拦截通知
59 | return method.invoke(mService, args);
60 |
61 | }
62 | });
63 |
64 | // 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的sService
65 | Field sServiceField = ClipboardManager.class.getDeclaredField("mService");
66 | sServiceField.setAccessible(true);
67 | sServiceField.set(clipboardManager, proxyInstance);
68 |
69 | }
70 |
71 | public static void hookClipboardService() throws Exception {
72 |
73 | //通过反射获取剪切板服务的远程Binder对象
74 | Class serviceManager = Class.forName("android.os.ServiceManager");
75 | Method getServiceMethod = serviceManager.getMethod("getService", String.class);
76 | IBinder remoteBinder = (IBinder) getServiceMethod.invoke(null, Context.CLIPBOARD_SERVICE);
77 |
78 | //新建一个我们需要的Binder,动态代理原来的Binder对象
79 | IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
80 | new Class[]{IBinder.class}, new ClipboardHookRemoteBinderHandler(remoteBinder));
81 |
82 | //通过反射获取ServiceManger存储Binder对象的缓存集合,把我们新建的代理Binder放进缓存
83 | Field sCacheField = serviceManager.getDeclaredField("sCache");
84 | sCacheField.setAccessible(true);
85 | Map sCache = (Map) sCacheField.get(null);
86 | sCache.put(Context.CLIPBOARD_SERVICE, hookBinder);
87 |
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/rxjava/RxJavaHookActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.rxjava
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import android.util.Log
6 | import android.view.View
7 | import com.xj.hookdemo.R
8 | import de.robv.android.xposed.DexposedBridge
9 | import de.robv.android.xposed.XC_MethodHook
10 | import io.reactivex.Observable
11 | import io.reactivex.Observer
12 | import io.reactivex.internal.operators.observable.ObservableFromCallable
13 | import io.reactivex.plugins.RxJavaPlugins
14 | import java.util.concurrent.Callable
15 |
16 | class RxJavaHookActivity : AppCompatActivity() {
17 |
18 | private val TAG = "RxJavaHookActivity"
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | setContentView(R.layout.activity_rx_java_hook)
23 | hookRxFromCallable()
24 | Rx2Utils.setRxOnErrorHandler()
25 |
26 | findViewById(R.id.btn_rx_task).setOnClickListener {
27 |
28 | backgroundTask(Callable {
29 | Log.i(TAG, "btn_rx_task: ")
30 | Thread.sleep(30)
31 | return@Callable null
32 | })?.subscribe()
33 |
34 | }
35 |
36 | findViewById(R.id.btn_rx_task_safe).setOnClickListener {
37 | try {
38 | backgroundTask(Callable {
39 | Log.i(TAG, "btn_rx_task: ")
40 | Thread.sleep(30)
41 | return@Callable null
42 | })?.subscribe()
43 | } catch (e: Exception) {
44 | e.printStackTrace()
45 | Log.e(TAG, "onCreate: e is $e")
46 | }
47 |
48 | }
49 | }
50 |
51 | /**
52 | * 创建一个rx的子线程任务Observable
53 | */
54 | private fun backgroundTask(callable: Callable?): Observable? {
55 | return Observable.fromCallable(callable)
56 | .compose(IOMain())
57 | }
58 |
59 | fun hookRxFromCallable() {
60 | // DexposedBridge.findAndHookMethod(ObservableFromCallable::class.java, "subscribeActual", Observer::class.java, RxMethodHook())
61 | DexposedBridge.findAndHookMethod(Observable::class.java, "fromCallable", Callable::class.java, object : XC_MethodHook() {
62 | override fun beforeHookedMethod(param: MethodHookParam?) {
63 | super.beforeHookedMethod(param)
64 | val args = param?.args
65 | args ?: return
66 | val javaClass = args[0].javaClass
67 | Log.i(TAG, "beforeHookedMethod: javaClass is $javaClass")
68 | val callable = args[0] as Callable<*>
69 | if (callable.call() == null) {
70 | val buildStackTrace = Rx2Utils.buildStackTrace()
71 | Log.e(TAG, "beforeHookedMethod: buildStackTrace is $buildStackTrace")
72 | }
73 | }
74 |
75 | override fun afterHookedMethod(param: MethodHookParam?) {
76 | super.afterHookedMethod(param)
77 | }
78 | })
79 | }
80 |
81 | internal class RxMethodHook : XC_MethodHook() {
82 | @Throws(Throwable::class)
83 | override fun beforeHookedMethod(param: MethodHookParam) {
84 | super.beforeHookedMethod(param)
85 | val buildStackTrace = Rx2Utils.buildStackTrace()
86 | val observableFromCallable = param.thisObject as ObservableFromCallable<*>
87 |
88 | val callableFiled = observableFromCallable.javaClass.getDeclaredField("callable")
89 | callableFiled.isAccessible = true
90 | val callable = callableFiled.get(observableFromCallable) as Callable<*>
91 | val call = callable.call()
92 | if (call == null) {
93 | Log.e(TAG, Log.getStackTraceString(Throwable()))
94 | Log.e(TAG, "error, callable#call should not return null, buildStackTrace is $buildStackTrace")
95 | }
96 |
97 |
98 | Log.i(TAG, "beforeHookedMethod declaredField:${callable}")
99 | Log.i(TAG, "beforeHookedMethod method:${param.method}")
100 | Log.i(TAG, "beforeHookedMethod thisObject:${param.thisObject}")
101 | Log.i(TAG, "beforeHookedMethod args:${param.args}")
102 | Log.i(TAG, "beforeHookedMethod result:${param.result}")
103 | Log.i(TAG, "beforeHookedMethod throwable:${param.throwable}")
104 | }
105 |
106 | @Throws(Throwable::class)
107 | override fun afterHookedMethod(param: MethodHookParam) {
108 | super.afterHookedMethod(param)
109 | val observableFromCallable = param.thisObject as ObservableFromCallable<*>
110 |
111 | Log.i(TAG, "afterHookedMethod method:${param.method}")
112 | Log.i(TAG, "afterHookedMethod thisObject:${param.thisObject}")
113 | Log.i(TAG, "afterHookedMethod args:${param.args}")
114 | Log.i(TAG, "afterHookedMethod result:${param.result}")
115 | Log.i(TAG, "afterHookedMethod throwable:${param.throwable}")
116 | }
117 |
118 | companion object {
119 | private const val TAG = "RxJavaHookActivity"
120 | }
121 | }
122 |
123 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/activity/AMSHookUtil.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook.activity;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.ActivityInfo;
7 | import android.content.pm.PackageInfo;
8 | import android.content.pm.PackageManager;
9 | import android.os.Build;
10 | import android.os.Handler;
11 | import android.os.Message;
12 | import android.util.Log;
13 |
14 | import java.lang.reflect.Field;
15 | import java.lang.reflect.InvocationHandler;
16 | import java.lang.reflect.InvocationTargetException;
17 | import java.lang.reflect.Method;
18 | import java.lang.reflect.Proxy;
19 |
20 | /**
21 | * @author xujun on 17/7/2018.
22 | */
23 | public class AMSHookUtil {
24 |
25 | public static final String ORIGINALLY_INTENT = AMSHookInvocationHandler.ORIGINALLY_INTENT;
26 | private static final String TAG = "AMSHookUtil";
27 |
28 | /**
29 | * 这里我们通过反射获取到AMS的代理本地代理对象
30 | * Hook以后动态串改Intent为已注册的来躲避检测
31 | *
32 | * @param context 上下文
33 | * @param isAppCompatActivity 是否是 AppCompatActivity
34 | */
35 | public static void hookActivity(Context context, boolean isAppCompatActivity) {
36 | if (context == null) {
37 | return;
38 | }
39 | try {
40 | // hook AMS
41 | hookAMS(context);
42 | // 在 activity launch 的时候欺骗 AMS
43 | hookLaunchActivity(context, isAppCompatActivity);
44 | } catch (Exception e) {
45 | e.printStackTrace();
46 | }
47 | }
48 |
49 | private static String getHostClzName(Context context, String pmName) {
50 | PackageInfo packageInfo = null;
51 | try {
52 | packageInfo = context.getPackageManager().getPackageInfo(pmName, PackageManager
53 | .GET_ACTIVITIES);
54 | } catch (PackageManager.NameNotFoundException e) {
55 | e.printStackTrace();
56 | return "";
57 | }
58 | ActivityInfo[] activities = packageInfo.activities;
59 | if (activities == null || activities.length == 0) {
60 | return "";
61 | }
62 | ActivityInfo activityInfo = activities[0];
63 | return activityInfo.name;
64 |
65 | }
66 |
67 | private static String getPMName(Context context) {
68 | // 获取当前进程已经注册的 activity
69 | Context applicationContext = context.getApplicationContext();
70 | return applicationContext.getPackageName();
71 | }
72 |
73 | private static void hookAMS(Context context) throws ClassNotFoundException,
74 | NoSuchFieldException, IllegalAccessException {
75 | // 第一步, API 26 以后,hook android.app.ActivityManager.IActivityManagerSingleton,
76 | // API 25 以前,hook android.app.ActivityManagerNative.gDefault
77 | Field gDefaultField = null;
78 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
79 | Class> activityManager = Class.forName("android.app.ActivityManager");
80 | gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
81 | } else {
82 | Class> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
83 | gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
84 | }
85 | gDefaultField.setAccessible(true);
86 | Object gDefaultObj = gDefaultField.get(null); //所有静态对象的反射可以通过传null获取。如果是实列必须传实例
87 | Class> singletonClazz = Class.forName("android.util.Singleton");
88 | Field amsField = singletonClazz.getDeclaredField("mInstance");
89 | amsField.setAccessible(true);
90 | Object amsObj = amsField.get(gDefaultObj);
91 |
92 | //
93 | String pmName = getPMName(context);
94 | String hostClzName = getHostClzName(context, pmName);
95 |
96 | // 第二步,获取我们的代理对象,这里因为是接口,所以我们使用动态代理的方式
97 | amsObj = Proxy.newProxyInstance(context.getClass().getClassLoader(), amsObj.getClass()
98 | .getInterfaces(), new AMSHookInvocationHandler(amsObj, pmName, hostClzName));
99 |
100 | // 第三步:设置为我们的代理对象
101 | amsField.set(gDefaultObj, amsObj);
102 | }
103 |
104 | /**
105 | *
106 | * @param context
107 | * @param isAppCompatActivity 表示是否是 AppCompatActivity
108 | * @throws Exception
109 | */
110 | private static void hookLaunchActivity(Context context, boolean isAppCompatActivity) throws
111 | Exception {
112 | Class> activityThreadClazz = Class.forName("android.app.ActivityThread");
113 | Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
114 | sCurrentActivityThreadField.setAccessible(true);
115 | Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null);
116 |
117 | Field mHField = activityThreadClazz.getDeclaredField("mH");
118 | mHField.setAccessible(true);
119 | Handler mH = (Handler) mHField.get(sCurrentActivityThreadObj);
120 | Field callBackField = Handler.class.getDeclaredField("mCallback");
121 | callBackField.setAccessible(true);
122 | callBackField.set(mH, new ActivityThreadHandlerCallBack(context, isAppCompatActivity));
123 | }
124 |
125 | public static class ActivityThreadHandlerCallBack implements Handler.Callback {
126 |
127 | private final boolean mIsAppCompatActivity;
128 | private final Context mContext;
129 |
130 | public ActivityThreadHandlerCallBack(Context context, boolean isAppCompatActivity) {
131 | mIsAppCompatActivity = isAppCompatActivity;
132 | mContext = context;
133 | }
134 |
135 | @Override
136 | public boolean handleMessage(Message msg) {
137 | int LAUNCH_ACTIVITY = 0;
138 | try {
139 | Class> clazz = Class.forName("android.app.ActivityThread$H");
140 | Field field = clazz.getField("LAUNCH_ACTIVITY");
141 | LAUNCH_ACTIVITY = field.getInt(null);
142 | } catch (Exception e) {
143 | }
144 | if (msg.what == LAUNCH_ACTIVITY) {
145 | handleLaunchActivity(mContext, msg, mIsAppCompatActivity);
146 | }
147 | return false;
148 | }
149 | }
150 |
151 | private static void handleLaunchActivity(Context context, Message msg, boolean
152 | isAppCompatActivity) {
153 | try {
154 | Object obj = msg.obj;
155 | Field intentField = obj.getClass().getDeclaredField("intent");
156 | intentField.setAccessible(true);
157 | Intent proxyIntent = (Intent) intentField.get(obj);
158 | //拿到之前真实要被启动的Intent 然后把Intent换掉
159 | Intent originallyIntent = proxyIntent.getParcelableExtra(ORIGINALLY_INTENT);
160 | if (originallyIntent == null) {
161 | return;
162 | }
163 | proxyIntent.setComponent(originallyIntent.getComponent());
164 |
165 | Log.e(TAG, "handleLaunchActivity:" + originallyIntent.getComponent().getClassName());
166 |
167 | // 如果不需要兼容 AppCompatActivity
168 | if (!isAppCompatActivity) {
169 | return;
170 | }
171 |
172 | //兼容AppCompatActivity
173 | hookPM(context);
174 | } catch (Exception e) {
175 | e.printStackTrace();
176 | }
177 | }
178 |
179 | private static void hookPM(Context context) throws ClassNotFoundException,
180 | NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
181 | InvocationTargetException {
182 | String pmName = getPMName(context);
183 | String hostClzName = getHostClzName(context, pmName);
184 |
185 | Class> forName = Class.forName("android.app.ActivityThread");
186 | Field field = forName.getDeclaredField("sCurrentActivityThread");
187 | field.setAccessible(true);
188 | Object activityThread = field.get(null);
189 | Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager");
190 | Object iPackageManager = getPackageManager.invoke(activityThread);
191 | PackageManagerHandler handler = new PackageManagerHandler(iPackageManager, pmName, hostClzName);
192 | Class> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
193 | Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
194 | Class>[]{iPackageManagerIntercept}, handler);
195 | // 获取 sPackageManager 属性
196 | Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager");
197 | iPackageManagerField.setAccessible(true);
198 | iPackageManagerField.set(activityThread, proxy);
199 | }
200 |
201 | private static class PackageManagerHandler implements InvocationHandler {
202 | private final String mPmName;
203 | private final String mHostClzName;
204 | private Object mActivityManagerObject;
205 |
206 | PackageManagerHandler(Object mActivityManagerObject, String pmName, String hostClzName) {
207 | this.mActivityManagerObject = mActivityManagerObject;
208 | mPmName = pmName;
209 | mHostClzName = hostClzName;
210 | }
211 |
212 | @Override
213 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
214 | if (method.getName().equals("getActivityInfo")) {
215 | ComponentName componentName = new ComponentName(mPmName, mHostClzName);
216 | args[0] = componentName;
217 | }
218 | return method.invoke(mActivityManagerObject, args);
219 | }
220 | }
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xj/hookdemo/hook/HookHelper.java:
--------------------------------------------------------------------------------
1 | package com.xj.hookdemo.hook;
2 |
3 | import android.app.Activity;
4 | import android.app.Instrumentation;
5 | import android.content.Context;
6 | import android.content.pm.PackageManager;
7 | import android.os.Build;
8 | import android.os.Handler;
9 | import android.util.Log;
10 | import android.view.View;
11 |
12 | import com.xj.hookdemo.hook.activity.AMSInvocationHandler;
13 | import com.xj.hookdemo.hook.activity.ActivityProxyInstrumentation;
14 | import com.xj.hookdemo.hook.activity.ActivityThreadHandlerCallback;
15 | import com.xj.hookdemo.hook.activity.ApplicationInstrumentation;
16 | import com.xj.hookdemo.hook.activity.IActivityManagerHandler;
17 | import com.xj.hookdemo.hook.activity.PMSHandler;
18 |
19 | import java.lang.reflect.Field;
20 | import java.lang.reflect.InvocationHandler;
21 | import java.lang.reflect.Method;
22 | import java.lang.reflect.Proxy;
23 |
24 | /**
25 | * @author xujun on 16/7/2018.
26 | */
27 | public class HookHelper {
28 |
29 | public static final String EXTRA_TARGET_INTENT = "extra_target_intent";
30 | private static final String TAG = "HookHelper";
31 |
32 |
33 | public static void attachContext() throws Exception {
34 | Log.i(TAG, "attachContext: ");
35 | // 先获取到当前的ActivityThread对象
36 | Class> activityThreadClass = Class.forName("android.app.ActivityThread");
37 | Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
38 | currentActivityThreadMethod.setAccessible(true);
39 | //currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数
40 | Object currentActivityThread = currentActivityThreadMethod.invoke(null);
41 |
42 | // 拿到原始的 mInstrumentation字段
43 | Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
44 | mInstrumentationField.setAccessible(true);
45 | Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
46 | // 创建代理对象
47 | Instrumentation evilInstrumentation = new ApplicationInstrumentation(mInstrumentation);
48 | // 偷梁换柱
49 | mInstrumentationField.set(currentActivityThread, evilInstrumentation);
50 | }
51 |
52 | public static void hookAMS() throws Exception {
53 | int sdkInt = Build.VERSION.SDK_INT;
54 | Log.d(TAG, "hookAMS: sdkInt=" + sdkInt);
55 | if (sdkInt >= Build.VERSION_CODES.O) {
56 | hookAMSAfter26();
57 | } else {
58 | hookAmsBefore26();
59 | }
60 | }
61 |
62 | public static void hookAmsBefore26() throws Exception {
63 | // 第一步:获取 IActivityManagerSingleton
64 | Class> forName = Class.forName("android.app.ActivityManagerNative");
65 | Field defaultField = forName.getDeclaredField("gDefault");
66 | defaultField.setAccessible(true);
67 | Object defaultValue = defaultField.get(null);
68 |
69 | Class> forName2 = Class.forName("android.util.Singleton");
70 | Field instanceField = forName2.getDeclaredField("mInstance");
71 | instanceField.setAccessible(true);
72 | Object iActivityManagerObject = instanceField.get(defaultValue);
73 |
74 | // 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
75 | Class> iActivity = Class.forName("android.app.IActivityManager");
76 | InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
77 | Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class>[]{iActivity}, handler);
78 |
79 | // 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
80 | instanceField.set(defaultValue, proxy);
81 | }
82 |
83 | public static void hookAMSAfter26() throws Exception {
84 | // 第一步:获取 IActivityManagerSingleton
85 | Class> aClass = Class.forName("android.app.ActivityManager");
86 | Field declaredField = aClass.getDeclaredField("IActivityManagerSingleton");
87 | declaredField.setAccessible(true);
88 | Object value = declaredField.get(null);
89 |
90 | Class> singletonClz = Class.forName("android.util.Singleton");
91 | Field instanceField = singletonClz.getDeclaredField("mInstance");
92 | instanceField.setAccessible(true);
93 | Object iActivityManagerObject = instanceField.get(value);
94 |
95 | // 第二步:获取我们的代理对象,这里因为 IActivityManager 是接口,我们使用动态代理的方式
96 | Class> iActivity = Class.forName("android.app.IActivityManager");
97 | InvocationHandler handler = new AMSInvocationHandler(iActivityManagerObject);
98 | Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
99 | Class>[]{iActivity}, handler);
100 |
101 | // 第三步:偷梁换柱,将我们的 proxy 替换原来的对象
102 | instanceField.set(value, proxy);
103 |
104 | }
105 |
106 |
107 |
108 | public static void hookOnClickListener(View view) throws Exception {
109 | // 第一步:反射得到 ListenerInfo 对象
110 | Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
111 | getListenerInfo.setAccessible(true);
112 | Object listenerInfo = getListenerInfo.invoke(view);
113 | // 第二步:得到原始的 OnClickListener事件方法
114 | Class> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
115 | Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
116 | mOnClickListener.setAccessible(true);
117 | View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
118 | // 第三步:用Hook代理类 替换原始的 OnClickListener
119 | View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
120 | mOnClickListener.set(listenerInfo, hookedOnClickListener);
121 | }
122 |
123 | public static void replaceInstrumentation(Activity activity) throws Exception {
124 | Class> k = Activity.class;
125 | //通过Activity.class 拿到 mInstrumentation字段
126 | Field field = k.getDeclaredField("mInstrumentation");
127 | field.setAccessible(true);
128 | //根据activity内mInstrumentation字段 获取Instrumentation对象
129 | Instrumentation instrumentation = (Instrumentation) field.get(activity);
130 | //创建代理对象
131 | Instrumentation instrumentationProxy = new ActivityProxyInstrumentation(instrumentation);
132 | //进行替换
133 | field.set(activity, instrumentationProxy);
134 | }
135 |
136 | public void hookPMS(Context context) {
137 | try {
138 | // 获取全局的ActivityThread对象
139 | Class> activityThreadClass = Class.forName("android.app.ActivityThread");
140 | Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
141 | Object currentActivityThread = currentActivityThreadMethod.invoke(null);//得到ActivityThread对象
142 |
143 | // 获取ActivityThread里面原始的 sPackageManager
144 | Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
145 |
146 | sPackageManagerField.setAccessible(true);
147 | Object sPackageManager = sPackageManagerField.get(currentActivityThread);
148 |
149 | // 准备好代理对象, 用来替换原始的对象
150 | Class> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
151 | Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
152 | new Class>[]{iPackageManagerInterface},
153 | new PMSHandler(sPackageManager));
154 |
155 | // 1. 替换掉ActivityThread里面的 sPackageManager 字段
156 | sPackageManagerField.set(currentActivityThread, proxy);
157 |
158 | // 2. 替换 ApplicationPackageManager里面的 mPM对象
159 | PackageManager pm = context.getPackageManager();
160 | Field mPmField = pm.getClass().getDeclaredField("mPM");
161 | mPmField.setAccessible(true);
162 | mPmField.set(pm, proxy);
163 | } catch (Exception e) {
164 | e.printStackTrace();
165 | }
166 | }
167 |
168 |
169 | /**
170 | * 主要完成的操作是 "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity"
171 | */
172 | public static void hookActivityManagerNative() throws Exception {
173 | Field gDefaultField =null;
174 | if (Build.VERSION.SDK_INT >= 26) {
175 | Class> activityManager = Class.forName("android.app.ActivityManager");
176 | gDefaultField = activityManager.getDeclaredField("IActivityManagerSingleton");
177 | }else{
178 | Class> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
179 | gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
180 | }
181 | gDefaultField.setAccessible(true);
182 | Object gDefault = gDefaultField.get(null);
183 | // gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段
184 | Class> singleton = Class.forName("android.util.Singleton");
185 | Field mInstanceField = singleton.getDeclaredField("mInstance");
186 | mInstanceField.setAccessible(true);
187 | // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象
188 | Object rawIActivityManager = mInstanceField.get(gDefault);
189 | // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活
190 | Class> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
191 | Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
192 | new Class>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
193 | mInstanceField.set(gDefault, proxy);
194 | }
195 |
196 |
197 | public static void hookActivityThreadHandler() throws Exception {
198 |
199 | // 先获取到当前的ActivityThread对象
200 | Class> activityThreadClass = Class.forName("android.app.ActivityThread");
201 | Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
202 | currentActivityThreadField.setAccessible(true);
203 | Object currentActivityThread = currentActivityThreadField.get(null);
204 |
205 | // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
206 | Field mHField = activityThreadClass.getDeclaredField("mH");
207 | mHField.setAccessible(true);
208 | Handler mH = (Handler) mHField.get(currentActivityThread);
209 | Field mCallBackField = Handler.class.getDeclaredField("mCallback");
210 | mCallBackField.setAccessible(true);
211 |
212 | mCallBackField.set(mH, new ActivityThreadHandlerCallback(mH));
213 |
214 | }
215 |
216 |
217 |
218 | }
219 |
--------------------------------------------------------------------------------