forked from Trietptm-on-Security/WooYun-2
-
Notifications
You must be signed in to change notification settings - Fork 7
/
APK签名校验绕过.html
200 lines (126 loc) · 120 KB
/
APK签名校验绕过.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
<html>
<head>
<title>APK签名校验绕过 - 3xpl0it</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<h1>原文地址:<a href="http://drops.wooyun.org/mobile/4296">http://drops.wooyun.org/mobile/4296</a></h1>
<p>
<h1>0x01 Android签名机制</h1>
<hr />
<p>将APK重命名为zip文件,然后可以看到有个META-INF的文件夹,里面有三个文件,分别名为MANIFEST.MF、CERT.SF和CERT.RSA,这些就是使用signapk.jar生成的签名文件。</p>
<p>1、 MANIFEST.MF文件:</p>
<p>程序遍历update.apk包中的所有文件(entry),对非文件夹非签名文件的文件,逐个生成SHA1的数字签名信息,再用Base64进行编码。具体代码见这个方法:</p>
<pre><code>#!java
private static Manifest addDigestsToManifest(JarFile jar)
</code></pre>
<p>关键代码是</p>
<!--more-->
<pre><code>#!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);
}
}
</code></pre>
<p>之后将生成的签名写入MANIFEST.MF文件。关键代码如下:</p>
<pre><code>#!java
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
</code></pre>
<p>2、 生成CERT.SF文件:</p>
<p>对前一步生成的Manifest,使用SHA1-RSA算法,用私钥进行签名。关键代码如下:</p>
<pre><code>#!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));
</code></pre>
<p>3、 生成CERT.RSA文件:</p>
<p>生成MANIFEST.MF没有使用密钥信息,生成CERT.SF文件使用了私钥文件。那么我们可以很容易猜测到,CERT.RSA文件的生成肯定和公钥相关。 CERT.RSA文件中保存了公钥、所采用的加密算法等信息。核心代码如下:</p>
<pre><code>#!java
je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(signature, publicKey, outputJar);
</code></pre>
<p>在程序中获取APK的签名时,通过signature方法进行获取,如下:</p>
<pre><code>#!java
packageInfo = manager.getPackageInfo(pkgname,PackageManager.GET_SIGNATURES);
signatures = packageInfo.signatures;
for (Signature signature : signatures) {
builder.append(signature.toCharsString());
}
signature = builder.toString();
</code></pre>
<p>所以一般的程序就是在代码中通过判断signature的值,来判断APK是否被重新打包过。</p>
<h1>0x02 签名绕过方式</h1>
<hr />
<p>在讲签名绕过的方式前,需要先明确DEX校验和签名校验:</p>
<p>1.将apk以压缩包的形式打开删除原签名后,再签名,安装能够正常打开,但是用IDE(即apk改之理,会自动反编译dex)工具二次打包,却出现非正常情况的,如:闪退/弹出非正版提示框。可以确定是dex文件的校验</p>
<p>2、将apk以压缩包的形式打开删除原签名再签名,安装之后打开异常的,则基本可以断定是签名检验。如果在断网的情况下同样是会出现异常,则是本地的签名检验;如果首先出现的是提示网络没有连接,则是服务器端的签名校验.</p>
<h2>2.1.Java层校验</h2>
<p>获取签名信息和验证的方法都写在android的java层。实例如下:</p>
<p>1、使用APKIDE反编译APK,不做任何操作,然后直接回编译,安装后运行,提示如下:</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430155652.jpg" alt="enter image description here" /></p>
<p>2、在APKIDE中搜索signatures(或者搜索错误提示),定位到签名验证的代码处。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430260169.jpg" alt="enter image description here" /></p>
<p>3、此处就是获取签名的,然后找程序判断签名的地方,进行修改,如下图,if-nez是进行判断的地方,将ne修改为eq。即if-eqz v2, :cond_0。则程序就可以绕过本地的签名交易。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430247372.jpg" alt="enter image description here" /></p>
<p><img src="http://static.wooyun.org/20141211/2014121102430293925.jpg" alt="enter image description here" /></p>
<h2>2.2.NDK校验</h2>
<p>将关键代码放到so中,在底层获取签名信息并验证。因为获取和验证的方法都封闭在更安全的so库里面,能够起到一定意义上的保护作用。实例如下:</p>
<p>1、使用APKIDE反编译APK,不做任何操作,然后直接回编译,安装后运行,程序直接退出,无任何提示。</p>
<p>2、在APKIDE中搜索signatures(或者搜索错误提示),定位到签名验证的代码处。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430212236.jpg" alt="enter image description here" /></p>
<p>3、使⽤用JD-GUI打开AppActivity,可以看到,此处是获取包名,然后进⾏行MD5计算。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430323032.jpg" alt="enter image description here" /></p>
<p>4.在程序中搜索getSignature,发现并没有调⽤用此函数的地⽅方,猜测在so⽂文件中,搜索loadLibrary。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430360656.jpg" alt="enter image description here" /></p>
<p>5.在代码中可以查找,可以找到调⽤用的是libcocos2dcpp.so</p>
<p>6.使⽤用IDA打开libcocos2dcpp.so,然后搜索getSiganture,找到调⽤用此函数的地方。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430394842.jpg" alt="enter image description here" /></p>
<p>从代码中可以看到,此函数调⽤用了org.cocos2dx.cpp.AppActivity.getSignature</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430474653.jpg" alt="enter image description here" /></p>
<p>7、查看F5代码,发现此函数是判断签名的函数,然后我们双击此函数的调⽤者,部分代码如下。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430492880.jpg" alt="enter image description here" /></p>
<p>8、从上图可以看出,只需要修改BEQ loc_11F754,让其不跳转到jjni——>error,就可以绕过签名校验。 查看HEX,使⽤010editor跳到0011F73E,修改D0为D1。成功绕过签名校验。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430578721.jpg" alt="enter image description here" /></p>
<h2>2.3.服务器验证</h2>
<p>在android的java层获取签名信息,上传服务器在服务端进行签名然后返回验证结果。</p>
<p>如下图,网络验证时,如果网络没连接,一般都会提示错误。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430510247.jpg" alt="enter image description here" /></p>
<p>既然是网络验证,肯定要把验证信息发送到服务端,然后进行验证,先看个简单的实例,下次会有个难度大的。</p>
<p>1、手机配置好抓包,然后抓包。第一种图是正常的APK的时候的数据包,第二个图是反编译的APK的数据包,通过对比,发现cookie中的public_key不一样,那么我们替换一下,发现可以正常使用APK的功能了。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430581926.jpg" alt="enter image description here" /></p>
<p><img src="http://static.wooyun.org/20141211/2014121102430691555.jpg" alt="enter image description here" /></p>
<p>2、将正确的public_key添加到APK中。打开反编译的代码,搜索signatures,定位到签名的代码。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430815044.jpg" alt="enter image description here" /></p>
<p>可以看到,代码将signatures的值传递到V4中,然后传递到Utils->mPublicKey函数中,于是我们将正确的public_key传给V4。</p>
<p><img src="http://static.wooyun.org/20141211/2014121102430821042.jpg" alt="enter image description here" /></p>
<p>然后重新打包,重新安装就可以了。</p>
<h1>0x03.总结</h1>
<hr />
<p>java层的校验很容易就可以破解掉,在so层实现校验相对来说分析会更难点,而网络验证,如果仅仅是字符串的比较,那么也很容易破解掉。</p>
<p>码子码的太累了。。</p>
<p>后面还有几篇正在写的文章,包括so分析等等。</p>
<p>摘抄: http://www.blogjava.net/zh-weir/archive/2011/07/19/354663.html</p> </p>
</body>
</html>