美文网首页
插件化原理之hook系统函数

插件化原理之hook系统函数

作者: 陆元伟 | 来源:发表于2018-04-30 19:24 被阅读0次

    插件化原理之hook系统函数

    插件化主要问题之一是如何startActivity一个未在注册表里面注册的acitivity

    我们都知道开启一个activity是涉及到app进程和系统服务进程的交互过程,其中验证要打开的acitivity是否在清单文件中也是在系统服务进程进行的,那么”如何”欺骗系统服务进程?

    方案一是设置一个代理ProxyActivity,这个ProxyActivity在清单文件中注册过,然后在该ProxyActivity里面注入一个真实的Activity,ProxyActivity所有的生命周期方法里面都回调真实的Activity生命周期方法。
    关于这个方案有个框架实现的挺好的,可以看动态代理DL框架。这个方案调用startyActivity是要特别处理,首先要把真实的activity信息隐藏在intent里面,然后在代理ProxyActivity里面在解析出真正的activity信息并且实例化,然后回调真正的activity生命周期方法。

    DL 的github地址:dynamic-load-apk

    方案二 是直接hook appstartActivity方法
    这里先梳理一下startActivity的逻辑。startActivity虽然只有一行代码,但里面涉及的调用却很复杂,app持有一个AMSBinder引用,在app端最终调用ActivityManagerNative.getDefault().startActivity后,系统服务进程开始做一些准备处理,而ActivityManagerNative.getDefault()是个单例模式,我们可以在这里传递一个注册表里面注册过的activity过去,这样系统服务进程验证的时候就会通过,然后回调给ApplicationThreadNative,ApplicationThreadNative再通过一个内部类H发送消息到ActivityThread,这样消息发送过来的时候我们再换回真实的activity,如此一来系统就会认为这个activity已经被系统验证过了,生命周期的调用和其他acitivity的生命周期方法调用过程一模一样。查看startActivity调用流程时,发现ActivityManagerNative.getDefault()是个单例,我们可以反射重新设置我们自己的代理对象,ActivityThread里面所有AMS发送过来的消息都会通过内部类H发送到主线程,查看Handler的源码我们发现,Hander在处理消息的方法是会先查看内部一个 变量Callback是否存在,如果存在则先处理Callback的方法,

    HandlerdispatchMessage方法源码,我们通常是复写handleMessage,如果我们要拦截哪个方法,我们看一看设置HandlerCallback 并且设置handleMessage返回true,这样就总不会走HandlerhandleMessage方法了。

         public void dispatchMessage(Message msg) {      
            if (msg.callback != null) {          
                      handleCallback(msg);      
                } else {         
                     if (mCallback != null) {             
                            if (mCallback.handleMessage(msg)) {                  
                                    return;           
                               }         
                      }          
                    handleMessage(msg);      
              }
        }
    

    我们可以在ActivityThreadH里面的mCallback里面拦截startActivity发出来的消息,然后在这里把真实的activity替换。

    本文讲解的是第二种方案,当然这种方案要对startActivity具体流程和动态代理有个清楚的认识,不清楚的可以查看我的另两篇博客
    startActivity方法解读
    设计模式之代理模式2

    主要方法如 下

    public void hookSystem() throws Exception{
    
        // 1 加载ActivityManagerNative类信息         
        Class<?> activityManagerNative=Class.forName("android.app.ActivityManagerNative");         
         //2 获取gDefault字段属性         
        Field field= getField(activityManagerNative,"gDefault");         
         //3 获取Singleton 对象        
        Object o= field.get(null);          
        
        //4获取Singleton类信息          
        Class<?> singleton=Class.forName("android.util.Singleton");  
            //5 获取mInstance 字段信息         
         Field field2= getField(singleton,"mInstance");
        //6 获取该第三步对象里面的变量对象         
         Object mInstance=field2.get(o);         
                   
        MyInvocationHandler myInvocationHandler=new MyInvocationHandler(mInstance);          
        //7 生成代理类          
        Object proxy= Proxy.newProxyInstance(mInstance.getClass().getClassLoader(),mInstance.getClass().getInterfaces(),myInvocationHandler);       
         //8 替换Singleton类里面的mInstance属性     
        field2.set(o,proxy);
        
        //9获取ActivityThread类信息  
         activityThreadClass=Class.forName("android.app.ActivityThread");
        
        //10获取mH字段信息,该变量是个Handler对象          
        Field mHField=getField(activityThreadClass,"mH");
        
        //11 ActivityThread类里面有个唯一对象,就是sCurrentActivityThread属性          
        Field sCurrentActivityThread=getField(activityThreadClass,"sCurrentActivityThread");   
        //12 获取到ActivityThread对象
        activityThread=sCurrentActivityThread.get(null);  
         //13 获取ActivityThread对象 mH对象          
        Object mH= mHField.get(activityThread);  
    
        //14 获取Handler类里面的mCallback字段信息         
         Field field1=getField(Handler.class,"mCallback");   
                
         //15 设置mH对面里面的callback为我们自己写的callback  
          field1.set(mH,myCallback);    
      }
    

    第二步我们就获取到ActivityManagerNative类里面的gDefault属性了

    图片1.png

    由于该变量是个static 所以第三步我们调用get传入null参数就可以获取该对象了,然后通过动态代理生成一个代理对象,由于代理对象的方法都会调用我们设置的MyInvocationHandler对象的invoke方法,故我们可以在这里拦截startActivity方法,我们把真实的activity随便替换一个在注册表里面注册过的activity信息传输到AMS那里。

    查看Singleton源代码我们可以知道,该get方法返回的就是create方法创建的对象,也是内部的一个变量,我们只要把这个对象替换成我们的对象就可以了。

    public abstract class Singleton<T> {      
        private T mInstance;    
        protected abstract T create();    
        public final T get() {          
          synchronized (this) {              
               if (mInstance == null) {                  
                       mInstance = create();            
                }             
                 return mInstance;  
           }     
         }  
    }
    

    到第八步的时候我们已经把ActivityManagerNative里面的gDefault变量里面的mInstance变量换成我们自己的代理对象,这样AcitivityManagerNative.getDefault对象也就是我们的代理对象了。

    图片2.png

    12步的时候我们已经获取到当前ActivityThread类的唯一对象,这个和我们在自定义Application返回Application实例一样。

    图片3.png

    13步的我们已经获取到ActivityThread里面mH这个变量

    图片4.png

    15步的时候我们已经把ActivityThread里面的mH对象里面的mCallback设置成我们自己的callback,这样AMS发送给APP的消息,我们这里都能进行拦截。

    MyInvocationHandler类代码如下

    这里我们拦截了APP的startActivity方法,把要开启的Activity信息保存起来,替换一个新注册过的Activity信息放在里面

    class MyInvocationHandler implements InvocationHandler {  
        Object target;  
        public MyInvocationHandler(Object o){              
                this.target=o;         
         }         
        @Override          
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
            //拦截startActivity方法           
            if (method.getName().equals("startActivity")){                     
                for (Object o:args){                     
                   if (o instanceof Intent){ 
                        // intent.setComponent(componentName);                         
                        Intent oldIntent= (Intent) o;                        
                        //把原来的intent的跳转类信息保存起来  //                       
                        Intent intent=new Intent();                                 
                         //替换一个在清单文件中存在的Activity类                          
                        ComponentName componentName=new ComponentName(context,TestAidlActivity.class);  
                        oldIntent.putExtra("realComponentName",oldIntent.getComponent());  
                        oldIntent.setComponent(componentName);  
                        break;                     
                    }                  
                }             
                       
              }             
             return method.invoke(target,args);      
         }   
       }
    

    我们注入H里面的callback

    Handler.Callback myCallback=new Handler.Callback() {  
        @Override     
         public boolean handleMessage(Message msg) {      
           if (msg.what==100){                         
            handleStartActivity(msg);         
         }         
            return false;     
         };  
    };
    

    查看H源码我们知道开启一个activity消息是100.所以我们这里处理msg.what==100的情况,看下面知道所有的生命周期回调都有对应的消息,我们都能进行拦截处理。目前我们暂时处理开启activity的消息

    图片5.png

    这个方法就好理解了,获取msg里面的intent对象,然后把里面的Component设置成我们保持的那个Component信息

    private void handleStartActivity(Message msg){      
         Object activityClientRecord= msg.obj; 
         try {           
            isReplaceOncreate=false;          
            Field field=getField(activityClientRecord.getClass(),"intent");    
             //拿到intent对象。         
            Intent intent= (Intent) field.get(activityClientRecord);  
            ComponentName componentName=intent.getParcelableExtra("realComponentName");           
            String className=componentName.getClassName();   
            Class<?> clazz=Class.forName(className); 
            if (clazz.newInstance() instanceof AppCompatActivity){  
                  isReplaceOncreate=true;          
            }          
            //重新替换过来        
             intent.setComponent(componentName);       
         } catch (Exception e) {         
            e.printStackTrace();     
         }  
    }
    

    Manifest.xml信息如下


    图片6.png

    TestAidi2Activity我并没有在清单文件中注册。运行后打印信息如下


    图片7.png

    所有代码上传到github

    相关文章

      网友评论

          本文标题:插件化原理之hook系统函数

          本文链接:https://www.haomeiwen.com/subject/rfjolftx.html