本文使用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 {....// 最终会调用到findStaticMaincaller = 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.Stubimplements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {....@Overridepublic 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

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

  1. 使用360进行apk加固并进行2次签名整体流程

    因新版360加固助手需要付费才能进行自动签名,故只能自己手动来签名了~ 1.使用Android studio进行首次签名并打包apk 首先选择build下该选项 选择apk 如果没有key,则点击新建 ...

  2. Android逆向之旅---动态方式破解apk终极篇(加固apk破解方式)

    一.前言 今天总算迎来了破解系列的最后一篇文章了,之前的两篇文章分别为: 第一篇:如何使用Eclipse动态调试smali源码 第二篇:如何使用IDA动态调试SO文件 现在要说的就是最后一篇了,如何应 ...

  3. Android逆向之旅---动态方式破解apk终极篇 加固apk破解方式

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 一.前言 ...

  4. ❤️Android Apk 的打包过程 ❤️ 只需两幅图

    官方介绍 在分析安装过程之前,需要先了解一下 Android 项目是如何经过编译->打包生成最终的 .apk 格式的安装包.谷歌有一张官方图片来描述 apk 的打包流程,如下图所示. Andro ...

  5. chromium 84.0.4122.0 WebView apk 启动流程

    之前的博客介绍了chromium代码的下载和编译,调试环境的搭建.接下来我们根据编译的WebViewInstrumentation.apk来梳理浏览器的入口,看看chromium demo apk的启 ...

  6. 安装ps显示计算机丢失adobe,win7系统安装PS显示检查许可证所需的adobe application manager丢失或损坏的解决方法...

    win7系统使用久了,好多网友反馈说win7系统安装PS显示检查许可证所需的adobe application manager丢失或损坏的问题,非常不方便.有什么办法可以永久解决win7系统安装PS显 ...

  7. 由于未安装所需的特性,无法启动操作

    由于未安装所需的特性,无法启动操作 win10 的 wsl升级成 wsl2 时需要在 windows Terminal 中 输入 wsl --set-version Ubuntu 2 ,来把 安装好的 ...

  8. 功能对等四个原则_佛山房屋加固工程需遵循的原则与步骤

    ​房屋加固工程与修缮工程的区别? 房屋加固工程一般是由于结构达不到设计要求的承载,或者因为某些问题,如结构裂缝.断裂.缺陷等问题而影响结构安全,达不到结构耐久性的,才需要做加固. 修缮工程是针对比较老 ...

  9. 关于apk软件的加固加壳和签名打包

    昨天搞了个apk软件.软件首先通过android killer进行反编译了一下.编译签名后是可以正常使用的.但是和朋友交流后结论是要学习进行一个加固处理. 由于自身目前是小白一个.正在学习网络技术知识 ...

最新文章

  1. zabbix trapper方式监控
  2. 自动驾驶技术之——虚拟场景数据库研究
  3. 2017 [六省联考] T5 分手是祝愿
  4. 算法题:找出整数数组中两个只出现一次的数字
  5. TortoiseSVN 菜单详解
  6. Silverlight与HTML双向交互
  7. mysql-innodb-undo和redo
  8. linux 负载高ssh连不上,关于ssh连不上问题的解决方法(必看)
  9. 敏捷开发模式下测试策略
  10. Laravel 生成Controller
  11. kali2022.1 firefox developer (换成开发者版本火狐)
  12. Dalvik与ART的介绍及区别(一)
  13. 安全私人云储存时代 H3C Magic M2脱颖而出
  14. Node 开发一个多人对战的射击游戏(实战长文)
  15. 【渝粤题库】国家开放大学2021春1253C语言程序设计答案
  16. MFC使用OpenCV两种版本实现mp4文件的播放
  17. Python爬取新浪微博评论数据,写入csv文件中
  18. 笔记本电脑英文技术规格解释
  19. zbb20180930 Postman 使用方法详解
  20. Simulink简单模型

热门文章

  1. 微信公众号笔记(二)
  2. 记一次逆向破解微信小程序参数签名
  3. html 添加physical,布局规划-1 在设计中添加physical only cells
  4. php mysql 排班表_要做排班表 灵活性比较强的
  5. ASP.NET教育管理系统源码【源码分享】
  6. 爱思助手(i4助手) v5.08 官方版​
  7. 迭代重建技术(ART)简要介绍
  8. Android 常用正则表达式,2021年Android大厂面试分享
  9. 树莓派4B-安装中文输入法Fcitx及Google拼音输入法
  10. ez4w.com的5折优惠码