欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

快速修改字节码并重打jar包

程序员文章站 2024-02-02 21:05:58
...

背景

不管是做Android项目还是Java后端Web项目,我们一般都会引用各种三方库。遇到特殊需求时,可能需要修改jar包中的代码。本文以实际示例讲解一些基本方法,方便大家快速入坑。

首先我们都知道直接解压jar包的话,都是class二进制文件,打开后是看不见代码的。之所以可以在开发环境中直接查看jar中的代码是因为IDE已经帮你反编译好了,才能看见Java源码。

入坑

如何修改这些jar包中的代码逻辑呢?大致有两种思路:

A、反编译class为java源文件 -> 修改Java源码 -> 重新编译成class文件 -> 将新的class文件覆盖jar包中的原有文件

B、解压得到class文件 -> 直接修改其中的二进制代码(即对应的字节码) -> 将修改后的class文件覆盖jar包中的原有文件

注: 下文基于Java 8环境。

A方法

这种方法也是最常见的方式,也相对较简单,但有一些弊端,后面会讲到。我们直接按步骤来走一遍。

1、先下载 JD-GUI 最新版,得益于Java的跨平台特性,各操作系统都能跑。如果是Windows系统,直接运行exe即可,若是Linux,可以下载deb包进行安装,也可直接java命令运行jar,macOS也是类似,命令运行就行。

2、通过JD-GUI我们打开想要修改的三方库jar包:

快速修改字节码并重打jar包

比如这里有一个Util类,其中有一个获取手机IMEI的方法,我想把它改成永远返回空字符串。先把此class文件导出成java文件(选择Save后JD-GUI将自动反编译):

快速修改字节码并重打jar包

3、修改Java代码,注意一点就是,在修改这种带参方法时,为了不影响外部调用出错,我们依然保留参数:

快速修改字节码并重打jar包

4、保存后我们将刚才的jar包和java文件放在同一个文件,执行命令:

javac -classpath test_a.jar Util.java

恭喜你,你肯定编译失败,会提示各种符号错误。因为这个类import了Android SDK中的相关包,原有的test_a.jar包是不包含的。所以我们同时需要 原jar包 和额外依赖的 android.jar (在你Android SDK目录下的platforms/android-xx文件夹中,xx表示API版本,一般选最新的就好)一起放在同一个目录。

javac -Djava.ext.dirs=/d/Downloads/test Util.java

5、编译产物就是Util.class文件,此时我们用压缩软件打开原test_a.jar包,把新的class文件复制粘贴到对应位置(一般来说用鼠标直接拖进去即可):

快速修改字节码并重打jar包

6、检验一下,用JD-GUI重新打开jar包,看看那个方法:

快速修改字节码并重打jar包

说明修改成功。

B方法

上述A方法适用于普通的jar包修改,如果一旦jar包在发布编译时, 进行了代码混淆 ,那么解压修改再重新编译就很可能失败。此时我们若能直接修改class文件中的字节码,那就可以无视混淆了。

比如这里我想修改test_b.jar中的一个方法b,让其永远返回空字符串:

快速修改字节码并重打jar包

直接解压jar包,用文本编辑器打开class文件后会发现都是十六进制字符,看不懂呀,我又不是机器:

快速修改字节码并重打jar包

所以我们需要可以翻译这些编码的工具,有很多类似的。

1、下载 JBE ,它也支持Win和Unix等系统平台,解压后运行其中的bat或sh脚本即可打开。然后我们打开需要修改的class文件:

快速修改字节码并重打jar包

从这里可以看见class文件中的各种常量池、成员变量和方法等等。找到我们需要修改的方法所对应的code。可以看见字节码指令已经被清楚地翻译出来。

2、选择Code Editor可以直接修改这些指令。这里简单地介绍一下这里涉及到的指令,ldc将int, float或String型常量值从常量池中推送至栈顶,astore将栈顶引用型数值存入指定本地变量,aload将指定的引用类型本地变量复制到栈顶。后面的数字表示操作第几个常量或变量。

所以这里我们的第0个就是入参,即context,第1个就是defaultAndroidId,以此类推。

ldc ""
astore_1 // defaultAndroidId变量赋值
ldc ""
astore_2 // androidId变量赋值
aload_0 // 开始引用context
invokevirtual android/content/Context/getContentResolver()Landroid/content/ContentResolver; // 并调用它的getContentResolver方法
ldc "android_id"
invokestatic android/provider/Settings$Secure/getString(Landroid/content/ContentResolver;Ljava/lang/String;)Ljava/lang/String; // 传入常量"android_id",并调用getString方法
astore_2 // 将上述方法调用返回结果赋值给androidId变量
aload_2 // 引用已赋值的变量,传入下面的isEmpty方法
invokestatic android/text/TextUtils/isEmpty(Ljava/lang/CharSequence;)Z
ifeq 15 // 若判断返回true则跳转第15行
ldc ""
astore_2
aload_2 // 这里是第15行指令
areturn

3、搞清楚含义之后,就可以为所欲为了。直接删掉相关逻辑,让方法直接返回空字符串:

快速修改字节码并重打jar包

4、点击Save method后,将此修改后的class粘贴到原来的jar包中覆盖旧class即可(和A方法中的最后步骤一样)。用JD-GUI打开检验一下:

快速修改字节码并重打jar包

其他注意事项

1、我最开始用的Java 13作为编译环境,但13的javac命令已经不支持-Djava.ext.dirs,目前暂时没有探究替代方法,所以又换回了Java 8。

2、在修改jar包时要确定其编译时所用的Java版本,我们需要与之一致,用JBE可以查看Major Version:

快速修改字节码并重打jar包

3、关于字节码指令大全,建议大家参考官方文档(JVM规范,见参考链接),网上搜出来的二次资料可能不保险。

参考

相关标签: Android Program