├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── bolex │ │ └── androidhookstartactivity │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── com │ │ │ └── bolex │ │ │ │ └── androidhookstartactivity │ │ │ │ ├── AMSHook │ │ │ │ ├── AMSHookUtil.java │ │ │ │ └── HookInvocationHandler.java │ │ │ │ ├── HostActivity.java │ │ │ │ └── MainActivity.java │ │ └── other │ │ │ └── packages │ │ │ └── OtherActivity.java │ └── res │ │ ├── layout │ │ ├── activity_host.xml │ │ ├── activity_main.xml │ │ └── activity_other.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── bolex │ └── androidhookstartactivity │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidHookStartActivity 2 | 3 | ## AMSHook 4 | 5 | 两行代码实现动态启动未注册的Activity。 6 | 7 | 8 | 1.application标签里配置一个壳Activity 9 | ``` 10 |         11 | ``` 12 | 13 | 2.注册一下其中this为context 14 | ``` 15 | AMSHookUtil.hookStartActivity(this); 16 | ``` 17 | 18 | 3.以后就可以按照标准的Intent启动为那些未被注册的Activity。 19 | ``` 20 | Intent intent = new Intent(MainActivity.this, OtherActivity.class); 21 | startActivity(intent); 22 | ``` 23 | 24 | 原理详解:http://www.jianshu.com/p/2ad105f54d07 25 | 26 | 27 | ## 更新 28 | 29 | - 1.修复多次hook的问题 30 | - 2.修复Activity无需和包名一致也能启动 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | /.idea/.name 10 | /.idea/compiler.xml 11 | /.idea/copyright/profiles_settings.xml 12 | /.idea/encodings.xml 13 | /.idea/gradle.xml 14 | /.idea/misc.xml 15 | /.idea/modules.xml 16 | /.idea/runConfigurations.xml 17 | /.idea/vcs.xml 18 | /javalib 19 | .idea/codeStyleSettings.xml 20 | .idea/inspectionProfiles/Project_Default.xml 21 | .idea/inspectionProfiles/profiles_settings.xml 22 | projectFilesBackup/.idea/workspace.xml 23 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "com.bolex.androidhookstartactivity" 8 | minSdkVersion 14 9 | targetSdkVersion 25 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 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.1.1' 28 | testCompile 'junit:junit:4.12' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android_sdk\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/bolex/androidhookstartactivity/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.bolex.androidhookstartactivity; 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 | * Instrumentation 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() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.bolex.androidhookstartactivity", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/bolex/androidhookstartactivity/AMSHook/AMSHookUtil.java: -------------------------------------------------------------------------------- 1 | package com.bolex.androidhookstartactivity.AMSHook; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.ActivityInfo; 8 | import android.content.pm.PackageInfo; 9 | import android.content.pm.PackageManager; 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.Method; 17 | import java.lang.reflect.Proxy; 18 | 19 | /** 20 | * Created by Bolex on 2017/6/20. 21 | */ 22 | 23 | public class AMSHookUtil { 24 | 25 | @SuppressLint("StaticFieldLeak") 26 | private static Context sContext; 27 | private static String sPackageName; 28 | private static String sHostClazzName; 29 | private static final String TAG = "AMSHookUtil"; 30 | 31 | /** 32 | * * 这里我们通过反射获取到AMS的代理本地代理对象 33 | * Hook以后动态串改Intent为已注册的来躲避检测 34 | * @param context 上下文 35 | */ 36 | public static void hookStartActivity(Context context) { 37 | if (context == null || sContext != null) { 38 | return; 39 | } 40 | try { 41 | Context applicationContext = context.getApplicationContext(); 42 | sContext = applicationContext; 43 | PackageManager manager = applicationContext.getPackageManager(); 44 | sPackageName = applicationContext.getPackageName(); 45 | PackageInfo packageInfo = manager.getPackageInfo(sPackageName, PackageManager.GET_ACTIVITIES); 46 | ActivityInfo[] activities = packageInfo.activities; 47 | if (activities == null || activities.length == 0) { 48 | return; 49 | } 50 | ActivityInfo activityInfo = activities[0]; 51 | sHostClazzName = activityInfo.name; 52 | Log.e(TAG,"pkgName:" + sPackageName + "\tHostClazzName:" + sHostClazzName); 53 | 54 | Class amnClazz = Class.forName("android.app.ActivityManagerNative"); 55 | Field defaultField = amnClazz.getDeclaredField("gDefault"); 56 | defaultField.setAccessible(true); 57 | Object gDefaultObj = defaultField.get(null); //所有静态对象的反射可以通过传null获取。如果是实列必须传实例 58 | Class singletonClazz = Class.forName("android.util.Singleton"); 59 | Field amsField = singletonClazz.getDeclaredField("mInstance"); 60 | amsField.setAccessible(true); 61 | Object amsObj = amsField.get(gDefaultObj); 62 | 63 | amsObj = Proxy.newProxyInstance(context.getClass().getClassLoader(), 64 | amsObj.getClass().getInterfaces(), 65 | new HookInvocationHandler(amsObj, sPackageName, sHostClazzName)); 66 | amsField.set(gDefaultObj, amsObj); 67 | hookLaunchActivity(); 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | 73 | /** 74 | * hook ActivityThread handle 在这里我们需要替换我们未被注册的Activity Intent 75 | * 76 | * @throws Exception 77 | */ 78 | private static void hookLaunchActivity() throws Exception { 79 | Class activityThreadClazz = Class.forName("android.app.ActivityThread"); 80 | Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread"); 81 | sCurrentActivityThreadField.setAccessible(true); 82 | Object sCurrentActivityThreadObj = sCurrentActivityThreadField.get(null); 83 | Field mHField = activityThreadClazz.getDeclaredField("mH"); 84 | mHField.setAccessible(true); 85 | Handler mH = (Handler) mHField.get(sCurrentActivityThreadObj); 86 | Field callBackField = Handler.class.getDeclaredField("mCallback"); 87 | callBackField.setAccessible(true); 88 | callBackField.set(mH, new ActivityThreadHandlerCallBack()); 89 | } 90 | 91 | private static class ActivityThreadHandlerCallBack implements Handler.Callback { 92 | 93 | @Override 94 | public boolean handleMessage(Message msg) { 95 | int LAUNCH_ACTIVITY = 0; 96 | try { 97 | Class clazz = Class.forName("android.app.ActivityThread$H"); 98 | Field field = clazz.getField("LAUNCH_ACTIVITY"); 99 | LAUNCH_ACTIVITY = field.getInt(null); 100 | } catch (Exception e) { 101 | } 102 | if (msg.what == LAUNCH_ACTIVITY) { 103 | handleLaunchActivity(msg); 104 | } 105 | return false; 106 | } 107 | } 108 | 109 | private static void handleLaunchActivity(Message msg) { 110 | try { 111 | Object obj = msg.obj; 112 | Field intentField = obj.getClass().getDeclaredField("intent"); 113 | intentField.setAccessible(true); 114 | Intent proxyIntent = (Intent) intentField.get(obj); 115 | //拿到之前真实要被启动的Intent 然后把Intent换掉 116 | Intent originallyIntent = proxyIntent.getParcelableExtra("originallyIntent"); 117 | if (originallyIntent == null) { 118 | return; 119 | } 120 | 121 | proxyIntent.setComponent(originallyIntent.getComponent()); 122 | 123 | 124 | Log.e(TAG,"handleLaunchActivity:" + originallyIntent.getComponent().getClassName()); 125 | 126 | //todo:兼容AppCompatActivity 127 | Class forName = Class.forName("android.app.ActivityThread"); 128 | Field field = forName.getDeclaredField("sCurrentActivityThread"); 129 | field.setAccessible(true); 130 | Object activityThread = field.get(null); 131 | Method getPackageManager = activityThread.getClass().getDeclaredMethod("getPackageManager"); 132 | Object iPackageManager = getPackageManager.invoke(activityThread); 133 | PackageManagerHandler handler = new PackageManagerHandler(iPackageManager); 134 | Class iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager"); 135 | Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 136 | new Class[]{iPackageManagerIntercept}, handler); 137 | // 获取 sPackageManager 属性 138 | Field iPackageManagerField = activityThread.getClass().getDeclaredField("sPackageManager"); 139 | iPackageManagerField.setAccessible(true); 140 | iPackageManagerField.set(activityThread, proxy); 141 | } catch (Exception e) { 142 | e.printStackTrace(); 143 | } 144 | } 145 | 146 | 147 | private static class PackageManagerHandler implements InvocationHandler { 148 | private Object mActivityManagerObject; 149 | 150 | PackageManagerHandler(Object mActivityManagerObject) { 151 | this.mActivityManagerObject = mActivityManagerObject; 152 | } 153 | 154 | @Override 155 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 156 | if (method.getName().equals("getActivityInfo")) { 157 | ComponentName componentName = new ComponentName(sPackageName, sHostClazzName); 158 | args[0] = componentName; 159 | } 160 | return method.invoke(mActivityManagerObject, args); 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/bolex/androidhookstartactivity/AMSHook/HookInvocationHandler.java: -------------------------------------------------------------------------------- 1 | package com.bolex.androidhookstartactivity.AMSHook; 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 | public class HookInvocationHandler implements InvocationHandler { 11 | 12 | private Object mAmsObj; 13 | private String mPackageName; 14 | private String cls; 15 | 16 | public HookInvocationHandler(Object amsObj, String packageName, String cls) { 17 | this.mAmsObj = amsObj; 18 | this.mPackageName = packageName; 19 | this.cls = cls; 20 | } 21 | 22 | @Override 23 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 24 | // TODO: 2017/6/20 对 startActivity进行Hook 25 | if (method.getName().equals("startActivity")) { 26 | int index = 0; 27 | // TODO: 2017/6/20 找到我们启动时的intent 28 | for (int i = 0; i < args.length; i++) { 29 | if (args[i] instanceof Intent) { 30 | index = i; 31 | break; 32 | } 33 | } 34 | 35 | 36 | // TODO: 2017/6/20 取出在真实的Intent 37 | Intent originallyIntent = (Intent) args[index]; 38 | Log.i("AMSHookUtil","HookInvocationHandler:" + originallyIntent.getComponent().getClassName()); 39 | // TODO: 2017/6/20 自己伪造一个配置文件已注册过的Activity Intent 40 | Intent proxyIntent = new Intent(); 41 | // TODO: 2017/6/20 因为我们调用的Activity没有注册,所以这里我们先偷偷换成已注册。使用一个假的Intent 42 | ComponentName componentName = new ComponentName(mPackageName, cls); 43 | proxyIntent.setComponent(componentName); 44 | // TODO: 2017/6/20 在这里把未注册的Intent先存起来 一会儿我们需要在Handle里取出来用 45 | proxyIntent.putExtra("originallyIntent", originallyIntent); 46 | args[index] = proxyIntent; 47 | } 48 | return method.invoke(mAmsObj, args); 49 | } 50 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bolex/androidhookstartactivity/HostActivity.java: -------------------------------------------------------------------------------- 1 | package com.bolex.androidhookstartactivity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import com.bolex.androidhookstartactivity.R; 7 | 8 | public class HostActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_host); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/bolex/androidhookstartactivity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.bolex.androidhookstartactivity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.Button; 8 | 9 | import com.bolex.androidhookstartactivity.AMSHook.AMSHookUtil; 10 | 11 | import other.packages.OtherActivity; 12 | 13 | public class MainActivity extends AppCompatActivity { 14 | 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(R.layout.activity_main); 19 | Button start = (Button) findViewById(R.id.start); 20 | 21 | AMSHookUtil.hookStartActivity(this); 22 | start.setOnClickListener(new View.OnClickListener() { 23 | @Override 24 | public void onClick(View v) { 25 | Intent intent = new Intent(MainActivity.this, OtherActivity.class); 26 | startActivity(intent); 27 | } 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/other/packages/OtherActivity.java: -------------------------------------------------------------------------------- 1 | package other.packages; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | 8 | import com.bolex.androidhookstartactivity.MainActivity; 9 | import com.bolex.androidhookstartactivity.R; 10 | 11 | public class OtherActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_other); 17 | 18 | findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() { 19 | @Override 20 | public void onClick(View v) { 21 | startActivity(new Intent(OtherActivity.this,MainActivity.class)); 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_host.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 |