您的位置:首页 > 编程语言 > Go语言

FakeID签名漏洞分析及利用(Google Bug 13678484)

2016-12-10 00:00 549 查看
作者:申迪
转载请注明出处: http://blogs.360.cn/360mobile BlueBox于7月30日宣布安卓从2010年以来一直存在一个apk签名问题[1],并且会在今年Blackhat上公布细节。
利用该漏洞可以提升权限,突破沙箱限制。我在细节公开之前对这个漏洞进行成功利用,在此分享一些漏洞利用的细节。

一、关于APK签名

安卓APP在发布之前需要进行签名,签名信息被放在apk压缩包的/META-INF目录中。通常出于以下目的,系统会校验这个签名:
–安装前验证apk中文件数据的完整性。
–识别apk的身份。如果一个apk有系统签名,会有更高的权限;如果两个apk的签名一致,这两个应用可以共享数据。一些特定场景下一些应用会验证其他应用是否有某个特定签名,比如webkit会验证一个插件程序是否是由Adobe签出。
发布应用之前,使用一个自签名的证书对apk进行签名是可以满足需求的。使用”keytool –printcert –file CERT.RSA”命令可以查看证书的详细信息。下图是一个普通的自签名证书。可以看到所有人和签发人是同一个。




然而签名文件格式符合RFC2315[2]和RFC2459[3] 的规范,所以这个证书同样可以使由CA颁布的。如果使用一个CA颁布的证书发行APK程序,那么在META-INF目录中的CERT.RSA中可能会存在一个证书链,包含根证书和子证书。
在安卓上我们可以使用如下代码获取到应用内证书链中的所有证书:

sig=packageManager.getPackageInfo(pkgName,PackageManager.GET_PERMISSIONS| PackageManager.GET_SIGNATURES);
for
(Signature sig : pkginfo.signatures)
Log.d(
"TEST"
,sig.toCharsString()+
"\n"
);

二、问题所在

这个漏洞的Google Bug ID 是13678484,从AOSP上的修复代码可以发现问题出现在签名校验的JarVerifier类中[4]。最关键的代码是在JarUtils中,添加了chainCheck的选项,可以对所有证书进行校验。

-
private
static
X509Certificate findCert(Principal issuer,X509Certificate[] candidates) {
+
private
static
X509Certificate findCert(Principal issuer,X509Certificate[] candidates,
+X509Certificate subjectCert,
boolean
chainCheck) {
   
for
(
int
i =
0
; i < candidates.length; i++) {
 
if
(issuer.equals(candidates[i].getSubjectDN())) {
+
if
(chainCheck) {
+  
try
{
+subjectCert.verify(candidates[i].getPublicKey());
+  } 
catch
(Exception e) {
+
continue
;
+  }
+}
 
return
candidates[i];
 
}
   
}
如果没有这句代码会怎样?我们可以这么构造一个恶意证书:

开发机上生成一个根证书(记为CA),并用这个证书去颁发一个子证书(记为SIGN)

然后使用这个子证书为我们即将发布的apk签名,这时APK中的.Rsa文件将包含两个证书,一个是SIGN,一个是CA。并且APK所有的文件都是可以用这个RSA文件验证其合法性的

对这个RSA文件进行篡改,只修改CA证书的内容(替换后的CA记为FakeCA),不修改证书中的SignerInfo部分,不影响PackageManger在安装前使用SignerInfo.encryptedDigest对APK包数据完整性进行校验

这个APK可以被成功安装,并且包含两个证书,一个是SIGN,一个是FakeCA

如果你之前像我一样不了解PKCS7签名文件格式,那么在尝试恶意修改rsa文件时将会遇到一些麻烦,我推荐使用开源项目pyASN1去修改RSA文件。因为这种格式本质上是使用DER编码,使用ASN1做了序列化,使用pyASN1能让我们快速熟悉这种文件格式,并且快速着手进行签名篡改。
我将文件修改代码放到了我的github上[5],注意这不是一份自动篡改证书的工具,内部包含了一些路径和格式的硬编码,你如果想使用这份代码,需要理解代码的意思并做一些修改。
我使用自己写的工具将一个证书篡改成由Adobe颁发的:




三、如何利用

上文已经提到过,PackageManger在安装APK时并不校验证书链上所有证书的合法性,只要存在被指定的SIGN能够校验APK中所有文件的合法性即可。
但是签名证书的另外一个功能,验证身份,则受到了这个漏洞的影响。系统中多处使用getPackageInfo获取安装包证书,如果获取到多个证书,通常认为只要有一个证书可信即可。比如WebKit插件认证adobe flash player插件的逻辑如下[6]

225
private
static
boolean
containsPluginPermissionAndSignatures(PackageInfo pkgInfo) {
226
227
// check if the plugin has the required permissions
228
String permissions[] = pkgInfo.requestedPermissions;
229
if
(permissions ==
null
) {
230
return
false
;
231
}
232
boolean
permissionOk =
false
;
233
for
(String permit : permissions) {
234
if
(PLUGIN_PERMISSION.equals(permit)) {
235
permissionOk =
true
;
236
break
;
237
}
238
}
239
if
(!permissionOk) {
240
return
false
;
241
}
242
243
// check to ensure the plugin is properly signed
244
Signature signatures[] = pkgInfo.signatures;
245
if
(signatures ==
null
) {
246
return
false
;
247
}
248
if
(SystemProperties.getBoolean(
"ro.secure"
,
false
)) {
249
boolean
signatureMatch =
false
;
250
for
(Signature signature : signatures) {
251
for
(
int
i =
0
; i < SIGNATURES.length; i++) {
252
if
(SIGNATURES[i].equals(signature)) {
253
signatureMatch =
true
;
254
break
;
255
}
256
}
257
}
258
if
(!signatureMatch) {
259
return
false
;
260
}
261
}
262
263
return
true
;
264
}
265
从这段代码以及PluginManager.java的其他代码,可以看到WebKit是这样认证一个APK是否是Adobe FlashPlayer插件的:
– APK证书中包含Adobe的签名证书,证书数据写死在代码中(PluginManager. SIGNATURE_1),这是adobe使用的签名
– APK申请了android.webkit.permission.PLUGIN权限
– APK声明了一个服务,Intent是android.webkit.PLUGIN,有个meta信息是type,type的值必须是native
在以上要求中唯一强制性限制就是证书验证,使用Fakeid,我们已经绕过了这个限制,其他校验自然不成问题。
在4.4以前,任何使用webview并且在访问了请求flash的页面(比如新浪首页)都会被我们的APK程序注入,下图是2345手机浏览器被注入的情况:




这里可以看到com.example.noperm和com.adobe.flashplayer都被认为是可信插件,成功进行了注入。
但是只是注入成功,我们的代码并没有得到执行机会。我们注入成功是因为webkit调用了PluginManager.getPluginClass将我们之前注册过接收android.webkit.PLUGIN 的服务load起来。但是我们的代码并不会被回调。希望注入代码被回调,还需要我们进一步理解webkit插件开发的规范。

291
/* package */
292
Class<?> getPluginClass(String packageName,String className)
293
throws
NameNotFoundException,ClassNotFoundException {
294
Context pluginContext = mContext.createPackageContext(packageName,
295
Context.CONTEXT_INCLUDE_CODE |
296
Context.CONTEXT_IGNORE_SECURITY);
297
ClassLoader pluginCL = pluginContext.getClassLoader();
298
return
pluginCL.loadClass(className);
299
}

四、理解Webkit Plugin,突破沙箱限制执行代码

上文中我们已经看到了webkit调用getPluginClass将我们的apk load到了虚拟机中,但是没有任何代码被触发。
实际上Webkit Plugin的核心是native程序,apk中的java代码仅仅是被JNI调用的。想要触发代码执行,还需要在我们的APK中放一个符合规范的SO文件供Webkit调用
AOSP中恰好有一份浏览器插件的代码[7],所以我们很容易了解插件编写规范。
首先我们需要导出4个接口。

extern
"C"
{
EXPORTNPError NP_Initialize(NPNetscapeFuncs* browserFuncs,NPPluginFuncs* pluginFuncs,
void
*java_env);
EXPORTNPError NP_GetValue(NPP instance,NPPVariable variable,
void
*value);
EXPORT
const
char
* NP_GetMIMEDescription(
void
);
EXPORT
void
NP_Shutdown(
void
);
};
并且在回调函数中告诉浏览器我们是一个Flash插件,这样在Webkit遇到页面的Flash请求时,会加载所有插件libs目录下的so文件并询问他是何种插件.
我们要做的就是告诉浏览器:我是一个flash处理插件。

const
char
*NP_GetMIMEDescription(
void
)
{
return
"application/x-shockwave-flash:swf:ShockwaveFlash;application/futuresplash:spl:Futu\
reSplash Player";
}
这样一来我们的SO也被加载了,代码已经突破沙箱执行了:




我将部分关键代码和编译好的poc放到了github上: https://github.com/retme7/FakeID_poc_by_retme_bug_13678484/ 相关链接:
[1] http://bluebox.com/blog/technical/android-fake-id-vulnerability/
[2] https://www.ietf.org/rfc/rfc2315.txt
[3] https://www.ietf.org/rfc/rfc2459
[4] https://android.googlesource.com/platform/libcore/+/android-cts-4.1_r4%5E%21/
[5]https://github.com/retme7/FakeID_poc_by_retme_bug_13678484/
[6]AOSP/frameworks/base/core/java/android/webkit/PluginManager.java
[7]AOSP/frameworks/base/tests/BrowserTestPlugin/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: