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 {....// 最终会调用到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启动流程相关推荐
- 使用360进行apk加固并进行2次签名整体流程
因新版360加固助手需要付费才能进行自动签名,故只能自己手动来签名了~ 1.使用Android studio进行首次签名并打包apk 首先选择build下该选项 选择apk 如果没有key,则点击新建 ...
- Android逆向之旅---动态方式破解apk终极篇(加固apk破解方式)
一.前言 今天总算迎来了破解系列的最后一篇文章了,之前的两篇文章分别为: 第一篇:如何使用Eclipse动态调试smali源码 第二篇:如何使用IDA动态调试SO文件 现在要说的就是最后一篇了,如何应 ...
- Android逆向之旅---动态方式破解apk终极篇 加固apk破解方式
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 一.前言 ...
- ❤️Android Apk 的打包过程 ❤️ 只需两幅图
官方介绍 在分析安装过程之前,需要先了解一下 Android 项目是如何经过编译->打包生成最终的 .apk 格式的安装包.谷歌有一张官方图片来描述 apk 的打包流程,如下图所示. Andro ...
- chromium 84.0.4122.0 WebView apk 启动流程
之前的博客介绍了chromium代码的下载和编译,调试环境的搭建.接下来我们根据编译的WebViewInstrumentation.apk来梳理浏览器的入口,看看chromium demo apk的启 ...
- 安装ps显示计算机丢失adobe,win7系统安装PS显示检查许可证所需的adobe application manager丢失或损坏的解决方法...
win7系统使用久了,好多网友反馈说win7系统安装PS显示检查许可证所需的adobe application manager丢失或损坏的问题,非常不方便.有什么办法可以永久解决win7系统安装PS显 ...
- 由于未安装所需的特性,无法启动操作
由于未安装所需的特性,无法启动操作 win10 的 wsl升级成 wsl2 时需要在 windows Terminal 中 输入 wsl --set-version Ubuntu 2 ,来把 安装好的 ...
- 功能对等四个原则_佛山房屋加固工程需遵循的原则与步骤
房屋加固工程与修缮工程的区别? 房屋加固工程一般是由于结构达不到设计要求的承载,或者因为某些问题,如结构裂缝.断裂.缺陷等问题而影响结构安全,达不到结构耐久性的,才需要做加固. 修缮工程是针对比较老 ...
- 关于apk软件的加固加壳和签名打包
昨天搞了个apk软件.软件首先通过android killer进行反编译了一下.编译签名后是可以正常使用的.但是和朋友交流后结论是要学习进行一个加固处理. 由于自身目前是小白一个.正在学习网络技术知识 ...
最新文章
- zabbix trapper方式监控
- 自动驾驶技术之——虚拟场景数据库研究
- 2017 [六省联考] T5 分手是祝愿
- 算法题:找出整数数组中两个只出现一次的数字
- TortoiseSVN 菜单详解
- Silverlight与HTML双向交互
- mysql-innodb-undo和redo
- linux 负载高ssh连不上,关于ssh连不上问题的解决方法(必看)
- 敏捷开发模式下测试策略
- Laravel 生成Controller
- kali2022.1 firefox developer (换成开发者版本火狐)
- Dalvik与ART的介绍及区别(一)
- 安全私人云储存时代 H3C Magic M2脱颖而出
- Node 开发一个多人对战的射击游戏(实战长文)
- 【渝粤题库】国家开放大学2021春1253C语言程序设计答案
- MFC使用OpenCV两种版本实现mp4文件的播放
- Python爬取新浪微博评论数据,写入csv文件中
- 笔记本电脑英文技术规格解释
- zbb20180930 Postman 使用方法详解
- Simulink简单模型
热门文章
- 微信公众号笔记(二)
- 记一次逆向破解微信小程序参数签名
- html 添加physical,布局规划-1 在设计中添加physical only cells
- php mysql 排班表_要做排班表 灵活性比较强的
- ASP.NET教育管理系统源码【源码分享】
- 爱思助手(i4助手) v5.08 官方版​
- 迭代重建技术(ART)简要介绍
- Android 常用正则表达式,2021年Android大厂面试分享
- 树莓派4B-安装中文输入法Fcitx及Google拼音输入法
- ez4w.com的5折优惠码