-
-
[旧帖] [原创]APK自我保护 - DEX/APK/证书校验 0.00雪花
-
发表于: 2016-6-8 16:57 2173
-
DEX/APK/证书 校验是在应用开发结束后,在本地计算出其 CRC 值或者 MD5值。在应用运行的时候,通过网络或者资源文件中获取事前算好的值进行比对,如果不一致则说明 DEX/APK/证书 可能存在APK被篡改或者证书被替换的风险。
### DEX校验
classes.dex 是 Android 虚拟机的可执行文件,我们所写的 java 代码其实都在这里面,所有很多对应用程序的篡改都是针对 classes.dex 文件的。
可以找一个 APK 解压就可以看到 classes.dex 文件。APK 其实就是一个压缩包,通过将后缀名改为 `zip` 就可以直接解压,或者用 `unzip` 命令。
####编写代码
代码比较简单,这里是通过建计算好的 crc 保存在 string.xml 文件里,当然我们事先可以随便拿一个值代替,等开发完成后替换掉。
**校验代码:**
```java
/**
* 校验Dex CRC值
*/
private void verifyDex(){
//获取String.xml中的value
Long dexCrc = Long.parseLong(this.getString(R.string.crc_value));
String apkPath = this.getPackageCodePath();
try {
ZipFile zipFile = new ZipFile(apkPath);
ZipEntry dexEntry = zipFile.getEntry("classes.dex");
//计算classes.dex的 crc
long dexEntryCrc = dexEntry.getCrc();
Log.d("DEX", dexEntryCrc + "");
//对比
if(dexCrc == dexEntryCrc){
Log.d("DEX", "dex hasn't been modified");
}else{
Log.d("DEX", "dex has been modified");
}
} catch (IOException e) {
e.printStackTrace();
}
}
```
####计算 CRC 值
这一步必须在应用开发完成的时候去计算,如果改动了代码就必须重新计算。
#####利用 unzip 解压
解压命令:
unzip -d output app-debug.apk
output 文件夹:
```shell
total 2200
drwxr-xr-x 8 xiangqing staff 272B 4 20 22:18 .
drwxr-xr-x 6 xiangqing staff 204B 4 20 22:18 ..
-rw-r--r-- 1 xiangqing staff 3.1K 9 15 2015 AndroidManifest.xml
drwxr-xr-x 7 xiangqing staff 238B 4 20 22:18 META-INF
-rw-r--r-- 1 xiangqing staff 1.1M 9 15 2015 classes.dex
drwxr-xr-x 3 xiangqing staff 102B 4 20 22:18 org
drwxr-xr-x 10 xiangqing staff 340B 4 20 22:18 res
-rw-r--r-- 1 xiangqing staff 4.4K 9 15 2015 resources.arsc
```
#####利用 crc32 命令获取 crc 值
mac系统自带 crc32 命令:
crc32 classes.dex
这里生成的 crc 值是16进制数我们可以转换成十进制。
#####保存到 string.xml 文件
因为 string.xml 文件是资源文件不会影响到 classes.dex 所以修改是没有关系的。
### APK校验
与 DEX 校验不同 APK 检验必须把把计算好的 Hash 值放在网络服务端,因为对 APK 的任何改动都会影响到最后的 Hash 值。
**校验代码:**
```java
/**
* 校验APK MD5值
*/
private void verifyApk(){
//获取data/app/****/base.apk 路径
String apkPath = getPackageResourcePath();
Log.d("APK", apkPath);
MessageDigest msgDigest;
try {
//获取apk并计算MD5值
msgDigest = MessageDigest.getInstance("MD5");
byte[] bytes = new byte[4096];
int count;
FileInputStream fis;
fis = new FileInputStream(new File(apkPath));
while((count = fis.read(bytes)) >0){
msgDigest.update(bytes, 0, count);
}
//计算出MD5值
BigInteger bInt = new BigInteger(1, msgDigest.digest());
String md5 = bInt.toString(16);
fis.close();
Log.d("APK", md5);
/**
* 获取服务端的 MD5值进行对比
*/
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
```
####计算 MD5 值
这一步必须在应用开发完成并且打包 release 版本的时候进行计算。
mac 系统为例:
md5 release.apk
###证书校验
每个APK都会经过开发者独有的证书进行签名,如果破解者对APK进行二次打包一般会用自己的签名证书打包。这时候我们就可以通过校验签名证书的MD5值进行校验。
**校验代码:**
```java
/**
* 获取签名证书
*/
public void verifySignature() {
String packageName = this.getPackageName();
PackageManager pm = this.getPackageManager();
PackageInfo pi;
String md5 = "";
try {
pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] s = pi.signatures;
//计算出MD5值
MessageDigest msgDigest = MessageDigest.getInstance("MD5");
msgDigest.reset();
msgDigest.update(s[0].toByteArray());
BigInteger bInt = new BigInteger(1, msgDigest.digest());
md5 = bInt.toString(16);
} catch (Exception e) {
e.printStackTrace();
}
Log.d("Sign", md5);
/**
* 获取服务端的 签名证书对比
*/
}
```
####获取签名证书 MD5
在release前我们可以获取证书的MD5值具体操作为:
解压apk
unzip -d output release.apk
解压后文件为
```shell
ll
total 5224
-rw-r--r-- 1 xiangqing staff 2.0K 6 5 11:42 AndroidManifest.xml
drwxr-xr-x 5 xiangqing staff 170B 6 5 11:56 META-INF
-rw-r--r-- 1 xiangqing staff 2.4M 6 5 11:42 classes.dex
drwxr-xr-x 22 xiangqing staff 748B 6 5 11:56 res
-rw-r--r-- 1 xiangqing staff 173K 6 5 11:28 resources.arsc
```
其中文件夹`META-INF`中的`CERT.RSA`文件就是签名文件,通过`keytool`工具就可以看到其中的MD5 等属性
```shell
→ keytool -printcert -file CERT.RSA
所有者: CN=Android Debug, O=Android, C=US
发布者: CN=Android Debug, O=Android, C=US
序列号: 44bff7c9
有效期开始日期: Wed Feb 18 21:01:26 CST 2015, 截止日期: Fri Feb 10 21:01:26 CST 2045
证书指纹:
MD5: 05:F1:5D:AF:1D:EA:56:A8:1B:96:F9:37:06:04:FA:A1
SHA1: 4E:13:A8:8F:89:B1:92:89:10:9D:24:F4:9A:B5:E2:C1:22:CD:84:B4
SHA256: BA:4E:CF:72:93:F3:AA:1B:A5:31:6E:67:6D:AE:E7:A6:84:41:BC:7C:B3:29:7B:45:56:48:36:DF:CE:58:30:2B
签名算法名称: SHA256withRSA
版本: 3
扩展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 25 CF E3 1E 4F 04 22 BA AA D8 41 6F BC 07 2D 5D %...O."...Ao..-]
0010: CB EE E3 84 ....
]
]
```
###总结
当然上述的保护方式容易被暴力破解, 完整性检查最终还是通过返回 true/false 来控制 后续代码逻辑的走向,如果攻击者直接修改代码逻辑,完整性检查始终返回 true,那这种方 法就无效了,所以类似文件完整性校验需要配合一些其他方法,或者有其他更为巧妙的方式 实现?
### DEX校验
classes.dex 是 Android 虚拟机的可执行文件,我们所写的 java 代码其实都在这里面,所有很多对应用程序的篡改都是针对 classes.dex 文件的。
可以找一个 APK 解压就可以看到 classes.dex 文件。APK 其实就是一个压缩包,通过将后缀名改为 `zip` 就可以直接解压,或者用 `unzip` 命令。
####编写代码
代码比较简单,这里是通过建计算好的 crc 保存在 string.xml 文件里,当然我们事先可以随便拿一个值代替,等开发完成后替换掉。
**校验代码:**
```java
/**
* 校验Dex CRC值
*/
private void verifyDex(){
//获取String.xml中的value
Long dexCrc = Long.parseLong(this.getString(R.string.crc_value));
String apkPath = this.getPackageCodePath();
try {
ZipFile zipFile = new ZipFile(apkPath);
ZipEntry dexEntry = zipFile.getEntry("classes.dex");
//计算classes.dex的 crc
long dexEntryCrc = dexEntry.getCrc();
Log.d("DEX", dexEntryCrc + "");
//对比
if(dexCrc == dexEntryCrc){
Log.d("DEX", "dex hasn't been modified");
}else{
Log.d("DEX", "dex has been modified");
}
} catch (IOException e) {
e.printStackTrace();
}
}
```
####计算 CRC 值
这一步必须在应用开发完成的时候去计算,如果改动了代码就必须重新计算。
#####利用 unzip 解压
解压命令:
unzip -d output app-debug.apk
output 文件夹:
```shell
total 2200
drwxr-xr-x 8 xiangqing staff 272B 4 20 22:18 .
drwxr-xr-x 6 xiangqing staff 204B 4 20 22:18 ..
-rw-r--r-- 1 xiangqing staff 3.1K 9 15 2015 AndroidManifest.xml
drwxr-xr-x 7 xiangqing staff 238B 4 20 22:18 META-INF
-rw-r--r-- 1 xiangqing staff 1.1M 9 15 2015 classes.dex
drwxr-xr-x 3 xiangqing staff 102B 4 20 22:18 org
drwxr-xr-x 10 xiangqing staff 340B 4 20 22:18 res
-rw-r--r-- 1 xiangqing staff 4.4K 9 15 2015 resources.arsc
```
#####利用 crc32 命令获取 crc 值
mac系统自带 crc32 命令:
crc32 classes.dex
这里生成的 crc 值是16进制数我们可以转换成十进制。
#####保存到 string.xml 文件
因为 string.xml 文件是资源文件不会影响到 classes.dex 所以修改是没有关系的。
### APK校验
与 DEX 校验不同 APK 检验必须把把计算好的 Hash 值放在网络服务端,因为对 APK 的任何改动都会影响到最后的 Hash 值。
**校验代码:**
```java
/**
* 校验APK MD5值
*/
private void verifyApk(){
//获取data/app/****/base.apk 路径
String apkPath = getPackageResourcePath();
Log.d("APK", apkPath);
MessageDigest msgDigest;
try {
//获取apk并计算MD5值
msgDigest = MessageDigest.getInstance("MD5");
byte[] bytes = new byte[4096];
int count;
FileInputStream fis;
fis = new FileInputStream(new File(apkPath));
while((count = fis.read(bytes)) >0){
msgDigest.update(bytes, 0, count);
}
//计算出MD5值
BigInteger bInt = new BigInteger(1, msgDigest.digest());
String md5 = bInt.toString(16);
fis.close();
Log.d("APK", md5);
/**
* 获取服务端的 MD5值进行对比
*/
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
```
####计算 MD5 值
这一步必须在应用开发完成并且打包 release 版本的时候进行计算。
mac 系统为例:
md5 release.apk
###证书校验
每个APK都会经过开发者独有的证书进行签名,如果破解者对APK进行二次打包一般会用自己的签名证书打包。这时候我们就可以通过校验签名证书的MD5值进行校验。
**校验代码:**
```java
/**
* 获取签名证书
*/
public void verifySignature() {
String packageName = this.getPackageName();
PackageManager pm = this.getPackageManager();
PackageInfo pi;
String md5 = "";
try {
pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] s = pi.signatures;
//计算出MD5值
MessageDigest msgDigest = MessageDigest.getInstance("MD5");
msgDigest.reset();
msgDigest.update(s[0].toByteArray());
BigInteger bInt = new BigInteger(1, msgDigest.digest());
md5 = bInt.toString(16);
} catch (Exception e) {
e.printStackTrace();
}
Log.d("Sign", md5);
/**
* 获取服务端的 签名证书对比
*/
}
```
####获取签名证书 MD5
在release前我们可以获取证书的MD5值具体操作为:
解压apk
unzip -d output release.apk
解压后文件为
```shell
ll
total 5224
-rw-r--r-- 1 xiangqing staff 2.0K 6 5 11:42 AndroidManifest.xml
drwxr-xr-x 5 xiangqing staff 170B 6 5 11:56 META-INF
-rw-r--r-- 1 xiangqing staff 2.4M 6 5 11:42 classes.dex
drwxr-xr-x 22 xiangqing staff 748B 6 5 11:56 res
-rw-r--r-- 1 xiangqing staff 173K 6 5 11:28 resources.arsc
```
其中文件夹`META-INF`中的`CERT.RSA`文件就是签名文件,通过`keytool`工具就可以看到其中的MD5 等属性
```shell
→ keytool -printcert -file CERT.RSA
所有者: CN=Android Debug, O=Android, C=US
发布者: CN=Android Debug, O=Android, C=US
序列号: 44bff7c9
有效期开始日期: Wed Feb 18 21:01:26 CST 2015, 截止日期: Fri Feb 10 21:01:26 CST 2045
证书指纹:
MD5: 05:F1:5D:AF:1D:EA:56:A8:1B:96:F9:37:06:04:FA:A1
SHA1: 4E:13:A8:8F:89:B1:92:89:10:9D:24:F4:9A:B5:E2:C1:22:CD:84:B4
SHA256: BA:4E:CF:72:93:F3:AA:1B:A5:31:6E:67:6D:AE:E7:A6:84:41:BC:7C:B3:29:7B:45:56:48:36:DF:CE:58:30:2B
签名算法名称: SHA256withRSA
版本: 3
扩展:
#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 25 CF E3 1E 4F 04 22 BA AA D8 41 6F BC 07 2D 5D %...O."...Ao..-]
0010: CB EE E3 84 ....
]
]
```
###总结
当然上述的保护方式容易被暴力破解, 完整性检查最终还是通过返回 true/false 来控制 后续代码逻辑的走向,如果攻击者直接修改代码逻辑,完整性检查始终返回 true,那这种方 法就无效了,所以类似文件完整性校验需要配合一些其他方法,或者有其他更为巧妙的方式 实现?
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
看原图
赞赏
雪币:
留言: