首页
社区
课程
招聘
[原创] Android 简单加密壳
发表于: 2022-8-1 02:09 37757

[原创] Android 简单加密壳

2022-8-1 02:09
37757

http://www.15pb.com.cn/my/course/915

https://www.kanxue.com/book-section_list-73.htm

在开发阶段就嵌入安全代码&验证&混淆, 主要有以下几种方式

image-20220415104418372

开启之后会将系统库名&类名全部修改为随机字符串

image-20220417170224448

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个文件和一个混淆配置文件

image-20220417164326902

proguard包括四个功能,shrinker(压缩), optimizer(优化),obfuscator(混淆),preverifier(预校验)。

想要自定义proguard混淆可以在 proguard-rules.pro文件中

通过对smali代码进行乱序来干扰代码逆向

实例

新建一个工程, 新建类Hello.java

image-20220417220751389

禁用mutidex

编译生成APK

使用Android Killer(或者使用apktool)打开apk, 打开解压后的smali工程

观察Hello.smali代码如下(使用反汇编工具如jd和jadx也能够正常反汇编)

代码分为三部分, 对这三部分进行乱序

image-20220417223420422

对smali代码进行乱序之后,重新编译, 在app-debug/build/dist/目录下生成apk

对smali代码进行乱序后,重新编译

image-20211127184447788

然后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负责把需要的类加载到内存中,然后进行实例化

image-20220530105819361

类加载时机

image-20220530132322920

安卓Dalvik/APT虚拟机类加载过程: 通过对android源码的分析, ClassLoader工作原理类似,用于加载jar和Dex

类加载器的种类和个数:一个运行的APP的生命周期中存在着多个ClassLoader, 所有的ClassLoader呈现出树状结构

image-20220428214241228

image-20220530132835022

代码查看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文件

image-20220418203555018

将仅含MyTestClass.smali的项目工程重新编译为dex文件

将生成的out.dex改名为 mytestclass.dex

在main文件夹 右键--> new Directory: assets (自定义资源目录) --> 把mytest.dex 复制过来

image-20220418204309400

然后MyTestClass.java 就没用了,直接删除即可, 这样就保持了相同包名和项目结构

确认总体代码功能

MainActivity.java

image-20220421101246593

加壳的流程: 修改application:name 优先启动壳, 壳加载 源dex 并调用Activity。 由于第一步很简单, 所以本节主要测试源dex加载和调用功能

新建空项目: Empty Activity

创建一个含有Activity的dex文件

新建一个Activity: 包名 右键 --> New Activity--> Empty Activity:Main2Activity

image-20220421104230711

修改main布局文件

activity_main.xml

对应的按钮事件

activity_main2.xml

编译运行, 功能正常

编译程序,得到apk, 使用Android Killer/ apktool反编译得到项目

在逆向工程中把 Main2Activity.smali 单独提取出来, 删除其他所有不需要的smali文件

image-20220418203555018

将仅含Main2Activity.smali的项目工程重新编译为dex文件

将生成的out.dex改名为 Main2Activity.dex

在main文件夹 右键--> new Directory: assets (自定义资源目录) --> 把mytest.dex 复制过来

image-20220418204309400

然后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调用栈,了解安卓启动过程中的关键数据

image-20220428102526529

从外向内部分析,

如下显示如何一步一步的找到 PathClassLoader 变量, 方便后面修改

ActivityThread 唯一实例 被保存在静态变量ActivityThread.sCurrentActivityThread

image-20220428221450527

该唯一实例可以通过调用静态函数直接获取

ActivityThread.currentActivityThread

image-20220428221729280

ActivityThread 类中有一个实例成员变量 sCurrentActivityThread.mBoundApplication 变量类型为 AppBindData, 如下

image-20220429141530905

image-20220429141325036

含有包含了Apk信息的 sCurrentActivityThread.mBoundApplication.info 变量类型为LoadedApk

image-20220429141853100

含有包含了Apk PathClassLoader的变量 sCurrentActivityThread.mBoundApplication.info.mClassLoader

值得注意的是, 在java中, 如果一个类没有定义toClone 函数自定义深拷贝, 默认类的赋值如 Test t1= new Test; Test t2=t1; 那么t2 与 t1 中的所有成员依然指向同一个引用。 所以修改了mClassLoader, 那么所有的mClassLoader都被修改了, 参考如下:

序列化来实现深拷贝

逐步获取如下

修改代码,替换classLoader为自定义的,这样startactivity 才能调用自定义的dex

运行

image-20220429160631564

测试Activity加载功能完成

一般手工加密壳的实现(推荐后一种,因为代码实现的时候比较方便)

优化步骤如下(直接植入smali代码,省略了把傀儡dex重新打包的步骤)

新建一个工程: DummyDex

image-20220503211024854

设置禁用mutidex

在清单文件中添加Application对象: .DummyApplication

image-20220503211249890

在DummyApplication: attachBaseContext方法中添加加载原dex和替换ClassLoader的代码

使用apktool 反编译 傀儡dex, 保留smali代码

image-20220503220344017

如果傀儡dex需要dex文件格式, 会生成out.dex

然后放到项目根目录下即可

image-20220718201457718

自己创建一个demo.apk(不演示了), 以demo.apk为例开始手工加固

demo注意要是单dex文件

反编译apk

修改 AndroidManifest.xml的 application.name ,指向 壳dex: DummyApplication (可以从smali代码中找到类路径)

image-20220504003247673

读取APK中的dex文件到assets目录, 并改名为src.dex

image-20220504004215784

删除原有smali文件夹,拷贝傀儡dex的smali 文件到smali目录中(必须删除无用代码,防止重复

image-20220504005030592

使用apktool重新打包, 生成的apk在demo/dist

签名

安装测试

代码加固流程, 与手工加固流程相同(直接植入smali代码,省略了把傀儡dex重新打包的步骤)

参考: 设计傀儡dex)

java 命令执行的函数Runtime.getRuntime().exec有如下重载

IDEA新建工程: ApkPacker

image-20220505152827778

Main.java 测试HelloWorld

封装命令执行工具类: CmdUtils.java

image-20220505154035825

测试工具类的功能

Main.java

对apk进行反编译的功能

复制apktool.jar到项目目录下

image-20220505163548559

测试功能

运行, 成功返回结果

image-20220505212352640

封装文件类: FileUtils,用于获取文件的文件名和拓展名

打包功能

Main.java

运行测试, 生成app.apk

修改 AndroidManifest.xml的 application.name ,有就修改,没有就创建,指向 壳dex: DummyApplication

由于需要对xml文件进行操作, 使用xml解析库: org.dom4j:dom4j:2.0.0-RC1

右键项目 --> open module setting (记得勾选下载到这个项目模块)

image-20220505222435096

导出启用

image-20220505222804881

新建封装XML文件处理类: XMLUtils.java

image-20220505223120143

运行测试

运行两次结果如下, 第一次出现如下1的android:name, 第二次出现meta-data

image-20220506145004763

将原apk中的classes.dex放入assets目录并改名 assets/src.dex

封装Zip工具类: ZipUtils.java, 从apk中读取classes.dex

测试 Main.java

运行, 成功复制

image-20220506160612007

把傀儡dex的smali代码复制,并改名为 dummyDexSmali

image-20220506211843457

继续封装工具类 FileUtils.java , 用于1.递归删除原有的smali文件夹, 2.复制傀儡dex的smali文件夹

功能测试 Main.java

运行完后, 成功将dummydex的smali文件夹复制过去了

image-20220506211556558

跟踪调试, 确保删除smali

把sign签名工具文件夹复制到项目目录下

image-20220506214045955

image-20220506214057285image-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"
 
 
# direct methods
.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"    # [Ljava/lang/String;
 
    .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"
 
 
# direct methods
.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"    # [Ljava/lang/String;
 
    .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"    # [Ljava/lang/String;
 
    .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"    # [Ljava/lang/String;
 
    .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"    # [Ljava/lang/String;
 
    .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"    # [Ljava/lang/String;
 
    .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期)

收藏
免费 12
支持
分享
最新回复 (7)
雪    币: 4439
活跃值: (6681)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
图片看不到
2022-8-1 08:44
0
雪    币: 2155
活跃值: (3216)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
3
请整理一下,图片已经挂了
2022-8-3 10:37
0
雪    币: 859
活跃值: (915)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
nice ths
2022-8-6 16:58
0
雪    币: 455
活跃值: (3885)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
5
LoadedApk 方式的不太靠谱吧, 遇到分包不太好处理
2022-8-28 02:22
0
雪    币: 429
活跃值: (665)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
我只是没人要 LoadedApk 方式的不太靠谱吧, 遇到分包不太好处理
是的,这种方法需要手动关闭apk的分包功能,请问有比较好的处理方法吗
2022-9-30 10:00
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
厉害了 感谢分享
2022-10-1 23:50
0
游客
登录 | 注册 方可回帖
返回
//