Android安全与逆向之Dex动态加载



Dex动态加载是为了解决什么问题?

在Android系统中,一个App的所有代码都在一个Dex文件里面。

Dex是一个类似Jar的存储了多个Java编译字节码的归档文件。

因为Android系统使用Dalvik虚拟机,所以需要把使用Java Compiler编译之后的class文件转换成Dalvik能够执行的class文件。这里需要强调的是,Dex和Jar一样是一个归档文件,里面仍然是Java代码对应的字节码文件。

当Android系统启动一个应用的时候,有一步是对Dex进行优化,这个过程有一个专门的工具来处理,叫DexOpt。DexOpt的执行过程是在第一次加载Dex文件的时候执行的。

这个过程会生成一个ODEX文件,即Optimised Dex。执行ODex的效率会比直接执行Dex文件的效率要高很多。

但是在早期的Android系统中,DexOpt有一个问题,也就是这篇文章想要说明并解决的问题。

DexOpt会把每一个类的方法id检索起来,存在一个链表结构里面。但是这个链表的长度是用一个short类型来保存的,导致了方法id的数目不能够超过65536个。

当一个项目足够大的时候,显然这个方法数的上限是不够的。尽管在新版本的Android系统中,DexOpt修复了这个问题,但是我们仍然需要对老系统做兼容。

Dex动态加载思路

一种有效的解决思路是把Dex文件分割成多个较小的Dex。这就如同很多项目会把自己分割成多个Jar文件一样,不同的功能在不同的Jar文件里面,通过一些配置和额外的操作,可以让虚拟机有选择性的加载Jar文件。

但是在Android系统中,一个应用是只允许有一个Dex文件的。也就是说在编译期的时候,所有的Jar文件最终会被合并成一个Dex文件。我们没有办法在Apk文件里面打包两个Dex,让DexOpt分别对两个Dex文件做处理,而Android系统也不会同时为一个Apk加载两个Dex。

如果我们把Dex分成多个文件,然后在程序运行的时候,再把多的那几个动态的加载进来是否可行呢?

也就是说我们能否在运行时阶段把代码加入虚拟机中。对于虚拟机来说,其实所有的代码都是在运行时被加载进来的。而不同于C语言还存在着静态链接。虚拟机在所有Java代码执行之前被启动,然后开始把字节码加载到环境中执行,我们可以理解成所有的代码都是动态加载到虚拟机里的。

而说到加载,不得不说的是ClassLoader。它的工作就是加载.class文件。在Android的Dalvik环境中,对应的是DexClassLoader,它们的功能是完全一样的。

ClassLoader的一大特点就是它是一个树状结构(双亲委派)。每个ClassLoader都有一个父亲ClassLoader。也就是说,ClassLoader不是把所有的Class放到一个巨大的数组或别的什么数据结构中来处理。ClassLoader在加载一个Jar中的类的时候,需要制定另一个ClassLoader作为父亲节点,当我们需要通过ClassLoader得到一个类类型的时候,ClassLoader会把请求优先交给父ClassLoader来处理,而父ClassLoader又会交给它的父,一直到根ClassLoader。

如果根ClassLoader有这个类,而返回这个类的类类型,否则把这个请求交给这个请求的来源子ClassLoader。这是一种向上传递,向下分发的机制。这种情况下,对于调用着来说,子ClassLoader永远都是包含最多Class的ClassLoader。

有一点我们需要注意,父ClassLoader只会向请求来源分发自己的处理结果。所以如果来源是自己,那么如果没有请求类它就会返回空,而不是遍历所有子ClassLoader去请求是否有被请求的类。

在Android系统中,对于一个应用来说,其实有两个ClassLoader,一个是SystemClass-Loader,这个ClassLoader里面除了Java标准的类库之外,还有一个android.jar,所有Android Framework层的类都在这里。

而另外一个重要的ClassLoader就是基于Android Context的ClassLoader。所有属于当前应用的类都是用这个ClassLoader来加载的,我们可以在Android源码中看到,所有的Activity,Service,View都是使用这个ClassLoader来反射并创建的。我们暂时把它叫做ContextClassLoader。

加载外部Dex

首先构建一个Dex文件,这一步并不复杂,首先我们把所需要的.class文件或者是Jar文件和一些源码一起编译生成一个Jar文件。然后使用Android SDK提供的dx工具把Jar文件转成Dex文件。

我们可以提前对它进行ODex操作,让它在被DexClassLoader加载的时候,跳过DexOpt的部分工作,从而加快加载的过程。

现在的工作就是在运行时加载这个Dex文件了。我们可以在Application启动的onCreate方法里面加载Dex,但是如果你的Dex太大,那么它会让你的App启动变慢。

我们也可以使用线程去加载,但我们必须保证加载完成之后再进行某个外部类的请求。当然也可以真正等到需要某个外部类的时候再进行Dex加载。这根本上取决于Dex文件本身的大小,太大了可以预加载,而比较小可以等到实际需要的时候再加载。

我们暂且把这个加载了外部Dex的ClassLoader成为ExternalClassLoader.

上面我们提到了树形结构和系统中的多个ClassLoader,当我们加载外部Dex的时候,我们是否需要指定一个父ClassLoader呢?我们当然需要一个父ClassLoader,否则ExternalClassLoader连一些基本的Java类都没有,它根本不可能成功的加载一个Dex。

进一步的,我们要选择哪一个ClassLoader来作为我们的父亲呢?是SystemClassLoader还是ContextClass-Loader?

这是根据情况来定的,如果外部的Dex文件里没有任何和Android相关的代码,那么SystemClassLoader是我们的首选,否则我们就应该用ContextClass-Loader。如果是后者的情况,我们的树可以被看成一个链表。

外部的View, Acitivity等的处理

我们知道,我们编写的四大组件都不是由我们自己来创建的,是由系统来给我们构造并管理其生命周期的。那么这个过程是什么样的呢?

拿Activity来举例,我们需要通过调用当前Activity/Context的startActivity,传入一个Intent来调用启动一个新的Activity。系统有一个ActivityManager来处理这里的逻辑。这里的逻辑相当的复杂,但简单来说,ActivityManager会收到并处理这个Intent,从而决定是是启动一个新的,还是把旧的放到前台。它会先查找这个Activity在哪个应用里面,这是通过扫描每个应用的Android-Manifest来确定。这些信息是在PackageManager里面被检索的。总之如果这个Activity不在任何的manifest里面,它就不可能被启动。

所以仅有一个Activity类是不够的,我们需要在manifest里面声明它。上面是Activity的情况,Service之类的也是同理。那么View怎么办?

尽管我们可以直接创建View,但是大部分的View都不是我们创建的,而是通过XML布局文件Inflate出来的。也就是说,我们在XML定义了一些外部Dex里面的View,那么显然这个XML是不能被成功的Inflate的。因为除非系统会使用我们的ExternalClassLoader,否则它肯定是找不到我们的类的:ContextClassLoader里面并没有外部Dex中的类。

也就是说问题的根本在于,对于那些Android系统为我们创建的对象,它是不能包含在外部Dex里面的。而Android系统中大部分的组件类的生命周期都交给了系统来管理。我们不可能自己来创建这些类对象。

那么另一种思路:我们是不是可以通过使用我们的ExternalClassLoader来代替ContextClassLoader呢?尽管系统的ContextClassLoader是私有的,但是我们可以通过反射强制的把它替换成我们的ExternalClassLoader。

而对于那些外部的组件(Activity等),尽管我们没有它们的类,但是并不影响我们在AndroidManifest里面声明这个Activity。因为Android系统只是把它作为一个检索,并不会真正检查它里面的组件是不是真的在虚拟机环境中已经被加载了,只有真正使用Intent启动某个组件的时候才会去检查。而只要我们保证这个时候我们已经加载了外部的ClassLoader,那么这个组件就可以被正常的启动。

还有一点,除了我们要为外部可能有的组件在AndroidManifest里面做声明一外,那些外部组件可能用到的权限我们也需要一一声明,例如如果外部Activity使用了相机功能,那么如果我们的Manifest里面没有声明使用相机功能的权限的话,即便这个Activity能成功为加载出来,仍然是不能使用的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292567.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python链接mysql报错2003_Python连接Mysql报错问题解决

import MySQLdb #打开数据库 db MySQLdb.connect("127.0.0.1","root","123456","testdb",3306) #使用corsor()方法获取操作游标 cursor db.cursor() #使用execute方法执行SQL语句 cursor.execute("SELECT VERSION()") #使…

死锁示例代码

死锁产生实例&#xff0c;两个线程两个互斥锁&#xff0c;每个线程占有一个互斥锁&#xff0c;同时想获得另一个互斥锁则会产生死锁。解决方案&#xff1a;  1.一次占有全部资源  2.每个线程占有锁的顺序是一致的。比如都是同时占有A&#xff0c;然后占有B锁。#include <…

从服务器上的数据库备份到本地

可以直接在数据库进行操作&#xff1a;把需要备份的数据库选择生成脚本。生成脚本有一个高级选项&#xff0c;可以设置数据库架构跟数据。然后在本地新建一个跟备份数据库一样的数据库名称&#xff0c;进行运行脚本。就可以了。如果不新建一个数据库。就会报错。转载于:https:/…

Android之在ubuntu上用aapt查看apk的名字以及相关信息

第一步:如何快速找把手机里面的apk文件本地 如果我们不知道apk的包名,我们先打开需要的apk,然后再打开终端,输入下面命令获取这个apk的包名 adb shell dumpsys activity 查看当前apk的包名,有了包名,然后我们可以快快速获取apk,参照我的另外一篇博客 http://blog.csdn.n…

nodejs项目_多人群聊实现其实很简单:Nodejs+WebSocket+Vue轻松实现Web IM

点击右上方红色按钮关注“web秀”&#xff0c;让你真正秀起来前言在《Nodejs WebSocket简单介绍及示例 - 第一章》中简单的介绍了&#xff0c;Nodejs WebSocket的使用方法及作用&#xff0c;今天就用它来搭建一个简单的聊天室功能。1、NodejsWebSocket创建后台服务器功能 2、…

支付宝 .NET SDK 报错:RSA签名遭遇异常,请检查私钥格式是否正确。

AlipaySDKNet 是 .NET 平台下用于对接支付宝支付的官方 SDK。Alipay SDK for .NET 让您不用复杂编程即可访问支付宝开放平台开放的各项能力&#xff0c;SDK可以自动帮您满足能力调用过程中所需的证书校验、加签、验签、发送HTTP请求等非功能性要求。其 Nuget 链接如下&#xff…

交友软件上的两种网友类型......

1 轻轻松松月入五千的方法&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 像极了期末复习的你&#xff08;via.段子楼&#xff0c;侵删&#xff09;▼3 听说你想要中国的熊猫▼4 听说学校附近有野人出没&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼5…

meta标签的作用

meta标签共有两个属性&#xff0c;它们分别是http-equiv属性和name属性&#xff0c;不同的属性又有不同的参数值&#xff0c;这些不同的参数值就实现了不同的网页功能。 一.http-equiv属性 定义和用法&#xff1a;把 content 属性连接到 HTTP 头部&#xff0c;它可以向浏览器传…

iCloud7_Next Steps

下一步Next Steps 在此次指导中&#xff0c;你创建了一个复杂的iOS应用&#xff0c;使用iCloud保存它的文档。设计一个支持 iCloud 的应用程序&#xff0c;涉及很多决策&#xff0c;即使这样&#xff0c;本教程只触及表面&#xff08;浅层&#xff09;。当你继续了解集成 iClou…

operation 多线程

2.Cocoa Operation 优点&#xff1a;不需要关心线程管理&#xff0c;数据同步的事情。Cocoa Operation 相关的类是 NSOperation &#xff0c;NSOperationQueue。NSOperation是个抽象类&#xff0c;使用它必须用它的子类&#xff0c;可以实现它或者使用它定义好的两个子类&#…

Android插件化开发基础之Java动态代理(proxy)机制的简单例子

一、代码 package com.sangfor.tree;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;interface ProxyInterFace { public void proxyMethod(); } class TargetObject implements ProxyInterFace { publ…

python获取键盘事件_50-用Python监听鼠标和键盘事件

PyHook是一个基于Python的“钩子”库&#xff0c;主要用于监听当前电脑上鼠标和键盘的事件。这个库依赖于另一个Python库PyWin32&#xff0c;如同名字所显示的&#xff0c;PyWin32只能运行在Windows平台&#xff0c;所以PyHook也只能运行在Windows平台。 关于PyHook的使用&…

解读最新的 Xamarin 更新

微软中国MSDN 点击上方蓝字关注我们Good news——Visual Studio 2022 包括了 Xamarin 对 Android 12和苹果最新的 Xcode 13 版本下的 iOS、iPadOS、macOS 和 tvOS 的支持&#xff0c;以及适用于支持它们的最新 Xamarin.Forms 版本。让我们一起来了解下最新 Xamarin版本&#x…

原来医生的处方不是随便乱写的...

1 奇奇怪怪的知识又增加了&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 像极了早上刚睡醒炸毛的你▼3 原来医生的处方不是瞎写的▼4 当爷爷不当孙子&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼5 40厘米的身高差&#xff08;素材来源网络&#xf…

Uva 11400,照明系统设计

题目链接&#xff1a;https://uva.onlinejudge.org/external/114/11400.pdf 题意&#xff1a;有一个照明系统需要用到n种灯&#xff0c;每种灯的电压为V&#xff0c;电源费用K&#xff0c;每个灯泡费用为C&#xff0c;需要该灯的数量为L。注意到&#xff0c;电压相同的灯泡只需…

Android之解决Gigaset手机不能设置DeviceOwner权限提示already provisioned问题

客户那里有Gigaset手机,安装我们的产品需要注入DeviceOwner,但是刚恢复默认出厂的Gigaset手机很奇葩,注入权限的提示下面错误,导致不能使用我们的产品 设置DeviceOwner权限是有限制的,需要手机账号(acount)为0 1 我们用命令查看手机的帐号 adb shell dumpsys account …

Python-理解装饰器

文章先由stackoverflow上面的一个问题引起吧&#xff0c;如果使用如下的代码&#xff1a; makebold makeitalic def say():return "Hello" 打印出如下的输出&#xff1a; <b><i>Hello<i></b> 你会怎么做&#xff1f;最后给出的答案是&#x…

收集网络状态(Ping),并用邮件通知管理员

在没有第三方工具对网络进行监控的话&#xff0c;要检查网络中某台主机&#xff0c;或是某个IP地址通讯是否正常&#xff0c;我们通常用手动PING来进行测试。有了PowerShell&#xff0c;我们可以用他定时Ping网络上的几个IP地址&#xff0c;然后把ping的个延时时间用邮件通知给…

sql 某列数据全部为0则不显示该列_数据产品经理养成记(五):汇总分析

学会了如何查找数据后&#xff0c;接下来就要对数据进行分析处理&#xff0c;比如求和、平均值、加总等等。这些对数据的加工处理通过汇总函数来实现。汇总函数在之前的两篇文章中都有涉及&#xff0c;这里采用概念--案例--总结的方式&#xff0c;集中介绍一下。1.什么是汇总函…

如何通过 反射 调用某个对象的私有方法?

咨询区 Jeromy Irvine我的类中有一组私有方法&#xff0c;我现在想根据灵活的输入值来动态调用其中的私有方法&#xff0c;代码类似是这个样子。MethodInfo dynMethod this.GetType().GetMethod("Draw_" itemType); dynMethod.Invoke(this, new object[] { methodP…