Markdown版本笔记 | 个人GitHub首页 | 个人博客 | 个人微信 | 个人邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
字节码 反编译 从新打jar包 MDjava
基本步骤android
JD-GUI
打开 jar
文件,这时你能够看到源码(多是混淆过的源码)class
文件保存为 java
文件(也能够一键保存整个jar
包中全部的Java
文件)java
文件javac
命令将该 java
文件编译为 class
文件这一步实际上是有问题的,通常状况下不可能编译成功,由于这个java文件极可能会引用到其余你提供不了源码的类,好比 Android 中的 Context,好比第三方库中的类
固然这种状况下你能够经过新建一个Java或Android或JavaEE工程,并将此Java类以及此Java类所引用的类、jar包等引入进来,这样能够解决引用问题
可是使用这种方式的工做量每每远超预期,由于此Java类所引用的类极可能还会引用更多的Java类,所以会致使你这个工程引入的类呈指数级增加git
Winrar
等解压工具将 jar
包解压到指定目录,替换须要修改的 class
文件cd
命令定位到要打包的目录,使用 jar -cvf bqt.jar .
或jar cvf bqt.jar *
命令从新打包注意。打包时要把
META-INF
也包含进去,不然虽然能打包成功,但使用时就会出错。github
主要使用Android逆向助手
来操做:spring
反编译apk
,完成后res下的全部资源就均可以正常使用了,至关于apktool的功能------目前已失效,可是直接用rar解压是能够的提取dex
,能够提取出apk文件中的.dex文件,若是有多个.dex文件,则只会提取第一个;建议手动把.apk更改后缀后解压并拷出.dex文件dex转jar
,至关于dex2jar的功能jd打开jar
,能够自动使用jd-gui
打开jar文件,如今已经能够查看源码了jd-gui
中不能修改源码,可点击把全部源码保存起来后就能修改了AXMLPrinter2
提取、转化XML文件经常使用工具:sql
apktool
:提取res目录下的【资源】文件(drawable、layout、anim、color等)dex2jar
:将【.dex】文件(可执行文件)转化为【.jar】文件jd-gui
:将编译后的【.jar】或【.class】文件以【.java】源码格式查看AXMLPrinter2
,做用:在apktool搞不定的状况下获取xml布局文件--目前apktool已失效,但本工具还能用XJad
:和jd-gui做用相似,是一款Java源程序反编译软件Android逆向助手
:整合以上工具的而成的一个图形化工具集官网及下载地址:微信
apktool的使用:apk反编译生成程序的源代码和图片、XML配置、语言资源等文件框架
官方的apktool工具只包含如下三个文件,只能使用CMD命令执行
eclipse
不过目前有不少从新打包后的版本,以下
编辑器
dex2jar,jd-gui,XJad的使用
XJad简介
Jad1.5.8e2
;*.class
文件,能够处理文件夹内的全部文件,甚至能够处理*.jar
文件;XJad使用说明
*.class
文件,XJad反编译后,重命名为*.java
文件,保存至当前文件夹,并在编辑器中打开查看;*.class
文件进行反编译,并保存至该文件夹下,依据包路径信息生成文件夹路径,如com.spring.framework.*
,将创建com\spring\framework
的文件夹结构;*.jar
文件,XJad将该Jar文件中的全部*.class
文件解压缩到临时目录并进行反编译,并将源文件带包路径信息保存至当前文件夹下名称为“~” + *.jar
的文件夹中;做用:目前不少APP已经混淆,使用APKTOOL已经提取不出资源文件,使用这个工具能够将二进制的xml布局文件转化为正常的形式
弊端:须要一个一个的转换,且转换出来后有大量须要修改的地方
下载地址 网上流传的部分版本可能不能使用
使用步骤:
java -jar AXMLPrinter2.jar bqt.xml > bqt2.xml
即可把二进制的 bqt.xm
文件反编译成能够阅读的bqt2.xml
文件eclipse安装使用JD插件:
注意,由于众所周知的缘由,不要选择联网更新之类的操做
这是实际项目中遇到的一个问题:lib目录下某个第三方jar包中的某个方法的返回值不符合咱们要求,而这个方法在不少地方被调用到,咱们怎么修改这个方法的返回值呢?
可能有不少解决方法,好比动态代理,好比AOP,可是这些方案都有不少问题。好比动态代理,咱们这个方法是一个普通的方法,使用JDK的动态代理是行不通的,而cglib在Android中是用不了的;而对于AOP,我也确实尝试了,但发现使用aspectj时,对于混淆过的jar包中的方法老是拦截不成功(固然,使用沪江封装的aspectjX是能够的)。另一个问题是,使用这些方案对项目改动都是很是大的,特别是引用AOP这些框架,因为很小的一点改动就这么大动干戈的话有点不合适。
那有没有其余方法呢?
其实有一个最简单的方案,那就是反编译,咱们只须要将jar包中那个相应的class文件中方法修改一下就能够了。
可是正如上面描述的那样,这种方式是有局限性的!可是若是借助 Javassist
去作的话,问题就至关简单了。
好比,我想将Gson库中Gson类的下面的方法
public String toJson(Object src) { if (src == null) { return toJson(JsonNull.INSTANCE); } return toJson(src, src.getClass()); }
修改成:
public String toJson(Object src) { if (src == null) { return ""; } return src.toString(); }
那么我只需执行如下逻辑就能够了:
public class Test { public static void main(String[] args) { try { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath("D:/test/gson-2.8.1.jar");//加载指定路径下的库。若是此库已被引入项目中,能够省略这一步 CtClass cc = pool.get("com.google.gson.Gson");//加载指定的类 CtClass[] params = new CtClass[] { pool.get("java.lang.Object") };//基本类型和引用类型的描述方式是不同的 CtMethod method = cc.getDeclaredMethod("toJson", params);//取得须要修改的方法 method.setBody("{" + // 你只须要正常写代码逻辑就能够了,复制过来时,一些IDE,好比AS会自动帮你添加转义字符 "if ($1 == null) {\n" + //$0表明的是this,$1表明方法参数的第一个参数、$2表明方法参数的第二个参数 "\treturn \"\";\n" + // "}\n" + // "return $1.toString();" + // "}"); //修改方法 cc.writeFile("D:/test");//保存到指定位置,执行完这一步后就会在指定目录生成你须要的 class 文件 } catch (Exception e) { e.printStackTrace(); } } }
执行后就会在指定目录生成你须要的 class
文件:
而后咱们按照上面的步骤打成jar包(注意最后的一个.
表明的是当前路径):
D:\test>jar -cvf mygson.jar .
要打包的路径下的文件:
而后咱们就可使用咱们修改后的Gson库了,反编译后里面的逻辑以下:
一、上面这种方式的思路是,在编译期
修改指定Java
类编译后生成的class
文件,而后从新打成jar
包。
二、其实还有另一种思路,那就是不在编译期处理,而在运行期
处理,也即咱们能够在运行期先修改、而后直接加载修改后的class
文件。
通过在Android和Java工程中测试发现,这种方式也是很是靠谱的。
三、在代码层面,和上面那种方式的区别是,咱们最后不是调用
cc.writeFile("D:/test");
方法将修改后的class
文件保存到指定位置,而是调用
cc.toClass();
方法返回、并加载此新生成的class
文件。
四、可是这种方式有一个局限性。
由于同一个 class 是不能在同个 ClassLoader 中被加载两次的,因此在调用 toClass()
前要保证此要修改的类不能是被加载过的类,不然直接报异常。
五、可是这种状况下也并非无解的。
咱们能够自定义一个 ClassLoader
去加载新生成的类:
Class clazz = classLoader.loadClass("com.bqt.test.Person")
六、可是要知道,若是这样作的话,此时JVM中是有两个Person类的,而且除非是经过返回的这个 clazz
建立的对象,其使用的是新生成的类:
clazz.getDeclaredMethod("hello", String.class).invoke(clazz.newInstance(), "你妹"); //调用的是新类的方法
其余任何方式建立的对象,使用的还是原始的类(包括经过反射方式):
new Person().hello("泥煤"); //调用的是原始类的方法 Class clazz2 = Class.forName("com.bqt.test.Person"); clazz2.getDeclaredMethod("hello", String.class).invoke(clazz2.newInstance(), "你美"); //调用原始类的方法
在使用原始的javac
编译Java类文件时,若是当前类文件对其余类有依赖,那么就可能会出现问题。
举例以下:在桌面新建一个文件夹test,而后创建两个类:A.java和B.java,两个类的代码都很简单,其中B类对A类有依赖:
import java.sql.Date; public class A { public static void i(Date date) { System.out.println(date.toString()); } }
import java.sql.Date; public class B { public static void i(String info) { Date date = new Date(System.currentTimeMillis()); A.i(date); } }
直接用javac命令编译两个源文件,且先编译 A.java
,结果以下:
C:\Users\baiqi>cd/d C:\Users\baiqi\Desktop\test C:\Users\baiqi\Desktop\test>javac A.java C:\Users\baiqi\Desktop\test>javac B.java
能够看到编译成功,生成了对应的class文件。
注意:
A.java
状况下直接编译 B.java
,则会同时
生成A和B对应的class文件。import java.sql.Date;
但由于是JDK中的类,因此仍能够直接编译咱们在B.java
中利用import语句导入一个包:
import java.sql.Date; import com.google.gson.Gson; public class B { public static void i(String info) { Date date = new Date(System.currentTimeMillis()); System.out.println(new Gson().toJson(date)); A.i(date); } }
而后直接编译B.java
:
C:\Users\baiqi\Desktop\test>javac B.java B.java:2: 错误: 程序包com.google.gson不存在 import com.google.gson.Gson; ^ B.java:7: 错误: 找不到符号 System.out.println(new Gson().toJson(date)); ^ 符号: 类 Gson 位置: 类 B 2 个错误
能够看到,B.java
文件编译失败,这是由于Gson并不是Java标准类库中的内容,所以编译器找不到对应的包,就会出错。
咱们将须要的jar
文件放入当前目录,并使用-cp
参数将库文件导入,而后继续编译:
C:\Users\baiqi\Desktop\test>javac -cp gson-2.8.1.jar B.java B.java:8: 错误: 找不到符号 A.i(date); ^ 符号: 变量 A 位置: 类 B 1 个错误
怎么提示找不到 A 呢?
这是由于,默认状况下,编译器会在当前目录
下寻找须要的类文件,可是若是咱们使用cp
参数修改了类文件查找路径,而并无包含当前目录
,那么就会编译失败。由于咱们在使用cp
参数时,须要将当前目录包含进去:
C:\Users\baiqi\Desktop\test>javac -cp gson-2.8.1.jar;. B.java
咱们在A.java
和B.java
文件中添加package
语句:
package com.bqt.test; import java.sql.Date; public class A { public static void i(Date date) { System.out.println(date.toString()); } }
package com.bqt.test; import java.sql.Date; public class B { public static void i(String info) { Date date = new Date(System.currentTimeMillis()); A.i(date); } }
而后咱们将A.java
和B.java
放入相应的包中:
而后直接编译B.java
(注意要指定相对或绝对路径):
C:\Users\baiqi\Desktop\test>javac com\bqt\test\B.java
咱们在上面第三种状况下再给B引入jar包:
package com.bqt.test; import java.sql.Date; import com.google.gson.Gson; public class B { public static void i(String info) { Date date = new Date(System.currentTimeMillis()); System.out.println(new Gson().toJson(date)); A.i(date); } }
一样,命令也结合以上两种形式便可:
C:\Users\baiqi\Desktop\test>javac -cp gson-2.8.1.jar;. com\bqt\test\B.java
2019-1-6