APK 反编译
一、APK反编译基本原理
1.APK分析
- assets文件夹:原始资源文件夹,对应着Android工程的assets文件夹,一般用于存放原始的图片、txt、css等资源文件。
- lib:存放应用需要的引用第三方SDK的so库。比如一些底层实现的图片处理、音视频处理、数据加密的库等。而该文件夹下有时会多一个层级,这是根据不同CPU 型号而划分的,如 ARM,ARM-v7a,x86等。
- META-INF:保存apk签名信息,保证apk的完整性和安全性。
- res:资源文件夹,其中的资源文件包括了布局(layout),常量值(values),颜色值(colors),尺寸值(dimens),字符串(strings),自定义样式(styles)等。
- AndroidManifest.xml文件:全局配置文件,里面包含了版本信息、activity、broadcasts等基本配置。不过这里的是二进制的xml文件,无法直接查看,需要反编译后才能查看。
- classes.dex文件:这是安卓代码的核心部分,,dex是在Dalvik虚拟机上可以执行的文件。这里有classes.dex和classes2.dex两个文件,说明工程的方法数较多,进行了dex拆分。
- resources.arsc文件:记录资源文件和资源id的映射关系。
2.APK打包
- 首先,.aidl(Android Interface Description Language)文件需要通过 aidl 工具转换成编译器能够处理的 Java 接口文件。
- 同时,资源文件(包括 AndroidManifest.xml、布局文件、各种 xml 资源等等)将被 AAPT(Asset Packaging Tool)(Android Gradle Plugin 3.0.0 及之后使用 AAPT2 替代了 AAPT)处理为最终的 resources.arsc,并生成 R.java 文件以保证源码编写时可以方便地访问到这些资源。
- 然后,通过 Java Compiler 编译 R.java、Java 接口文件、Java 源文件,最终它们会统一被编译成 .class 文件。
- 因为 .class 并不是 Android 系统所能识别的格式,所以还需要通过 dex 工具将它们转化为相应的 Dalvik 字节码(包含压缩常量池以及清除冗余信息等工作)。这个过程中还会加入应用所依赖的所有 “第三方库”。
- 下一步,通过 ApkBuilder 工具将资源文件、DEX 文件打包生成 APK 文件。
- 接着,系统将上面生成的 DEX、资源包以及其它资源通过 apkbuilder 生成初始的 APK 文件包。
- 然后,通过签名工具 Jarsigner 或者其它签名工具对 APK 进行签名得到签名后的 APK。如果是在 Debug 模式下,签名所用的 keystore 是系统自带的默认值,否则我们需要提供自己的私钥以完成签名过程。
- 最后,如果是正式版的 APK,还会利用 ZipAlign 工具进行对齐处理,以提高程序的加载和运行速度。而对齐的过程就是将 APK 文件中所有的资源文件距离文件的起始位置都偏移4字节的整数倍,这样通过 mmap 访问 APK 文件的速度会更快,并且会减少其在设备上运行时的内存占用。
3.APK反编译
1.使用dex2jar将dex文件转换成Jar包
2.使用jd-gui将Jar包文件反编译成java源文件
3.使用apktool工具查看apk里的二进制文件
安全措施
proguard
这里不再过多介绍
签名
签名过程可以细分为 三步,如下所示:
1、计算摘要:通过 Hash 算法提取出原始数据的摘要。(SHA1)
2、计算签名:再通过基于密钥(私钥)的非对称加密算法对提取出的摘要进行加密,加密后的数据就是签名信息。(RSA)
3、写入签名:将签名信息写入原始数据的签名区块内。
校验签名
校验过程:
校验过程同样也可以分为 三步,如下:
1、提取摘要:首先用同样的 Hash 算法从接收到的数据中提取出摘要。
2、解密签名:使用发送方的公钥对数字签名进行解密,解密出原始摘要。
3、比较摘要:如果解密后的数据和提取的摘要一致,则校验通过;如果数据被第三方篡改过,解密后的数据和摘要将会不一致,则校验不通过。
那么,我们该如何保证公钥的可靠性呢?答案是 数字证书。
数字证书
需要注意的是,Apk 的证书通常是自签名的,也就是由开发者自己制作,没有向 CA 机构申请。Android 在安装 Apk 时并没有校验证书本身的合法性,只是从证书中提取公钥和加密算法,这也正是对第三方 Apk 重新签名后,还能够继续在没有安装这个 Apk 的系统中继续安装的原因。
keystore 和证书格式
keystore 文件中包含了 私钥、公钥和数字证书。根据编码不同,keystore 文件分为很多种,Android 使用的是 Java 标准 keystore 格式 JKS(Java Key Storage),所以通过 Android Studio 导出的 keystore 文件是以 .jks 结尾的。
keystore 使用的 证书标准是 X.509,X.509 标准也有多种 编码格式,常用的有两种:pem(Privacy Enhanced Mail)和 der(Distinguished Encoding Rules)。jks 使用的是 der 格式,但是,Android 也支持直接使用 pem 格式的证书进行签名。
下面,我们了解下两种证书编码格式的区别,如下所示:
DER(Distinguished Encoding Rules):二进制格式,所有类型的证书和私钥都可以存储为 der 格式。
PEM(Privacy Enhanced Mail):base64 编码,内容以-----BEGIN xxx----- 开头,以-----END xxx----- 结尾。
/**
获取签名证书的SHA1值
**/
public static String getSign(Context ctx) {
try {
PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(),
PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
Signature sign = signs[0];
MessageDigest md1 = MessageDigest.getInstance("MD5");
md1.update(sign.toByteArray());
byte[] digest = md1.digest();
String res = toHexString(digest);
MessageDigest md2 = MessageDigest.getInstance("SHA1");
md2.update(sign.toByteArray());
byte[] digest2 = md2.digest();
String res2 = toHexString(digest2);
return res2;
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
通过检查签名文件classes.dex文件的哈希值来判断代码文件是否被篡改
@param orginalSHA 原始Apk包的SHA-1值
**/
public static void apkVerifyWithSHA(Context context, String baseSHA) {
String apkPath = context.getPackageCodePath(); // 获取Apk包存储路径
try {
MessageDigest dexDigest = MessageDigest.getInstance("SHA-1");
byte[] bytes = new byte[1024];
int byteCount;
FileInputStream fis = new FileInputStream(new File(apkPath)); // 读取apk文件
while ((byteCount = fis.read(bytes)) != -1) {
dexDigest.update(bytes, 0, byteCount);
}
BigInteger bigInteger = new BigInteger(1, dexDigest.digest()); // 计算apk文件的哈希值
String sha = bigInteger.toString(16);
fis.close();
if (!sha.equals(baseSHA)) { // 将得到的哈希值与原始的哈希值进行比较校验
Process.killProcess(Process.myPid()); // 验证失败则退出程序
}
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
思考
这种纯粹的字符比较都很容易破解掉,直接在 smali 中全局搜索干掉或修改你的签名验证逻辑就行了,实际上用处不大。
进阶:签名验证放到 native 层用 NDK 开发
jarsigner 和 apksigner 的区别
Android 提供了 两种对 Apk 的签名方式,一种是基于 JAR 的签名方式,另一种是基于 Apk 的签名方式,它们的 主要区别在于使用的签名文件不一样:jarsigner 使用 keystore 文件进行签名;而 apksigner 除了支持使用 keystore 文件进行签名外,还支持直接指定 pem 证书文件和私钥进行签名。
在我们签名时,除了要指定 keystore 文件和密码外,也要指定 alias 和 key 的密码,这是为什么呢?
keystore 是一个密钥库,也就是说它可以存储多对密钥和证书,keystore 的密码是用于保护 keystore 本身的,每一对密钥和证书是通过 alias 来区分的。所以 jarsigner 是支持使用多个证书对 Apk 进行签名的,apksigner 也同样支持。
Android Apk V1 验证签名的原理
Android Apk V1 验证签名的过程主要可以分为如下 四步:
1、解析出 CERT.RSA 文件中的证书、公钥,解密 CERT.RSA 中的加密数据。
2、解密结果和 CERT.SF 的指纹进行对比,保证 CERT.SF 没有被篡改。
3、接着,将 CERT.SF 中的内容再和 MANIFEST.MF 中的指纹对比,保证 MANIFEST.MF 文件没有被篡改。
4、MANIFEST.MF 中的内容和 APK 所有文件指纹逐一对比,保证 APK 没有被篡改。