在实际应用中反编译有下面这几种用途:
1.测试应用的安全性。
这无疑是最重要的,不过难度很高需要专门做安全的人来做才有效果。
2.反编译别的的apk
可以获取别的资源文件。如果要破解代码,那难度就更高了。
3.混淆查看效果
还有一个就是混淆看有没有效果,或者混淆出问题了。
通常的用法是通过AS生成release版本的apk,然后通过apktool反编译获取到资源文件。通过dex2jar将classes.dex(可能存在多个)转化为classes.jar。再通过jd-gui打开jar文件查看反编译的源码。
不过这个过程还是比较麻烦的,有些步骤都是可以自动完成的。通过写自动脚本就可以将这些全部串联起来。
0.目的
这篇博客的主要目的是实战。在Android开发过程中有时候我们需要反编译并查看反编译后的源码。这篇博客的目的就是要实现这个过程的自动化。
我们先来做一下分析。
第一步:定位需要处理的apk文件
一般我们生成带签名的release apk,默认目录是app/release/app-release.apk。
一般路径不太会更改,但apk的名字可能会更改,一般会加上日期。
例如:app/release/app-release-1970-01-01.apk
第二步:生成反编译文件到build目录
为了查看方便,我们不希望我们生成的反编译文件到处乱放。
我们默认放到app/build/decompile/app-release-1970-01-01 这样的目录。
第三步:查看反编译的jar源码
调用jd-gui查看反编译的jar源码
下面我们将根据这个目的介绍这三个反编译工具的使用。其实这三个工具使用都非常简单。自动化执行节约时间才是关键。
1.下载
三个工具都是开源免费的。
apktool:主要的反编译工具,可以将apk包反编译,但不能反编译dex文件。
dex2jar:就是用来反编译dex
jd-gui:java源代码查看工具,用于查看直接查看jar包的java源码。
apktool:https://ibotpeaches.github.io/Apktool/
dex2jar:/pxb1988/dex2jar
jd-gui:/java-decompiler/jd-gui/releases
2.使用
2.1 apktool的使用
执行下面的命令就会反编译
java -jar apktool.jar d pathTo/app-release.apk -o pathTo/outputPath
生成的目录就是上面这个样子。这里我们主要关心两个目录
res目录:
一个是res目录,这里面有我们的资源文件,通常是盗用别人的图标。
smali目录:
还有一个是smali文件,里面全是.smali结尾的文件。这个其实就是我们的源码。但是是一种叫smali的字节码。当然我们是看不懂的,这时候可以通过dex2jar工具转化成jar。后面会介绍。
一键代码实现
通过java调用shell命令或者cmd命令来执行apktool.jar,因为jar文件是跨平台的,所以命令代码是一样的。
下面的代码只是实现了apktool的最基本命令。
/*** 调用apktool*/public static void doApkTool() {try {File file=new File("app/build/decompile");if (file.exists()){FileUtils.deleteDirectory(file);}String[] cmd = {"sh", "-c", "java -jar " + apktool + " d " + sourceApk + " -o "+outPath};Process p = Runtime.getRuntime().exec(cmd);//创建实例进程执行命令行代码p.waitFor();p.destroy();} catch (Exception e) {e.printStackTrace();}}
用到的变量:
/*** 反编译工具目录*/private static String toolsHome = "/home/xzh/Android/tools/";/*** apktool.jar 主要用于反编译生成smill文件*/private static String apktool = toolsHome + "apktool.jar";/*** 用于查看反编译的jar包的源码*/private static String jdGui = toolsHome + "jd-gui.jar";/*** dex2jar工具,用于将.dex转化为.jar文件,不过官网推荐是直接从apk生成成.jar*/private static String dex2jar=toolsHome+"dex2jar/d2j-dex2jar.sh";/*** ----------------------------------下面的三个变量需要配置-----------------------------------------------------*//*** 一般这个名字是加上日期的,可以当作解压包名*/private static String apkName = "app-release.apk";/*** 需要反编译的Apk包*/private static String sourceApk = "app/release/" + apkName;/*** 反编译文件生成位置*/private static String outPath = "app/build/decompile/"+ apkName.replace(".apk","");
不出意外就会生成这些文件。当然apktool还有打包文件的作用,不过这不主要,我们以实现开头的目的为主。其它功能–help查看一下就知道了,非常的简单。我们的目的是要生成jar文件。可以通过dex2jar工具将smali转成jar。或者将classes.dex转化为jar。不过这里我们没有发现classes.dex文件。因为只有通过直接解压apk才会得到classes.dex文件。
通过直接解压居然可以得到一个和通过apktool反编译差不多的目录。而且有一个classes.dex文件。网上很多的教程都是讲这种直接解压的。不过,如果我们只是想查看反编译的jar文件。根本就用不着这个apktool工具,也不需要解压。dex2jar是有办法直接生成反编译的jar的。
2.dex2jar
这个工具才是反编译的工具,因为他提供了非常多的工具。
我们可以看到他提供了非常多的.sh文件,在windows版本下就是.bat文件。可以实现各种转化。不过最有用的还是官网提供的命令:
sh d2j-dex2jar.sh -f ~/path/to/apk_to_decompile.apk
通过这个代码就可以直接从apk提取反编译的jar文件。
java代码实现:
/*** 调用dex2jar*/public static void doDex2Jar() {// unzipApk();long start = System.currentTimeMillis();System.out.println("开始装换");try {File file=new File(outPath+"/classes.jar");if (file.exists()){FileUtils.deleteQuietly(file);}String[] cmd = {"sh", "-c", " sh " + dex2jar + " -f "+ sourceApk +" -o "+outPath+"/classes.jar >> /dev/null 2>&1 "};Process p = Runtime.getRuntime().exec(cmd);//创建实例进程执行命令行代码p.waitFor();p.destroy();} catch (Exception e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("耗时:"+(end-start)/1000+"秒");}
我们可以通过-o知道输出的文件路径,这里我们设置成app/build/decompile/app-release。
不出意外就会生成classes.jar这个文件。名字是我们自己设定的。
得到了jar文件,就可以通过我们的jd-gui来查看了。
3.jd-gui
使用也非常简单。
java -jar jd-gui.jar pathTo/classes.jar
Java代码实现
/*** 调用jd-gui解析jar包内容*/public static void doJdGui() {try {String[] cmd = {"sh", "-c", "java -jar " + jdGui +" "+outPath+"/classes.jar >> /dev/null 2>&1"};Process p = Runtime.getRuntime().exec(cmd);//创建实例进程执行命令行代码p.waitFor();p.destroy();} catch (Exception e) {e.printStackTrace();}}
不出意外的话,就会自动弹出jd-gui的窗口。可以查看反编译的源码,这里我开启了混淆,如果不开启混淆的话,源码差不多都是可以直接看到的,所以说不混淆是非常危险的。
最后贴出完整的代码:
package com.xzh.utils;import mons.io.FileUtils;import java.io.File;/*** 反编译工具类*/public class ReverseUtil {/*** 反编译工具目录*/private static String toolsHome = "/home/xzh/Android/tools/";/*** apktool.jar 主要用于反编译生成smill文件*/private static String apktool = toolsHome + "apktool.jar";/*** 用于查看反编译的jar包的源码*/private static String jdGui = toolsHome + "jd-gui.jar";/*** dex2jar工具,用于将.dex转化为.jar文件,不过官网推荐是直接从apk生成成.jar*/private static String dex2jar=toolsHome+"dex2jar/d2j-dex2jar.sh";/*** ----------------------------------下面的三个变量需要配置-----------------------------------------------------*//*** 一般这个名字是加上日期的,可以当作解压包名*/private static String apkName = "app-release.apk";/*** 需要反编译的Apk包*/private static String sourceApk = "app/release/" + apkName;/*** 反编译文件生成位置*/private static String outPath = "app/build/decompile/"+ apkName.replace(".apk","");/*** 解压Apk*/public static void unzipApk() {try {//必须添加>> /dev/null 2>&1不然卡死//覆盖也会卡死 需要删除File file = new File(outPath);if (file.exists()) {FileUtils.deleteDirectory(file);}String[] cmd = {"sh", "-c", "unzip -d " + outPath + " " + sourceApk + " >> /dev/null 2>&1"};Process p = Runtime.getRuntime().exec(cmd);//创建实例进程执行命令行代码p.waitFor();p.destroy();} catch (Exception e) {e.printStackTrace();}System.out.println("解压完成");}/*** 调用apktool*/public static void doApkTool() {try {File file=new File("app/build/decompile");if (file.exists()){FileUtils.deleteDirectory(file);}String[] cmd = {"sh", "-c", "java -jar " + apktool + " d " + sourceApk + " -o "+outPath};Process p = Runtime.getRuntime().exec(cmd);//创建实例进程执行命令行代码p.waitFor();p.destroy();} catch (Exception e) {e.printStackTrace();}}/*** 调用dex2jar*/public static void doDex2Jar() {// unzipApk();long start = System.currentTimeMillis();System.out.println("开始装换");try {File file=new File(outPath+"/classes.jar");if (file.exists()){FileUtils.deleteQuietly(file);}String[] cmd = {"sh", "-c", " sh " + dex2jar + " -f "+ sourceApk +" -o "+outPath+"/classes.jar >> /dev/null 2>&1 "};Process p = Runtime.getRuntime().exec(cmd);//创建实例进程执行命令行代码p.waitFor();p.destroy();} catch (Exception e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("耗时:"+(end-start)/1000+"秒");}/*** 调用jd-gui解析jar包内容*/public static void doJdGui() {try {String[] cmd = {"sh", "-c", "java -jar " + jdGui +" "+outPath+"/classes.jar >> /dev/null 2>&1"};Process p = Runtime.getRuntime().exec(cmd);//创建实例进程执行命令行代码p.waitFor();p.destroy();} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {//通过dex2jar直接获取apk的classes.jar文件//ReverseUtil.doApkTool();// ReverseUtil.doDex2Jar();//查看源码ReverseUtil.doJdGui();}}