安卓一代壳(落地加载)反射部分详解
2020-12-09 12:31:25 Author: mp.weixin.qq.com(查看原文) 阅读量:45 收藏

本文为看雪论优秀文章

看雪论坛作者ID:寒星三两

前言

本文会默认读者已经读完了姜维大佬的“Android中的Apk的加固(加壳)原理解析和实现”博客或是他的书籍关于 dex 加固的章节,并就姜维大佬可能觉得太简单或是觉得不是很值得解释的反射部分进行详解。
 
在他的博文中,安卓一代壳中有这么一段:

 
源码如下:
Object currentActivityThread = RefInvoke.invokeStaticMethod( "android.app.ActivityThread", "currentActivityThread", new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493 String packageName = this.getPackageName();//当前apk的包名 //下面两句不是太理解 ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName); //创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码) DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader")); //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//? //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader ----有点c++中进程环境的意思~~ RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader);

就我个人而言,除开这一段之外在整个一代壳的壳 apk (可以把一代壳分为三个部分:壳 apk,源 apk 以及加壳 java 程序)中,可以说没有别的难以理解的地方了,这段代码是从姜维大佬的博客中摘出来的,如果你看了他的书的话,会发现书上的内容跟博客内容相似,但是少了很多不确定的疑问,例如他注释中说的“下面两句不是太理解”,即使在书中任然没有说明。
 
让我们回到最开始从头开始 ,反射是什么,这就要从 Java 反射开始说起,关于 Java 反射可以看我朋友的一篇博客:
https://chenzhuo233.github.io/2019/12/16/Java-%E5%8F%8D%E5%B0%84/#more
 
类比到安卓的反射(不用看得太仔细):http://blog.qiji.tech/archives/4374
 
现在应该能够明白反射基础含义了,但是可能还有些懵,这是什么?为什么要这么做?如果你有学习过 Frida 应该比较早就接触过 Java 反射了,在我看来,反射的用处就是我们可以通过反射获取到其他 Application 进程的信息。

第一句:

我们用AndroidStudio的 debug 看看,将前面内容全部注释,并在 Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread","currentActivityThread",new Class[] {}, new Object[] {}); 处下断点,在这一行结束之后,看一下 currentActivityThread 的值: 
 
有很多的键值对,但是没有一眼能够看明白的,结合 currentActivityThread 的翻译来看,是指 当前的主进程。
先追到 RefInvoke.invokeStaticMethod 看一下:
可以概括理解为:
Class.forName(class_name).getMethod(method_namepareTyple).invoke(nullpareVaules)

我猜测是反射得到:指定类(class_name)下的指定方法(method_name)的返回值。

而在源码中我们输入的 class_name 是 ActivityThread,输入的 method_name currentActivity

所以继续追到 ActivityThread 下的 currentActivityThread 方法,查看源码:https://www.androidos.net.cn/android/7.0.0_r31/xref/frameworks/base/core/java/android/app/ActivityThread.java

返回值是 sCurrentActivityThread ,继续搜这个 sCurrentActivityThread:
发现值得注意的点只有这里,对了,在研究过程中我发现有很多理不清楚的地方,于是找 @windy_ll 要了一份他编译出来的壳 apk 源码,但是他给我的版本是在安卓 4.4 编译的,引入了一个 MultiDex 的概念。
我查询了一番之后发现在 5.0 之后就废除了,所以为了简化学习,我查询源码采用 7.0 版本,这也是我最终成功编译的版本。
 
回到问题,在整个源码中最值得关注的地方就是这里了,Volatile 关键字是我第一次见到,参考了一篇博客:
https://www.cnblogs.com/zhengbin/p/5654805.html
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
 
问题来了,这是一个共享的变量,也就是说他返回的这个玩意我任然不知道是什么。
 
那只能回到 ActivityThread 本身来,这是干什么的?在注释中,姜维大佬自己推荐了一篇博客,也就是:
http://blog.csdn.net/myarrow/article/details/14223493
 
我的理解是:该类负责管理主线程也就是 UI线程,我第一次写 Auto.js ui 界面的时候就因为堵塞了 ui 线程而无限崩溃,因为当 UI 没有响应一定时间后,安卓系统会自动告诉用户你的程序出问题了(弹窗未响应),但实际上可能没有问题,可是架不住用户当真了,于是 app 就被关掉了。
 
扯回来,在这篇博文中,我发现它的 currentActivityThread 方法有些不一样。

老规矩,搜索这个 sThreadLocal :
static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>();

不知道作者用的是哪一个版本的源码,但是确实这么一看就明白多了,而且通过 debug 得到的键也能在 ActivityThread.java 一一对应。所以说我们在"壳 apk "的第一步得到的就是当前负责管理主线程的 ActivityThread 里面所有的参数,也就是:
那代表着什么呢?其实每个参数都有不同的含义,不过大部分都能用 ActivityThread * 来搜索到内容了.所以这里就不再展开说明,不过就我个人的理解,这里装的是当前所有主线程的信息。

第三、四句:

第二句获取包名不予讨论,跳到第三、四句。第三句和第四句也是姜维大佬在那一年说没太弄明白的:
//下面两句不是太理解 ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( "android.app.ActivityThread", currentActivityThread, "mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName);
其实单看第三句来说并不难理解,在我们已知 currentActivityThread 的内容之后,直接搜索 ActivityThread mPackages 就能搜到挺多有用的资料,比如说:https://blog.csdn.net/lu1024188315/article/details/75722420
//很明显这个集合就是为了保存Application实例的,一个APP应用中使用一个类继承Application,子类的onCreate只被调用一次,//这里为什么使用集合了呢//在LoadedAPK的makeApplication方法也能体现这一点,mApplication为null就创建一个Application实例,否则就返回它。//但是其下面还有一行代码:mActivityThread.mAllApplications.add(app);在这里把刚刚创建Application实例到//mAllApplications中保存起来了,那只有LoadedAPK角度分析,会发现在handleReceiver、handleCreateService方法//都有创建LoadedAPK实例,也调用了 makeApplication方法当然这个时候也会创建一个Application实例,//所以不要单纯地以为只有启动Activity的时候才使用Application。final ArrayList<Application> mAllApplications = new ArrayList<Application>();//这个集合是为了保存LoadedApk实例,进一步证明了Application实例可不只会被创建一个final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<String, WeakReference<LoadedApk>>();//下面这两个集合都为Provider,只是方式不一样final ArrayMap<ProviderKey,ProviderClientRecord> mProviderMap = new ArrayMap<ProviderKey,ProviderClientRecord>();final ArrayMap<IBinder,ProviderClientRecord> mLocalProviders = new ArrayMap<IBinder ProviderClientRecord>();
虽然我没有直接证据表明我的在上一节的猜测是正确的,但是好像是这么个东西。
 
这里还是能理解的,至于姜维大佬说看不懂,我觉得是指两句话加一块不能理解(其实是我)。第四句,先看内容:
WeakReference wr = (WeakReference) mPackages.get(packageName);

weakReference ,弱引用,其实如果在 Java 程序里出现这个我不奇怪,弱引用通常用于 JVM 内存优化,什么意思呢?这就要从 Reference (引用)开始说起,看到这里可能有 Java 大佬要说了,你这个菜逼讲弱引用不讲 GC ,你不讲武德。
首先嘛,我承认我是菜逼,但是这里不讲 GC 主要是因为据我的判断这里好像并没有涉及到 GC 的问题,而你让我一个小菜比说 GC 就是在为难我。先看这篇博文吧:https://zhuanlan.zhihu.com/p/29254258 这位大佬讲得很厉害,可以直接跳到 “强引用和弱引用” 开始阅读。
至于更深的以及 GC 问题,其实不读也罢。至少在一代壳中间,这个弱引用的用处很单一,弱引用在安卓中的应用据我朋友所讲是检索内存,而其 get() 方法是从堆里面得到这个对象,如果在哪一次 get() 方法时得到了 Null ,则说明该对象已经被 GC 了(应该可以通俗理解为被清后台了)。
 
但是!!!
 
事情肯定没有这么简单,为什么这么说呢?因为这里弱引用的对象是我们当前包。如果这能被GC ,我杀我自己?而且"我杀我自己" 都比这个更好实现,至少他是理论上有可行性的。弱引用的 GC 方式是:"因为对象变成了垃圾,所以发生 GC" 。而对于该程序本身,如果自身还想用弱引用自身导致 GC ,显然是死锁。
 
那这里弱引用的用途就是另一个了,就是真的要获取到这个对象。还记得上文中的 mPackages 是什么吗?是 LoadedAPK 实例,而通过 “壳 apk” 的包名反射获取的,自然就是 “壳 apk” 的 LoadedAPK 实例。那为什么要获取呢?其实目的是很明确的,因为在下一行就用上了。

第五句:

DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,libPath, (ClassLoader) RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader"));

在 DexClassLoader 这里有四个参数,分别是 apk文件名(源码中是 apk 文件路径),odex文件路径,lib文件路径,以及父母ClassLoader。

前面三个参数都很好理解,都是在前面就完成设置了的静态变量,至于最后一个对应着 parent 的 "RefInvoke.getFieldOjbect( "android.app.LoadedApk", wr.get(), "mClassLoader")" 在理解前面三四句的基础上也就很简单了。
wr.get() 是 "壳 apk" 的 LoadedAPK 实例,反射获取了 "壳 apk" LoadedApk 实例中的 mClassLoader 成员变量,也就是 "壳 apk" 的 ClassLoader 类,那为什么要 “壳 apk” 的 ClassLoader ?他在这里作为一个 父类的 ClassLoader 被 “源 apk” 的DexClassLoader 所替换。
 
当然在这一句还没有完成替换动作,它还只是创建了一个适用于 “源 apk” 替换 “壳 apk” 的 DexClassLoader 的对象,但是这个对象还没有取代真正的 DexClassLoader

第六句:

在前面都完成后,第六句自然就是真正取代 DexClassLoader ,同时这一句也是这一段唯一一句通过反射修改内容,而非获取。先看代码:
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader);
再看一下 RefInvoke.setFieldOjbect
在理解前面反射的概念之后,这里就很好理解了,读者不妨自己思考一下。
 
参考答案:反射获取得到 android.app.LoadedApk 类的 mClassLoader 成员变量,根据 wr.get() 也就是“壳 apk” 的 LoadedAPK 实例 锁定具体地址,将其值修改为 dLoader 也就是 适用于 “源 apk” 替换 “壳 apk” 的 DexClassLoader 的对象。

总结

本文到这里就结束了,虽然在 Oncreate 中仍有一段反射的内容,不过方法和思路跟本文已经相差无几了,希望本文有帮到你。同时因为本菜狗对 Java 不是很懂,可能会在 对象、成员、实例 等一系列专有名词中搞混,如果让你看得很难受希望别骂我(逃

- End -

看雪ID:寒星三两

https://bbs.pediy.com/user-home-881544.htm

 *本文由看雪论坛 寒星三两 原创,转载请注明来自看雪社区。

# 往期推荐

公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458377917&idx=2&sn=22d427cf890604b5e836456dc4f4ec63&chksm=b180ee3786f767214b6b53925d9cdb3b0ec7648555c4a6318d2dddee332c32c9bda1811d2345#rd
如有侵权请联系:admin#unsafe.sh