Hook Android Gradle插件修改编译后的dex(转载)

Android Gradle学习(四):Project详解

https://www.jianshu.com/p/179fff0dd7df

本文着重介绍如何Hook Android Gradle插件的实现,涉及到的Gradle以及Groovy基础会稍微提下,具体可以参考文末给出的博客。

本文分为两部分:

1. Hook Android Gradle插件

2. 使用dexlib2修改编译中的dex文件--修改dex中的函数名字

1. Hook Android Gradle插件

Android Gradle Plugin可以简单的理解为是由好多task组成的,这么多task组成一个task graph。在build apk的时候有的task负责编译每个class,有的task负责组成dex,有的task负责打包生成apk。这些task按照task graph上的顺序依次执行生成一个apk。

Executing tasks: [:app:assembleDebug]
:app:preBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:compileDebugAidl UP-TO-DATE
:app:compileDebugRenderscript UP-TO-DATE
:app:checkDebugManifest UP-TO-DATE
:app:generateDebugBuildConfig UP-TO-DATE
:app:prepareLintJar UP-TO-DATE
:app:mainApkListPersistenceDebug UP-TO-DATE
:app:generateDebugResValues UP-TO-DATE
:app:generateDebugResources UP-TO-DATE
:app:mergeDebugResources UP-TO-DATE
:app:createDebugCompatibleScreenManifests UP-TO-DATE
:app:processDebugManifest UP-TO-DATE
:app:splitsDiscoveryTaskDebug UP-TO-DATE
:app:processDebugResources UP-TO-DATE
:app:generateDebugSources UP-TO-DATE
:app:javaPreCompileDebug
:app:compileDebugJavaWithJavac
:app:compileDebugNdk NO-SOURCE
:app:compileDebugSources
:app:mergeDebugShaders
:app:compileDebugShaders
:app:generateDebugAsset
:app:mergeDebugAssets
:app:transformClassesWithDexBuilderForDebug
:app:transformDexArchiveWithExternalLibsDexMergerForDebug
:app:transformDexArchiveWithDexMergerForDebug
:app:mergeDebugJniLibFolders
:app:transformNativeLibsWithMergeJniLibsForDebug
:app:processDebugJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForDebug
:app:validateSigningDebug
:app:packageDebug
:app:assembleDebug
BUILD SUCCESSFUL in 8s
26 actionable tasks: 13 executed, 13 up-to-date

我们的目的是在Android gradle plugin自己build apk的时候hook transformDexArchiveWithDexMergerForDebug这个task,这个task可以在上图中找到,Android Gradle Plugin会把各个class编译生成dex,transformDexArchiveWithDexMergerForDebug这个task的作用就是把生成的各个dex组合成一个dex。所以我们看中了这个task,只要hook它,可以在它生成整体的dex后使用dexlib2更改其中的class或者method。

首先要先掌握如何自定义Android Gradle插件,这样把它打包上传后就可以在其他Android工程中直接使用了,很方便,这一步可以直接参考这个博客,写的很详细,一步一步按着做即可。在AndroidStudio中自定义Gradle插件 这个同学写的几篇关于Gradle插件的文章都可以看看,写的不错的。这个链接给出的教程只是开发一个独立的Android gradle插件,这个插件里面只有一个task,就是输出一句话而已,我们在直接工程中加入apply plugin:'XXXXX',再指定这个plugin的路径,build的时候就会输出这句话。这个插件的作用就是在Android gradle插件build project的时候加上了一个task。但是这个task是一个独立的task,并没有对Android gradle插件中的task作用,Android插件该怎么build怎么build,没有更改它的task graph,也谈不上hook。

下面直接上代码了,上面链接中已经讲述了如何生成一个Android gradle plugin。直接在上面代码的基础上改:

public class MyPlugin implements Plugin<Project> {

    void apply(Project project1) {
        System.out.println("========================");
        System.out.println("Hello gradle plugin!");
        System.out.println("========================");
        project1.afterEvaluate { project ->
            project.tasks.transformDexArchiveWithDexMergerForDebug << {
                println 'add my own step from plugin'
                //Get the inputs of this task
                project.tasks.transformDexArchiveWithDexMergerForDebug.getInputs().getFiles().collect().each { element1 ->
                    println "inputs " + element1
                }
                //Get the outputs of this task
                project.tasks.transformDexArchiveWithDexMergerForDebug.getOutputs().getFiles().collect().each() { element ->
                    def file = new File(element.toString())
                    def files = file.listFiles()
                    def files2 = files[0].listFiles()
                    String dexfilepath = files2[0]
                    println "Outputs Dex file's path: "+dexfilepath
                    //Modify the dex 可先注释掉
                    testRewrite(dexfilepath)
                }
            }
        }
    }
}

上述代码的作用很简单,里面的变量project就是Android Gradle plugin的project,然后它执行到transformDexArchiveWithDexMergerForDebug这个task的时候就加上自己的一个action,这个action很简单就是打印这个task的输入和输出,然后testRewrite()函数就是用来更改这个task生成的dex的。看一下我们hook的结果。

Executing tasks: [:app:assembleDebug]
========================
Hello gradle plugin!
========================
The Task.leftShift(Closure) method has been deprecated and is scheduled to be removed in Gradle 5.0. Please use Task.doLast(Action) instead.
:app:preBuild UP-TO-DATE
//省略了部分task
:app:transformClassesWithDexBuilderForDebug
:app:transformDexArchiveWithExternalLibsDexMergerForDebug
//这个task就是被我们hook的task
:app:transformDexArchiveWithDexMergerForDebug
add my own step from plugin
//很多inputs 这里只列出了一部分
inputs /Users/xxx/AndroidStudioProjects/gradleTest/app/build/intermediates/transforms/dexBuilder/debug/7.jar
inputs /Users/xxx/AndroidStudioProjects/gradleTest/app/build/intermediates/transforms/dexBuilder/debug/6.jar
inputs /Users/xxx/AndroidStudioProjects/gradleTest/app/build/intermediates/transforms/dexBuilder/debug/19/com/example/dean/gradletest/R$bool.dex
inputs /Users/xxx/AndroidStudioProjects/gradleTest/app/build/intermediates/transforms/dexBuilder/debug/19/com/example/dean/gradletest/R$integer.dex
inputs /Users/xxx/AndroidStudioProjects/gradleTest/app/build/intermediates/transforms/externalLibsDexMerger/debug/0/classes.dex
//output只是一个dex
Outputs Dex file's path: /Users/xxxAndroidStudioProjects/gradleTest/app/build/intermediates/transforms/dexMerger/debug/0/classes.dex
:app:mergeDebugJniLibFolders
:app:transformNativeLibsWithMergeJniLibsForDebug
:app:processDebugJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForDebug
:app:validateSigningDebug
:app:packageDebug
:app:assembleDebug
BUILD SUCCESSFUL in 4s
26 actionable tasks: 13 executed, 13 up-to-date

通过这种方式我们可以hook Android Gradle Plugin中的任意一个task。

2. 修改dex文件--修改dex中的函数名字

这里就需要用到dexlib2这个工具了,直接在自定义插件的build.gradle中的dependency中加入compile group: 'org.smali', name: 'dexlib2', version: '2.2.4',如下所示。注:不要自行下载jar包加载,这样这个jar包无法打包到自己的插件里,会出现bug。

dependencies {
    // https://mvnrepository.com/artifact/org.smali/dexlib2
    compile group: 'org.smali', name: 'dexlib2', version: '2.2.4'
    //gradle sdk
    compile gradleApi()
    //groovy sdk
    compile localGroovy()
}

在自定义插件类的统一文件中加入以下代码

 static void testRewrite(String dexfilepath){
        DexFile dexFile
        try {
            //把要修改的dex load进来
            dexFile = DexFileFactory.loadDexFile(dexfilepath, Opcodes.getDefault())
            println "dexFile: " + dexFile.getClass().getName()
            DexRewriter rewriter = new DexRewriter(new RewriterModule() {
                @Override
                Rewriter<org.jf.dexlib2.iface.Method> getMethodRewriter(
                        @Nonnull Rewriters rewriters) {
                    return new MyMethod()
                }
            })
            //删除原dex
            DexFile rewrittenDexFile = rewriter.rewriteDexFile(dexFile);
            File olddex = new File(dexfilepath)
            if(olddex.exists()){
                println "delete original dex"
                olddex.delete()
            }
            //生成新dex
            DexFileFactory.writeDexFile(dexfilepath, rewrittenDexFile);
        } catch (IOException e) {
            println "failed"
            e.printStackTrace();
        }
    }
}
//修改dex中的method
class MyMethod implements Rewriter<org.jf.dexlib2.iface.Method> {
    @Nonnull
    @Override
    org.jf.dexlib2.iface.Method rewrite(@Nonnull final org.jf.dexlib2.iface.Method value) {
       //找到 helloMethod 
        if (value.getName().contains("helloMethod")) {
            println "rewrite: "+value.getName()
            return new org.jf.dexlib2.iface.Method() {
                @Override
                public int compareTo(@Nonnull MethodReference o) {
                    return value.compareTo(o);
                }
                @Nonnull
                @Override
                public List<? extends CharSequence> getParameterTypes() {
                    return value.getParameters();
                }
                @Nonnull
                @Override
                public String getDefiningClass() {
                    return value.getDefiningClass();
                }
                @Nonnull
                @Override
                public List<? extends MethodParameter> getParameters() {
                    return value.getParameters();
                }
                @Nonnull
                @Override
                public String getReturnType() {
                    return value.getReturnType();
                }
                @Override
                public int getAccessFlags() {
                    return value.getAccessFlags();
                }
                @javax.annotation.Nullable
                @Override
                public MethodImplementation getImplementation() {
                    return value.getImplementation();
                }
                @Nonnull
                @Override
                public Set<? extends org.jf.dexlib2.iface.Annotation> getAnnotations() {
                    return value.getAnnotations();
                }
                @Nonnull
                @Override
                //将helloMethod重命名为MyMethod
                public String getName() {
                    return "MyMethod"
                }
            };
        }
        return value;
    }
}

Bingo!!!
最后, 同学点个赞吧!!! 加个关注好么
Gradle for Android(一) 使用Gradle和Android Studio 这个人写了一系列,可以看看。
晚点有时间我会上传到git上。

源码:https://github.com/chuanweizhang2013/androidGradlePlugin

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容