插件化原理之hook系统函数

插件化原理之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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容