前言:
Android渗透过程中,会经常遇见https证书校验,不能抓取数据包。就比如我手机无法Root,每次都要用到模拟器,但是有些App它会检查是否在模拟器中运行,从而闪退无法正常使用。于是,这篇文章诞生了。
基础学习:
APP是HTTPS的服务提供方自己开发的客户端,开发者可以先将自己服务器的证书打包内置到自己的APP中,或者将证书签名内置到APP中,当客户端在请求服务器建立连接期间收到服务器证书后,先使用内置的证书信息校验一下服务器证书是否合法,如果不合法,直接断开
当APP是HTTPS时,单纯的使用Burpsuite无法抓取数据包,原因是APP启用了SSL Pinning(又叫做“SSL证书绑定”)
如果这里不懂的话,可以了解以下https的建立过程:
几乎所有网络数据的抓包都是采用中间人的方式(MITM),包括大家常用的Fiddler、Charles等知名抓包工具
不论是使用burpsuite还是fiddler,当前的抓包工具基本原理都是采用的中间人的方式。原理就是这些工具作为中间人,对客户端伪装成服务端,对服务端伪装成客户端
Android App 抓包有三种情况(https):
情况1,客户端不存在证书校验,服务器也不存在证书校验。
情况2,客户端存在校验服务端证书,服务器也不存在证书校验,单项校验。
情况3、客户端存在证书校验,服务器也存在证书校验,双向校验。
1、使用jd-gui进行反编译后,全局搜索:checkClientTrusted 或者checkServerTrusted 字符串,如下图:
情况1:apk程序客户端与服务端都没有存在证书校验
SSL pinning:
SSL Pinning是一种防止中间人攻击的技术,主要机制是在客户端发起请求–>收到服务器发来的证书进行校验,如果收到的证书不被客户端信任,就直接断开连接不继续求情。可以发现中间人攻击的要点的伪造了一个假的服务端证书给了客户端,客户端误以为真。解决思路就是,客户端也预置一份服务端的证书,比较一下就知道真假了。
SSL-pinning有两种方式:
证书锁定(Certificate Pinning) 和公钥锁定( Public Key Pinning)
证书锁定:
需要在客户端代码内置仅接受指定域名的证书,而不接受操作系统或浏览器内置的CA根证书对应的任何证书,通过这种授权方式,保障了APP与服务端通信的唯一性和安全性,因此客户端与服务端(例如API网关)之间的通信是可以保证绝对安全。但是CA签发证书都存在有效期问题,缺点是在
证书续期后需要将证书重新内置到APP中
公钥锁定:
提取证书中的公钥并内置到客户端中,通过与服务器对比公钥值来验证连接的正确性。制作证书密钥时,公钥在证书的续期前后都可以保持不变(即密钥对不变),所以可以避免证书有效期问题,一般推荐这种做法。
https需要CA证书,我们之前说的中间人需要对客户端伪装成真正的服务端,要求就是当客户端向我们发送网络请求时,我们必须能够给指定域名签发公钥证书,且公钥证书能够通过系统的安全校验。对于我们是不是真正的客户端,通常来说服务器是不太会关心的,他是不会去关心你是谷歌浏览器还是百度浏览器,当然了也会有例外。接下来要说的双向验证就是如此
绕过方案一:
burpsuite CA证书安装绕过:
在已经root的设备上安装CA证书,然后直接抓包即可,在本机无法取得root的情况下,通过模拟器也可抓到https数据包
绕过方案二(无需root,通用):
反编译apk,找到校验证书方法,将校验部分删除,从而变成情况1,再编译apk,成功抓取数据包
利用条件:
客户端程序没有对自身完整性进行校验应用完整性校检
利用Androidkiller.exe反编译apk文件,找到checkServerTrusted方法的smali代码:
Xposed框架+JustTrustMe 绕过:
Xposed框架是一款开源框架,其功能是可以在不修改APK的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。Xposed 就好比是 Google 模块化手机的主体,只是一个框架的存在,在添加其他功能模块(Modules)之前,发挥不了什么作用,但是没了它也不行。也正因为如此,Xposed 具有比较高的可定制化程度。Moto X 用户可定制手机的外观、壁纸、开机动画等,Xposed 则允许用户自选模块对手机功能进行自定义扩充。
JustTrustMe: https://github.com/Fuzion24/JustTrustMe/releases
JustTrustMe 一个用来禁用、绕过 SSL 证书检查的基于 Xposed 模块。简单来说,JustTrustMe 是将 APK 中所有用于校验 SSL 证书的 API 都进行了 Hook,从而绕过证书检查
在夜神模拟器上就可以直接下载安装:
Frida绕过,详情参考如下:
Frida详细安装教程
https://github.com/WooyunDota/DroidDrops/blob/master/2018/Frida.Android.Practice.md
情况三:双向认证
当服务器启用了证书双向认证之后,除了客户端去验证服务器端的证书外,服务器也同时需要验证客户端的证书,也就是会要求客户端提供自己的证书,如果没有通过验证,则会拒绝连接,如果通过验证,服务器获得用户的公钥
详细过程:
(1)客户端发起HTTPS请求,将SSL协议版本的信息发送给服务端。
(2)服务端去CA机构申请来一份CA证书,在前面提过,证书里面有服务端公钥和签名。将CA证书发送给客户端
(3)客户端读取CA证书的明文信息,采用相同的hash散列函数计算得到信息摘要(hash目的:验证防止内容被修改),然后用操作系统带的CA的公钥去解密签名(因为签名是用CA的私钥加密的),对比证书中的信息摘要。如果一致,则证明证书是可信的,然后取出了服务端公钥
(4)客户端发送自己的客户端证书给服务端,证书里面有客户端的公钥:C_公钥
(5)客户端发送支持的对称加密方案给服务端,供其选择
(6)服务端选择完加密方案后,用刚才得到的C_公钥去加密选好的加密方案
(7)客户端用自己的C_私钥去解密选好的加密方案,客户端生成一个随机数(密钥F),用刚才等到的服务端B_公钥去加密这个随机数形成密文,发送给服务端。
(8)服务端和客户端在后续通讯过程中就使用这个密钥F进行通信了。和之前的非对称加密不同,这里开始就是一种对称加密的方式
双向认证需要Server支持,Client必须内置一套公钥证书 + 私钥。在SSL/TLS握手过程中,Server端会向Client端请求证书,Client端必须将内置的公钥证书发给Server,Server验证公钥证书的真实性
双向认证内置的公钥证书+私钥是额外的一套,不同于证书固定内置的公钥证书
用于双向认证的公钥证书和私钥代表了Client端身份,所以其是隐秘的,一般都是用.p12或者.bks文件+密钥进行存放。由于是内置在Client中,存储的密钥一般也是写死在Client代码中,有些App为了防反编译会将密钥写到so库中,比如S匿名社交App,但是只要存在于Client端中都是有办法提取出来的
这里我们以Soul举例:
该app直接封装了客户端的证书,相比于单项认证,多了一个服务器端验证客户端证书的过程,而在以往的用代理工具如burp这类工具,抓取https的包时,除了浏览器获取的是代理工具的证书外,默认是不发送证书给服务器端的。burp在抓取https报文的过程中也提供了双向认证的证书发送,但是是使用了burp提供的证书文件,也就是CA证书。app的服务端不认证这个burp提供的CA证书,那么我们就需要拿到匹配的证书,以其对服务端进行匹配
首先,解压APK,提取出.p12/.pfx或者.bks文件,二进制的文件一般存放都在raw或者assets目录
app解密的代码逻辑:
客户端发送数据包以后,需要去从app中读取这个证书文件,密码是以硬编码形式放在了代码中,利用这个代码中的密码字段去解密证书文件,从中读取以后,再进行解密并回传给服务器端进行确认。由此推断,寻找证书名称应该就可以拿到安装密码
利用搜索来找client.p12的值(或者关键字PKCS12,这是通常读取证书需要用到的关键字)
一般通过逆向可以从APK中提取出密钥,怪我太菜java完全不会,Android渗透刚刚接触,这里先略过,以后再来填坑……
证书成功导入后,勾选即可使用
双向验证与SSL pinning的区别:
SSL pinning实际上是客户端锁定服务器端的证书, 在要与服务器进行交互的时候, 服务器端会将CA证书发送给客户端, 客户端会调用函数对服务器端的证书进行校验, 与本地的服务器端证书(存放在.\asset目录或\res\raw下)进行比对。而双向认证是添加了客户端向服务器发送CA证书, 服务器端对客户端的证书进行校验的部分。在app上,https双向认证的方案也可以防止中间人劫持,但这种双向认证开销较大,且安全性与SSL pinning一致,目前大多数app都采用SSL Pinning这种方案
CA证书:
抓包应用内置的CA证书要洗白,必须安装到系统中。而Android系统将CA证书又分为两种:用户CA证书和系统CA证书。顾明思议,用户CA证书是由用户自行安装的,系统CA证书是由系统内置的,很明显后者更加真实有效
系统CA证书存放在/etc/security/cacerts/目录下,名称是CA证书subjectDN的Md5值前四位移位取或,后缀名是.0,比如00673b5b.0。考虑到安全原因,系统CA证书需要有Root权限才能进行添加和删除。
对于非Root的Android设备,用户只能安装用户CA证书。
使用限制:
Android从7.0开始系统不再信任用户CA证书(应用targetSdkVersion >= 24时生效,如果targetSdkVersion < 24即使系统是7.0+依然会信任)。也就是说即使安装了用户CA证书,在Android 7.0+的机器上,targetSdkVersion >= 24的应用的HTTPS包就抓不到了
绕过方案一:配置networkSecurityConfig
1、在AndroidManifest中配置networkSecurityConfig
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:networkSecurityConfig="@xml/network_security_config"
</application>
</manifest>
2、在项目res目录下新增一个文件夹,命名xml,并且新建一个xml文件,命名为network_security_config.xml,命名名称跟上面匹配。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
这样即表示,App信任用户CA证书,让系统对用户CA证书的校验给予通过
详情请参考:https://developer.android.com/training/articles/security-config
最开始直接通过adb安装,发现无法抓到包
绕过方案二:调低targetSdkVersion < 24
如果想抓一个App的包,可以找个历史版本,只需要其targetSdkVersion < 24即可。然而,随着GooglePlay开始限制targetSdkVersion,现在要求其必须>=26,2019年8月1日后必须>=28,国内应用市场也开始逐步响应这种限制。然后目前绝大多数App的targetSdkVersion都大于24
绕过方案三:平行空间抓包:
如果我们希望抓targetSdkVersion >= 24的应用的包,那又该怎么办呢?我们可以使用平行空间或者VirtualApp来曲线救国。平行空间和VirtualApp这种多开应用可以作为宿主系统来运行其它应用,如果平行空间和VirtualApp的targetSdkVersion < 24,那么问题也就解决了。
绕过方案四:安装到系统CA证书目录(需root)
非Http协议抓包:
如果确认了以上几点,仍然抓包失败,那么极有可能使用的并非是HTTP协议。比如像微信聊天,视频直播等,使用的就不是HTTP协议,这种情况需要使用其它的抓包工具,比如Packet Capture这种直接解析TCP/UDP协议的,但是往往非HTTP协议的数据包即使抓到了也无法解析出来,因为大概率都是二进制而非文本格式的。
补充:所有https能抓到包的前天是基于https证书校验不严格,如果证书校验严格的话是抓不到的
参考如下: