From d89122c1d797f3c0c5be620cb6f65b3192522991 Mon Sep 17 00:00:00 2001 From: tiann <923551233@qq.com> Date: Wed, 11 May 2016 12:19:52 +0800 Subject: [PATCH 01/11] service pluginial complete --- service-management/.gitignore | 1 + service-management/build.gradle | 41 ++++ service-management/proguard-rules.pro | 17 ++ .../app/ApplicationTest.java | 13 ++ .../src/main/AndroidManifest.xml | 21 ++ .../service_management/app/MainActivity.java | 51 +++++ .../service_management/app/ProxyService.java | 39 ++++ .../app/ServiceManager.java | 211 ++++++++++++++++++ .../app/UPFApplication.java | 45 ++++ .../upf/service_management/app/Utils.java | 61 +++++ .../app/hook/AMSHookHelper.java | 79 +++++++ .../hook/BaseDexClassLoaderHookHelper.java | 59 +++++ .../app/hook/IActivityManagerHandler.java | 86 +++++++ .../src/main/res/layout/main.xml | 30 +++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../src/main/res/values/strings.xml | 3 + .../src/main/res/values/styles.xml | 4 + settings.gradle | 2 +- 21 files changed, 762 insertions(+), 1 deletion(-) create mode 100644 service-management/.gitignore create mode 100644 service-management/build.gradle create mode 100644 service-management/proguard-rules.pro create mode 100644 service-management/src/androidTest/java/com/weishu/upf/service_management/app/ApplicationTest.java create mode 100644 service-management/src/main/AndroidManifest.xml create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/MainActivity.java create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/ProxyService.java create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/ServiceManager.java create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/UPFApplication.java create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/Utils.java create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/hook/AMSHookHelper.java create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/hook/BaseDexClassLoaderHookHelper.java create mode 100644 service-management/src/main/java/com/weishu/upf/service_management/app/hook/IActivityManagerHandler.java create mode 100644 service-management/src/main/res/layout/main.xml create mode 100644 service-management/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 service-management/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 service-management/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 service-management/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 service-management/src/main/res/values/strings.xml create mode 100644 service-management/src/main/res/values/styles.xml diff --git a/service-management/.gitignore b/service-management/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/service-management/.gitignore @@ -0,0 +1 @@ +/build diff --git a/service-management/build.gradle b/service-management/build.gradle new file mode 100644 index 0000000..59c8e71 --- /dev/null +++ b/service-management/build.gradle @@ -0,0 +1,41 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.2.3' + } +} +apply plugin: 'com.android.application' + +repositories { + jcenter() +} + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.weishu.upf.service_management.app" + minSdkVersion 23 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_6 + targetCompatibility JavaVersion.VERSION_1_6 + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/service-management/proguard-rules.pro b/service-management/proguard-rules.pro new file mode 100644 index 0000000..10fada3 --- /dev/null +++ b/service-management/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/weishu/dev/env/android-sdk-macosx/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/service-management/src/androidTest/java/com/weishu/upf/service_management/app/ApplicationTest.java b/service-management/src/androidTest/java/com/weishu/upf/service_management/app/ApplicationTest.java new file mode 100644 index 0000000..252b3e7 --- /dev/null +++ b/service-management/src/androidTest/java/com/weishu/upf/service_management/app/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.weishu.upf.service_management.app; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} diff --git a/service-management/src/main/AndroidManifest.xml b/service-management/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e41d9b1 --- /dev/null +++ b/service-management/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/MainActivity.java b/service-management/src/main/java/com/weishu/upf/service_management/app/MainActivity.java new file mode 100644 index 0000000..e243be8 --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/MainActivity.java @@ -0,0 +1,51 @@ +package com.weishu.upf.service_management.app; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +/** + * @author weishu + * @date 16/5/9 + */ +public class MainActivity extends Activity implements View.OnClickListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.main); + findViewById(R.id.startService1).setOnClickListener(this); + findViewById(R.id.startService2).setOnClickListener(this); + findViewById(R.id.stopService1).setOnClickListener(this); + findViewById(R.id.stopService2).setOnClickListener(this); + + } + + @Override + public void onClick(View v) { + switch (v.getId()) { + case R.id.startService1: + startService(new Intent().setComponent( + new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService1"))); + + break; + case R.id.startService2: + startService(new Intent().setComponent( + new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService2"))); + break; + + case R.id.stopService1: + stopService(new Intent().setComponent( + new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService1"))); + break; + + case R.id.stopService2: + stopService(new Intent().setComponent( + new ComponentName("com.weishu.upf.demo.app2", "com.weishu.upf.demo.app2.TargetService2"))); + break; + } + } +} diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/ProxyService.java b/service-management/src/main/java/com/weishu/upf/service_management/app/ProxyService.java new file mode 100644 index 0000000..df53b09 --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/ProxyService.java @@ -0,0 +1,39 @@ +package com.weishu.upf.service_management.app; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.util.Log; + +/** + * @author weishu + * @date 16/5/10 + */ +public class ProxyService extends Service { + + private static final String TAG = "ProxyService"; + + @Override + public void onCreate() { + Log.d(TAG, "onCreate() called"); + super.onCreate(); + } + + @Override + public void onStart(Intent intent, int startId) { + Log.d(TAG, "onStart() called with " + "intent = [" + intent + "], startId = [" + startId + "]"); + ServiceManager.getInstance().onStart(intent, startId); + super.onStart(intent, startId); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onDestroy() { + Log.d(TAG, "onDestroy() called"); + super.onDestroy(); + } +} diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/ServiceManager.java b/service-management/src/main/java/com/weishu/upf/service_management/app/ServiceManager.java new file mode 100644 index 0000000..a964fe2 --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/ServiceManager.java @@ -0,0 +1,211 @@ +package com.weishu.upf.service_management.app; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.Binder; +import android.os.IBinder; +import android.util.Log; + +import com.weishu.upf.service_management.app.hook.AMSHookHelper; + +/** + * @author weishu + * @date 16/5/10 + */ +public final class ServiceManager { + + private static final String TAG = "ServiceManager"; + + private static volatile ServiceManager sInstance; + + private Map mServiceMap = new HashMap(); + + // 存储插件的Service信息 + private Map mServiceInfoMap = new HashMap(); + + public synchronized static ServiceManager getInstance() { + if (sInstance == null) { + sInstance = new ServiceManager(); + } + return sInstance; + } + + /** + * 启动某个插件Service; 如果Service还没有启动, 那么会创建新的插件Service + * @param proxyIntent + * @param startId + */ + public void onStart(Intent proxyIntent, int startId) { + + Intent targetIntent = proxyIntent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT); + + ServiceInfo serviceInfo = selectPluginService(targetIntent); + + if (serviceInfo == null) { + Log.w(TAG, "can not found service : " + targetIntent.getComponent()); + return; + } + try { + + if (!mServiceMap.containsKey(serviceInfo.name)) { + // service还不存在, 先创建 + proxyCreateService(serviceInfo); + } + + Service service = mServiceMap.get(serviceInfo.name); + service.onStart(targetIntent, startId); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 停止某个插件Service, 当全部的插件Service都停止之后, ProxyService也会停止 + * @param targetIntent + * @return + */ + public int stopService(Intent targetIntent) { + ServiceInfo serviceInfo = selectPluginService(targetIntent); + if (serviceInfo == null) { + Log.w(TAG, "can not found service: " + targetIntent.getComponent()); + return 0; + } + Service service = mServiceMap.get(serviceInfo.name); + if (service == null) { + Log.w(TAG, "can not runnning, are you stopped it multi-times?"); + return 0; + } + service.onDestroy(); + mServiceMap.remove(serviceInfo.name); + if (mServiceMap.isEmpty()) { + // 没有Service了, 这个没有必要存在了 + Log.d(TAG, "service all stopped, stop proxy"); + Context appContext = UPFApplication.getContext(); + appContext.stopService(new Intent().setComponent(new ComponentName(appContext.getPackageName(), ProxyService.class.getName()))); + } + return 1; + } + + /** + * 选择匹配的ServiceInfo + * @param pluginIntent 插件的Intent + * @return + */ + private ServiceInfo selectPluginService(Intent pluginIntent) { + for (ComponentName componentName : mServiceInfoMap.keySet()) { + if (componentName.equals(pluginIntent.getComponent())) { + return mServiceInfoMap.get(componentName); + } + } + return null; + } + + /** + * 通过ActivityThread的handleCreateService方法创建出Service对象 + * @param serviceInfo 插件的ServiceInfo + * @throws Exception + */ + private void proxyCreateService(ServiceInfo serviceInfo) throws Exception { + IBinder token = new Binder(); + + // 创建CreateServiceData对象, 用来传递给ActivityThread的handleCreateService 当作参数 + Class createServiceDataClass = Class.forName("android.app.ActivityThread$CreateServiceData"); + Constructor constructor = createServiceDataClass.getDeclaredConstructor(); + constructor.setAccessible(true); + Object createServiceData = constructor.newInstance(); + + // 写入我们创建的createServiceData的token字段, ActivityThread的handleCreateService用这个作为key存储Service + Field tokenField = createServiceDataClass.getDeclaredField("token"); + tokenField.setAccessible(true); + tokenField.set(createServiceData, token); + + // 写入info对象 + // 这个修改是为了loadClass的时候, LoadedApk会是主程序的ClassLoader, 我们选择Hook BaseDexClassLoader的方式加载插件 + serviceInfo.applicationInfo.packageName = UPFApplication.getContext().getPackageName(); + Field infoField = createServiceDataClass.getDeclaredField("info"); + infoField.setAccessible(true); + infoField.set(createServiceData, serviceInfo); + + // 写入compatInfo字段 + // 获取默认的compatibility配置 + Class compatibilityClass = Class.forName("android.content.res.CompatibilityInfo"); + Field defaultCompatibilityField = compatibilityClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO"); + Object defaultCompatibility = defaultCompatibilityField.get(null); + Field compatInfoField = createServiceDataClass.getDeclaredField("compatInfo"); + compatInfoField.setAccessible(true); + compatInfoField.set(createServiceData, defaultCompatibility); + + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); + Object currentActivityThread = currentActivityThreadMethod.invoke(null); + + // private void handleCreateService(CreateServiceData data) { + Method handleCreateServiceMethod = activityThreadClass.getDeclaredMethod("handleCreateService", createServiceDataClass); + handleCreateServiceMethod.setAccessible(true); + + handleCreateServiceMethod.invoke(currentActivityThread, createServiceData); + + // handleCreateService创建出来的Service对象并没有返回, 而是存储在ActivityThread的mServices字段里面, 这里我们手动把它取出来 + Field mServicesField = activityThreadClass.getDeclaredField("mServices"); + mServicesField.setAccessible(true); + Map mServices = (Map) mServicesField.get(currentActivityThread); + Service service = (Service) mServices.get(token); + + // 获取到之后, 移除这个service, 我们只是借花献佛 + mServices.remove(token); + + // 将此Service存储起来 + mServiceMap.put(serviceInfo.name, service); + } + + /** + * 解析Apk文件中的 , 并存储起来 + * 主要是调用PackageParser类的generateServiceInfo方法 + * @param apkFile 插件对应的apk文件 + * @throws Exception 解析出错或者反射调用出错, 均会抛出异常 + */ + public void preLoadServices(File apkFile) throws Exception { + Class packageParserClass = Class.forName("android.content.pm.PackageParser"); + Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class); + + Object packageParser = packageParserClass.newInstance(); + + // 首先调用parsePackage获取到apk对象对应的Package对象 + Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, PackageManager.GET_RECEIVERS); + + // 读取Package对象里面的services字段 + // 接下来要做的就是根据这个List 获取到Service对应的ServiceInfo + Field servicesField = packageObj.getClass().getDeclaredField("services"); + List services = (List) servicesField.get(packageObj); + + // 调用generateServiceInfo 方法, 把PackageParser.Service转换成ServiceInfo + Class packageParser$ServiceClass = Class.forName("android.content.pm.PackageParser$Service"); + Class packageUserStateClass = Class.forName("android.content.pm.PackageUserState"); + Class userHandler = Class.forName("android.os.UserHandle"); + Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId"); + int userId = (Integer) getCallingUserIdMethod.invoke(null); + Object defaultUserState = packageUserStateClass.newInstance(); + + // 需要调用 android.content.pm.PackageParser#generateActivityInfo(android.content.pm.ActivityInfo, int, android.content.pm.PackageUserState, int) + Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateServiceInfo", + packageParser$ServiceClass, int.class, packageUserStateClass, int.class); + + // 解析出intent对应的Service组件 + for (Object service : services) { + ServiceInfo info = (ServiceInfo) generateReceiverInfo.invoke(packageParser, service, 0, defaultUserState, userId); + mServiceInfoMap.put(new ComponentName(info.packageName, info.name), info); + } + } +} diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/UPFApplication.java b/service-management/src/main/java/com/weishu/upf/service_management/app/UPFApplication.java new file mode 100644 index 0000000..121cee7 --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/UPFApplication.java @@ -0,0 +1,45 @@ +package com.weishu.upf.service_management.app; + +import java.io.File; + +import android.app.Application; +import android.content.Context; + +import com.weishu.upf.service_management.app.hook.AMSHookHelper; +import com.weishu.upf.service_management.app.hook.BaseDexClassLoaderHookHelper; + +/** + * 这个类只是为了方便获取全局Context的. + * + * @author weishu + * @date 16/3/29 + */ +public class UPFApplication extends Application { + + private static Context sContext; + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + sContext = base; + + try { + // 拦截startService, stopService等操作 + AMSHookHelper.hookActivityManagerNative(); + Utils.extractAssets(base, "test.apk"); + File apkFile = getFileStreamPath("test.apk"); + File odexFile = getFileStreamPath("test.odex"); + + // Hook ClassLoader, 让插件中的类能够被成功加载 + BaseDexClassLoaderHookHelper.patchClassLoader(getClassLoader(), apkFile, odexFile); + // 解析插件中的Service组件 + ServiceManager.getInstance().preLoadServices(apkFile); + } catch (Exception e) { + throw new RuntimeException("hook failed"); + } + } + + public static Context getContext() { + return sContext; + } +} diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/Utils.java b/service-management/src/main/java/com/weishu/upf/service_management/app/Utils.java new file mode 100644 index 0000000..0f3284c --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/Utils.java @@ -0,0 +1,61 @@ +package com.weishu.upf.service_management.app; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import android.content.Context; +import android.content.res.AssetManager; + +/** + * @author weishu + * @date 16/3/29 + */ +public class Utils { + + /** + * 把Assets里面得文件复制到 /data/data/files 目录下 + * + * @param context + * @param sourceName + */ + public static void extractAssets(Context context, String sourceName) { + AssetManager am = context.getAssets(); + InputStream is = null; + FileOutputStream fos = null; + try { + is = am.open(sourceName); + File extractFile = context.getFileStreamPath(sourceName); + fos = new FileOutputStream(extractFile); + byte[] buffer = new byte[1024]; + int count = 0; + while ((count = is.read(buffer)) > 0) { + fos.write(buffer, 0, count); + } + fos.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + closeSilently(is); + closeSilently(fos); + } + + } + + // -------------------------------------------------------------------------- + private static void closeSilently(Closeable closeable) { + if (closeable == null) { + return; + } + try { + closeable.close(); + } catch (Throwable e) { + // ignore + } + } + + private static File sBaseDir; + +} diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/hook/AMSHookHelper.java b/service-management/src/main/java/com/weishu/upf/service_management/app/hook/AMSHookHelper.java new file mode 100644 index 0000000..0038372 --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/hook/AMSHookHelper.java @@ -0,0 +1,79 @@ +package com.weishu.upf.service_management.app.hook; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; + +/** + * @author weishu + * @date 16/3/21 + */ +public class AMSHookHelper { + + public static final String EXTRA_TARGET_INTENT = "extra_target_intent"; + + /** + * Hook AMS + *

+ * 主要完成的操作是 "把真正要启动的Activity临时替换为在AndroidManifest.xml中声明的替身Activity" + *

+ * 进而骗过AMS + * + * @throws ClassNotFoundException + * @throws NoSuchMethodException + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws NoSuchFieldException + */ + public static void hookActivityManagerNative() throws ClassNotFoundException, + NoSuchMethodException, InvocationTargetException, + IllegalAccessException, NoSuchFieldException { + + // 17package android.util; + // 18 + // 19/** + // 20 * Singleton helper class for lazily initialization. + // 21 * + // 22 * Modeled after frameworks/base/include/utils/Singleton.h + // 23 * + // 24 * @hide + // 25 */ + // 26public abstract class Singleton { + // 27 private T mInstance; + // 28 + // 29 protected abstract T create(); + // 30 + // 31 public final T get() { + // 32 synchronized (this) { + // 33 if (mInstance == null) { + // 34 mInstance = create(); + // 35 } + // 36 return mInstance; + // 37 } + // 38 } + // 39} + // 40 + + Class activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative"); + + Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault"); + gDefaultField.setAccessible(true); + + Object gDefault = gDefaultField.get(null); + + // gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段 + Class singleton = Class.forName("android.util.Singleton"); + Field mInstanceField = singleton.getDeclaredField("mInstance"); + mInstanceField.setAccessible(true); + + // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象 + Object rawIActivityManager = mInstanceField.get(gDefault); + + // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活 + Class iActivityManagerInterface = Class.forName("android.app.IActivityManager"); + Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), + new Class[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager)); + mInstanceField.set(gDefault, proxy); + } + +} diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/hook/BaseDexClassLoaderHookHelper.java b/service-management/src/main/java/com/weishu/upf/service_management/app/hook/BaseDexClassLoaderHookHelper.java new file mode 100644 index 0000000..286197d --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/hook/BaseDexClassLoaderHookHelper.java @@ -0,0 +1,59 @@ +package com.weishu.upf.service_management.app.hook; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; + +import dalvik.system.DexClassLoader; +import dalvik.system.DexFile; + +/** + * 由于应用程序使用的ClassLoader为PathClassLoader + * 最终继承自 BaseDexClassLoader + * 查看源码得知,这个BaseDexClassLoader加载代码根据一个叫做 + * dexElements的数组进行, 因此我们把包含代码的dex文件插入这个数组 + * 系统的classLoader就能帮助我们找到这个类 + * + * 这个类用来进行对于BaseDexClassLoader的Hook + * 类名太长, 不要吐槽. + * @author weishu + * @date 16/3/28 + */ +public final class BaseDexClassLoaderHookHelper { + + public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile) + throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException { + // 获取 BaseDexClassLoader : pathList + Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList"); + pathListField.setAccessible(true); + Object pathListObj = pathListField.get(cl); + + // 获取 PathList: Element[] dexElements + Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements"); + dexElementArray.setAccessible(true); + Object[] dexElements = (Object[]) dexElementArray.get(pathListObj); + + // Element 类型 + Class elementClass = dexElements.getClass().getComponentType(); + + // 创建一个数组, 用来替换原始的数组 + Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1); + + // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数 + Constructor constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class); + Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)); + + Object[] toAddElementArray = new Object[] { o }; + // 把原始的elements复制进去 + System.arraycopy(dexElements, 0, newElements, 0, dexElements.length); + // 插件的那个element复制进去 + System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); + + // 替换 + dexElementArray.set(pathListObj, newElements); + + } +} diff --git a/service-management/src/main/java/com/weishu/upf/service_management/app/hook/IActivityManagerHandler.java b/service-management/src/main/java/com/weishu/upf/service_management/app/hook/IActivityManagerHandler.java new file mode 100644 index 0000000..e8e39c9 --- /dev/null +++ b/service-management/src/main/java/com/weishu/upf/service_management/app/hook/IActivityManagerHandler.java @@ -0,0 +1,86 @@ +package com.weishu.upf.service_management.app.hook; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +import android.content.ComponentName; +import android.content.Intent; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.weishu.upf.service_management.app.ProxyService; +import com.weishu.upf.service_management.app.ServiceManager; +import com.weishu.upf.service_management.app.UPFApplication; + +/** + * @author weishu + * @dete 16/1/7. + */ +/* package */ class IActivityManagerHandler implements InvocationHandler { + + private static final String TAG = "IActivityManagerHandler"; + + Object mBase; + + public IActivityManagerHandler(Object base) { + mBase = base; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + if ("startService".equals(method.getName())) { + // 只拦截这个方法 + // API 23: + // public ComponentName startService(IApplicationThread caller, Intent service, + // String resolvedType, int userId) throws RemoteException + + // 找到参数里面的第一个Intent 对象 + Pair integerIntentPair = foundFirstIntentOfArgs(args); + Intent newIntent = new Intent(); + + // 代理Service的包名, 也就是我们自己的包名 + String stubPackage = UPFApplication.getContext().getPackageName(); + + // 这里我们把启动的Service替换为ProxyService, 让ProxyService接收生命周期回调 + ComponentName componentName = new ComponentName(stubPackage, ProxyService.class.getName()); + newIntent.setComponent(componentName); + + // 把我们原始要启动的TargetService先存起来 + newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, integerIntentPair.second); + + // 替换掉Intent, 达到欺骗AMS的目的 + args[integerIntentPair.first] = newIntent; + + Log.v(TAG, "hook method startService success"); + return method.invoke(mBase, args); + + } + + // public int stopService(IApplicationThread caller, Intent service, + // String resolvedType, int userId) throws RemoteException + if ("stopService".equals(method.getName())) { + Intent raw = foundFirstIntentOfArgs(args).second; + if (!TextUtils.equals(UPFApplication.getContext().getPackageName(), raw.getComponent().getPackageName())) { + // 插件的intent才做hook + Log.v(TAG, "hook method stopService success"); + return ServiceManager.getInstance().stopService(raw); + } + } + + return method.invoke(mBase, args); + } + + private Pair foundFirstIntentOfArgs(Object... args) { + int index = 0; + + for (int i = 0; i < args.length; i++) { + if (args[i] instanceof Intent) { + index = i; + break; + } + } + return Pair.create(index, (Intent) args[index]); + } +} diff --git a/service-management/src/main/res/layout/main.xml b/service-management/src/main/res/layout/main.xml new file mode 100644 index 0000000..0885e81 --- /dev/null +++ b/service-management/src/main/res/layout/main.xml @@ -0,0 +1,30 @@ + + + +