Android Hook动态替换目标Activity(免AndroidManifest注册 )

30 篇文章 0 订阅
本文详细介绍了在Android7.1系统下APP的启动流程,特别是Activity的启动过程,并展示了如何通过反射和动态代理技术实现Activity的Hook,以在不注册的情况下启动目标Activity。通过占坑Activity和替换Intent组件名称,骗过AMS,然后在ActivityThread的Handler中恢复真实Activity,成功启动未注册的目标Activity。
摘要由CSDN通过智能技术生成

Android 7.1 APP 启动流程分析 里面讲解的有Activity的启动流程图,想要使用hook替换目标Activity需要先了解AMS如何创建的Activity。

总体流程

总体的思路就是通过反射,先使用占坑Activity创建activity,然后再替换成目标activity,大致流程如下:

由上图可知

1.通过反射 使用 如下代码设置 MyIActivityManagerHandler.java 到AMS中

        我们先使用占坑的activity替换成我们启动的activity,然后当activity创建完成之后再替换成我们目标的activity。

2.再使用反射获取 ActivityThread.java 设置 MyActivityThreadHandlerCallback,在MyActivityThreadHandlerCallback中替换成自己的目标Activity。

        hook的好处是可以不在AndroidManifest.xml注册我们Activity也能启动我们的Activity。

项目下载地址

 

项目详解

整体的项目目录

├── main
│   ├── AndroidManifest.xml
│   ├── java
│   │   └── com
│   │       └── ccl
│   │           └── demo
│   │               ├── GoalActivity.java   //目标类 ,未注册
│   │               ├── HookUtils.java    //hook工具
│   │               ├── MainActivity.java   //主函数
│   │               ├── MyActivityThreadHandlerCallback.java  //Handler.Callback子类
│   │               ├── MyIActivityManagerHandler.java  //InvocationHandler子类
│   │               └── PitActivity.java  //占坑类 

首先查看AndroidManifest.xml代码,PitActivity是用来天坑的Activity

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ccl.demo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
     <!--占坑类-->
        <activity android:name=".PitActivity"/>  
    </application>

</manifest>

GoalActivity.java 

public class GoalActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("chencl_"," 目标activity GoalActivity onCreate ");
    }
}

PitActivity.java

public class PitActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("chencl_"," 占坑 Activity PitActivity onCreate ");
    }
}

HookUtils.java 主要负责hook的类,hookIActivityManager 方法用于获取 android.util.Singleton 并且将 new MyIActivityManagerHandler(rawIActivityManager)替换到 Proxy.newProxyInstance 中进行动态化代理。hookActivityThreadHandler方法  获取到 android.app.ActivityThread 将MyActivityThreadHandlerCallback 设置进去,在MyActivityThreadHandlerCallback 中替换为我们自己的activity。

public class HookUtils {
    public static final String EXTRA_TARGET_INTENT = "activity";
    /**
     * Hook AMS
     * 主要完成的操作是,代理Hook AMS 在应用进程的本地代理IActivityManager ,
     * 把真正要启动的Activity临时替换为在AndroidManifest.xml 里坑位Activity
     * 进而骗过AMS
     */
    public static void hookIActivityManager() throws Exception {
        Field amSingletonField =null;
        if (Build.VERSION.SDK_INT >= 26) {
            Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
            amSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
        }else{
            Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
            amSingletonField = activityManagerNativeClass.getDeclaredField("gDefault");
        }
        amSingletonField.setAccessible(true);

        Object iamSingletonObj = amSingletonField.get(null);

        // 得到iamSingletonObj ,得到iamSingletonObj 对象里的mInstance 字段值,这个值就是那个需要的单例,
        // 就是AMS 在应用进程的本地代理对象
        Class<?> singleton = Class.forName("android.util.Singleton");
        Field mInstanceField = singleton.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);

        // 原始的 IActivityManager对象
        Object rawIActivityManager = mInstanceField.get(iamSingletonObj);

        // 用动态代理,这里在执行相应方法执行我们的一些逻辑(这里指的是修改Intent 使用坑位Activity ,从而可以越过AMS)
        // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活
        Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class<?>[] { iActivityManagerInterface }, new MyIActivityManagerHandler(rawIActivityManager));
        mInstanceField.set(iamSingletonObj, proxy);
    }
    /**
     * 由于之前我们用替身欺骗了AMS; 现在我们要换回我们真正需要启动的Activity ,不然就真的启动替身了
     * 到最终要启动Activity的时候,会交给ActivityThread 的一个内部类叫做 H 来完成
     * H 会完成这个消息转发,这里对H 的mCallback 进行处理
     */
    public static void hookActivityThreadHandler() throws Exception {

        // 先获取到当前的ActivityThread 对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
        currentActivityThreadField.setAccessible(true);
        Object currentActivityThread = currentActivityThreadField.get(null);

        // 获取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
        mCallBackField.set(mH, new MyActivityThreadHandlerCallback(mH));
    }
}

MyIActivityManagerHandler.java 负责具体的替换过程

class MyIActivityManagerHandler implements InvocationHandler {
    private static final String TAG = "MyIActivityManagerHandler";
    Object mBase;
    public MyIActivityManagerHandler(Object base) {
        mBase = base;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startActivity".equals(method.getName())) {
            // 找到参数里面的第一个Intent 对象
            Intent raw;
            int index = 0;
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];
            Intent newIntent = new Intent();
            // 替身Activity的包名, 也就是我们自己的包名
            String stubPackage = "com.ccl.demo";
            // 这里我们把启动的Activity临时替换为坑位 StubActivity
            ComponentName componentName = new ComponentName(stubPackage, PitActivity.class.getName());
            newIntent.setComponent(componentName);
            // 把我们原始要启动的TargetActivity先存起来
            newIntent.putExtra(HookUtils.EXTRA_TARGET_INTENT, raw);
            // 替换掉Intent, 达到欺骗AMS的目的
            args[index] = newIntent;
            return method.invoke(mBase, args);
        }
        return method.invoke(mBase, args);
    }
}

MyActivityThreadHandlerCallback.java 负责替换为我们目标activity

class MyActivityThreadHandlerCallback implements Handler.Callback {
    Handler mBase;
    public MyActivityThreadHandlerCallback(Handler base) {
        mBase = base;
    }
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            // ActivityThread里面 "LAUNCH_ACTIVITY" 这个字段的值是100
            // 本来使用反射的方式获取最好, 这里为了简便而直接使用硬编码
            case 100:
                handleLaunchIntent(msg);
                break;
        }
        mBase.handleMessage(msg);
        return true;
    }
    private void handleLaunchIntent(Message msg) {
        // 这里简单起见,直接取出TargetActivity;
        Object obj = msg.obj;
        // 根据源码,这个msg.obj 对象是 ActivityClientRecord 类型,修改它的intent字段,恢复目标Activity
        try {
            // 把替身恢复成真身
            Field intent = obj.getClass().getDeclaredField("intent");
            intent.setAccessible(true);
            Intent raw = (Intent) intent.get(obj);
            Intent target = raw.getParcelableExtra(HookUtils.EXTRA_TARGET_INTENT);
            raw.setComponent(target.getComponent());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

MainActivity.java 调用的时候先调用  hookIActivityManager hookActivityThreadHandler

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            //先执行 hookIActivityManager hookActivityThreadHandler
            HookUtils.hookIActivityManager();
            HookUtils.hookActivityThreadHandler();
        } catch (Exception e) {
            Log.d("chencl_",e.toString());
            e.printStackTrace();
        }
        //开启目标Activity   GoalActivity
        startActivity(new Intent(MainActivity.this, GoalActivity.class));
    }
}

执行项目打印的log是 

06-07 17:27:56.230 32029-32029/com.ccl.demo D/chencl_:  目标activity GoalActivity onCreate 

而 GoalActivity 并未在 AndroidManifest.xml 中注册。

 

 

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值