Apk应用安全加固所需了解的Application启动流程

本文使用Android Q(API 29)版本源代码进行讲解

很多人认为Android应用加载入口是Application的onCreate,实则不然。当点击进入应用时,Zygote进程会fork出一个独立进程, 通过RuntimeInit#findStaticMain找到ActivityThread#main并在ZygoteInit#main中进行调用


// ZygoteInit#main
public static void main(String argv[]) {
    ....
    Runnable caller;
    try {
       ....
       // 最终会调用到findStaticMain
       caller = zygoteServer.runSelectLoop(abiList);
    } catch (Throwable ex) {
       ....
    } finally {
       ....
    }
       ....
    if (caller != null) {
        caller.run();
    }
}

public class RuntimeInit {
    protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;
        try {
            // 使用反射拿到类实例
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            .....
            Method m;
            try {
                // 获取main方法
                m = cl.getMethod("main", new Class[] { String[].class });
            } 
            .....
        }
        .....
        // 封装返回
        return new MethodAndArgsCaller(m, argv);
    }

    static class MethodAndArgsCaller implements Runnable {
        private final Method mMethod;
        private final String[] mArgs;
        public MethodAndArgsCaller(Method method, String[] args) {
            mMethod = method;
            mArgs = args;
        }
        public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }
    }

}

 从此Applaction才真正开始了加载流程,所以Android应用加载入口可以理解是ActivityThread#main。

ActivityThread#main


public static void main(String[] args) {
        ....
        Looper.prepareMainLooper();
        ....
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        ....
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
}

ActivityThread#main这个方法看上去非常亲切,倘若C中的void main, 我们来看看这里做了哪些事情。

首先是Looper#prepareMainLooper,我们看看其中的具体实现。


// 主线程
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
}

// 其他线程
public static void prepare() {
        prepare(true);
}

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
}


public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

我们可以清晰的看到Looper#prepare(boolean)方法是一个private类型,仅可通过prepareMainLooper与prepare两个public的静态方法进行调用,从参数字面意思可以看出主线程Looper是不允许退出的。 系统通过这个方法创建了一个Looper实例。

回到主干,接下来创建了一个ActivityThread的对象实例,ActivityThread其实并非真正意义上的Thread,因为其并没有继承Thread,只是给人线程的感觉,其实其仍然运行在Zygote fork出的进程中。接下来系统执行了ActivityThread#attach开始加载应用,这个后面会讲,接下来系统获取随着AcitvityThread对象实例的成员实例Handler,将其保存到静态成员。


final Handler getHandler() {
    return mH;
}

class ActivityThread extends ClientTransactionHandler {
    ....
    final H mH = new H(); 
    ....
}


class Hanlder() {
    /**
      * Default constructor associates this handler with the {@link Looper} for the
      * current thread.
      * If this thread does not have a looper, this handler won't be able to receive messages
      * so an exception is thrown.
      */
    public Handler() {
        this(null, false);
    } 
}

我们知道采用无参方式创建Handler实例时,Handler实例会默认绑定当前线程,如果当前线程没有绑定Looper将不会收到任何消息,在前面我们已经为当前线程绑定了Looper实例。

接下来调用了Looper.loop()开始进行轮询,这里有人可能会问为什么采用主线程死循环不会出现ANR问题?其实当前并非仅有一个主线程,其实在attach中会创建一个ApplicatioThread,其目的就是完成与操作系统的通讯,由操作系统指挥完成生命周期,后面我们就可以看到他的具体操作。Handler的设计采用了Linux的epoll/pipe机制,当主线程消息队列中没有消息时,会主动释放CPU资源进入休眠状态,当有新的消息到达时在通过pipe管道唤醒主线程工作。有兴趣的同学可以跟一下Handler源码,这里就不着重分析了。

ActvityThread#attach(boolean, long)

回到主干,我们进入ActvityThread#attach(boolean, long)看看接下来发生了哪些事情。


public final class ActivityThread extends ClientTransactionHandler {
    ....
    private static volatile ActivityThread sCurrentActivityThread;
    final ApplicationThread mAppThread = new ApplicationThread(); 
    ....
    private void attach(boolean system, long startSeq) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        // 非系统线程
        if (!system) {
            ...
            final IActivityManager mgr = ActivityManager.getService();
            try {
               mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
               throw ex.rethrowFromSystemServer();
            }
            ....
        } else {
            ....
        }
        ....
    }
    public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
    }
}

我们看到这里首先会看到会将当前ActivityThread对象实例保存到了sCurrentActivityThread这个静态变量中,作为一个私有静态变量仅可通过currentActivityThread静态方法获取的,这里很关键,后续加固过程中可通过反射拿到这个ActivityThread对象实例对其进行篡改,接下来通过mSystemThread标记当前ActivityThread是否来自系统。Google编码规范还是很给力的,通过变量名可以清晰知道这个变量到底是静态成员还是普通成员。

接下来通过ActivityManager获取到AMS(ActivityManagerService)的Binder对象,AMS运行于system_service的子线程中,此时其实完成的是建立了跨进程的通讯,我们可以进入AcitvityManagerService查看ActivityManagerService#attachApplication中具体完成了哪些事情。

ActivityManagerService#attachApplication


public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    ....
    @Override
    public final void attachApplication(IApplicationThread thread, long startSeq) {
        synchronized (this) {
            ....
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);
            ....
        }
    }
    ....
    private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid, int callingUid, long startSeq) { 
        ...
        thread.bindApplication(processName, appInfo, providers,
                        instr2.mClass,
                        profilerInfo, instr2.mArguments,
                        instr2.mWatcher,
                        instr2.mUiAutomationConnection, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
                        new Configuration(app.getWindowProcessController().getConfiguration()),
                        app.compat, getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, autofillOptions, contentCaptureOptions);
        ...
    }
}

我们可以发现最终回调了ApplicationThread#bindApplication,我们回到ApplicationThread做了哪些事情。ApplicationThread作为一个内部类在ActivityThread类中。


public final class ActivityThread extends ClientTransactionHandler { 
    private class ApplicationThread extends IApplicationThread.Stub {
        public final void bindApplication(String processName, ApplicationInfo appInfo,List<ProviderInfo> providers, ComponentName instrumentationName
       ,ProfilerInfo profilerInfo, Bundle instrumentationArgs
       ,IInstrumentationWatcher instrumentationWatcher
       ,IUiAutomationConnection instrumentationUiConnection
       ,int debugMode,boolean enableBinderTracking, boolean trackAllocation
       ,boolean isRestrictedBackupMode,boolean persistent
       ,Configuration config,CompatibilityInfo compatInfo
       ,Map services, Bundle coreSettings
       ,String buildSerial, AutofillOptions autofillOptions
       ,ContentCaptureOptions contentCaptureOptions) {
            ....
            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillOptions = autofillOptions;
            data.contentCaptureOptions = contentCaptureOptions;
            sendMessage(H.BIND_APPLICATION, data);
        }
}


看这密密麻麻的参数有些头痛,设计者把这些参数封装成一个AppBindData对象实例然后sendMessage,交给主线程的Handler来进行处理,这个Handler上文提过还记得吗?我们看看这个Handler实现的handleMessage方法。

ActivityThread#handleBindApplication

public final class ActivityThread extends ClientTransactionHandler {
    class H extends Handler {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                ....
            } 
        }
    }

    private void handleBindApplication(AppBindData data) { 
        ....
        mBoundApplication = data;
        data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
        Application app;
        ....
        try {
            ....
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                }
            }
            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                ....
            }

        }finally {
            ....
        }
    }
}

我们可以在handleMessage中通过字段快速找到对应的处理方法,然后透传至ActivityThread#handleBindApplication进行处理,首先将AppBindData对象实例保存在mBoundApplication中,这里很关键,因为AppBindData中的ApplicationData成员className保存待加载Application对象类名,加固过程需要反射进行篡改。

紧接着调用了getPackageInfoNoCheck方法,我们进入其中看看做了哪些事情。


public final class ActivityThread extends ClientTransactionHandler {
    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
    }

    private LoadedApk getPackageInfo(ApplicationInfo aInfo
         ,CompatibilityInfo compatInfo
         ,ClassLoader baseLoader, boolean securityViolation
         ,boolean includeCode, boolean registerPackage) {
            final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
            synchronized (mResourcesManager) {
                ....
                LoadedApk packageInfo = ref != null ? ref.get() : null;
                ....
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode
                            && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0,         
                                registerPackage);
                ....
            if (differentUser) {
                ....
            } else if (includeCode) {
                mPackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            } else {
                ....
            }
            return packageInfo;
        }
    }
}

还是透传逻辑,这里要注意得失倒数第二个参数,这里是true,这也是mPackages对象唯一进行put的地方,其主要记录packageName与LoadedApk的映射关系。这个地方也很重要,后续我们需要根据包名反射拿到其对应的LoadedApk对象,替换类加载器。

紧接着创建了LoadedApk对象实例,我们可以发现这个LoadedApk对象实例的mApplicationInfo其实使用的就是AppBindData#appInfo,这个也很关键。然后结束后将生成的LoadedApk返回。回到主干,我们继续往下看。

private void handleBindApplication(AppBindData data) { 
        ....
        mBoundApplication = data;
        data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
        Application app;
        ....
        try {
            ....
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                }
            }
            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                ....
            }

        }finally {
            ....
        }
}

public class Instrumentation {
    ....
    public void callApplicationOnCreate(Application app) {
        app.onCreate();
    }
    ....
}

接下来使用刚生成的LoadedApk对象实例的makeApplication方法来生成Application对象实例,这里我们后面会继续说,紧接着将生成的Application对象实例保存到mInitialApplication中,然后调用installContentProviders方法完成contentProviders的加载过程,然后最后会通过调用callApplicationOnCreate方法完成Application对象实例的create生命周期,这里可能会有人问了,那Application生命周期中的onAttach呢,onAttach其实隐藏在makeApplication方法内。 我们接下来看看makeApplication的具体实现。

LoadedApk#makeApplication


public final class LoadedApk {
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            ....
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
        } catch (Exception e) {
            ....
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;
        ....
        return app;
    }
}

public class Instrumentation {
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
        app.attach(context);
        return app;
    }
}


我们发现在调用makeApplication方法时,首先会判断mApplication是否已经被加载过,说明如果我们想重新makeApplication方法来生成其他Application对象实例,则必须对mApplication置空。接下来可以看出Application对象是根据mApplicationInfo.className反射拿到的类实例,然后通过newApplication方法来创建Application对象实例,通过查看newApplication实现可以发现还是系统还是使用反射来创建Application实例,并调用了Application的onAttach方法,然后将生成的Application对象实例存在Activity对象实例的mAllApplications中,后面加固过程我们可以处理这个列表。

由此我们可以发现,整个流程是Application.onAttach -> ContentProvider.onCreate -> Application.onCreate。

重定向Application步骤

1. 通过静态方法ActivityThread#currentActivityThread(ActivityThread类型),即可拿到当前应用的ActivityThread实例

2. 修改currentActivityThread.mBoundApplication(AppBindData类型)内部成员appInfo(ApplicationInfo类型)的className字段

回顾一下,mBoundApplication是在ActivityThread#handleBindApplication中进行绑定,在Application.onAttach之前。

源工程的className是在壳工程AndroidManifest中记录的

3. 修改currentActivityThread.mBoundApplication(AppBindData类型)内部成员info(ApplicationInfo类型)的mApplicationInfo(ApplicationInfo类型)的className字段

仅修改第二步即可,原因在于在构造LoadedApk对象实例时使用的是mBoundApplication(AppBindData类型)内部成员appInfo,所以他们指向的是同一个实例,不需要重复进行修改。具体参考ActivityThread#handleBindApplication的描述。

回顾一下,mBoundApplication是在ActivityThread#handleBindApplication中进行绑定,在Application.onAttach之前

4. 将currentActivityThread.mBoundApplication(AppBindData类型)内部成员info(LoadedApk类型)的mApplication设置NULL

这里的目的是为了后面重新调用LoadedApk#makeApplication生成源工程的Application对象实例。

5. 根据currentActivityThread.mInitialApplication(Application类型)将currentActivityThread.mAllApplications列表中的对象进行删除

回顾一下currentActivityThread.mInitialApplication(Application类型)是在makeApplication后被赋值的。

6.  修改currentActivityThread.mBoundApplication(AppBindData类型)内部成员info(LoadedApk类型)的mClassLoader,将mClassLoader设置为能够加载源工程dex文件的类加载器,并将父加载器设置为壳工程的,也就是当前的mClassLoader,这是利用类加载器的双亲委派机制。

这里插一句题外话,有人会根据包名通过currentActivityThread.mPackages(ArrayMap类型),通过映射拿到LoadedApk,这也是一种获取方式,我自测发现,其实都指向的是同一对象实例,所以修改一个地方就可以。

7. 反射调用currentActivityThread.mBoundApplication(AppBindData类型)内部成员info(LoadedApk类型)的makeApplication方法,重新生成源工程的application对象,重新设置currentActivityThread.mInitialApplication,然后手动onCreate完成Application的重定向

注意事项

其实Application启动流程是十分复杂的,本文仅以重定向Application角度来解读加载流程,其中省略了很多东西,暂时还不能讲清楚。本文其实对于Application启动流程的解读也存在着很多错误问题,希望同学在阅读时切记要多加一些自己的思考。

参考资料

https://www.jianshu.com/p/7687b4f6b683

https://juejin.im/entry/58a560dc61ff4b0062a61833

https://blog.csdn.net/nbalichaoq/article/details/51967753

http://www.520monkey.com/archives/553

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值