http://www.15pb.com.cn/my/course/915
https://www.kanxue.com/book-section_list-73.htm
在开发阶段就嵌入安全代码&验证&混淆, 主要有以下几种方式
开启之后会将系统库名&类名全部修改为随机字符串
release默认开启, 在项目目录app下的build.gradle中有配置信息minifyEnabled
build.gradle(module)配置 minifyEnabled:true
配置完后进行编译, 反编译就能观察到变化
release 版本生成: Build--> Generate Signed Bundle/APK --> APK 生成
Proguard的混淆结果会输出到 <module-name>/build/outputs/mapping/release或者debug/
中的3个文件和一个混淆配置文件
proguard包括四个功能,shrinker
(压缩), optimizer
(优化),obfuscator
(混淆),preverifier
(预校验)。
想要自定义proguard混淆可以在 proguard-rules.pro文件中
通过对smali代码进行乱序来干扰代码逆向
实例
新建一个工程, 新建类Hello.java
禁用mutidex
编译生成APK
使用Android Killer(或者使用apktool)打开apk, 打开解压后的smali工程
观察Hello.smali代码如下(使用反汇编工具如jd和jadx也能够正常反汇编)
代码分为三部分, 对这三部分进行乱序
对smali代码进行乱序之后,重新编译, 在app-debug/build/dist/目录下生成apk
对smali代码进行乱序后,重新编译
然后dex2jar就不能用了,但是jadx可以识破
为了应对逆向分析中常用的字符串分析, 对使用到的字符串进行加密, 主要有如下方法:
普通方式定义字符串
反编译的smali代码
可以看到 "Hello World" 被直接内联到代码里面
编码混淆
反编译的smali代码
在演示过程中可以直接创建crc_value资源保存dex中的crc,然后与dex文件中的crc进行比对 在实践应用中可以从服务端远程获取crc校验值进行比较。
与dex校验不同, apk检验必须把计算好的Hash值放到网络服务器, 因为对APK的任何改动都会影响到最后的Hash值
签名校验工具: keytool , 解压apk/META_INF/CERT.RSA
Android安全人鱼对APK加固主要有以下几个方面
APK加固主要有如下技术点:
JAVA虚拟机类加载过程: ClassLoader负责把需要的类加载到内存中,然后进行实例化
类加载时机
安卓Dalvik/APT虚拟机类加载过程: 通过对android源码的分析, ClassLoader工作原理类似,用于加载jar和Dex
类加载器的种类和个数:一个运行的APP的生命周期中存在着多个ClassLoader, 所有的ClassLoader呈现出树状结构
代码查看ClassLoader层级:
输出
一个应用至少存在两个ClassLoader: BootClassLoader 和 PathClassLoader
创建自己的ClassLoader: 通常用于动态加载外部的dex文件中的Class, 需要创建的ClassLoader实例加入到ClassLoader树中(双亲代理模型)
双亲代理模型加载类的特点和作用:
JVM中ClassLoader通过defineClass方法加载jar中的Class
Android中ClassLoader通过loadClass
方法加载Dex中的Class, 根据ClassLoader.loadClass
源码分析,loadClass类加载流程如下
这种实现方式导致了如下类加载的特点:
测试:把一个简单类(不包含四大组件)的Dex加载并调用
ClassLoader是一个抽象类, 具体实现的类加载器有:
动态加载简单dex(不包含四大组件), 步骤如下:
dex代码:创建一个class动态设置view(没有四大组件), 方便动态加载成功时显示
创建新项目
新建类: 包名 右键--> new Class: MyTestClass
编译, 然后反编译apk
在逆向工程中把 MyTestClass.smali 单独提取出来, 删除其他所有不需要的smali文件
将仅含MyTestClass.smali的项目工程重新编译为dex文件
将生成的out.dex改名为 mytestclass.dex
在main文件夹 右键--> new Directory: assets (自定义资源目录) --> 把mytest.dex 复制过来
然后MyTestClass.java 就没用了,直接删除即可, 这样就保持了相同包名和项目结构
确认总体代码功能
MainActivity.java
加壳的流程: 修改application:name 优先启动壳, 壳加载 源dex 并调用Activity。 由于第一步很简单, 所以本节主要测试源dex加载和调用功能
新建空项目: Empty Activity
创建一个含有Activity的dex文件
新建一个Activity: 包名 右键 --> New Activity--> Empty Activity:Main2Activity
修改main布局文件
activity_main.xml
对应的按钮事件
activity_main2.xml
编译运行, 功能正常
编译程序,得到apk, 使用Android Killer/ apktool反编译得到项目
在逆向工程中把 Main2Activity.smali 单独提取出来, 删除其他所有不需要的smali文件
将仅含Main2Activity.smali的项目工程重新编译为dex文件
将生成的out.dex改名为 Main2Activity.dex
在main文件夹 右键--> new Directory: assets (自定义资源目录) --> 把mytest.dex 复制过来
然后Main2Activity.java 就没用了,直接删除即可(注意不能在IDE中删除,这样会把AndroidManifest中的Activity声明给删除) , 这样就保持了相同包名和项目结构
确认总体代码功能
MainActivity.java
此时编译运行会报错, 无法实例化Main2Activity
这是因为虽然我们用自己的DexClassLoader加载了Main2Activity, 但是startActivity等系统函数依然无法实例化Main2Activity, 系统函数是从程序的PathClassLoader中寻找并加载类的
解决方法:
直接使用PathClassLoader加载我们需要的类
使用ClassLoader替换掉PathClassLoader
分析Android源码, 利用java的反射机制修改保存的PathClassLoader
把自己的DexClassLoader 改为 PathClassLoader的父ClassLoader
树状结构 BootClassLoader <-- PathClassLoader <-- 自定义的DexClassLoader
参考
安卓源码分析: 应用程序的启动过程 https://blog.csdn.net/Luoshengyang/article/details/6689748
整个应用程序的启动过程要执行很多步骤,但是整体来看,主要分为以下五个阶段:
Step1 - Step 11:Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity;
Step 12 - Step 16:ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态;
Step 17 - Step 24:Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行;
Step 25 - Step 27:ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信;
Step 28 - Step 35:ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。
之前分析了整个app的调用过程, 现在详细分析onCreate调用栈,了解安卓启动过程中的关键数据
从外向内部分析,
如下显示如何一步一步的找到 PathClassLoader 变量, 方便后面修改
ActivityThread 唯一实例 被保存在静态变量ActivityThread.sCurrentActivityThread
该唯一实例可以通过调用静态函数直接获取
ActivityThread.currentActivityThread
ActivityThread 类中有一个实例成员变量 sCurrentActivityThread.mBoundApplication 变量类型为 AppBindData, 如下
含有包含了Apk信息的 sCurrentActivityThread.mBoundApplication.info 变量类型为LoadedApk
含有包含了Apk PathClassLoader的变量 sCurrentActivityThread.mBoundApplication.info.mClassLoader
值得注意的是, 在java中, 如果一个类没有定义toClone 函数自定义深拷贝, 默认类的赋值如 Test t1= new Test; Test t2=t1; 那么t2 与 t1 中的所有成员依然指向同一个引用。 所以修改了mClassLoader, 那么所有的mClassLoader都被修改了, 参考如下:
序列化来实现深拷贝
逐步获取如下
修改代码,替换classLoader为自定义的,这样startactivity 才能调用自定义的dex
运行
测试Activity加载功能完成
一般手工加密壳的实现(推荐后一种,因为代码实现的时候比较方便)
优化步骤如下(直接植入smali代码,省略了把傀儡dex重新打包的步骤)
新建一个工程: DummyDex
设置禁用mutidex
在清单文件中添加Application对象: .DummyApplication
在DummyApplication: attachBaseContext方法中添加加载原dex和替换ClassLoader的代码
使用apktool 反编译 傀儡dex, 保留smali代码
如果傀儡dex需要dex文件格式, 会生成out.dex
然后放到项目根目录下即可
自己创建一个demo.apk(不演示了), 以demo.apk为例开始手工加固
demo注意要是单dex文件
反编译apk
修改 AndroidManifest.xml的 application.name ,指向 壳dex: DummyApplication (可以从smali代码中找到类路径)
读取APK中的dex文件到assets目录, 并改名为src.dex
删除原有smali文件夹,拷贝傀儡dex的smali 文件到smali目录中(必须删除无用代码,防止重复)
使用apktool重新打包, 生成的apk在demo/dist
签名
安装测试
代码加固流程, 与手工加固流程相同(直接植入smali代码,省略了把傀儡dex重新打包的步骤)
参考: 设计傀儡dex)
java 命令执行的函数Runtime.getRuntime().exec有如下重载
IDEA新建工程: ApkPacker
Main.java 测试HelloWorld
封装命令执行工具类: CmdUtils.java
测试工具类的功能
Main.java
对apk进行反编译的功能
复制apktool.jar到项目目录下
测试功能
运行, 成功返回结果
封装文件类: FileUtils,用于获取文件的文件名和拓展名
打包功能
Main.java
运行测试, 生成app.apk
修改 AndroidManifest.xml的 application.name ,有就修改,没有就创建,指向 壳dex: DummyApplication
由于需要对xml文件进行操作, 使用xml解析库: org.dom4j:dom4j:2.0.0-RC1
右键项目 --> open module setting (记得勾选下载到这个项目模块)
导出启用
新建封装XML文件处理类: XMLUtils.java
运行测试
运行两次结果如下, 第一次出现如下1的android:name, 第二次出现meta-data
将原apk中的classes.dex放入assets目录并改名 assets/src.dex
封装Zip工具类: ZipUtils.java, 从apk中读取classes.dex
测试 Main.java
运行, 成功复制
把傀儡dex的smali代码复制,并改名为 dummyDexSmali
继续封装工具类 FileUtils.java , 用于1.递归删除原有的smali文件夹, 2.复制傀儡dex的smali文件夹
功能测试 Main.java
运行完后, 成功将dummydex的smali文件夹复制过去了
跟踪调试, 确保删除smali
把sign签名工具文件夹复制到项目目录下
image-20220506214045955
测试代码 Main.java
加固成功, 输出signed.apk
完整项目代码: https://github.com/overturncat/reverse_android/tree/master/ApkPacker
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'
),
'proguard-rules.pro'
}
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'
),
'proguard-rules.pro'
}
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'
),
'proguard-rules.pro'
}
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile(
'proguard-android-optimize.txt'
),
'proguard-rules.pro'
}
}
goto :sign1
:sign3
指令
3
goto :end
:sign2
指令
2
goto :sign3
:sign1
指令
1
goto :sign2
:end
goto :sign1
:sign3
指令
3
goto :end
:sign2
指令
2
goto :sign3
:sign1
指令
1
goto :sign2
:end
public
class
Hello {
public static void main(String[] argc){
/
/
字符串的声明和定义
String a
=
"1"
;
String b
=
"2"
;
/
/
字符串拼接
String c
=
a
+
b;
/
/
打印字符串
System.out.println(c);
}
}
public
class
Hello {
public static void main(String[] argc){
/
/
字符串的声明和定义
String a
=
"1"
;
String b
=
"2"
;
/
/
字符串拼接
String c
=
a
+
b;
/
/
打印字符串
System.out.println(c);
}
}
android {
compileSdkVersion
21
buildToolsVersion
"21.1.0"
defaultConfig {
...
minSdkVersion
14
targetSdkVersion
21
...
/
/
Enabling multidex support.
multiDexEnabled false
}
...
}
android {
compileSdkVersion
21
buildToolsVersion
"21.1.0"
defaultConfig {
...
minSdkVersion
14
targetSdkVersion
21
...
/
/
Enabling multidex support.
multiDexEnabled false
}
...
}
java
-
jar apktool_latest.jar d app
-
debug.apk
java
-
jar apktool_latest.jar d app
-
debug.apk
.
class
public Lcom
/
example
/
myapplication
/
Hello;
.
super
Ljava
/
lang
/
Object
;
.source
"Hello.java"
.method public constructor <init>()V
.
locals
0
.line
3
invoke
-
direct {p0}, Ljava
/
lang
/
Object
;
-
><init>()V
return
-
void
.end method
.method public static main([Ljava
/
lang
/
String;)V
/
/
字符串的声明和定义
.
locals
4
.param p0,
"argc"
.line
5
const
-
string v0,
"1"
.line
6
.local v0,
"a"
:Ljava
/
lang
/
String;
const
-
string v1,
"2"
/
/
字符串拼接
.line
7
.local v1,
"b"
:Ljava
/
lang
/
String;
new
-
instance v2, Ljava
/
lang
/
StringBuilder;
invoke
-
direct {v2}, Ljava
/
lang
/
StringBuilder;
-
><init>()V
invoke
-
virtual {v2, v0}, Ljava
/
lang
/
StringBuilder;
-
>append(Ljava
/
lang
/
String;)Ljava
/
lang
/
StringBuilder;
invoke
-
virtual {v2, v1}, Ljava
/
lang
/
StringBuilder;
-
>append(Ljava
/
lang
/
String;)Ljava
/
lang
/
StringBuilder;
invoke
-
virtual {v2}, Ljava
/
lang
/
StringBuilder;
-
>toString()Ljava
/
lang
/
String;
move
-
result
-
object
v2
/
/
打印字符串
.line
8
.local v2,
"c"
:Ljava
/
lang
/
String;
sget
-
object
v3, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
invoke
-
virtual {v3, v2}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
.line
9
return
-
void
.end method
.
class
public Lcom
/
example
/
myapplication
/
Hello;
.
super
Ljava
/
lang
/
Object
;
.source
"Hello.java"
.method public constructor <init>()V
.
locals
0
.line
3
invoke
-
direct {p0}, Ljava
/
lang
/
Object
;
-
><init>()V
return
-
void
.end method
.method public static main([Ljava
/
lang
/
String;)V
/
/
字符串的声明和定义
.
locals
4
.param p0,
"argc"
.line
5
const
-
string v0,
"1"
.line
6
.local v0,
"a"
:Ljava
/
lang
/
String;
const
-
string v1,
"2"
/
/
字符串拼接
.line
7
.local v1,
"b"
:Ljava
/
lang
/
String;
new
-
instance v2, Ljava
/
lang
/
StringBuilder;
invoke
-
direct {v2}, Ljava
/
lang
/
StringBuilder;
-
><init>()V
invoke
-
virtual {v2, v0}, Ljava
/
lang
/
StringBuilder;
-
>append(Ljava
/
lang
/
String;)Ljava
/
lang
/
StringBuilder;
invoke
-
virtual {v2, v1}, Ljava
/
lang
/
StringBuilder;
-
>append(Ljava
/
lang
/
String;)Ljava
/
lang
/
StringBuilder;
invoke
-
virtual {v2}, Ljava
/
lang
/
StringBuilder;
-
>toString()Ljava
/
lang
/
String;
move
-
result
-
object
v2
/
/
打印字符串
.line
8
.local v2,
"c"
:Ljava
/
lang
/
String;
sget
-
object
v3, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
invoke
-
virtual {v3, v2}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
.line
9
return
-
void
.end method
java
-
jar apktool_latest.jar b app
-
debug
java
-
jar apktool_latest.jar b app
-
debug
public
class
Hello {
public static void main(String[] argc){
String
str
=
"Hello World"
;
System.out.println(
str
);
}
}
public
class
Hello {
public static void main(String[] argc){
String
str
=
"Hello World"
;
System.out.println(
str
);
}
}
.method public static main([Ljava
/
lang
/
String;)V
.registers
3
.param p0,
"argc"
.line
5
const
-
string v0,
"Hello World"
.line
6
.local v0,
"str"
:Ljava
/
lang
/
String;
sget
-
object
v1, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
invoke
-
virtual {v1, v0}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
.line
7
return
-
void
.end method
.method public static main([Ljava
/
lang
/
String;)V
.registers
3
.param p0,
"argc"
.line
5
const
-
string v0,
"Hello World"
.line
6
.local v0,
"str"
:Ljava
/
lang
/
String;
sget
-
object
v1, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
invoke
-
virtual {v1, v0}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
.line
7
return
-
void
.end method
public
class
Hello {
public static void main(String[] argc){
byte[] strBytes
=
{
0x48
,
0x65
,
0x6c
,
0x6c
,
0x6f
,
0x57
,
0x6f
,
0x72
,
0x6c
,
0x64
};
String
str
=
new String(strBytes);
System.out.println(
str
);
}
}
public
class
Hello {
public static void main(String[] argc){
byte[] strBytes
=
{
0x48
,
0x65
,
0x6c
,
0x6c
,
0x6f
,
0x57
,
0x6f
,
0x72
,
0x6c
,
0x64
};
String
str
=
new String(strBytes);
System.out.println(
str
);
}
}
.method public static main([Ljava
/
lang
/
String;)V
.registers
4
.param p0,
"argc"
.line
5
const
/
16
v0,
0xa
new
-
array v0, v0, [B
fill
-
array
-
data v0, :array_12
.line
6
.local v0,
"strBytes"
:[B
new
-
instance v1, Ljava
/
lang
/
String;
invoke
-
direct {v1, v0}, Ljava
/
lang
/
String;
-
><init>([B)V
.line
7
.local v1,
"str"
:Ljava
/
lang
/
String;
sget
-
object
v2, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
invoke
-
virtual {v2, v1}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
.line
8
return
-
void
:array_12
.array
-
data
1
0x48t
0x65t
0x6ct
0x6ct
0x6ft
0x57t
0x6ft
0x72t
0x6ct
0x64t
.end array
-
data
.end method
.method public static main([Ljava
/
lang
/
String;)V
.registers
4
.param p0,
"argc"
.line
5
const
/
16
v0,
0xa
new
-
array v0, v0, [B
fill
-
array
-
data v0, :array_12
.line
6
.local v0,
"strBytes"
:[B
new
-
instance v1, Ljava
/
lang
/
String;
invoke
-
direct {v1, v0}, Ljava
/
lang
/
String;
-
><init>([B)V
.line
7
.local v1,
"str"
:Ljava
/
lang
/
String;
sget
-
object
v2, Ljava
/
lang
/
System;
-
>out:Ljava
/
io
/
PrintStream;
invoke
-
virtual {v2, v1}, Ljava
/
io
/
PrintStream;
-
>println(Ljava
/
lang
/
String;)V
.line
8
return
-
void
:array_12
.array
-
data
1
0x48t
0x65t
0x6ct
0x6ct
0x6ft
0x57t
0x6ft
0x72t
0x6ct
0x64t
.end array
-
data
.end method
/
*
校验Dex CRC值
*
/
private void verifyDex(){
/
/
获取String.xml中的value, 实践中应该联网获取用于比对的CRC值
Long
dexCrc
=
Long
.parseLong(this.getString(R.string.crc_value));
String apkPath
=
this.getPackageCodePath();
Log.d(TAG,
"verifyDex: PackageCodePath: "
+
apkPath);
try
{
ZipFile zipFile
=
new ZipFile(apkPath);
ZipEntry dexEntry
=
zipFile.getEntry(
"classes.dex"
);
/
/
计算classes.dex的crc
long
dexEntryCrc
=
dexEntry.getCrc();
Log.d(TAG,
"verifyDex: dexEntryCrc: "
+
dexEntryCrc);
if
(dexCrc
=
=
dexEntryCrc){
Log.d(TAG,
"dex has not been modified'"
);
}
else
{
Log.d(TAG,
"dex has been modified"
);
}
}catch (Exception e){
e.printStackTrace();
}
}
/
*
校验Dex CRC值
*
/
private void verifyDex(){
/
/
获取String.xml中的value, 实践中应该联网获取用于比对的CRC值
Long
dexCrc
=
Long
.parseLong(this.getString(R.string.crc_value));
String apkPath
=
this.getPackageCodePath();
Log.d(TAG,
"verifyDex: PackageCodePath: "
+
apkPath);
try
{
ZipFile zipFile
=
new ZipFile(apkPath);
ZipEntry dexEntry
=
zipFile.getEntry(
"classes.dex"
);
/
/
计算classes.dex的crc
long
dexEntryCrc
=
dexEntry.getCrc();
Log.d(TAG,
"verifyDex: dexEntryCrc: "
+
dexEntryCrc);
if
(dexCrc
=
=
dexEntryCrc){
Log.d(TAG,
"dex has not been modified'"
);
}
else
{
Log.d(TAG,
"dex has been modified"
);
}
}catch (Exception e){
e.printStackTrace();
}
}
/
*
校验APK MD5值
*
与dex校验不同, apk检验必须把计算好的
Hash
值放到网络服务器, 因为对APK的任何改动都会影响到最后的
Hash
值
*
*
/
private void verifyApk(){
/
/
/
data
/
app
/
com.example.myapplication
-
1
/
base.apk
String apkPath
=
this.getPackageCodePath();
MessageDigest msgDigest;
try
{
/
/
获取apk并计算MD5值
msgDigest
=
MessageDigest.getInstance(
"MD5"
);
byte[] bytes
=
new byte[
4096
];
int
count;
FileInputStream fis
=
new FileInputStream(new
File
(apkPath));
while
((count
=
fis.read(bytes)) >
0
){
msgDigest.update(bytes,
0
, count);
}
/
/
计算出MD5值
BigInteger bInt
=
new BigInteger(
1
, msgDigest.digest());
String md5
=
bInt.toString(
16
);
fis.close();
Log.d(TAG,
"verifyApk: md5: "
+
md5);
/
/
与服务端的MD5值进行对比
/
/
code ....
}catch (Exception e){
e.printStackTrace();
}
/
*
校验APK MD5值
*
与dex校验不同, apk检验必须把计算好的
Hash
值放到网络服务器, 因为对APK的任何改动都会影响到最后的
Hash
值
*
*
/
private void verifyApk(){
/
/
/
data
/
app
/
com.example.myapplication
-
1
/
base.apk
String apkPath
=
this.getPackageCodePath();
MessageDigest msgDigest;
try
{
/
/
获取apk并计算MD5值
msgDigest
=
MessageDigest.getInstance(
"MD5"
);
byte[] bytes
=
new byte[
4096
];
int
count;
FileInputStream fis
=
new FileInputStream(new
File
(apkPath));
while
((count
=
fis.read(bytes)) >
0
){
msgDigest.update(bytes,
0
, count);
}
/
/
计算出MD5值
BigInteger bInt
=
new BigInteger(
1
, msgDigest.digest());
String md5
=
bInt.toString(
16
);
fis.close();
Log.d(TAG,
"verifyApk: md5: "
+
md5);
/
/
与服务端的MD5值进行对比
/
/
code ....
}catch (Exception e){
e.printStackTrace();
}
/
*
获取签名的Md5
*
/
public void verifySignature(){
String packageName
=
this.getPackageName();
PackageManager pm
=
this.getPackageManager();
PackageInfo pi;
String md5
=
"";
try
{
pi
=
pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] s
=
pi.signatures;
/
/
计算出MD5值
MessageDigest msgDigest
=
MessageDigest.getInstance(
"MD5"
);
msgDigest.reset();
msgDigest.update(s[
0
].toByteArray());
BigInteger bInt
=
new BigInteger(
1
, msgDigest.digest());
md5
=
bInt.toString();
}catch(Exception e){
e.printStackTrace();
}
Log.d(TAG,
"verifySignature: md5"
+
md5);
/
/
与服务端的MD5值进行对比
/
/
code ....
}
/
*
获取签名的Md5
*
/
public void verifySignature(){
String packageName
=
this.getPackageName();
PackageManager pm
=
this.getPackageManager();
PackageInfo pi;
String md5
=
"";
try
{
pi
=
pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] s
=
pi.signatures;
/
/
计算出MD5值
MessageDigest msgDigest
=
MessageDigest.getInstance(
"MD5"
);
msgDigest.reset();
msgDigest.update(s[
0
].toByteArray());
BigInteger bInt
=
new BigInteger(
1
, msgDigest.digest());
md5
=
bInt.toString();
}catch(Exception e){
e.printStackTrace();
}
Log.d(TAG,
"verifySignature: md5"
+
md5);
/
/
与服务端的MD5值进行对比
/
/
code ....
}
keytool
-
printcert
-
file
CERT.RSA
keytool
-
printcert
-
file
CERT.RSA
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showClassloaderTree();
}
private static final String TAG
=
"ayuan-log"
;
public void showClassloaderTree(){
int
count
=
0
;
ClassLoader classLoader
=
getClassLoader();
while
(classLoader !
=
null){
Log.d(TAG,
"[onCreate ClassLoader]: "
+
count
+
": "
+
classLoader.toString());
classLoader
=
classLoader.getParent();
count
+
+
;
}
}
}
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showClassloaderTree();
}
private static final String TAG
=
"ayuan-log"
;
public void showClassloaderTree(){
int
count
=
0
;
ClassLoader classLoader
=
getClassLoader();
while
(classLoader !
=
null){
Log.d(TAG,
"[onCreate ClassLoader]: "
+
count
+
": "
+
classLoader.toString());
classLoader
=
classLoader.getParent();
count
+
+
;
}
}
}
[onCreate ClassLoader]:
0
: dalvik.system.PathClassLoader
[onCreate ClassLoader]:
1
: java.lang.BootClassLoader@
1ca27e8
[onCreate ClassLoader]:
0
: dalvik.system.PathClassLoader
[onCreate ClassLoader]:
1
: java.lang.BootClassLoader@
1ca27e8
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
/
/
1.
首先, 检查当前ClassLoader 是否已经加载该类了, 有就返回
Class<?> c
=
findLoadedClass(name);
if
(c
=
=
null) {
try
{
/
/
2.
如果没有, 查询 Parent_ClassLoader 是否加载该类了, 有就返回
if
(parent !
=
null) {
c
=
parent.loadClass(name, false);
}
else
{
c
=
findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
/
/
ClassNotFoundException thrown
if
class
not
found
/
/
from
the non
-
null parent
class
loader
}
/
/
3.
如果都没有加载过该类, 就通过 findCLass 通过名称加载该类
if
(c
=
=
null) {
/
/
If still
not
found, then invoke findClass
in
order
/
/
to find the
class
.
c
=
findClass(name);
}
}
return
c;
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
/
/
1.
首先, 检查当前ClassLoader 是否已经加载该类了, 有就返回
Class<?> c
=
findLoadedClass(name);
if
(c
=
=
null) {
try
{
/
/
2.
如果没有, 查询 Parent_ClassLoader 是否加载该类了, 有就返回
if
(parent !
=
null) {
c
=
parent.loadClass(name, false);
}
else
{
c
=
findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
/
/
ClassNotFoundException thrown
if
class
not
found
/
/
from
the non
-
null parent
class
loader
}
/
/
3.
如果都没有加载过该类, 就通过 findCLass 通过名称加载该类
if
(c
=
=
null) {
/
/
If still
not
found, then invoke findClass
in
order
/
/
to find the
class
.
c
=
findClass(name);
}
}
return
c;
}
public
class
MyTestClass {
public void createView(Activity ac){
/
/
创建布局,设置参数
FrameLayout.LayoutParams params
=
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT );
params.topMargin
=
0
;
params.gravity
=
Gravity.TOP | Gravity.CENTER_HORIZONTAL;
/
/
动态创建TextView
TextView tv
=
new TextView(ac);
tv.setText(
"MyTestClass Textview"
);
/
/
添加textVIew 到Activity中
ac.addContentView(tv, params);
}
}
public
class
MyTestClass {
public void createView(Activity ac){
/
/
创建布局,设置参数
FrameLayout.LayoutParams params
=
new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT );
params.topMargin
=
0
;
params.gravity
=
Gravity.TOP | Gravity.CENTER_HORIZONTAL;
/
/
动态创建TextView
TextView tv
=
new TextView(ac);
tv.setText(
"MyTestClass Textview"
);
/
/
添加textVIew 到Activity中
ac.addContentView(tv, params);
}
}
java
-
jar apktool_latest.jar d app
-
debug.apk
java
-
jar apktool_latest.jar d app
-
debug.apk
ayuan@ayuan
-
X5:~
/
Downloads
/
demo
/
app
-
debug$ java
-
jar smali.jar .
/
smali
ayuan@ayuan
-
X5:~
/
Downloads
/
demo
/
app
-
debug$ java
-
jar smali.jar .
/
smali
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,
"onCreate: MainActivity启动"
);
loadDex();
}
private static final String TAG
=
"ayuan-log"
;
private void loadDex() {
Log.d(TAG,
"loadDex: 开始加载"
);
/
/
1.
拷贝自定义资源(assets)的dex到程序目录下
String dexPath
=
copyDex(
"mytestclass.dex"
);
/
/
2.
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
DexClassLoader dexClassLoader
=
getLoader(dexPath);
/
/
3.
通过反射, 调用加载dex中的
class
方法
execClassMethod(
dexClassLoader,
/
/
classLoader
"com.example.myapplication.MyTestClass"
,
/
/
类名
"createView"
,
/
/
方法名
this);
/
/
参数: MainActivity.this
}
private void execClassMethod(DexClassLoader dexClassLoader,
/
/
类所属的ClassLoader
String className,
/
/
函数所处的类名
String methodName,
/
/
函数名
Activity as ) {
/
/
函数参数, 此处传入了MainActivity.this 用于显示
try
{
/
/
类类型
Class testDex
=
dexClassLoader.loadClass(className);
/
/
调用构造函数(无参构造), 获取实例 getConstructor(参数类型数组)
Constructor cons
=
testDex.getConstructor(new Class[]{});
/
/
调用构造方法创建对象, newInstance(参数数组)
Object
instance
=
cons.newInstance(new
Object
[]{});
/
/
获取成员方法
Method methodTest
=
testDex.getDeclaredMethod(methodName,
new Class[]{Activity.
class
});
/
/
取消java 访问检查
methodTest.setAccessible(true);
/
/
调用方法 invoke(实例, 方法参数数组)
methodTest.invoke(instance, new
Object
[]{as});
}catch(Exception e){
e.printStackTrace();
}
}
/
/
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
private DexClassLoader getLoader(String dexPath) {
DexClassLoader dexClassLoader
=
new DexClassLoader(
dexPath,
/
/
要加载的Dex文件路径
getCacheDir().toString(),
/
/
优化之后的文件路径
null,
/
/
native 库路径, 由于没有使用到native,null即可
getClassLoader()
/
/
父ClassLoader
);
Log.d(TAG,
"getLoader: "
+
dexClassLoader);
return
dexClassLoader;
}
/
/
拷贝assets
/
文件 到 app
/
files
/
下
private String copyDex(String dexName) {
/
/
获取assets目录管理器
AssetManager as
=
getAssets();
/
/
目的地址
String path
=
getFilesDir()
+
File
.separator
+
dexName;
/
/
/
data
/
user
/
0
/
com.example.myapplication
/
files
/
mytestclass.dex
Log.d(TAG,
"copyDex: path: "
+
path);
/
/
将文件拷贝到目的地址
try
{
/
/
创建文件流
FileOutputStream out
=
new FileOutputStream(path);
/
/
打开文件 assets
/
dexName
InputStream
is
=
as.
open
(dexName);
/
/
循环读取并写入文件
byte[]
buffer
=
new byte[
1024
];
int
len
=
0
;
while
((
len
=
is
.read(
buffer
))!
=
-
1
){
out.write(
buffer
,
0
,
len
);
}
/
/
关闭文件
out.close();
}catch(Exception e){
e.printStackTrace();
return
"";
}
return
path;
}
}
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,
"onCreate: MainActivity启动"
);
loadDex();
}
private static final String TAG
=
"ayuan-log"
;
private void loadDex() {
Log.d(TAG,
"loadDex: 开始加载"
);
/
/
1.
拷贝自定义资源(assets)的dex到程序目录下
String dexPath
=
copyDex(
"mytestclass.dex"
);
/
/
2.
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
DexClassLoader dexClassLoader
=
getLoader(dexPath);
/
/
3.
通过反射, 调用加载dex中的
class
方法
execClassMethod(
dexClassLoader,
/
/
classLoader
"com.example.myapplication.MyTestClass"
,
/
/
类名
"createView"
,
/
/
方法名
this);
/
/
参数: MainActivity.this
}
private void execClassMethod(DexClassLoader dexClassLoader,
/
/
类所属的ClassLoader
String className,
/
/
函数所处的类名
String methodName,
/
/
函数名
Activity as ) {
/
/
函数参数, 此处传入了MainActivity.this 用于显示
try
{
/
/
类类型
Class testDex
=
dexClassLoader.loadClass(className);
/
/
调用构造函数(无参构造), 获取实例 getConstructor(参数类型数组)
Constructor cons
=
testDex.getConstructor(new Class[]{});
/
/
调用构造方法创建对象, newInstance(参数数组)
Object
instance
=
cons.newInstance(new
Object
[]{});
/
/
获取成员方法
Method methodTest
=
testDex.getDeclaredMethod(methodName,
new Class[]{Activity.
class
});
/
/
取消java 访问检查
methodTest.setAccessible(true);
/
/
调用方法 invoke(实例, 方法参数数组)
methodTest.invoke(instance, new
Object
[]{as});
}catch(Exception e){
e.printStackTrace();
}
}
/
/
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
private DexClassLoader getLoader(String dexPath) {
DexClassLoader dexClassLoader
=
new DexClassLoader(
dexPath,
/
/
要加载的Dex文件路径
getCacheDir().toString(),
/
/
优化之后的文件路径
null,
/
/
native 库路径, 由于没有使用到native,null即可
getClassLoader()
/
/
父ClassLoader
);
Log.d(TAG,
"getLoader: "
+
dexClassLoader);
return
dexClassLoader;
}
/
/
拷贝assets
/
文件 到 app
/
files
/
下
private String copyDex(String dexName) {
/
/
获取assets目录管理器
AssetManager as
=
getAssets();
/
/
目的地址
String path
=
getFilesDir()
+
File
.separator
+
dexName;
/
/
/
data
/
user
/
0
/
com.example.myapplication
/
files
/
mytestclass.dex
Log.d(TAG,
"copyDex: path: "
+
path);
/
/
将文件拷贝到目的地址
try
{
/
/
创建文件流
FileOutputStream out
=
new FileOutputStream(path);
/
/
打开文件 assets
/
dexName
InputStream
is
=
as.
open
(dexName);
/
/
循环读取并写入文件
byte[]
buffer
=
new byte[
1024
];
int
len
=
0
;
while
((
len
=
is
.read(
buffer
))!
=
-
1
){
out.write(
buffer
,
0
,
len
);
}
/
/
关闭文件
out.close();
}catch(Exception e){
e.printStackTrace();
return
"";
}
return
path;
}
}
<LinearLayout xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:orientation
=
"vertical"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:layout_margin
=
"5dp"
android:padding
=
"5dp"
>
<LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"horizontal"
>
<Button
android:
id
=
"@+id/btn1"
android:onClick
=
"onClick"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_weight
=
"1"
android:text
=
"启动Activity"
/
>
<
/
LinearLayout>
<
/
LinearLayout>
<LinearLayout xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:orientation
=
"vertical"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:layout_margin
=
"5dp"
android:padding
=
"5dp"
>
<LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"horizontal"
>
<Button
android:
id
=
"@+id/btn1"
android:onClick
=
"onClick"
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_weight
=
"1"
android:text
=
"启动Activity"
/
>
<
/
LinearLayout>
<
/
LinearLayout>
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
Intent intent
=
new Intent(this, Main2Activity.
class
);
startActivity(intent);
}
}
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
Intent intent
=
new Intent(this, Main2Activity.
class
);
startActivity(intent);
}
}
<LinearLayout xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:orientation
=
"vertical"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:layout_margin
=
"5dp"
android:padding
=
"5dp"
>
<LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"horizontal"
>
<TextView
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"center_horizontal"
android:text
=
"第二个Activity"
android:textSize
=
"20dp"
/
>
<
/
LinearLayout>
<
/
LinearLayout>
<LinearLayout xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:orientation
=
"vertical"
android:layout_width
=
"match_parent"
android:layout_height
=
"match_parent"
android:layout_margin
=
"5dp"
android:padding
=
"5dp"
>
<LinearLayout
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
android:orientation
=
"horizontal"
>
<TextView
android:layout_width
=
"wrap_content"
android:layout_height
=
"wrap_content"
android:layout_gravity
=
"center_horizontal"
android:text
=
"第二个Activity"
android:textSize
=
"20dp"
/
>
<
/
LinearLayout>
<
/
LinearLayout>
java
-
jar apktool_latest.jar d app
-
debug.apk
java
-
jar apktool_latest.jar d app
-
debug.apk
java
-
jar smali.jar .
/
smali_classes3
java
-
jar smali.jar .
/
smali_classes3
public
class
MainActivity extends AppCompatActivity {
private static final String TAG
=
"ayuan-log"
;
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,
"onCreate: MainActivity启动"
);
}
public void onClick(View view) {
loadDex();
}
private void loadDex() {
Log.d(TAG,
"loadDex: 开始加载"
);
/
/
1.
拷贝自定义资源(assets)的dex到程序目录下
String dexPath
=
copyDex(
"Main2Activity.dex"
);
/
/
2.
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
DexClassLoader dexClassLoader
=
getLoader(dexPath);
/
/
3.
通过反射, 启动dex中的Activity
execClassMethod(
dexClassLoader,
/
/
classLoader
"com.example.myapplication.Main2Activity"
,
/
/
类名
"onCreate"
,
/
/
方法名
this);
/
/
参数: MainActivity.this
}
private void execClassMethod(DexClassLoader dexClassLoader,
/
/
类所属的ClassLoader
String className,
/
/
函数所处的类名
String methodName,
/
/
函数名
Activity as ) {
/
/
函数参数, 此处传入了MainActivity.this 用于显示
try
{
/
/
类类型
Class clazz
=
dexClassLoader.loadClass(className);
Intent intent
=
new Intent(MainActivity.this, clazz);
startActivity(intent);
/
/
调用构造函数(无参构造), 获取实例 getConstructor(参数类型数组)
/
/
Constructor cons
=
clazz.getConstructor(new Class[]{});
/
/
调用构造方法创建对象, newInstance(参数数组)
/
/
Object
instance
=
cons.newInstance(new
Object
[]{});
/
/
获取成员方法
/
/
Method methodTest
=
clazz.getDeclaredMethod(methodName, new Class[]{Activity.
class
});
/
/
取消java 访问检查
/
/
methodTest.setAccessible(true);
/
/
调用方法 invoke(实例, 方法参数数组)
/
/
methodTest.invoke(instance, new
Object
[]{as});
}catch(Exception e){
e.printStackTrace();
}
}
/
/
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
private DexClassLoader getLoader(String dexPath) {
DexClassLoader dexClassLoader
=
new DexClassLoader(
dexPath,
/
/
要加载的Dex文件路径
getCacheDir().toString(),
/
/
优化之后的文件路径
null,
/
/
native 库路径, 由于没有使用到native,null即可
getClassLoader()
/
/
父ClassLoader
);
Log.d(TAG,
"getLoader: "
+
dexClassLoader);
return
dexClassLoader;
}
/
/
拷贝assets
/
文件 到 app
/
files
/
下
private String copyDex(String dexName) {
/
/
获取assets目录管理器
AssetManager as
=
getAssets();
/
/
目的地址
String path
=
getFilesDir()
+
File
.separator
+
dexName;
/
/
/
data
/
user
/
0
/
com.example.myapplication
/
files
/
Main2Activity.dex
Log.d(TAG,
"copyDex: path: "
+
path);
/
/
将文件拷贝到目的地址
try
{
/
/
创建文件流
FileOutputStream out
=
new FileOutputStream(path);
/
/
打开文件 assets
/
dexName
InputStream
is
=
as.
open
(dexName);
/
/
循环读取并写入文件
byte[]
buffer
=
new byte[
1024
];
int
len
=
0
;
while
((
len
=
is
.read(
buffer
))!
=
-
1
){
out.write(
buffer
,
0
,
len
);
}
/
/
关闭文件
out.close();
}catch(Exception e){
e.printStackTrace();
return
"";
}
return
path;
}
}
public
class
MainActivity extends AppCompatActivity {
private static final String TAG
=
"ayuan-log"
;
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,
"onCreate: MainActivity启动"
);
}
public void onClick(View view) {
loadDex();
}
private void loadDex() {
Log.d(TAG,
"loadDex: 开始加载"
);
/
/
1.
拷贝自定义资源(assets)的dex到程序目录下
String dexPath
=
copyDex(
"Main2Activity.dex"
);
/
/
2.
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
DexClassLoader dexClassLoader
=
getLoader(dexPath);
/
/
3.
通过反射, 启动dex中的Activity
execClassMethod(
dexClassLoader,
/
/
classLoader
"com.example.myapplication.Main2Activity"
,
/
/
类名
"onCreate"
,
/
/
方法名
this);
/
/
参数: MainActivity.this
}
private void execClassMethod(DexClassLoader dexClassLoader,
/
/
类所属的ClassLoader
String className,
/
/
函数所处的类名
String methodName,
/
/
函数名
Activity as ) {
/
/
函数参数, 此处传入了MainActivity.this 用于显示
try
{
/
/
类类型
Class clazz
=
dexClassLoader.loadClass(className);
Intent intent
=
new Intent(MainActivity.this, clazz);
startActivity(intent);
/
/
调用构造函数(无参构造), 获取实例 getConstructor(参数类型数组)
/
/
Constructor cons
=
clazz.getConstructor(new Class[]{});
/
/
调用构造方法创建对象, newInstance(参数数组)
/
/
Object
instance
=
cons.newInstance(new
Object
[]{});
/
/
获取成员方法
/
/
Method methodTest
=
clazz.getDeclaredMethod(methodName, new Class[]{Activity.
class
});
/
/
取消java 访问检查
/
/
methodTest.setAccessible(true);
/
/
调用方法 invoke(实例, 方法参数数组)
/
/
methodTest.invoke(instance, new
Object
[]{as});
}catch(Exception e){
e.printStackTrace();
}
}
/
/
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
private DexClassLoader getLoader(String dexPath) {
DexClassLoader dexClassLoader
=
new DexClassLoader(
dexPath,
/
/
要加载的Dex文件路径
getCacheDir().toString(),
/
/
优化之后的文件路径
null,
/
/
native 库路径, 由于没有使用到native,null即可
getClassLoader()
/
/
父ClassLoader
);
Log.d(TAG,
"getLoader: "
+
dexClassLoader);
return
dexClassLoader;
}
/
/
拷贝assets
/
文件 到 app
/
files
/
下
private String copyDex(String dexName) {
/
/
获取assets目录管理器
AssetManager as
=
getAssets();
/
/
目的地址
String path
=
getFilesDir()
+
File
.separator
+
dexName;
/
/
/
data
/
user
/
0
/
com.example.myapplication
/
files
/
Main2Activity.dex
Log.d(TAG,
"copyDex: path: "
+
path);
/
/
将文件拷贝到目的地址
try
{
/
/
创建文件流
FileOutputStream out
=
new FileOutputStream(path);
/
/
打开文件 assets
/
dexName
InputStream
is
=
as.
open
(dexName);
/
/
循环读取并写入文件
byte[]
buffer
=
new byte[
1024
];
int
len
=
0
;
while
((
len
=
is
.read(
buffer
))!
=
-
1
){
out.write(
buffer
,
0
,
len
);
}
/
/
关闭文件
out.close();
}catch(Exception e){
e.printStackTrace();
return
"";
}
return
path;
}
}
Unable to instantiate activity ComponentInfo{com.example.myapplication
/
com.example.myapplication.Main2Activity}:
java.lang.ClassNotFoundException: Didn't find
class
"com.example.myapplication.Main2Activity"
on path: DexPathList[[
zip
file
"/data/app/com.example.myapplication-1/base.apk"
]
Unable to instantiate activity ComponentInfo{com.example.myapplication
/
com.example.myapplication.Main2Activity}:
java.lang.ClassNotFoundException: Didn't find
class
"com.example.myapplication.Main2Activity"
on path: DexPathList[[
zip
file
"/data/app/com.example.myapplication-1/base.apk"
]
onCreate:
11
, MainActivity (com.example.myapplication)
performCreate:
6679
, Activity (android.app)
callActivityOnCreate:
1118
, Instrumentation (android.app)
performLaunchActivity:
2618
, ActivityThread (android.app)
handleLaunchActivity:
2726
, ActivityThread (android.app)
-
wrap12:
-
1
, ActivityThread (android.app)
handleMessage:
1477
, ActivityThread$H (android.app)
dispatchMessage:
102
, Handler (android.os)
loop:
154
, Looper (android.os)
main:
6119
, ActivityThread (android.app)
invoke:
-
1
, Method (java.lang.reflect)
run:
886
, ZygoteInit$MethodAndArgsCaller (com.android.internal.os)
main:
776
, ZygoteInit (com.android.internal.os)
onCreate:
11
, MainActivity (com.example.myapplication)
performCreate:
6679
, Activity (android.app)
callActivityOnCreate:
1118
, Instrumentation (android.app)
performLaunchActivity:
2618
, ActivityThread (android.app)
handleLaunchActivity:
2726
, ActivityThread (android.app)
-
wrap12:
-
1
, ActivityThread (android.app)
handleMessage:
1477
, ActivityThread$H (android.app)
dispatchMessage:
102
, Handler (android.os)
loop:
154
, Looper (android.os)
main:
6119
, ActivityThread (android.app)
invoke:
-
1
, Method (java.lang.reflect)
run:
886
, ZygoteInit$MethodAndArgsCaller (com.android.internal.os)
main:
776
, ZygoteInit (com.android.internal.os)
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Test t1
=
new Test();
Test t2
=
t1;
t1.
str
=
"hello ayuan"
;
t1.number
=
2
;
/
/
onCreate: t1.
str
: hello ayuant2.
str
: hello ayuan
Log.d(TAG,
"onCreate: t1.str: "
+
t1.
str
+
"t2.str: "
+
t2.
str
);
/
/
onCreate: t1.number:
2t2
.number:
2
Log.d(TAG,
"onCreate: t1.number: "
+
t1.number
+
"t2.number: "
+
t2.number);
Log.d(TAG,
"onCreate: ------------------------------------------------"
);
}
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Test t1
=
new Test();
Test t2
=
t1;
t1.
str
=
"hello ayuan"
;
t1.number
=
2
;
/
/
onCreate: t1.
str
: hello ayuant2.
str
: hello ayuan
Log.d(TAG,
"onCreate: t1.str: "
+
t1.
str
+
"t2.str: "
+
t2.
str
);
/
/
onCreate: t1.number:
2t2
.number:
2
Log.d(TAG,
"onCreate: t1.number: "
+
t1.number
+
"t2.number: "
+
t2.number);
Log.d(TAG,
"onCreate: ------------------------------------------------"
);
}
1.
private static ActivityThread sCurrentActivityThread
=
ActivityThread.currentActivityThread()
2.
private ActivityThread.AppBinData mBoundApplication
=
sCurrentActivityThread 反射 mBoundApplication
3.
private LoadedApk info
=
mBoundApplication 反射 info
4.
private ClassLoader mClassLoader
=
info 反射 mClassLoader
1.
private static ActivityThread sCurrentActivityThread
=
ActivityThread.currentActivityThread()
2.
private ActivityThread.AppBinData mBoundApplication
=
sCurrentActivityThread 反射 mBoundApplication
3.
private LoadedApk info
=
mBoundApplication 反射 info
4.
private ClassLoader mClassLoader
=
info 反射 mClassLoader
public
class
MainActivity extends AppCompatActivity {
private static final String TAG
=
"ayuan-log"
;
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,
"onCreate: MainActivity启动"
);
}
public void onClick(View view) {
loadDex();
}
private void loadDex() {
Log.d(TAG,
"loadDex: 开始加载"
);
/
/
1.
拷贝自定义资源(assets)的dex到程序目录下
String dexPath
=
copyDex(
"Main2Activity.dex"
);
/
/
2.
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
DexClassLoader dexClassLoader
=
getLoader(dexPath);
/
/
3.
通过反射, 启动dex中的Activity
execClassMethod(
dexClassLoader,
/
/
classLoader
"com.example.myapplication.Main2Activity"
,
/
/
类名
"onCreate"
,
/
/
方法名
this);
/
/
参数: MainActivity.this
}
private void execClassMethod(DexClassLoader dexClassLoader,
/
/
类所属的ClassLoader
String className,
/
/
函数所处的类名
String methodName,
/
/
函数名
Activity as ) {
/
/
函数参数, 此处传入了MainActivity.this 用于显示
try
{
/
/
替换PathClassLoader为自定义classLoader, startActivity 底层会从PathClassLoader寻找加载的类
replaceClassLoader(dexClassLoader);
/
/
获取类类型, 构造Intent
Class clazz
=
dexClassLoader.loadClass(className);
Intent intent
=
new Intent(MainActivity.this, clazz);
startActivity(intent);
/
/
调用构造函数(无参构造), 获取实例 getConstructor(参数类型数组)
/
/
Constructor cons
=
clazz.getConstructor(new Class[]{});
/
/
调用构造方法创建对象, newInstance(参数数组)
/
/
Object
instance
=
cons.newInstance(new
Object
[]{});
/
/
获取成员方法
/
/
Method methodTest
=
clazz.getDeclaredMethod(methodName, new Class[]{Activity.
class
});
/
/
取消java 访问检查
/
/
methodTest.setAccessible(true);
/
/
调用方法 invoke(实例, 方法参数数组)
/
/
methodTest.invoke(instance, new
Object
[]{as});
}catch(Exception e){
e.printStackTrace();
}
}
private void replaceClassLoader(DexClassLoader dexClassLoader) {
try
{
/
/
1.
private static ActivityThread sCurrentActivityThread
=
ActivityThread.currentActivityThread()
/
/
获取类类型
Class clzActivityThread
=
Class.forName(
"android.app.ActivityThread"
);
/
/
获取静态方法
Method methodCurrentActivityThread
=
clzActivityThread.getDeclaredMethod(
"currentActivityThread"
);
/
/
调用静态方法, 返回静态成员变量
Object
sCurrentActivityThread
=
methodCurrentActivityThread.invoke(null, new
Object
[]{});
/
/
2.
private ActivityThread$AppBindData mBoundApplication
=
sCurrentActivityThread 反射 mBoundApplication
/
/
获取类类型
Class clzAppBinData
=
Class.forName(
"android.app.ActivityThread$AppBindData"
);
/
/
获取字段类型
Field fieldBoundApplication
=
clzActivityThread.getDeclaredField(
"mBoundApplication"
);
/
/
获取实例sCurrentActivityThread 对应的字段对象
fieldBoundApplication.setAccessible(true);
Object
mBoundApplication
=
fieldBoundApplication.get(sCurrentActivityThread);
/
/
3.
private LoadedApk info
=
mBoundApplication 反射 info
/
/
获取字段类类型
Class clzLoadedApk
=
Class.forName(
"android.app.LoadedApk"
);
/
/
获取字段类型
Field fieldInfo
=
clzAppBinData.getDeclaredField(
"info"
);
/
/
获取实例mBoundApplication 对应的字段对象
fieldInfo.setAccessible(true);
Object
info
=
fieldInfo.get(mBoundApplication);
/
/
4.
private ClassLoader mClassLoader
=
info 反射 mClassLoader
/
/
获取类类型
Class clzClassLoader
=
Class.forName(
"java.lang.ClassLoader"
);
/
/
获取字段类型
Field fieldClassLoader
=
clzLoadedApk.getDeclaredField(
"mClassLoader"
);
/
/
获取实例mBoundApplication 对应的字段对象
fieldClassLoader.setAccessible(true);
fieldClassLoader.
set
(info, dexClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
/
/
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
private DexClassLoader getLoader(String dexPath) {
DexClassLoader dexClassLoader
=
new DexClassLoader(
dexPath,
/
/
要加载的Dex文件路径
getCacheDir().toString(),
/
/
优化之后的文件路径
null,
/
/
native 库路径, 由于没有使用到native,null即可
getClassLoader()
/
/
父ClassLoader
);
Log.d(TAG,
"getLoader: "
+
dexClassLoader);
return
dexClassLoader;
}
/
/
拷贝assets
/
文件 到 app
/
files
/
下
private String copyDex(String dexName) {
/
/
获取assets目录管理器
AssetManager as
=
getAssets();
/
/
目的地址
String path
=
getFilesDir()
+
File
.separator
+
dexName;
/
/
/
data
/
user
/
0
/
com.example.myapplication
/
files
/
Main2Activity.dex
Log.d(TAG,
"copyDex: path: "
+
path);
/
/
将文件拷贝到目的地址
try
{
/
/
创建文件流
FileOutputStream out
=
new FileOutputStream(path);
/
/
打开文件 assets
/
dexName
InputStream
is
=
as.
open
(dexName);
/
/
循环读取并写入文件
byte[]
buffer
=
new byte[
1024
];
int
len
=
0
;
while
((
len
=
is
.read(
buffer
))!
=
-
1
){
out.write(
buffer
,
0
,
len
);
}
/
/
关闭文件
out.close();
}catch(Exception e){
e.printStackTrace();
return
"";
}
return
path;
}
}
public
class
MainActivity extends AppCompatActivity {
private static final String TAG
=
"ayuan-log"
;
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG,
"onCreate: MainActivity启动"
);
}
public void onClick(View view) {
loadDex();
}
private void loadDex() {
Log.d(TAG,
"loadDex: 开始加载"
);
/
/
1.
拷贝自定义资源(assets)的dex到程序目录下
String dexPath
=
copyDex(
"Main2Activity.dex"
);
/
/
2.
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
DexClassLoader dexClassLoader
=
getLoader(dexPath);
/
/
3.
通过反射, 启动dex中的Activity
execClassMethod(
dexClassLoader,
/
/
classLoader
"com.example.myapplication.Main2Activity"
,
/
/
类名
"onCreate"
,
/
/
方法名
this);
/
/
参数: MainActivity.this
}
private void execClassMethod(DexClassLoader dexClassLoader,
/
/
类所属的ClassLoader
String className,
/
/
函数所处的类名
String methodName,
/
/
函数名
Activity as ) {
/
/
函数参数, 此处传入了MainActivity.this 用于显示
try
{
/
/
替换PathClassLoader为自定义classLoader, startActivity 底层会从PathClassLoader寻找加载的类
replaceClassLoader(dexClassLoader);
/
/
获取类类型, 构造Intent
Class clazz
=
dexClassLoader.loadClass(className);
Intent intent
=
new Intent(MainActivity.this, clazz);
startActivity(intent);
/
/
调用构造函数(无参构造), 获取实例 getConstructor(参数类型数组)
/
/
Constructor cons
=
clazz.getConstructor(new Class[]{});
/
/
调用构造方法创建对象, newInstance(参数数组)
/
/
Object
instance
=
cons.newInstance(new
Object
[]{});
/
/
获取成员方法
/
/
Method methodTest
=
clazz.getDeclaredMethod(methodName, new Class[]{Activity.
class
});
/
/
取消java 访问检查
/
/
methodTest.setAccessible(true);
/
/
调用方法 invoke(实例, 方法参数数组)
/
/
methodTest.invoke(instance, new
Object
[]{as});
}catch(Exception e){
e.printStackTrace();
}
}
private void replaceClassLoader(DexClassLoader dexClassLoader) {
try
{
/
/
1.
private static ActivityThread sCurrentActivityThread
=
ActivityThread.currentActivityThread()
/
/
获取类类型
Class clzActivityThread
=
Class.forName(
"android.app.ActivityThread"
);
/
/
获取静态方法
Method methodCurrentActivityThread
=
clzActivityThread.getDeclaredMethod(
"currentActivityThread"
);
/
/
调用静态方法, 返回静态成员变量
Object
sCurrentActivityThread
=
methodCurrentActivityThread.invoke(null, new
Object
[]{});
/
/
2.
private ActivityThread$AppBindData mBoundApplication
=
sCurrentActivityThread 反射 mBoundApplication
/
/
获取类类型
Class clzAppBinData
=
Class.forName(
"android.app.ActivityThread$AppBindData"
);
/
/
获取字段类型
Field fieldBoundApplication
=
clzActivityThread.getDeclaredField(
"mBoundApplication"
);
/
/
获取实例sCurrentActivityThread 对应的字段对象
fieldBoundApplication.setAccessible(true);
Object
mBoundApplication
=
fieldBoundApplication.get(sCurrentActivityThread);
/
/
3.
private LoadedApk info
=
mBoundApplication 反射 info
/
/
获取字段类类型
Class clzLoadedApk
=
Class.forName(
"android.app.LoadedApk"
);
/
/
获取字段类型
Field fieldInfo
=
clzAppBinData.getDeclaredField(
"info"
);
/
/
获取实例mBoundApplication 对应的字段对象
fieldInfo.setAccessible(true);
Object
info
=
fieldInfo.get(mBoundApplication);
/
/
4.
private ClassLoader mClassLoader
=
info 反射 mClassLoader
/
/
获取类类型
Class clzClassLoader
=
Class.forName(
"java.lang.ClassLoader"
);
/
/
获取字段类型
Field fieldClassLoader
=
clzLoadedApk.getDeclaredField(
"mClassLoader"
);
/
/
获取实例mBoundApplication 对应的字段对象
fieldClassLoader.setAccessible(true);
fieldClassLoader.
set
(info, dexClassLoader);
} catch (Exception e) {
e.printStackTrace();
}
}
/
/
创建一个加载了该Dex文件的Dex类加载器, 可以反射获取其中的类和函数
private DexClassLoader getLoader(String dexPath) {
DexClassLoader dexClassLoader
=
new DexClassLoader(
dexPath,
/
/
要加载的Dex文件路径
getCacheDir().toString(),
/
/
优化之后的文件路径
null,
/
/
native 库路径, 由于没有使用到native,null即可
getClassLoader()
/
/
父ClassLoader
);
Log.d(TAG,
"getLoader: "
+
dexClassLoader);
return
dexClassLoader;
}
/
/
拷贝assets
/
文件 到 app
/
files
/
下
private String copyDex(String dexName) {
/
/
获取assets目录管理器
AssetManager as
=
getAssets();
/
/
目的地址
String path
=
getFilesDir()
+
File
.separator
+
dexName;
/
/
/
data
/
user
/
0
/
com.example.myapplication
/
files
/
Main2Activity.dex
Log.d(TAG,
"copyDex: path: "
+
path);
/
/
将文件拷贝到目的地址
try
{
/
/
创建文件流
FileOutputStream out
=
new FileOutputStream(path);
/
/
打开文件 assets
/
dexName
InputStream
is
=
as.
open
(dexName);
/
/
循环读取并写入文件
byte[]
buffer
=
new byte[
1024
];
int
len
=
0
;
while
((
len
=
is
.read(
buffer
))!
=
-
1
){
out.write(
buffer
,
0
,
len
);
}
/
/
关闭文件
out.close();
}catch(Exception e){
e.printStackTrace();
return
"";
}
return
path;
}
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)