本文着重介绍如何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上。