字节码 反编译 APKTool 从新打jar包 MD

Markdown版本笔记 个人GitHub首页 个人博客 个人微信 个人邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

字节码 反编译 从新打jar包 MDjava


目录

如何修改一个 Jar 包中的内容

基本步骤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

  • 至此,就会在指定目录生成咱们修改后的新的jar包

反编译工具简介

最佳实践

主要使用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

apktool的使用:apk反编译生成程序的源代码和图片、XML配置、语言资源等文件框架

  • 解压获得3个文件:aapt.exe,apktool.bat,apktool.jar
  • 将须要反编译的APK文件放到该目录下
  • 打开命令行界面 ,定位到apktool文件夹,输入如下命令:【apktool.bat d -f】【test.apk bqt】(test.apk是要反编译的APK文件全名,能够直接拖拽过来,bqt为反编译后存放的目录)
  • 成功后发如今文件夹下多了个bqt文件夹,点击即可以查看该应用的全部资源文件了。
  • 若是你想将反编译完的文件从新打包成apk,输入apktool.bat b test,完成后在test文件下便多了2个文件夹:build和dist(存放着打包出来的APK文件)

官方的apktool工具只包含如下三个文件,只能使用CMD命令执行
eclipse

不过目前有不少从新打包后的版本,以下
编辑器

dex2jar、jd-gui、XJad

dex2jar,jd-gui,XJad的使用

  • 首先将apk文件后缀改成zip或rar并解压,获得其中的classes.dex,它就是java文件编译再经过dx工具打包而成的
  • 而后将classes.dex复制到d2j-dex2jar.bat(名称可能不太同样)所在目录文件夹中
  • 在命令行下定位到d2j-dex2jar.bat所在目录,运行ded2j-dex2jar.bat classes.dex
  • 最后,进入jdgui文件夹双击jd-gui.exe,打开上面生成的jar包classes_dex2jar.jar,便可看到源代码了
  • 不过,被混淆过的类文件名称以及里面的方法名称都会以a,b,c....之类的样式命名

XJad简介

  • XJad是基于Jad核心的Java源程序反编译软件,内置Jad1.5.8e2
  • 可处理多个*.class文件,能够处理文件夹内的全部文件,甚至能够处理*.jar文件;
  • 带有多页面文本编辑器,也可集成在资源管理器中,随时点击右键均可进行操做;
  • 支持java语法的高亮显示;

XJad使用说明

  • 打开一个或者多个*.class文件,XJad反编译后,重命名为*.java文件,保存至当前文件夹,并在编辑器中打开查看;
  • 打开一个文件夹,XJad将该文件夹下全部*.class文件进行反编译,并保存至该文件夹下,依据包路径信息生成文件夹路径,如com.spring.framework.*,将创建com\spring\framework的文件夹结构;
  • 打开一个*.jar文件,XJad将该Jar文件中的全部*.class文件解压缩到临时目录并进行反编译,并将源文件带包路径信息保存至当前文件夹下名称为“~” + *.jar的文件夹中;

AXMLPrinter2

做用:目前不少APP已经混淆,使用APKTOOL已经提取不出资源文件,使用这个工具能够将二进制的xml布局文件转化为正常的形式

弊端:须要一个一个的转换,且转换出来后有大量须要修改的地方

下载地址 网上流传的部分版本可能不能使用

使用步骤:

  • 更改apk后缀名为.rar并解压
  • 找到解压后res目录下的要参考的布局文件,并将此文件复制到和AXMLPrinter2同目录下
  • 打开cmd并定位到此目录下
  • 执行命令java -jar AXMLPrinter2.jar bqt.xml > bqt2.xml 即可把二进制的 bqt.xm文件反编译成能够阅读的bqt2.xml文件

eclipse 安装 jd-gui 插件

eclipse安装使用JD插件:

  • Download and unzip the JD-Eclipse Update Site,
  • Launch Eclipse,
  • Click on "Help > Install New Software...",
  • Click on button "Add..." to add an new repository,
  • Enter "JD-Eclipse Update Site" and select the local site directory,
  • Check "Java Decompiler Eclipse Plug-in",
  • Next, next, next... and restart Eclipse.Installation

注意,由于众所周知的缘由,不要选择联网更新之类的操做

借助 Javassist 修改 jar 包

背景

这是实际项目中遇到的一个问题: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 命令编译类的注意事项

在使用原始的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中的类,因此仍能够直接编译

引入 jar 包的状况

咱们在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

添加 package 的状况

咱们在A.javaB.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.javaB.java放入相应的包中:

而后直接编译B.java(注意要指定相对或绝对路径):

C:\Users\baiqi\Desktop\test>javac com\bqt\test\B.java

引入 jar 包并添加 package 的状况

咱们在上面第三种状况下再给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

相关文章
相关标签/搜索