前言


怎样应对Andriod面试官 -> 怎样 Hook Activity 的发动流程?

本章继续解说 AMS 相关的知识点,怎样 Hook Activity 的发动流程;

attach


咱们这样直接从 attach 入口讲起

private void attach(boolean system, long startSeq) {
    // 省掉部分代码
    // AMS
    final IActivityManager mgr = ActivityManager.getService();
    try {
        // 这儿直接调用到了 AMS 的 attachApplication 办法;
        mgr.attachApplication(mAppThread, startSeq);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    } 
}

attach 中直接调用到了 AMS 的 attachApplication 办法,咱们进入这个办法看下:

public final void attachApplication(IApplicationThread thread, long startSeq) {
    if (thread == null) {
        throw new SecurityException("Invalid application interface");
    }
    synchronized (this) {
        // 获取调用端的 pid
        int callingPid = Binder.getCallingPid();
        // 获取调用端的 uid
        final int callingUid = Binder.getCallingUid();
        // 把 pid/uid 设置成 AMS 的
        final long origId = Binder.clearCallingIdentity();
        // 
        attachApplicationLocked(thread, callingPid, callingUid, startSeq);
        // 恢复为 app 的
        Binder.restoreCallingIdentity(origId);
    }
}

这儿有一个比较要害的点便是:把 binder 的 id 设置成 AMS 的,这样做是为了权限验证;Binder.clearCallingIdentity() 终究调用到 IPCThreadState.cpp 中的同名办法,咱们进入这个 cpp 文件的同名办法看下:


int64_t IPCThreadState::clearCallingIdentity() { 
    int64_t token = ((int64_t)mCallingUid<<32) | mCallingPid; 
    clearCaller(); 
    return token; 
}

这儿调用了 clearCaller 函数,咱们进入这个函数看下:

// 清空远程调用端的uid和pid,用当前本地进程的uid和pid代替
void IPCThreadState::clearCaller() { 
    mCallingPid = getpid(); 
    mCallingUid = getuid(); 
}

restoreCallingIdentity 也是调用的同名办法;

// 从token中解分出pid和uid,并赋值给相应变量,该办法正好是clearCallingIdentity的相反过程
void IPCThreadState::restoreCallingIdentity(int64_t token) { 
    mCallingUid = (int)(token>>32); 
    mCallingPid = (int)token; 
}

怎样应对Andriod面试官 -> 怎样 Hook Activity 的发动流程?

AndroidManifest.xml 中的 Activity 是在哪里注册的?


咱们在发动一个 Activity 的时分,假如没有在 AndroidManifest 中进行注册的话,就会抛出一段反常,那么 AndroidManifest.xml 中的 Activity 是在哪里注册的呢?

咱们直接进入发动 Activity 的代码中看下:

public ActivityResult execStartActivity(
    Context who, IBinder contextThread, IBinder token, String target,
    Intent intent, int requestCode, Bundle options) {
    // 省掉部分代码
    // 调用 AMS 的 startActivity 办法并获取一个放回成果,然后检测这个成果
    int result = ActivityTaskManager.getService().startActivity(whoThread,
            who.getOpPackageName(), who.getAttributionTag(), intent,
            intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,
            requestCode, 0, null, options);
    checkStartActivityResult(result, intent);
}

这儿调用 AMS 的 startActivity 办法并获取一个回来成果,然后检测这个成果;

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);
     }
}

能够看到,在这个检测成果办法中,抛出了 Activity 未注册的反常,到这儿,咱们的报错点找到了;

咱们接着来看下发动流程中是在哪里回来的 result 的,根据上一章节的发动流程介绍,咱们很简单找到在 ActivityStarter 的 executeRequest 办法中回来了一个 result;

private int executeRequest(Request request) {
    // 省掉部分代码
    // 能够看到 error 的初始值为 SUCCESS,默认初始化成功,咱们接着往下看 err 的赋值的当地
    int err = ActivityManager.START_SUCCESS;
    // 权限拒绝的时分回来的 err 信息;
    if (caller != null) {
        callerApp = mService.getProcessController(caller);
        if (callerApp != null) {
            callingPid = callerApp.getPid();
            callingUid = callerApp.mInfo.uid;
        } else {
            // 权限拒绝回来 PERMISSION_DENIED
            err = ActivityManager.START_PERMISSION_DENIED;
        }
    }
    // 从 Intent 中无法找到相应的 Component
    if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
        err = ActivityManager.START_INTENT_NOT_RESOLVED;
    }
    // 从 Intent 中无法找到相应的 ActivityInfo
    if (err == ActivityManager.START_SUCCESS && aInfo == null) {
        // We couldn't find the specific class specified in the Intent.
        // Also the end of the line.
        err = ActivityManager.START_CLASS_NOT_FOUND;
    }
}

到这儿的时分,咱们就找到前面说的反常抛出的原因,是因为 aInfo == null,那么这个 aInfo 是怎样赋值的呢?它又是因为什么原因是 null 呢?咱们接着往回看;

private int executeRequest(Request request) {
    // 是从 request 的 activityInfo 获取的,这个 request 是传递进来的,咱们接着往上一层看
    ActivityInfo aInfo = request.activityInfo;
}

是从 request 的 activityInfo 获取的,这个 request 是传递进来的,咱们接着往上一层看;

int execute() {
    // 解析 activityInfo 
    if (mRequest.activityInfo == null) {
        mRequest.resolveActivity(mSupervisor);
    }
}

能够看到在 execute 中会履行 activityInfo 的解析逻辑,咱们进入这个办法看下它在什么情况下会回来 null;

void resolveActivity(ActivityTaskSupervisor supervisor) {
    // 假如 rInfo 不为 null 则获取 rInfo 的 activityInfo 赋值给 aInfo 并回来;
    final ActivityInfo aInfo = rInfo != null ? rInfo.activityInfo : null;
}

假如 rInfo 不为 null 则获取 rInfo 的 activityInfo 赋值给 aInfo 并回来;那么这个 rInfo 的 activityInfo 是什么情况在为空的呢?这个 rInfo 又是在哪里赋值并传递进来的呢?咱们接着回来看;

void resolveActivity(ActivityTaskSupervisor supervisor) {
    // 省掉部分代码
    // 在这儿获取 rInfo
    resolveInfo = supervisor.resolveIntent(intent, resolvedType, userId,
        0 /* matchFlags */,
        computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid));
}

经过 resolveIntent 获取 rInfo,咱们进入这个 resolveIntent 办法看下;

ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags,
        int filterCallingUid) {
    // 省掉部分代码
    // 回来 ResolveInfo
    try {
        return mService.getPackageManagerInternalLocked().resolveIntent(
                intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true,
                filterCallingUid);
    } finally {
        Binder.restoreCallingIdentity(token);
    }
}

这儿又回到了 AMS 中的 resolveIntent 办法,咱们进入这个办法看下;

PackageManagerInternal getPackageManagerInternalLocked() {
    if (mPmInternal == null) {
        mPmInternal = LocalServices.getService(PackageManagerInternal.class);
    }
    return mPmInternal;
}

这儿是先获取 PackageManagerInternal 然后调用其 resolveIntent 办法,这个 PackageManagerInternal 是一个笼统类,resolveIntent 是一个笼统办法,咱们需求进入 PackageManagerInternal 的完成类看下, 它的详细完成便是 PackageManagerService,咱们进入它的 resolveIntent 办法看下,它终究调用的是 resolveIntentInternal;

private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags,
        @PrivateResolveFlags int privateResolveFlags, int userId, boolean resolveForStart,
        int filterCallingUid) {
    // 省掉部分代码
    // 获取 ResolveInfo 并回来
    final ResolveInfo bestChoice =
        chooseBestActivity(
                intent, resolvedType, flags, privateResolveFlags, query, userId,
                queryMayBeFiltered);
}

经过调用 chooseBestActivity 获取 ResolveInfo 并回来,咱们进入这个办法看下;

private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
        int flags, int privateResolveFlags, List<ResolveInfo> query, int userId,
        boolean queryMayBeFiltered) {
    // 省掉部分的代码
    // 获取 ResolveInfo,假如不为空,直接回来
    ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
        flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered);
    if (ri != null) {
        return ri;
    }
}

咱们接着进入这个 findPreferredActivityNotLocked 办法看下;

ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
        List<ResolveInfo> query, int priority, boolean always,
        boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
    // 省掉部分代码
    // 这儿首要是获取 ActivityInfo
    final ActivityInfo ai = getActivityInfo(
        pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
                | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
        userId);
}

findPreferredActivityNotLocked 首要是为了获取 ActivityInfo 并将 ActivityInfo 赋值给 ResolveInfo 的 resolveInfo;

public final ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
    return getActivityInfoInternal(component, flags, Binder.getCallingUid(), userId);
}
public final ActivityInfo getActivityInfoInternal(ComponentName component, int flags,
        int filterCallingUid, int userId) {
    // 省掉部分代码
    return getActivityInfoInternalBody(component, flags, filterCallingUid, userId);
}
protected ActivityInfo getActivityInfoInternalBody(ComponentName component, int flags,
        int filterCallingUid, int userId) {
    ParsedActivity a = mComponentResolver.getActivity(component);
    if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
    AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
    if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {
        PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
        if (ps == null) return null;
        if (shouldFilterApplicationLocked(
                ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
            return null;
        }
        return PackageInfoUtils.generateActivityInfo(pkg,
                a, flags, ps.readUserState(userId), userId, ps);
    }
    if (resolveComponentName().equals(component)) {
        return PackageParser.generateActivityInfo(
                mResolveActivity, flags, new PackageUserState(), userId);
    }
    return null;
}

能够看到往下的逻辑,便是解析 AndroidManifest.xml 文件来获取对应的方针 Activity 信息并回来,假如解析不到,则抛出相应的反常,关于解析 AndroidManifest.xml 会在 PackageManagerService 的解说中要点解说;

Hook Activity


Hook Activity 本质便是绕过 AMS 的检查,能够发动咱们并没有在 AndroidManifest 中声明的 Activity;

本章只是解说下 Hook 的原理,详细实战能够关注我的后续文章,后边在插件化实战的时分,会详细完成 Hook AMS;

在 startActivity 的时分,会和 AMS 通信,经过 AMS 的验证之后,回来 app 所在进程,经过 Instrumention 创立方针 Activity,AMS 操控 Instrumention 履行 Activity 的生命周期;

也就说,咱们不能传递真实的 Activity 给 AMS,咱们需求传递一个假的(也便是在 AndroidManifest 中注册的 Activity),当 AMS 验证经过并回来到 app 的时分,咱们需求再替换回方针 Activity 来完成,方针 Activity 的发动;

咱们需求寻觅至少两个 Hook 点;

  1. startActivity 的时分,将方针 Activity 替换成假的 Activity;
  2. AMS 验证经过之后,再替换回去;

怎样应对Andriod面试官 -> 怎样 Hook Activity 的发动流程?

整体的发动流程如上图,咱们需求在这几个流程中找到比较简单 hook 的点;

找 hook 点,咱们一般找 public static 的,这样比较简单反射修正;

从图中以及结合咱们前面讲的发动流程,

  1. Instrumentation 是一个 hook 点(滴滴 VirtualAPK 开源结构计划);
  2. IActivityManager 是一个 hook 点(360 DroidPlugin 开源结构计划);

这儿我贴了一下 360 的详细完成计划(仅 Hook IActivityManager 的当地)

public static void hookIActivityTaskManager(){
    try{
        Field singletonField = null;
        // 这儿要适配不同的体系版别,后边插件化的时分会详细解说
        Class<?> actvityManager = Class.forName("android.app.ActivityTaskManager");
        singletonField = actvityManager.getDeclaredField("IActivityTaskManagerSingleton");
        singletonField.setAccessible(true);
        Object singleton = singletonField.get(null);
        // 拿IActivityManager目标
        Class<?> singletonClass = Class.forName("android.util.Singleton");
        Field mInstanceField = singletonClass.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        // 原始的IActivityTaskManager
        final Object IActivityTaskManager = mInstanceField.get(singleton);
        Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
                , new Class[]{Class.forName("android.app.IActivityTaskManager")}
                , new InvocationHandler() {
                    @Override
                    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
                        Intent raw = null;
                        int index = -1;
                        if ("startActivity".equals(method.getName())) {
                            for (int i = 0; i < args.length; i++) {
                                if(args[i] instanceof  Intent){
                                    raw = (Intent)args[i];
                                    index = i;
                                }
                            }
                            // 代替的Intent
                            Intent newIntent = new Intent();
                            newIntent.setComponent(new ComponentName("com.example.hook", StubActivity.class.getName()));
                            newIntent.putExtra(EXTRA_TARGET_INTENT,raw);
                            args[index] = newIntent;
                        }
                        return method.invoke(IActivityTaskManager, args);
                    }
                });
        mInstanceField.set(singleton, proxy);
    } catch (Exception e){
        e.printStackTrace();
    }
}

AMS 验证之前的 Hook,接下来是 AMS 验证之后的 Hook;

public static void hookHandler() {
    try {
        // 这儿要适配不同的体系版别,后边插件化的时分会详细解说
        Class<?> atClass = Class.forName("android.app.ActivityThread");
        Field sCurrentActivityThreadField = atClass.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThreadField.setAccessible(true);
        Object sCurrentActivityThread = sCurrentActivityThreadField.get(null);
        // ActivityThread 一个app进程 只有一个,获取它的mH
        Field mHField = atClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        final Handler mH = (Handler) mHField.get(sCurrentActivityThread);
        //获取mCallback
        Field mCallbackField = Handler.class.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        mCallbackField.set(mH, new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case 100: {
                    }
                    break;
                    case 159: {
                        Object obj = msg.obj;
                        Log.i(TAG, "handleMessage: obj=" + obj);
                        try {
                            Field mActivityCallbacksField = obj.getClass().getDeclaredField("mActivityCallbacks");
                            mActivityCallbacksField.setAccessible(true);
                            List mActivityCallbacks = (List) mActivityCallbacksField.get(obj);
                            if (mActivityCallbacks.size() > 0) {
                                Log.i(TAG, "handleMessage: size= " + mActivityCallbacks.size());
                                String className = "android.app.servertransaction.LaunchActivityItem";
                                if (mActivityCallbacks.get(0).getClass().getCanonicalName().equals(className)) {
                                    Object object = mActivityCallbacks.get(0);
                                    Field intentField = object.getClass().getDeclaredField("mIntent");
                                    intentField.setAccessible(true);
                                    Intent intent = (Intent) intentField.get(object);
                                    Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
                                    intent.setComponent(targetIntent.getComponent());
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                }
                mH.handleMessage(msg);
                return true;
            }
        });
    } catch (Exception e) {
        Log.e(TAG, "hookHandler: " + e.getMessage());
        e.printStackTrace();
    }
}

没有贴上不同体系版别的兼容处理,感兴趣的,能够自己查看不同版别的详细完成,从而针对性的进行修正适配;

下一章预告


PackageManagerService

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~