首页
论坛
课程
招聘
[原创]安卓一代壳(落地加载)反射部分详解
2020-11-20 21:35 3595

[原创]安卓一代壳(落地加载)反射部分详解

2020-11-20 21:35
3595

安卓一代壳(落地加载)反射部分详解

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

 

在他的博文中,安卓一代壳中有这么一段:

 

源码

 

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 的值: debug

 

有很多的键值对,但是没有一眼能够看明白的,结合 currentActivityThread 的翻译来看,是指 当前的主进程。

 

先追到 RefInvoke.invokeStaticMethod 看一下: invoke
可以概括理解为:

1
Class.forName(class_name).getMethod(method_name, pareTyple).invoke(null, pareVaules)

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

 

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

 

所以继续追到 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 :
懒得打

1
static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>();

不知道作者用的是哪一个版本的源码,但是确实这么一看就明白多了,而且通过 debug 得到的键也能在 ActivityThread.java 一一对应。所以说我们在"壳 apk "的第一步得到的就是当前负责管理主线程的 ActivityThread 里面所有的参数,也就是:
懒得打

 

那代表着什么呢?其实每个参数都有不同的含义,不过大部分都能用 ActivityThread * 来搜索到内容了.所以这里就不再展开说明,不过就我个人的理解,这里装的是当前所有主线程的信息。

第三、四句

第二句获取包名不予讨论,跳到第三、四句。第三句和第四句也是姜维大佬在那一年说没太弄明白的:

1
2
3
4
5
//下面两句不是太理解
            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
懒得打

1
2
3
4
5
6
7
8
9
10
11
12
13
//很明显这个集合就是为了保存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>();
 

虽然我没有直接证据表明我的在上一节的猜测是正确的,但是好像是这么个东西。

 

这里还是能理解的,至于姜维大佬说看不懂,我觉得是指两句话加一块不能理解(其实是我)。第四句,先看内容:

1
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 实例。那为什么要获取呢?其实目的是很明确的,因为在下一行就用上了。

第五句

1
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 ,同时这一句也是这一段唯一一句通过反射修改内容,而非获取。先看代码:

1
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 不是很懂,可能会在 对象、成员、实例 等一系列专有名词中搞混,如果让你看得很难受希望别骂我(逃


看雪学院推出的专业资质证书《看雪安卓应用安全能力认证 v1.0》(中级和高级)!

最后于 2020-11-20 21:55 被寒星三两编辑 ,原因: 替换图床图片
收藏
点赞2
打赏
分享
最新回复 (14)
雪    币: 6744
活跃值: 活跃值 (1799)
能力值: (RANK:190 )
在线值:
发帖
回帖
粉丝
LowRebSwrd 活跃值 4 2020-11-23 09:57
2
0
雪    币: 1443
活跃值: 活跃值 (475)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
琅環玉碎 活跃值 2020-11-26 15:31
3
0
雪    币: 173
活跃值: 活跃值 (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_Simba 活跃值 2020-11-26 19:29
4
0
大佬你这个壳能实现吗?资源加载部分怎么解决
雪    币: 173
活跃值: 活跃值 (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_Simba 活跃值 2020-11-28 16:02
5
0
大佬求求回复下,磕头了
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
YenKoc 活跃值 2020-12-1 15:54
6
0
感觉就是反射写复杂了,动态加载dex,然后替换mclassLoader,
雪    币: 247
活跃值: 活跃值 (219)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
寒星三两 活跃值 2020-12-2 15:58
7
0
wx_Simba 大佬求求回复下,磕头了[em_9]
不好意思最近有点事刚看到,最终在资源加载部分我仍然没有成功,我一开始以为是把整个资源文件直接拷贝过去就可以了,但是任然找不到我想要的资源文件,我现在也没有找到合适的办法达到想要的效果,等我在摸索看看吧
雪    币: 247
活跃值: 活跃值 (219)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
寒星三两 活跃值 2020-12-2 16:00
8
0
YenKoc 感觉就是反射写复杂了,动态加载dex,然后替换mclassLoader,
哈哈哈哈哈原理是这样的,但是我个人弄不清楚到底是怎么实现的,毕竟实现不了的东西知道原理挺空的,就稍微摸索了一下
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
YenKoc 活跃值 2020-12-2 17:21
9
0
感觉就是得对app的启动流程熟悉,我最近也在看,话说网上一代加固的代码都是jiangwei的那个,refInvoke这个是被移除了吗,androidstudio一直报错
雪    币: 247
活跃值: 活跃值 (219)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
寒星三两 活跃值 2020-12-7 11:58
10
0
YenKoc 感觉就是得对app的启动流程熟悉,我最近也在看,话说网上一代加固的代码都是jiangwei的那个,refInvoke这个是被移除了吗,androidstudio一直报错
脱壳的过程本来也就是重新加载一个activity,Reflnvok 是一个自己写的工具类,姜维并没有贴出来,可以看一下我博客里面贴的源码 http://starrynight.cool/archives/1daikewenti
雪    币: 173
活跃值: 活跃值 (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_Simba 活跃值 2020-12-9 20:14
11
0
寒星三两 不好意思最近有点事刚看到,最终在资源加载部分我仍然没有成功,我一开始以为是把整个资源文件直接拷贝过去就可以了,但是任然找不到我想要的资源文件,我现在也没有找到合适的办法达到想要的效果,等我在摸索看看吧 ...
大佬找到方法的话麻烦告知一下! 感激不尽!
雪    币: 1846
活跃值: 活跃值 (147)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
youbitch 活跃值 2020-12-11 12:20
12
0
wx_Simba 大佬找到方法的话麻烦告知一下! 感激不尽!
同上,希望能及时分享。
雪    币: 173
活跃值: 活跃值 (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_Simba 活跃值 2020-12-17 21:40
13
0
寒星三两 不好意思最近有点事刚看到,最终在资源加载部分我仍然没有成功,我一开始以为是把整个资源文件直接拷贝过去就可以了,但是任然找不到我想要的资源文件,我现在也没有找到合适的办法达到想要的效果,等我在摸索看看吧 ...
还是别摸索资源加载了,参考这位大佬的方法https://bbs.pediy.com/thread-246249.htm实现了资源加载。但是要加载AppCompatActivity还是不行,继续研究好像已经偏离初衷了。
雪    币: 173
活跃值: 活跃值 (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_Simba 活跃值 2020-12-17 21:40
14
0
youbitch 同上,希望能及时分享。
还是别摸索资源加载了,参考这位大佬的方法https://bbs.pediy.com/thread-246249.htm实现了资源加载。但是要加载AppCompatActivity还是不行,继续研究好像已经偏离初衷了。
雪    币: 173
活跃值: 活跃值 (144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_Simba 活跃值 2020-12-19 22:42
15
0
寒星三两 不好意思最近有点事刚看到,最终在资源加载部分我仍然没有成功,我一开始以为是把整个资源文件直接拷贝过去就可以了,但是任然找不到我想要的资源文件,我现在也没有找到合适的办法达到想要的效果,等我在摸索看看吧 ...
成功了!我是将合并的dex放到要加固的APK中打包这样资源加载就没啥错了。前面说的那个AppCompat的问题是minSDKVersion的问题,minSDKversion至少得19
游客
登录 | 注册 方可回帖
返回