# APK签名校验绕过

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2014-12-11T02:41:00Z
Canonical: https://wepostx.com/topics/659

> 乌云历史资料归档
>
> **原始作者：** 3xpl0it
> **原始编号：** superkieran-wooyundrops:422
> **原始发布时间：** 2014-12-11 10:41
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

# 0x01 Android签名机制

将APK重命名为zip文件，然后可以看到有个META-INF的文件夹，里面有三个文件，分别名为MANIFEST.MF、CERT.SF和CERT.RSA，这些就是使用signapk.jar生成的签名文件。
1、 MANIFEST.MF文件：
程序遍历update.apk包中的所有文件(entry)，对非文件夹非签名文件的文件，逐个生成SHA1的数字签名信息，再用Base64进行编码。具体代码见这个方法：

```text
#!java private static Manifest addDigestsToManifest(JarFile jar)
```
关键代码是

```text
#!java for (JarEntry entry: byName.values()) { String name = entry.getName(); if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) && !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) && (stripPattern == null ||!stripPattern.matcher(name).matches())){ InputStream data = jar.getInputStream(entry); while ((num = data.read(buffer)) > 0) { md.update(buffer, 0, num); } Attributes attr = null; if (input != null) attr = input.getAttributes(name); attr = attr != null ? new Attributes(attr) : new Attributes(); attr.putValue("SHA1-Digest", base64.encode(md.digest())); output.getEntries().put(name, attr); } }
```
之后将生成的签名写入MANIFEST.MF文件。关键代码如下：

```text
#!java Manifest manifest = addDigestsToManifest(inputJar); je = new JarEntry(JarFile.MANIFEST_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); manifest.write(outputJar);
```
2、 生成CERT.SF文件：
对前一步生成的Manifest，使用SHA1-RSA算法，用私钥进行签名。关键代码如下：

```text
#!java Signature signature = Signature.getInstance("SHA1withRSA"); signature.initSign(privateKey); je = new JarEntry(CERT_SF_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureFile(manifest, new SignatureOutputStream(outputJar, signature));
```
3、 生成CERT.RSA文件：
生成MANIFEST.MF没有使用密钥信息，生成CERT.SF文件使用了私钥文件。那么我们可以很容易猜测到，CERT.RSA文件的生成肯定和公钥相关。 CERT.RSA文件中保存了公钥、所采用的加密算法等信息。核心代码如下：

```text
#!java je = new JarEntry(CERT_RSA_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureBlock(signature, publicKey, outputJar);
```
在程序中获取APK的签名时，通过signature方法进行获取，如下：

```text
#!java packageInfo = manager.getPackageInfo(pkgname,PackageManager.GET_SIGNATURES); signatures = packageInfo.signatures; for (Signature signature : signatures) { builder.append(signature.toCharsString()); } signature = builder.toString();
```
所以一般的程序就是在代码中通过判断signature的值，来判断APK是否被重新打包过。

# 0x02 签名绕过方式

在讲签名绕过的方式前，需要先明确DEX校验和签名校验：
1.将apk以压缩包的形式打开删除原签名后，再签名，安装能够正常打开，但是用IDE（即apk改之理，会自动反编译dex）工具二次打包，却出现非正常情况的，如：闪退/弹出非正版提示框。可以确定是dex文件的校验
2、将apk以压缩包的形式打开删除原签名再签名，安装之后打开异常的，则基本可以断定是签名检验。如果在断网的情况下同样是会出现异常，则是本地的签名检验；如果首先出现的是提示网络没有连接，则是服务器端的签名校验.

## 2.1.Java层校验
获取签名信息和验证的方法都写在android的java层。实例如下：
1、使用APKIDE反编译APK，不做任何操作，然后直接回编译，安装后运行,提示如下：

![原文图片](/media/2016/06/f9277eb4ccf4da85e5b7eb87e41781a6)

2、在APKIDE中搜索signatures(或者搜索错误提示),定位到签名验证的代码处。

![原文图片](/media/2016/06/ce46670101bd2221db6ebbd2481f8d5c)

3、此处就是获取签名的，然后找程序判断签名的地方，进行修改，如下图，if-nez是进行判断的地方，将ne修改为eq。即if-eqz v2, :cond_0。则程序就可以绕过本地的签名交易。

![原文图片](/media/2016/06/02b3f9d7f8cc8bf7209af32d08ae3af4)

![原文图片](/media/2016/06/196677b5e608d6114f4ff01657b1e36d)

## 2.2.NDK校验
将关键代码放到so中，在底层获取签名信息并验证。因为获取和验证的方法都封闭在更安全的so库里面，能够起到一定意义上的保护作用。实例如下：
1、使用APKIDE反编译APK，不做任何操作，然后直接回编译，安装后运行,程序直接退出，无任何提示。
2、在APKIDE中搜索signatures(或者搜索错误提示),定位到签名验证的代码处。

![原文图片](/media/2016/06/5362f9671d3e18b65a14c4c604e1d3da)

3、使⽤用JD-GUI打开AppActivity，可以看到,此处是获取包名,然后进⾏行MD5计算。

![原文图片](/media/2016/06/ffc04459b7751d7648a21f24c6379baf)

4.在程序中搜索getSignature,发现并没有调⽤用此函数的地⽅方,猜测在so⽂文件中,搜索loadLibrary。

![原文图片](/media/2016/06/757a90344ef51bbe52bd41393a8fb5a7)

5.在代码中可以查找,可以找到调⽤用的是libcocos2dcpp.so
6.使⽤用IDA打开libcocos2dcpp.so,然后搜索getSiganture,找到调⽤用此函数的地方。

![原文图片](/media/2016/06/c0ac54145cb1f02fd5bfbb92cc5c1963)

从代码中可以看到,此函数调⽤用了org.cocos2dx.cpp.AppActivity.getSignature

![原文图片](/media/2016/06/ebc0fe36f223e8e81a43815c8a85b367)

7、查看F5代码,发现此函数是判断签名的函数,然后我们双击此函数的调⽤者,部分代码如下。

![原文图片](/media/2016/06/8c31f5235c8ace37b4459221bf5effd8)

8、从上图可以看出,只需要修改BEQ loc_11F754,让其不跳转到jjni——>error,就可以绕过签名校验。 查看HEX,使⽤010editor跳到0011F73E,修改D0为D1。成功绕过签名校验。

![原文图片](/media/2016/06/54fa6d1f0c411f41d0929016c9e8dff8)

## 2.3.服务器验证
在android的java层获取签名信息，上传服务器在服务端进行签名然后返回验证结果。
如下图，网络验证时，如果网络没连接，一般都会提示错误。

![原文图片](/media/2016/06/b68271f5f2a28bc4ca3e10383b4211ed)

既然是网络验证，肯定要把验证信息发送到服务端，然后进行验证，先看个简单的实例，下次会有个难度大的。
1、手机配置好抓包，然后抓包。第一种图是正常的APK的时候的数据包，第二个图是反编译的APK的数据包，通过对比，发现cookie中的public_key不一样，那么我们替换一下，发现可以正常使用APK的功能了。

![原文图片](/media/2016/06/095c7644049fd51cfcfb74bc162eb410)

![原文图片](/media/2016/06/d10f47fd0b2c153df99eefc697c51da2)

2、将正确的public_key添加到APK中。打开反编译的代码，搜索signatures，定位到签名的代码。

![原文图片](/media/2016/06/fe856836d69731fbfeb3a3881a5194ac)

可以看到，代码将signatures的值传递到V4中，然后传递到Utils->mPublicKey函数中，于是我们将正确的public_key传给V4。

![原文图片](/media/2016/06/918ee27302ca6ed7c9d56b4469635a5b)

然后重新打包，重新安装就可以了。

# 0x03.总结

java层的校验很容易就可以破解掉，在so层实现校验相对来说分析会更难点，而网络验证，如果仅仅是字符串的比较，那么也很容易破解掉。
码子码的太累了。。
后面还有几篇正在写的文章，包括so分析等等。
摘抄： http://www.blogjava.net/zh-weir/archive/2011/07/19/354663.html

## Replies
