Android Hook实现无清单启动Activity

正常开发中,所有Activity都要在AndroidManifest.xml中进行注册,才可以正常跳转。通过hook,可以绕过系统对activity注册的检测,即使不注册,也可以正常跳转。

整体思路

我们已经实现Activity启动流程的hook,最终采用的方案,是Hook AMS,实现全局startActivity动作的劫持。现在就从这个AMShook为起点,来实现无清单启动Activity

我们既然侦测到了startActivity这个方法的调用,那么自然就可以拿到里面的实参,比如Intent。Intent是跳转意图,从哪里来,跳到哪里去的信息,都包含在Intent里面。而manifest Activity的检测,也是要根据Intent里面的信息来的。

所以,要骗过系统,假装我们跳的Activity是已经注册过的,那么只需要将Intent里面的信息换成已经在manifest中注册的某个Activity即可(如APP启动的LauncherActivity)

确定思路:

    1. 在AMS的hook函数中,将真实的Intent中的信息,替换成manifest中已有的Activity信息,骗过系统的检测机制。
    1. 虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到"假"的Activity,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将真实的Activity信息,还原回去,跳到原本该去的Activity

对应的核心代码,其实也就两句话:

/**
 * hookAMS AMS(ActivityManagerService)兼容 26以上,以及26以下的版本(SDK 26对AMS实例的获取进行了代码更改)
 * 在已经能够实现全局hook MS的方案下,进一步改造,实现无清单启动Activity
 */
public static void hook(Context context) {

    hookAMS(context);//使用假的Activity,骗过AMS的检测

    hookActivityThread_mH(context);

    hookPMAfter28(context);//由于AppCompatActivity存在PMS检测,如果这里不hook的话,就会包PackageNameNotFoundException
}

源码索引

调用流程图

下图大致画出:从Activity.startActivity动作开始,到Activity.startActivityForResult,到最终跳转动作的最终执行者的全过程

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }
        if (requestCode >= 0) {
            // If this start is requesting a result, we can avoid making
            // the activity visible until the result is received.  Setting
            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
            // activity hidden during this time, to avoid flickering.
            // This can only be done when a result is requested because
            // that guarantees we will get information back when the
            // activity is finished, no matter what happens to it.
            mStartedActivity = true;
        }

        cancelInputsAndStartExitTransition(options);
        // TODO Consider clearing/flushing other event sources and events for child windows.
    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            // Note we want to go through this method for compatibility with
            // existing applications that may have overridden it.
            mParent.startActivityFromChild(this, intent, requestCode);
        }
    }
}
Instrumentation.ActivityResult ar =
    mInstrumentation.execStartActivity(
        this, mMainThread.getApplicationThread(), mToken, who,
        intent, requestCode, options);
if (ar != null) {
    mMainThread.sendActivityResult(
        mToken, who, requestCode,
        ar.getResultCode(), ar.getResultData());
}
  • execStartActivity只是对一些参数的合法性校验,如果不合法,那就会直接抛出异常,比如之前的have you declared this activity in your AndroidManifest.xml

  • sendActivityResult才是真正的跳转动作执行者

execStartActivity

先进入Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity看看,既然是合法性校验,且看它是如何校验的。

这是Instrumentation.javaexecStartActivity方法

try {
    intent.migrateExtraStreamToClipData();
    intent.prepareToLeaveProcess(who);
    int result = ActivityTaskManager.getService()
        .startActivity(whoThread, who.getBasePackageName(), intent,
                intent.resolveTypeIfNeeded(who.getContentResolver()),
                token, target != null ? target.mEmbeddedID : null,
                requestCode, 0, null, options);
    checkStartActivityResult(result, intent);
} catch (RemoteException e) {
    throw new RuntimeException("Failure from system", e);
}

结论:它是通过AMS去校验的,AMS startActivity会返回一个int数值,随后checkStartActivityResult方法会根据这个int值,抛出响应的异常,或者什么都不做

public static void checkStartActivityResult(int res, Object intent) {
    if (!ActivityManager.isStartResultFatalError(res)) {
        return;
    }

    switch (res) {
        case ActivityManager.START_INTENT_NOT_RESOLVED:
        case ActivityManager.START_CLASS_NOT_FOUND:
            if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                throw new ActivityNotFoundException(
                        "Unable to find explicit activity class "
                        + ((Intent)intent).getComponent().toShortString()
                        + "; have you declared this activity in your AndroidManifest.xml?");
            throw new ActivityNotFoundException(
                    "No Activity found to handle " + intent);
        case ActivityManager.START_PERMISSION_DENIED:
            throw new SecurityException("Not allowed to start activity "
                    + intent);
        case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
            throw new AndroidRuntimeException(
                    "FORWARD_RESULT_FLAG used while also requesting a result");
        case ActivityManager.START_NOT_ACTIVITY:
            throw new IllegalArgumentException(
                    "PendingIntent is not an activity");
        case ActivityManager.START_NOT_VOICE_COMPATIBLE:
            throw new SecurityException(
                    "Starting under voice control not allowed for: " + intent);
        case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
            throw new IllegalStateException(
                    "Session calling startVoiceActivity does not match active session");
        case ActivityManager.START_VOICE_HIDDEN_SESSION:
            throw new IllegalStateException(
                    "Cannot start voice activity on a hidden session");
        case ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION:
            throw new IllegalStateException(
                    "Session calling startAssistantActivity does not match active session");
        case ActivityManager.START_ASSISTANT_HIDDEN_SESSION:
            throw new IllegalStateException(
                    "Cannot start assistant activity on a hidden session");
        case ActivityManager.START_CANCELED:
            throw new AndroidRuntimeException("Activity could not be started for "
                    + intent);
        default:
            throw new AndroidRuntimeException("Unknown error code "
                    + res + " when starting " + intent);
    }
}

sendActivityResult

再进入第二句mMainThread.sendActivityResult看真正的跳转动作是如何执行的:

ps:这里其实有个诀窍,既然我们的终极目标是要骗过系统的Activity Intent检测,那么跟着Intent这个变量,就不会偏离方向

public final void sendActivityResult(
        IBinder token, String id, int requestCode,
        int resultCode, Intent data) {
    if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
            + " req=" + requestCode + " res=" + resultCode + " data=" + data);
    ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
    list.add(new ResultInfo(id, requestCode, resultCode, data));  //将Intent对象进行了封装,最后形成了一个ClientTransaction
    final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
    clientTransaction.addCallback(ActivityResultItem.obtain(list));
    try {
        mAppThread.scheduleTransaction(clientTransaction);  //最后,由AppThread来执行
    } catch (RemoteException e) {
        // Local scheduling
    }
}

既然intent被封装到了ClientTransaction,交给了mAppThread,那么继续:

@Override
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
    ActivityThread.this.scheduleTransaction(transaction);
}

前方有坑,请注意:Android Studio里并不能直接跳转,所以要手动,找到父类中的scheduleTransaction方法,这个ClientTransactionHandlerActivityThread的父类

/**
 * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
 * can perform on client.
 * @hide
 */
public abstract class ClientTransactionHandler {

    // Schedule phase related logic and handlers.

    /** Prepare and schedule transaction for execution. */
    void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

    ...

调用sendMessage(int, Object),在ActivityThread中找到这个方法的实现:

void sendMessage(int what, Object obj) {
    sendMessage(what, obj, 0, 0, false);
}

找它的最终实现:

private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
    if (DEBUG_MESSAGES) {
        Slog.v(TAG,
                "SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj);
    }
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    if (async) {
        msg.setAsynchronous(true);
    }
    mH.sendMessage(msg);
}

找到另一个关键点:mH,H类的定义:

final H mH = new H();
class H extends Handler {
    ...
    public static final int EXECUTE_TRANSACTION = 159;

    String codeToString(int code) {
        if (DEBUG_MESSAGES) {
            switch (code) {
                case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
                case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
            }
        }
        return Integer.toString(code);
    }
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                if (isSystem()) {
                    // Client transactions inside system process are recycled on the client side
                    // instead of ClientLifecycleManager to avoid being cleared before this
                    // message is handled.
                    transaction.recycle();
                }
                // TODO(lifecycler): Recycle locally scheduled transactions.
                break;
                ...
        }
        Object obj = msg.obj;
        if (obj instanceof SomeArgs) {
            ((SomeArgs) obj).recycle();
        }
        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
    }
}

很明显,它就是一个Handler的普通子类,定义了主线程ActivityThread中可能发生的各种事件。

PS: 这里,留下case EXECUTE_TRANSACTION:分支,是因为,之前ClientTransactionHandler抽象类里面sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);,就是用的这个EXECUTE_TRANSACTION常量。

终于找到了startActivity的最终执行代码!

final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);

Ok,就到这里了(事实上,本来还想往下追查,Intent被封装到ClientTransaction之后,又被得到了什么样的处理,最后发现居然查到了一个源码中都不存在的类,我表示看不懂了,就到这里吧,不影响我们hook)

hook核心代码

还记得我们的整体思路么?

    1. AMShook函数中,将真实的Intent中的信息,替换成manifest中已有的Activity信息,骗过系统的检测机制
    1. 虽然骗过了系统的检测机制,但是这么一来,每一次的跳转,都会跳到"假"的Activity,这肯定不是我们想要的效果,那么就必须,在真正的跳转时机之前,将真实的Activity信息还原回去,跳到原本该去的Activity

说通俗一点就是:
(1)伪造一个Intent,骗过Activity Manifest检测
(2)真正要跳转之前,把原始的Intent还原回去

开始撸代码,大量反射代码即将到来,注释应该很详尽了,特别注意:看反射代码要对照源代码来看,不然很容易走神:

伪造intent,骗过Activity Manifest检测

这里,请对照:ActivityManager.javaActivityTaskManager.java

SDK 28 ~ Android 9.0: ActivityManager.java

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

/** @hide */
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

SDK 29 ~ Android 10.0: ActivityTaskManager.java

private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
        new Singleton<IActivityTaskManager>() {
            @Override
            protected IActivityTaskManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                return IActivityTaskManager.Stub.asInterface(b);
            }
        };

/** @hide */
public static IActivityTaskManager getService() {
    return IActivityTaskManagerSingleton.get();
}

hook核心代码如下

/**
 * 这里对AMS进行hook
 * ActivityManager(ActivityManagerNative)里的IActivityManager是一个单例,用我们的代理对象替换它!
 *
 * @param context
 */
private static void hookAMS(Context context) {
    try {
        final Class<?> ActivityManagerClz;
        final String getServiceMethodStr;
        final String IActivityManagerSingletonFieldStr;
        if (ifSdkOverIncluding29()) {//29的ams获取方式是通过ActivityTaskManager.getService()
            ActivityManagerClz = Class.forName("android.app.ActivityTaskManager");
            getServiceMethodStr = "getService";
            IActivityManagerSingletonFieldStr = "IActivityTaskManagerSingleton";
        } else if (ifSdkOverIncluding26()) {//26,27,28的ams获取方式是通过ActivityManager.getService()
            ActivityManagerClz = Class.forName("android.app.ActivityManager");
            getServiceMethodStr = "getService";
            IActivityManagerSingletonFieldStr = "IActivityManagerSingleton";
        } else {//25往下,是ActivityManagerNative.getDefault()
            ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");
            getServiceMethodStr = "getDefault";
            IActivityManagerSingletonFieldStr = "gDefault";
        }

        //这个就是ActivityManager实例
        Object ActivityManagerObj = ReflectUtil.invokeStaticMethod(ActivityManagerClz, getServiceMethodStr);
        //这个就是这个就是ActivityManager实例中的IActivityManager单例对象
        Object IActivityManagerSingleton = ReflectUtil.staticFieldValue(ActivityManagerClz,
                IActivityManagerSingletonFieldStr);

        // 2.现在创建我们的IActivityManager实例
        // 由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
        // 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
        Class<?> IActivityManagerClz;
        if (ifSdkOverIncluding29()) {
            IActivityManagerClz = Class.forName("android.app.IActivityTaskManager");
        } else {
            IActivityManagerClz = Class.forName("android.app.IActivityManager");
        }

        // 构建代理类需要两个东西用于创建伪装的Intent
        String packageName = Util.getPMName(context);
        String clz = Util.getHostClzName(context, packageName);
        Object proxyIActivityManager =
                Proxy.newProxyInstance(
                        Thread.currentThread().getContextClassLoader(),
                        new Class[]{IActivityManagerClz},
                        new AMSProxyInvocation(ActivityManagerObj, packageName, clz));

        //3.拿到AMS实例,然后用代理的AMS换掉真正的AMS,代理的AMS则是用 假的Intent骗过了 activity manifest检测.
        //偷梁换柱
        Field mInstanceField = ReflectUtil.findSingletonField("mInstance");
        mInstanceField.set(IActivityManagerSingleton, proxyIActivityManager);

    } catch (Exception e) {
        e.printStackTrace();
    }
}

private static final String ORI_INTENT_TAG = "origin_intent";

/**
 * 把InvocationHandler的实现类提取出来,因为这里包含了核心技术逻辑,最好独立,方便维护
 */
private static class AMSProxyInvocation implements InvocationHandler {

    Object amObj;
    String packageName;//这两个String是用来构建Intent的ComponentName的
    String clz;

    public AMSProxyInvocation(Object amObj, String packageName, String clz) {
        this.amObj = amObj;
        this.packageName = packageName;
        this.clz = clz;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Log.e("GlobalActivityHook", "method.getName() = " + method.getName());
        //proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
        if (method.getName().equals("startActivity")) {
            Log.d("GlobalActivityHook", "全局hook 到了 startActivity");

            Intent currentRealIntent = null;//侦测到startActivity动作之后,把intent存到这里
            int intentIndex = -1;
            //遍历参数,找到Intent
            for (int i = 0; i < args.length; i++) {
                Object temp = args[i];
                if (temp instanceof Intent) {
                    currentRealIntent = (Intent) temp;//这是原始的Intent,存起来,后面用得着
                    intentIndex = i;
                    break;
                }
            }

            //构造自己的Intent,这是为了绕过manifest检测
            Intent proxyIntent = new Intent();
            ComponentName componentName = new ComponentName(packageName, clz);//用ComponentName重新创建一个intent
            proxyIntent.setComponent(componentName);
            proxyIntent.putExtra(ORI_INTENT_TAG, currentRealIntent);//将真正的proxy作为参数,存放到extras中,后面会拿出来还原

            args[intentIndex] = proxyIntent;//替换掉intent
            //哟,已经成功绕过了manifest清单检测. 那么,我不能老让它跳到 伪装的Activity啊,我要给他还原回去,那么,去哪里还原呢?
            //继续看源码。

        }
        return method.invoke(amObj, args);
    }
}

//设备系统版本是不是大于等于29(Android 10)
private static boolean ifSdkOverIncluding29() {
    int SDK_INT = Build.VERSION.SDK_INT;
    return SDK_INT >= 29;
}

//设备系统版本是不是大于等于26(Android 8.0 Oreo)
private static boolean ifSdkOverIncluding26() {
    int SDK_INT = Build.VERSION.SDK_INT;
    return SDK_INT >= 26;
}

//设备系统版本是不是大于等于28(Android 9.0 Pie)
private static boolean ifSdkOverIncluding28() {
    int SDK_INT = Build.VERSION.SDK_INT;
    return SDK_INT >= 28;
}

真正要跳转之前,把原始的Intent还原回去

PS: 这里hook mH的手段,并不是针对mH本身做代理,而是对mHmCallback成员。因为:

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

handlerdispatchMessage逻辑,是先执行mCallbackhandleMessage,然后根据它的返回值决定要不要执行handler本身的handlerMessage函数
我们的目的是还原Intent,并不需要对ActivityThread原本的mH做出逻辑修改,所以,hook mCallback,加入还原Intent的逻辑,即可

本次hook,对照的源码是(源码太长,直接截取了ActivityThread里面一些关键的代码):

final H mH = new H();

class H extends Handler {
    ...
    public static final int EXECUTE_TRANSACTION = 159;
    ...

    String codeToString(int code) {
        if (DEBUG_MESSAGES) {
            switch (code) {
                ...
                case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
                ...
            }
        }
        return Integer.toString(code);
    }
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            ...
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                if (isSystem()) {
                    // Client transactions inside system process are recycled on the client side
                    // instead of ClientLifecycleManager to avoid being cleared before this
                    // message is handled.
                    transaction.recycle();
                }
                // TODO(lifecycler): Recycle locally scheduled transactions.
                break;
            ...
        }
        Object obj = msg.obj;
        if (obj instanceof SomeArgs) {
            ((SomeArgs) obj).recycle();
        }
        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
    }
}

下面是Hook Mh的完整代码:

//下面进行ActivityThread的mH的hook
//将真实的Intent还原回去,让系统可以跳到原本该跳的地方
private static void hookActivityThread_mH(Context context) {

    try {
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");

        Object sCurrentActivityThread = ReflectUtil.staticFieldValue(activityThreadClazz, "sCurrentActivityThread");

        Handler mH = (Handler) ReflectUtil.fieldValue(sCurrentActivityThread, "mH");

        Field mCallBackField = ReflectUtil.findField(Handler.class, "mCallback");

        Handler.Callback callback;
        if (ifSdkOverIncluding28()) {
            //2.现在,造一个代理
            // 他就是一个简单的Handler子类
            callback = new ProxyHandlerCallback();//不需要重写全部mH,只需要对mH的callback进行重新定义
        } else {
            callback = new ActivityThreadHandlerCallBack(context);
        }

        //3.替换
        //将Handler的mCallback成员,替换成创建出来的代理HandlerCallback
        mCallBackField.set(mH, callback);

    } catch (Exception e) {
        e.printStackTrace();
    }

}

/**
 * 注意,这里有个坑
 * android.os.handler 这个类有 3个 callback,按照优先级,依次是 msg的callback,自己成员变量mCallback,自己的成员方法 handleMessage()
 *
 * 其中,msg.callback一般很少用,但是它是最优先的,如果有一个Message.存在callback非空成员,那么它是先执行,后面两个就没戏了。
 * 如果 handler自己的成员变量mCallback,非空,那么 handlerMessage()方法就没戏了
 * 前面两个都执行,那么handlerMessage才会执行,
 * 这个叫责任链模式?根据实际条件决定代码分支。
 */
private static class ProxyHandlerCallback implements Handler.Callback {

    private int EXECUTE_TRANSACTION = 159;//这个值,是android.app.ActivityThread的内部类H 中定义的常量EXECUTE_TRANSACTION

    @Override
    public boolean handleMessage(Message msg) {
        boolean result = false;//返回值,请看Handler的源码,dispatchMessage就会懂了
        //Handler的dispatchMessage有3个callback优先级,首先是msg自带的callback,其次是Handler的成员mCallback,最后才是Handler类自身的handlerMessage方法,
        //它成员mCallback.handleMessage的返回值为true,则不会继续往下执行 Handler.handlerMessage
        //我们这里只是要hook,插入逻辑,所以必须返回false,让Handler原本的handlerMessage能够执行.
        if (msg.what == EXECUTE_TRANSACTION) {//这是跳转的时候,要对intent进行还原
            try {
                //先把相关@hide的类都建好
                Class<?> ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
                Class<?> LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");

                Field mActivityCallbacksField = ClientTransactionClz.getDeclaredField("mActivityCallbacks");//ClientTransaction的成员
                mActivityCallbacksField.setAccessible(true);
                //类型判定,好习惯
                if (!ClientTransactionClz.isInstance(msg.obj)) return true;
                Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);//根据源码,在这个分支里面,msg.obj就是 ClientTransaction类型,所以,直接用
                //拿到了ClientTransaction的List<ClientTransactionItem> mActivityCallbacks;
                List list = (List) mActivityCallbacksObj;

                if (list.size() == 0) return false;
                Object LaunchActivityItemObj = list.get(0);//所以这里直接就拿到第一个就好了

                if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;
                //这里必须判定 LaunchActivityItemClz,
                // 因为 最初的ActivityResultItem传进去之后都被转化成了这LaunchActivityItemClz的实例

                Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
                mIntentField.setAccessible(true);
                Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);

                Bundle extras = mIntent.getExtras();
                if (extras != null) {
                    Intent oriIntent = (Intent) extras.get(ORI_INTENT_TAG);
                    //那么现在有了最原始的intent,应该怎么处理呢?
                    Log.d("1", "2");
                    mIntentField.set(LaunchActivityItemObj, oriIntent);
                }

                return result;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}

public static class ActivityThreadHandlerCallBack implements Handler.Callback {

    private final Context mContext;

    public ActivityThreadHandlerCallBack(Context context) {
        mContext = context;
    }

    @Override
    public boolean handleMessage(Message msg) {
        int LAUNCH_ACTIVITY = 0;
        try {
            Class<?> clazz = Class.forName("android.app.ActivityThread$H");
            LAUNCH_ACTIVITY = (int) ReflectUtil.staticFieldValue(clazz, "LAUNCH_ACTIVITY");
        } catch (Exception e) {
        }
        if (msg.what == LAUNCH_ACTIVITY) {
            handleLaunchActivity(mContext, msg);
        }
        return false;
    }
}

private static void handleLaunchActivity(Context context, Message msg) {
    try {
        Object obj = msg.obj;

        Intent proxyIntent = (Intent) ReflectUtil.fieldValue(obj, "intent");
        //拿到之前真实要被启动的Intent 然后把Intent换掉
        Intent originallyIntent = proxyIntent.getParcelableExtra(ORI_INTENT_TAG);
        if (originallyIntent == null) {
            return;
        }
        proxyIntent.setComponent(originallyIntent.getComponent());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

PS:这里有个坑(请看上面if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;,为什么要加这个判断?因为,通过debug,发现从mH里面的msg.what得到的ClientTransaction,它有这么一个成员List<ClientTransactionItem> mActivityCallbacks;,注意看,从list里面拿到的ClientTransactionItem的实际类型是:LaunchActivityItem

之前我索引源码的时候,追查Intent的去向,只知道它最后被封装成了一个ClientTransaction

public final void sendActivityResult(
        IBinder token, String id, int requestCode,
        int resultCode, Intent data) {
    if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
            + " req=" + requestCode + " res=" + resultCode + " data=" + data);
    ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
    list.add(new ResultInfo(id, requestCode, resultCode, data));  //将Intent对象进行了封装,最后形成了一个ClientTransaction
    final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
    clientTransaction.addCallback(ActivityResultItem.obtain(list));  //这里拿到的是List<ActivityResultItem>,这个list被addCallback
    try {
        mAppThread.scheduleTransaction(clientTransaction);  //最后,由AppThread来执行
    } catch (RemoteException e) {
        // Local scheduling
    }
}
public void handleMessage(Message msg) {
    ...
    switch (msg.what) {
        ...
        case EXECUTE_TRANSACTION:
            final ClientTransaction transaction = (ClientTransaction) msg.obj;  //它的mActivityCallbacks的list<?>,?居然变成了LaunchActivityItem,塞进去的是ActivityResultItem
            mTransactionExecutor.execute(transaction);
            if (isSystem()) {
                // Client transactions inside system process are recycled on the client side
                // instead of ClientLifecycleManager to avoid being cleared before this
                // message is handled.
                transaction.recycle();
            }
            // TODO(lifecycler): Recycle locally scheduled transactions.
            break;
        ...
    }
    ...
}

查看ClientTransaction源码:

public class ClientTransaction implements Parcelable, ObjectPoolItem {

    /** A list of individual callbacks to a client. */
    private List<ClientTransactionItem> mActivityCallbacks;

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/servertransaction/ClientTransaction.java

但是,最后从mHwitch case EXECUTE_TRANSACTION分支,去debug(因为无法继续往下查源码)的时候,发现原本塞进去的ActivityResultItemlist,居然变成了LaunchActivityItemlist,查了半天,也没查到是在源码何处发生的变化

LaunchActivityItemActivityResultItem他们两个都是ClientTransaction的子类

public class LaunchActivityItem extends ClientTransactionItem 
public class ActivityResultItem extends ClientTransactionItem

不过,最后能够确定,从mHswitch case EXECUTE_TRANSACTION分支得到的transaction,就是包含了Intent的包装对象,所以只需要解析这个对象,就可以拿到intent,进行还原

OK,大功告成,安装好 Android 9.0 SDK 28的模拟器,启动起来,运行程序,看看能不能无清单跳转:

cn.appblog.activityhookdemo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: cn.appblog.activityhookdemo, PID: 28253
    java.lang.RuntimeException: Unable to start activity ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)
        at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
        at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61)
        at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
        at cn.appblog.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 
     Caused by: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
        at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:240)
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:219)
        at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155) 
        at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:61) 
        at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72) 
        at cn.appblog.activityhookdemo.methodA.Main2Activity.onCreate(Main2Activity.java:14) 
        at android.app.Activity.performCreate(Activity.java:7136) 
        at android.app.Activity.performCreate(Activity.java:7127) 
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271) 
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 

提取关键信息:

Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{cn.appblog.activityhookdemo/cn.appblog.activityhookdemo.methodA.Main2Activity}
        at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222)

居然找不到包?问题出在:NavUtils.getParentActivityName

进去NavUtils.getParentActivityName()去看看:

public static String getParentActivityName(@NonNull Activity sourceActivity) {
    try {
        return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
    } catch (NameNotFoundException e) {
        // Component name of supplied activity does not exist...?
        throw new IllegalArgumentException(e);
    }
}

看来就是这里报的错,继续:

public static String getParentActivityName(@NonNull Context context,
        @NonNull ComponentName componentName)
        throws NameNotFoundException {
    PackageManager pm = context.getPackageManager();
    int flags = PackageManager.GET_META_DATA;
    // Check for disabled components to handle cases where the
    // ComponentName points to a disabled activity-alias.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
    } else {
        flags |= PackageManager.GET_DISABLED_COMPONENTS;
    }
    ActivityInfo info = pm.getActivityInfo(componentName, flags);
    if (Build.VERSION.SDK_INT >= 16) {
        String result = info.parentActivityName;
        if (result != null) {
            return result;
        }
    }
    if (info.metaData == null) {
        return null;
    }
    String parentActivity = info.metaData.getString(PARENT_ACTIVITY);
    if (parentActivity == null) {
        return null;
    }
    if (parentActivity.charAt(0) == '.') {
        parentActivity = context.getPackageName() + parentActivity;
    }
    return parentActivity;
}

找到可疑点:

ActivityInfo info = pm.getActivityInfo(componentName, flags);

可能就是这里抛出的异常,继续:

PackageManager.java

public abstract ActivityInfo getActivityInfo(@NonNull ComponentName component,
            @ComponentInfoFlags int flags) throws NameNotFoundException;

然而,它是一个接口,那么就找它的实现类(注意,如果这个接口涉及到隐藏@hide的类,你用ctrl+T是不能找到的,不过也有办法,回到NavUtil.java):

public static String getParentActivityName(@NonNull Context context,
        @NonNull ComponentName componentName)
        throws NameNotFoundException {
    PackageManager pm = context.getPackageManager();
    int flags = PackageManager.GET_META_DATA;
    // Check for disabled components to handle cases where the
    // ComponentName points to a disabled activity-alias.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        flags |= PackageManager.MATCH_DISABLED_COMPONENTS;
    } else {
        flags |= PackageManager.GET_DISABLED_COMPONENTS;
    }
    ActivityInfo info = pm.getActivityInfo(componentName, flags);

原来pm对象是来自context,既然提到了context这个抽象类,它的很多抽象方法的实现都在ContextImpl,手动进入ContextImpl找这个方法:

@Override
public PackageManager getPackageManager() {
    if (mPackageManager != null) {
        return mPackageManager;
    }

    IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) {
        // Doesn't matter if we make more than one instance.
        return (mPackageManager = new ApplicationPackageManager(this, pm));
    }

    return null;
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref//frameworks/base/core/java/android/app/ContextImpl.java

这个pm对象原来是来自ActivityThread然后进行了一次封装,最后返回出去的是一个ApplicationPackageManager对象,那就进入主线程咯:

public static IPackageManager getPackageManager() {
    if (sPackageManager != null) {
        //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
        return sPackageManager;
    }
    IBinder b = ServiceManager.getService("package");
    //Slog.v("PackageManager", "default service binder = " + b);
    sPackageManager = IPackageManager.Stub.asInterface(b);
    //Slog.v("PackageManager", "default service = " + sPackageManager);
    return sPackageManager;
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/ActivityThread.java

看看IPackageManager的内容:它是AIDL动态生成的接口,用Android Studio是看不到接口内容的,只能到源码官网,查到的接口如下:

interface IPackageManager {
    ...
    ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId);
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/content/pm/IPackageManager.aidl

Ok,看到IBinder,就知道应该无法继续往下追查了,已经跨进程了

前面提到了,从主线程拿到的pm,被封装成了ApplicationPackageManager,那么,进入它里面去找getActivityInfo方法:

@Override
public ActivityInfo getActivityInfo(ComponentName className, int flags)
        throws NameNotFoundException {
    try {
        ActivityInfo ai = mPM.getActivityInfo(className, flags, mContext.getUserId());
        if (ai != null) {
            return ai;
        }
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }

    throw new NameNotFoundException(className.toString());
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/app/ApplicationPackageManager.java

原来异常是这里抛出的,当mPm.getActivityInfo为空的时候,才会抛出。OK,就查到这里,得出结论:

源码,其实对Activity的合法性进行了两次检测,一次是在AMS,一次是在这里的PMS,前面的AMS,我们用一个已有的Activity伪装了一下,通过了验证,那么这里的PMS,我们也可以采用同样的方式

注:上述代码的参数ComponentName className,其实,它就是IntentComponentName成员:

public class Intent implements Parcelable, Cloneable {
    ....
    private String mAction;
    private Uri mData;
    private String mType;
    private String mIdentifier;
    private String mPackage;
    private ComponentName mComponent;
    private int mFlags;
    private ArraySet<String> mCategories;
    @UnsupportedAppUsage
    private Bundle mExtras;

这里对Intent又进行了一次检查,检查的就是这个ComponentName

接下来采用同样的方式对PMS的检测进行hook,让它不再报异常。此次hook的参照的源码是:

ActivityThread.java

public static IPackageManager getPackageManager() {
    if (sPackageManager != null) {
        //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
        return sPackageManager;
    }
    IBinder b = ServiceManager.getService("package");
    //Slog.v("PackageManager", "default service binder = " + b);
    sPackageManager = IPackageManager.Stub.asInterface(b);
    //Slog.v("PackageManager", "default service = " + sPackageManager);
    return sPackageManager;
}

hook核心代码如下(对sPackageManager进行代理替换,让代理类检查的永远是合法的Activity):

/**
 * 由于我只在SDK 28 对应的9.0设备上做过成功的试验,所以此方法命名为hookPMAfter28
 *
 * @param context
 */
private static void hookPMAfter28(Context context) {
    try {
        String pmName = Util.getPMName(context);
        String hostClzName = Util.getHostClzName(context, pmName);

        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
        Object sCurrentActivityThread = ReflectUtil.staticFieldValue(activityThreadClazz, "sCurrentActivityThread");//PM居然是来自ActivityThread

        Object iPackageManager = ReflectUtil.invokeMethod(sCurrentActivityThread, "getPackageManager");

        String packageName = Util.getPMName(context);
        PMSInvocationHandler handler = new PMSInvocationHandler(iPackageManager, packageName, hostClzName);
        Class<?> iPackageManagerIntercept = Class.forName("android.content.pm.IPackageManager");
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new
                Class<?>[]{iPackageManagerIntercept}, handler);
        // 获取 sPackageManager 属性
        Field sPackageManagerField = ReflectUtil.findField(sCurrentActivityThread, "sPackageManager");
        sPackageManagerField.set(sCurrentActivityThread, proxy);
    } catch (
            Exception e) {
        e.printStackTrace();
    }
}

static class PMSInvocationHandler implements InvocationHandler {

    private Object base;
    private String packageName;
    private String hostClzName;

    public PMSInvocationHandler(Object base, String packageName, String hostClzName) {
        this.packageName = packageName;
        this.base = base;
        this.hostClzName = hostClzName;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (method.getName().equals("getActivityInfo")) {
            ComponentName componentName = new ComponentName(packageName, hostClzName);
            return method.invoke(base, componentName, PackageManager.GET_META_DATA, 0); //破费,一定是这样
        }

        return method.invoke(base, args);
    }
}

工具类

ReflectUtil.java

import android.content.Context;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Created by zhangshaowen on 16/8/22.
 */
public class ReflectUtil {

    /**
     * Locates a given field anywhere in the class inheritance hierarchy.
     *
     * @param instance an object to search the field into.
     * @param name     field name
     * @return a field object
     * @throws NoSuchFieldException if the field cannot be located
     */
    public static Field findField(Object instance, String name) throws NoSuchFieldException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(name);

                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                return field;
            } catch (NoSuchFieldException e) {
                // ignore and search next
            }
        }

        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    }

    public static Object fieldValue(Object instance, String name) throws NoSuchFieldException, IllegalAccessException {
        Field field = findField(instance, name);
        return field.get(instance);
    }

    public static Object staticFieldValue(Class<?> originClazz, String name) throws NoSuchFieldException, IllegalAccessException {
        Field field = findField(originClazz, name);
        return field.get(null);
    }

    public static Field findSingletonField(String name) throws NoSuchFieldException, ClassNotFoundException {
        Class<?> SingletonClz = Class.forName("android.util.Singleton");//反射创建一个Singleton的class
        return findField(SingletonClz, name);
    }

    public static Field findField(Class<?> originClazz, String name) throws NoSuchFieldException {
        for (Class<?> clazz = originClazz; clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Field field = clazz.getDeclaredField(name);

                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                return field;
            } catch (NoSuchFieldException e) {
                // ignore and search next
            }
        }

        throw new NoSuchFieldException("Field " + name + " not found in " + originClazz);
    }

    /**
     * Locates a given method anywhere in the class inheritance hierarchy.
     *
     * @param instance       an object to search the method into.
     * @param name           method name
     * @param parameterTypes method parameter types
     * @return a method object
     * @throws NoSuchMethodException if the method cannot be located
     */
    public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
        throws NoSuchMethodException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Method method = clazz.getDeclaredMethod(name, parameterTypes);

                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }

                return method;
            } catch (NoSuchMethodException e) {
                // ignore and search next
            }
        }

        throw new NoSuchMethodException("Method "
            + name
            + " with parameters "
            + Arrays.asList(parameterTypes)
            + " not found in " + instance.getClass());
    }

    /**
     * Locates a given method anywhere in the class inheritance hierarchy.
     *
     * @param clazz          a class to search the method into.
     * @param name           method name
     * @param parameterTypes method parameter types
     * @return a method object
     * @throws NoSuchMethodException if the method cannot be located
     */
    public static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes)
            throws NoSuchMethodException {
        for (; clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Method method = clazz.getDeclaredMethod(name, parameterTypes);

                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }

                return method;
            } catch (NoSuchMethodException e) {
                // ignore and search next
            }
        }

        throw new NoSuchMethodException("Method "
                + name
                + " with parameters "
                + Arrays.asList(parameterTypes)
                + " not found in " + clazz);
    }

    public static Object invokeMethod(Object instance, String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = findMethod(instance, name);
        return method.invoke(instance);
    }

    public static Object invokeStaticMethod(Class<?> clazz, String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = findMethod(clazz, name);
        return method.invoke(null);
    }

    /**
     * Locates a given constructor anywhere in the class inheritance hierarchy.
     *
     * @param instance       an object to search the constructor into.
     * @param parameterTypes constructor parameter types
     * @return a constructor object
     * @throws NoSuchMethodException if the constructor cannot be located
     */
    public static Constructor<?> findConstructor(Object instance, Class<?>... parameterTypes)
            throws NoSuchMethodException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Constructor<?> ctor = clazz.getDeclaredConstructor(parameterTypes);

                if (!ctor.isAccessible()) {
                    ctor.setAccessible(true);
                }

                return ctor;
            } catch (NoSuchMethodException e) {
                // ignore and search next
            }
        }

        throw new NoSuchMethodException("Constructor"
                + " with parameters "
                + Arrays.asList(parameterTypes)
                + " not found in " + instance.getClass());
    }

    /**
     * Replace the value of a field containing a non null array, by a new array containing the
     * elements of the original array plus the elements of extraElements.
     *
     * @param instance      the instance whose field is to be modified.
     * @param fieldName     the field to modify.
     * @param extraElements elements to append at the end of the array.
     */
    public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field jlrField = findField(instance, fieldName);

        Object[] original = (Object[]) jlrField.get(instance);
        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);

        // NOTE: changed to copy extraElements first, for patch load first

        System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
        System.arraycopy(original, 0, combined, extraElements.length, original.length);

        jlrField.set(instance, combined);
    }

    /**
     * Replace the value of a field containing a non null array, by a new array containing the
     * elements of the original array plus the elements of extraElements.
     *
     * @param instance      the instance whose field is to be modified.
     * @param fieldName     the field to modify.
     */
    public static void reduceFieldArray(Object instance, String fieldName, int reduceSize)
        throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        if (reduceSize <= 0) {
            return;
        }

        Field jlrField = findField(instance, fieldName);

        Object[] original = (Object[]) jlrField.get(instance);
        int finalLength = original.length - reduceSize;

        if (finalLength <= 0) {
            return;
        }

        Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), finalLength);

        System.arraycopy(original, reduceSize, combined, 0, finalLength);

        jlrField.set(instance, combined);
    }

    public static Object getActivityThread(Context context,
                                           Class<?> activityThread) {
        try {
            if (activityThread == null) {
                activityThread = Class.forName("android.app.ActivityThread");
            }
            Method m = activityThread.getMethod("currentActivityThread");
            m.setAccessible(true);
            Object currentActivityThread = m.invoke(null);
            if (currentActivityThread == null && context != null) {
                // In older versions of Android (prior to frameworks/base 66a017b63461a22842)
                // the currentActivityThread was built on thread locals, so we'll need to try
                // even harder
                Field mLoadedApk = context.getClass().getField("mLoadedApk");
                mLoadedApk.setAccessible(true);
                Object apk = mLoadedApk.get(context);
                Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
                mActivityThreadField.setAccessible(true);
                currentActivityThread = mActivityThreadField.get(apk);
            }
            return currentActivityThread;
        } catch (Throwable ignore) {
            return null;
        }
    }

    /**
     * Handy method for fetching hidden integer constant value in system classes.
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    public static int getValueOfStaticIntField(Class<?> clazz, String fieldName, int defVal) {
        try {
            final Field field = findField(clazz, fieldName);
            return field.getInt(null);
        } catch (Throwable thr) {
            return defVal;
        }
    }
}

Util.java

import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

public class Util {
    /**
     * 获取当前应用的第一个Activity的name
     *
     * @param context
     * @param pmName
     * @return
     */
    public static String getHostClzName(Context context, String pmName) {
        PackageInfo packageInfo = null;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(pmName, PackageManager
                    .GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return "";
        }
        ActivityInfo[] activities = packageInfo.activities;
        if (activities == null || activities.length == 0) {
            return "";
        }
        ActivityInfo activityInfo = activities[0];
        return activityInfo.name;

    }

    /**
     * 获取包名
     *
     * @param context
     * @return
     */
    public static String getPMName(Context context) {
        // 获取当前进程已经注册的 activity
        Context applicationContext = context.getApplicationContext();
        return applicationContext.getPackageName();
    }
}

最终效果

Application中启用hook

import android.app.Application;
import android.content.Context;

import cn.appblog.androidnomenifestdemo.GlobalActivityHookHelper;
import me.weishu.reflection.Reflection;

public class AppMain extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        Reflection.unseal(base);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        GlobalActivityHookHelper.hook(this);
    }
}

添加3个Activity间跳转,但是清单文件只有一个launchActivity

<application
    android:name=".app.AppMain"
    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=".ui.MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivityByActivity();
            }
        });
        Log.d("LifeCircle", "onCreate()");
    }

    private void startActivityByActivity() {
        Intent i = new Intent(MainActivity.this, Main2Activity.class);
        startActivity(i);
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d("LifeCircle", "onRestart()");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d("LifeCircle", "onStart()");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("LifeCircle", "onResume()");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d("LifeCircle", "onStop()");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d("LifeCircle", "onPause()");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d("LifeCircle", "onDestroy()");
    }
}

MainActivity2.java

public class Main2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivityByActivity();
            }
        });
    }

    private void startActivityByActivity() {
        Intent i = new Intent(Main2Activity.this, Main3Activity.class);
        startActivity(i);
    }
}

MainActivity3.java

public class Main3Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main3);
    }
}

结语

无清单注册,启动Activity也完成。所谓hook,套路是简单的,就是

(1)找到hook点(比如上面,3次hook,一次是系统的AMS,一次是ActivityThreadmH,还有一次是ActivityThreadsPackageManager,注意,这里sPackageManagerhook,只针对了SDK28-9.0设备进行了hook,在其他版本的设备上运行可能会出现其他问题,比如,Intent中的参数传递不正常等)

(2)用合适的方式创建代理对象,通常要hook一个系统类,就用继承的方式,重写某方法。hook一个系统接口的实现类,那就用JDK的Proxy动态代理

(3)最后用代理对象,反射set,替换被hook的对象

套路并不难,掌握好反射,以及代理模式,即可。真正难的是哪里?是源码的阅读能力,还有写出兼容性Hook核心代码的能力!Android SDK 有很多版本迭代,现在最新的是SDK 28,我们要保证我们的hook代码能够兼容所有的系统版本,就需要大量阅读源码,确保万无一失。比如上面,如果不是在SDK 28-Android9.0的模拟器上运行发现报异常,也根本就不会去做最后一次的hook

(4)在 SDK 28之前手机上我发现了一个bug,无法隐式启动Activity。修改handleLaunchActivity方法的一行代码即可:

proxyIntent.setComponent(originallyIntent.getComponent());

改为

ComponentName cn = originallyIntent.resolveActivity(context.getPackageManager());
if (cn == null) {
    return;
}
proxyIntent.setComponent(cn);

本文转载至:https://www.jianshu.com/p/eb772e50c690

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/29/android-hook-implement-launching-activity-without-manifests/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Android Hook实现无清单启动Activity
正常开发中,所有Activity都要在AndroidManifest.xml中进行注册,才可以正常跳转。通过hook,可以绕过系统对activity注册的检测,即使不注册,也可以正常跳转……
<<上一篇
下一篇>>
文章目录
关闭
目 录