8 classes to get plug-in - Service implementation scheme

write in front

This article is original, please indicate the address in the form of a link for reprint: http://kymjs.com/code/2016/05/22/01
Dynamically load a Service into the application, which also uses the same camouflage as Activity to deceive the system to identify plan. 

Continued from the previous article: 8 classes to get plug-in - Activity implementation plan 
This article mainly introduces how to run the service in the apk that is not installed in the Android plug-in development. Different from the solution I mentioned two years ago ( running the Service in the apk that is not installed ), the solution implemented this time has no restrictions at all. The plug-in apk can be a completely independent application without special syntax. Revise.

Android plug-in

Service loading process

The same as the dynamic loading principle of Activity, first of all, we need to talk about the startup and loading process of Service. The main process is as follows:

Android Plugin 2

Service The startup is  Activity similar, and ActivityManagerServicethe methods inside will be called eventually.

  • Then there is startServiceLocked()safety monitoring;
  • After the security verification is completed, scheduleCreateService()prepare to createService
  • scheduleServiceArgs()message again
  • The sent message is eventually  ActivityThread.Callbackprocessed in .Handle

After clearly Servicestarting the whole set of processes, it is found that although it is very similar to the starting process mentioned in the previous article Activity, the approach of Activity cannot be used, because Instrumentationthis class is not used at all.
And like in Activity, we can't override the verification method startServiceLocked() to tamper with the system verification, because it runs in another system process system_server.
Finally, there is another problem. Unlike Activity, Service can start multiple instances. If the same Service is executed, the  onCreate()method will not be called again.

Replace the service creation process of the system

Although there is no way Instrumentationto create Serviceit through, we still have a way to replace the system creation process.
First find out where the service object newcame from, check the source code to know that in the last step , many message types ActivityThread.Callbackare Handlesent, including: CREATE_SERVICE, SERVICE_ARGS, BIND_SERVICE, etc... Not only the creation of the service, but also the life cycle of the Activity The method is also called in this callback.
In the CREATE_SERVICE message, a handleCreateService(CreateServiceData data)method called is called, and the main code is:

privatevoidhandleCreateService(CreateServiceDatadata){  
	LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo); 
	Service service = null;
	try {
	 java.lang.ClassLoader cl = packageInfo.getClassLoader();
	service = (Service) cl.loadClass(data.info.name).newInstance();
	} catch (Exception e) {
	}
	
	Application app = packageInfo.makeApplication(false, mInstrumentation);
    service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault());
    service.onCreate();
    mServices.put(data.token, service);
    try {
    ActivityManagerNative.getDefault().serviceDoneExecuting(data.token, 0, 0, 0);
    } catch (RemoteException e) {
    }
}

可以看到,其实Service也是一个普通的类,在这里就是系统new出来并执行了他的onCreate()方法。
所以我们就可以通过替换掉这个 callback 类,并修改其逻辑如果是 CREATE_SERVICE 这条消息,就执行我们自己的Service创建逻辑。
而我们自己的逻辑,就通过判断,如果正在加载的 service 是一个插件 service 就替换ClassLoader为插件 classloader,加载出来的类一切照原宿主service的流程走一遍,包括那些attach()onCreate()方法,都手动调用一遍。
替换方法依旧是通过反射,找到原本ActivityThread类中的mH这个类。

Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(currentActivityThread);

Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);

//修改它的callback为我们的,从而HOOK掉
ActivityThreadHandlerCallback callback = new ActivityThreadHandlerCallback(mH);
mCallBackField.set(mH, callback);

真的是要吐槽一下 Android 源码,里面充斥着各种奇葩命名,比如这个 mH,它其实是一个 Handle,但是它的类名就一个字母,一个大写的 H,所以他的对象叫 mH。 然后还有,前一个 ActivityInfo 类型的变量叫 aInfo,后面又出现一个 ApplicationInfo 的对象也叫 aInfo,然后时不时还来个 ai,你也不知道到底是啥还得再翻回去找它的类型。
OK,回正题,替换完 callback 后,创建 Service 就可以由我们自己的方法来执行了。但是还有一个问题,就是onCreate不会多次调用的问题,因此我们同时还要修改handleMessage()的逻辑,如果是 SERVICE_ARGS 或者 BIND_SERVICE 这两个消息,则首先进行一次判断,如果传入的插件 service 是个没有创建过的,那么就需要再次运行handleCreateService()方法去创建一次。

@Override
publicbooleanhandleMessage(Messagemsg){
    switch (msg.what) {
    case 114: //CREATE_SERVICE
        if (!handleCreateService(msg.obj)) {
            mOldHandle.handleMessage(msg);
        }
        break;
    case 115: //SERVICE_ARGS
        handleBindService(msg.obj);
        mOldHandle.handleMessage(msg);
        break;
    case 121: //BIND_SERVICE
        handleBindService(msg.obj);
        mOldHandle.handleMessage(msg);
        break;
    }
    return true;
}

/**
 * startService时调用,如果插件Service是首次启动,则首先执行创建
 *
 * @param data BindServiceData对象
 */
privatevoidhandleBindService(Objectdata){
    ServiceStruct struct = pluginServiceMap.get(IActivityManagerHook.currentClazzName);
    //如果这个插件service没有启动过
    if (struct == null) {
        //本来这里应该是传一个CreateServiceData对象,但是由于本方法实际使用的只有CreateServiceData.token
        //这个token在BindServiceData以及ServiceArgsData中有同名field,所以这里偷个懒,直接传递了
        handleCreateService(data);
    }
}

踩坑与爬坑

如果你照着上面的思路实现了整个插件化,你会发现其实还有两个巨大的坑:

  • 插件 service 虽然创建了,但是如果启动了多个插件 service,那么除了最后一次启动的那个 service,其他插件 service 的onCreate()以外的其他生命周期方法一个都没有调用。
  • 插件 service 不会调用onDestroy()方法。

首先解决第一个问题,生命周期方法。之前说过,每个生命周期方法其实也是通过这个 handle 来处理的。找到相应的消息事件:SERVICE_ARGS、BIND_SERVICE、STOP_SERVICE,发现这三个事件调用的方法都有一句共同的代码:Service s = mServices.get(data.token);
原来所有创建过的 service 都会被加入到一个 map 中(这个 map 在 4.0 以前是HashMap,4.0 以后是ArrayMap),在需要使用的时候就从这个 map 中根据 key 也就是 token 对象来读取,如果读不到,就不会调用生命周期方法了。
再翻回之前的 service 创建的代码handleCreateService(),那句mServices.put(data.token, service);原来就是做这个用的。同样也解释了为什么其他 service 不会调用生命周期方法了,因为 map 的值都被覆盖了嘛。 那么简单,这个 key 值 token 我们自己来创建并加入到里面就行了。

The second pit onDestroy() is not executed. After repeated tests, it is found that the actual problem is that the message with the STOP_SERVICE logo is not sent. The specific reason is unknown, and the guess may be that the security check failed. The solution is also very simple. Since the system has not sent it, just send the message manually once.
Find the source of all messages sent - ActivityManagerService, then it is very simple, through the dynamic proxy, you can replace the method we are concerned about.
Find two methods related to destroy, named: stopServiceToken()and unbindService(). When these two methods are executed, call doServiceDestroy()yourself to send a message manually. Then when the other side receives this message, it executes the plug-inonDestroy()

publicvoiddoServiceDestroy(){
    Message msg = mH.obtainMessage();
    msg.what = ActivityThreadHandlerCallback.PLUGIN_STOP_SERVICE;
    mH.sendMessage(msg);
}

privatevoidhandleCreateService(CreateServiceDatadata){  
	switch (msg.what) {
    case 116: //STOP_SERVICE
    case PLUGIN_STOP_SERVICE:
        if (!handleStopService()) {
            mOldHandle.handleMessage(msg);
        }
        break;
    }
    return true;
} 

/**
 * destroy策略,如果是最后一个service,则停止真实Service
 */
privatebooleanhandleStopService(){
    ServiceStruct struct = pluginServiceMap.get(IActivityManagerHook.currentClazzName);
    if (struct != null) {
        pluginServiceMap.remove(IActivityManagerHook.currentClazzName);
        if (pluginServiceMap.size() == 0) {
            return false;
        } else {
            struct.service.onDestroy();
            return true;
        }
    }
    return false;
}

Dynamic loading of resources and so files

Activity In this way, the sums  in the dynamically loaded uninstalled APK Serviceare all solved. Looking back, only 6 classes are needed in total, so why do you say that 8 classes are used for plug-in, because there are two types for processing Dynamic loading of resources and so files.
Let's talk about the so file first. In fact, it DexClassLoadersupports dynamic loading natively, but why the solib we passed in is not loaded, or because of permissions. The SD card on the Android phone does not have executable permissions, so we must copy the so file to the storage area in the application package, whether it is getFilesDir()or is getCacheDir()a directory with executable permissions, when constructing the plug- DexClassLoaderin, the third You can pass in a path with executable permissions as a parameter. 
The resource is even simpler. Since we only need to dynamically load an apk, the problem of plug-in resource conflict is not involved at all, and only one method is needed:

publicvoidloadRes(Contextcontext,StringresPath)throwsException{
    assetManager = AssetManager.class.newInstance();
    Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
    addAssetPath.invoke(assetManager, resPath);
    //在插件的Activity中替换掉原本的resource就可以了
    resources = new Resources(assetManager, context.getResources().getDisplayMetrics(),
            context.getResources().getConfiguration());
}

end

There are no tidbits at the end~ 
It's just 8 simple categories, do you still have any questions?

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326712230&siteId=291194637