Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Ivonhoe/DexGuard

Open more actions menu

Repository files navigation

TODO LIST

  • plugin支持增量编译
  • 以子之矛攻子之盾

如何使用

  • 在root project的build.gradle中添加依赖classpath 'ivonhoe.gradle.dexguard:dexguard-gradle:0.0.4-SNAPSHOT'
buildscript {
    repositories {
        maven { url 'https://raw.githubusercontent.com/Ivonhoe/mvn-repo/master/' }
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
        classpath 'ivonhoe.gradle.dexguard:dexguard-gradle:0.0.4-SNAPSHOT'
    }
}
  • 在app项目的build.gradle中添加插件,map.txt中配置需要保护的方法名
apply plugin: 'ivonhoe.dexguard'
dexguard {
    guardConfig = "${rootDir}/map.txt"
}

一、概述

上一篇文章,我大致分析了美团外卖Android客户端是如何防止其Java代码被dex2jar工具转换,而做的防护,那就是将有语法错误的字节码插入到想要保护的Java函数中中,借助dex2jar的语法检查机制,以达到dex2jar转换出错的目的。这篇文章就大致记录下如何开发Gradle编译插件,在编译过程中实现上述防护思路。

二、思路

先看下Android APK打包流程:

Android apk打包流程

Android APK打包流程如上图所示,Java代码先通过Java Compiler生成.class文件,在通过dx工具生成dex文件,最后使用apkbuilder工具完成代码与资源文件的打包,并使用jarsigner签名,最后可能还有使用zipalign对签名后的apk做对齐处理。

如果需要完成对特定函数的代码注入,可以在Java代码编译生成class文件后,在dex文件生成前,针对class字节码进行操作,以本例为例需要动态生成Exsit类文件的字节码(不清楚Exsit的作用可以看上一篇文章)。

// 动态生成Exist.class
public class Exist {
    public static boolean a() {
        return false;
    }

    public static void b(int test) {
    }
}

动态修改特定方法的字节码,将下列Java代码转换成字节码插入特定的函数中。

// 插入到特定的Java函数内
Exist.b(Exist.a());

并将修改后的.class文件放入dex打包目录中,完成dex打包,具体流程如下图所示:

Gradle提供了叫Transform的API,允许三方插件在class文件转换为dex文件前操作编译好的class文件,这个API的目标就是简化class文件的自定义的操作而不用对Task进行处理,并且可以更加灵活地进行操作。详细的可以参考区长的博客

四、ASM操作Java字节码

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直 接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。这里推荐一个IDEA插件:ASM ByteCode Outline。可以查看.class文件的字节码,并可以生成成ASM框架代码。安装ASM Bytecode Outline插件后,可以在Intellij IDEA->Code->Show Bytecode Outline查看类文件对应个字节码和ASM框架代码,利用ASM框架代码就可以生成相应的.class文件了。

生成Exist字节码的具体实现,生成Exist.java的构造函数:

ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;

cw.visit(51, ACC_PUBLIC + ACC_SUPER, "ivonhoe/dexguard/java/Exist", null, "java/lang/Object", null);

cw.visitSource("Exist.java", null);

mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(7, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(RETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable("this", "Livonhoe/dexguard/java/Exist;", null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
        

声明一个函数名为a,返回值为boolean类型的无参函数:

mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "a", "()Z", null, null);
mv.visitCode();
l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(10, l0);
mv.visitInsn(ICONST_0);
mv.visitInsn(IRETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();

声明一个函数名为b,参数为int型,返回类型为void的函数

MV = CW.VISITmETHOD(acc_public + acc_static, "b", "(i)v", NULL, NULL);
MV.VISITcODE();
L0 = NEW lABEL();
MV.VISITlABEL(L0);
MV.VISITlINEnUMBER(14, L0);
MV.VISITiNSN(return);
L1 = NEW lABEL();
MV.VISITlABEL(L1);
MV.VISITlOCALvARIABLE("TEST", "i", NULL, L0, L1, 0);
MV.VISITmAXS(0, 1);
MV.VISITeND();

在指定函数内,插入Exist.b(Exist.a());对应的字节码的具体实现,绕过Java编译器的语法检查:

//refer hack class when object init
private static byte[] referHackWhenInit(InputStream inputStream, String methodName) {
    ClassReader cr = new ClassReader(inputStream);
    ClassWriter cw = new ClassWriter(cr, 0);

    ClassVisitor cv = new InjectCassVisitor(Opcodes.ASM4, cw, methodName);
    cr.accept(cv, 0);
    return cw.toByteArray();
}
static class InjectClassVisitor extends ClassVisitor {

        private String methodName;

        InjectClassVisitor(int i, ClassVisitor classVisitor, String method) {
            super(i, classVisitor)

            this.methodName = method;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc,
                                         String signature, String[] exceptions) {

            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
            mv = new MethodVisitor(Opcodes.ASM4, mv) {

                @Override
                void visitCode() {
                    // 在方法体开始调用时
                    if (name.equals(methodName)) {
                        mv.visitMethodInsn(INVOKESTATIC, "ivonhoe/dexguard/java/Exist", "a", "()Z", false);
                        mv.visitMethodInsn(INVOKESTATIC, "ivonhoe/dexguard/java/Exist", "b", "(I)V", false);
                    }
                    super.visitCode()
                }

                @Override
                public void visitMaxs(int maxStack, int maxLocal) {
                    if (name.equals(methodName)) {
                        super.visitMaxs(maxStack + 1, maxLocal);
                    } else {
                        super.visitMaxs(maxStack, maxLocal);
                    }
                }
            }
            return mv;
        }
    }

五、源码

详细的Gradle源码和实例可参考https://github.com/Ivonhoe/dexguard

六、参考文档

Android 热修复使用Gradle Plugin1.5改造Nuwa插件

ASM-操作字节码初探

手摸手增加字节码往方法体内插代码

About

Android app防dex2jar的gradle插件

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.