跳至主要內容

Android 通过 Hook 启动未注册 Activity

JI,XIAOYONG...大约 3 分钟

简介

hook 是钩子的意思,hook 的过程是通过反射、代理等改变系统原有的行为以达到自己的目的。

本文主要是通过 hook android 中的 ActivityManagerService 和 Handler.CallBack,欺骗系统调起 activity 的过程,在调用 startActivity 时将 targetIntent 通过 proxy 伪装为 proxyIntent,等到通过系统验证,正式启动 activity 时,再讲 proxyIntent 恢复为 targetIntent,从而实现调用未在 AndroidManifest.xml 中注册的 activity。

需要注意,本方法只在 Api<26 下有效。具体原因见后面。

具体实现

1.新建 Activity 等

IndexActivity.java用于启动targetIntent

 ((Button)findViewById(R.id.btn1)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //启动未在 AndroidManifest.xml 注册的 activity
                mContext.startActivity(new Intent(mContext,TargetActivity.class));
            }
        });

TargetActivity.javaProxyActivity.java 分别设置对应页面布局setContentView(R.layout.activity_xxx);

HookApplication.java 用于调用 hook 方法

public class HookApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Utils.hookAms(this);
        Utils.hookHandle();
    }
}

AndroidManifest.xml中注册IndexActivityProxyActivity,Application 使用HookApplication

2.Utils.java 实现 hook 具体逻辑

Utils.hookAms() 实现拦截 targetIntent 并发起 proxyIntent,欺骗系统对 activity 是否已注册的验证,其中 proxyIntent 通过proxyIntent.putExtra(TARGET_KEY, targetIntent); 方法携带 targetIntent。

//hookAms() 核心代码
Class hookActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
            //在 api>26 时无此变量:gDefault,该方法失效
            Field gDefault = hookActivityManagerNative.getDeclaredField("gDefault");
            gDefault.setAccessible(true);
            Object object = gDefault.get(null);

            Class hookSingleton = Class.forName("android.util.Singleton");
            Field mInstance = hookSingleton.getDeclaredField("mInstance");
            mInstance.setAccessible(true);

            Object oldAms = mInstance.get(object);

            Class hookIActivityManagerService = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class<?>[]{hookIActivityManagerService},
                    new MAmsInvocationHandler(context,oldAms));

			//将原有的 ActivityManagerService 替换为我们自定义的
            mInstance.set(object,proxy);

MAmsInvocationHandler 里面实现 targetIntent 和 proxy 的转换

//MAmsInvocationHandler 核心代码
public class MAmsInvocationHandler implements InvocationHandler{
  public static final String TARGET_KEY = "targetIntent";
  ...
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startActivity".equals(method.getName())) {
            int index = 0;
            Intent targetIntent = null;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    targetIntent = (Intent) args[i];
                    break;
                }
            }
            if (targetIntent != null) {
                Intent proxyIntent = new Intent(mContext, ProxyActivity.class);
                proxyIntent.putExtra(TARGET_KEY, targetIntent);
                args[index] = proxyIntent;
            }
        }
        return method.invoke(mOldAms,args);
    }
}

至此,已经对 activity.startActivity 做了拦截,所有的 targetIntent 都会被拦截,存储在 proxyIntent 中,以通过系统的检查。

接下来,通过系统检查后,hookHandle()通过重写 Handler.CallBack,对启动 proxyIntent 事件做拦截,使之启动 targetIntent 对应的 Activity。

//hookHandle() 核心代码
Class activityThreadCls = Class.forName("android.app.ActivityThread");
Method currentActivityThread = activityThreadCls.getDeclaredMethod("currentActivityThread");
currentActivityThread.setAccessible(true);

Object activityThread = currentActivityThread.invoke(null);

Field mH = activityThreadCls.getDeclaredField("mH");
mH.setAccessible(true);
Handler handler = (Handler) mH.get(activityThread);
Field callBack = Handler.class.getDeclaredField("mCallback");
callBack.setAccessible(true);
callBack.set(handler, new ActivityThreadHandlerCallBack(handler));

其中ActivityThreadHandlerCallBack 将返回我们自定义的 CallBack 以替换系统的,实现启动 targetIntent 而非 proxyIntent。

//ActivityThreadHandlerCallBack 核心代码
public class ActivityThreadHandlerCallBack implements Handler.Callback{
   @Override
    public boolean handleMessage(Message msg) {
        if (msg.what == 100) {
            handleLaunchActivity(msg);
        }
        mHandler.handleMessage(msg);
        return true;
    }

  	//主要代码,在这里将 proxyIntent 转化为 targetIntent
     private void handleLaunchActivity(Message msg) {
        Object object = msg.obj;
        try {
            Field intent = object.getClass().getDeclaredField("intent");
            intent.setAccessible(true);
            Intent proxyIntent = (Intent) intent.get(object);
            Intent targetIntent = proxyIntent.getParcelableExtra(MAmsInvocationHandler.TARGET_KEY);
            if (targetIntent != null) {
                proxyIntent.setComponent(targetIntent.getComponent());
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

到这里,就实现了启动通过已经注册了的 ProxyActivity 启动未注册 TargetActivity 的全过程。

主要思想是找到系统实现该过程的逻辑,在对应地方通过反射获取到对应变量,插入自己的逻辑,从而达到目的。

附录

上面涉及到的代码路径:

github 源代码路径open in new window

参考了几篇文章,其中较为完整的一篇如下:

Android 插件化系列第(一)篇---Hook 技术之 Activity 的启动过程拦截open in new window

文章标题:《Android 通过 Hook 启动未注册 Activity》
本文作者: JI,XIAOYONG
发布时间: 2018/01/16 00:10:41 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/26eab50a.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8