作者:杨充
链接:
https://juejin.cn/post/7175325220109025339
本文由作者授权发布。
1.1 项目背景
- 针对该项的主要检测手段就是使用中间人代理机制对网络传输数据进行抓包、拦截和篡改,以检验App在核心链路上是否有安全漏洞。
- 通过charles等工具可以对app的网络请求进行抓包,这样这些信息就会被清除的提取出来,会被不法分子进行利用。
- 不想自身App的数据被别人轻而易举地抓包获取到,从而进行类似业务或数据分析、爬虫或网络攻击等破坏性行为。
1.2 思考问题
- 开发项目的时候,都需要抓包,很多情况下即使是Https也能正常抓包正常。那么问题来了:
- 抓包的原理是?任何Https的 app 都能抓的到吗?如果不能,哪些情况下可以抓取,哪些情况下抓取不到?
- 使用HTTPS协议进行通信时,客户端需要对服务器身份进行完整性校验,以确认服务器是真实合法的目标服务器。
- 如果没有校验,客户端可能与仿冒的服务器建立通信链接,即“中间人攻击”。
1.3 设计目标
- 目前只是针对App端有需要做防抓包措施,后期其他业务线可能也有这个需要。因此下沉为工具库,傻瓜式调用很有必要。
- 第一点:必须是低入侵性,对原有代码改动少,最简单的加入是一行代码设置即可。完全解耦合。
- 第二点:可以动态灵活配置,支持配置禁止代理,支持配置是否证书校验,支持配置域名合法性过滤,支持拦截器加解密数据。
- 第三点:可以检测App是否在双开,挂载,Xposed攻击环境。
- 第四点:可以灵活设置加解密的key,可以灵活替换加解密方式,比如目前采用RC4,另一个项目想用DES,可以灵活更换。
1.4 收益分析
- 提高产品App的数据安全,必须对数据传输做好安全保护措施和完整性校验,以防止自身数据在网络传输中裸奔,甚至是被三方恶意利用或攻击。
2.1 Https三要素
要清楚HTTPS抓包的原理,首先需要先说清楚 HTTPS 实现数据安全传输的工作原理,主要分为三要素和三阶段。2.认证:通过数字签名实现。(因为私钥只有 “合法的发送方” 持有,其他人伪造的数字签名无法通过验证)3.报文完整性:通过数字签名实现。(因为数字签名中使用了消息摘要,其他人篡改的消息无法通过验证)1.CA 证书校验:CA 证书校验发生在 TLS 的前两次握手,客户端和服务端通过报文获得服务端 CA 证书,客户端验证 CA 证书合法性,从而确认 CA 证书中的公钥合法性(大多数场景不会做双向认证,即服务端不会认证客户端合法性,这里先不考虑)。2.密钥协商:密钥协商发生在 TLS 的后两次握手,客户端和服务端分别基于公钥和私钥进行非对称加密通信,协商获得 Master Secret 对称加密私钥(不同算法的协商过程细节略有不同)。3.数据传输:数据传输发生在 TLS 握手之后,客户端和服务端基于协商的对称密钥进行对称加密通信。2.2 抓包核心原理
- Fiddler、Charles等抓包工具,其实都是采用了中间人攻击的方案:将客户端的网络流量代理到MITM(中间人)主机,再通过一系列的面板或工具将网络请求结构化地呈现出来。
- CA证书校验是否合法;数据传递过程中的加密和解密。如果是要抓包,则需要突破这两点的技术,无非就是MITM(中间人)伪造证书和使用自己的加解密方式。
- 中间人截获客户端向发起的HTTPS请求,佯装客户端,向真实的服务器发起请求;
- 中间人截获真实服务器的返回,佯装真实服务器,向客户端发送数据;
- 中间人获取了用来加密服务器公钥的非对称秘钥和用来加密数据的对称秘钥,处理数据加解密。
2.3 搞定CA证书
- HTTPS抓包的原理还是挺简单的,简单来说,就是Charles作为“中间人代理”,拿到了服务器证书公钥和HTTPS连接的对称密钥。
- 前提是客户端选择信任并安装Charles的CA证书,否则客户端就会“报警”并中止连接。这样看来,HTTPS还是很安全的。
- 抓包应用内置的 CA 证书要洗白,必须安装到系统中。而 Android 系统将 CA 证书又分为两种:用户 CA 证书和系统 CA 证书(必要Root权限)。
- 只有系统(system)证书才会被信任。用户(user)导入的Charles根证书是不被信任的。相当于可以理解Android系统增加了安全校验!
- 第一种方式:AndroidManifest 中配置 networkSecurityConfig,App 信任用户 CA 证书,让系统对用户 CA 证书的校验给予通过。
- 第二种方式:调低 targetSdkVersion < 24,不过这种方式谷歌市场有限制,意味着抓 HTTPS 的包越来越难操作。
- 第三种方式:挂载App抓包,VirtualApp 这种多开应用可以作为宿主系统来运行其它应用,利用xposed避开CA证书校验。
- 第四种方式:Root手机,把 CA 证书安装到系统 CA 证书目录中,那这个假 CA 证书就是真正洗白了,难度较大。
2.4 突破CA证书校验
1.设置targetSdkVersion大于24,去掉清单文件中networkSecurityConfig文件中的system和user配置,设置不信任用户证书。2.公钥证书固定。指 Client 端内置 Server 端真正的公钥证书。在 HTTPS 请求时,Server 端发给客户端的公钥证书必须与 Client 端内置的公钥证书一致,请求才会成功。 • 证书固定的一般做法是,将公钥证书(.crt 或者 .cer 等格式)内置到 App 中,然后创建 TrustManager 时将公钥证书加进去。第一种:JustTrustMe 破解证书固定。Xposed 和 Magisk 都有相应的模块,用来破解证书固定,实现正常抓包。破解的原理大致是,Hook 创建 SSLContext 等涉及 TrustManager 相关的方法,将固定的证书移除。第二种:基于 VirtualApp 的 Hook 机制破解证书固定。在 VirtualApp 中加入 Hook 代码,然后利用 VirtualApp 打开目标应用进行抓包。具体看:VirtualHook。
https://github.com/PAGalaxyLab/VirtualHook加密和解密都是用相同密钥。只有一把密钥,如果密钥暴露,内容就会暴露。但是这一块逆向破解有些难度。而破解解密方式就是用密钥逆向解密,或者中间人冒充使用自己的加解密方式!不一定安全。中间人伪造自己的公钥和私钥,然后拦截信息,进行篡改。Charles 通过将软件本身设置成系统的网络访问代理服务器,使得所有的网络请求都会走一遍 Charles 代理,从而 Charles 可以截取经过它的请求,然后我们就可以对其进行网络包的分析。Charles对应设置:将代理功能打开,并设置一个固定的端口。默认情况下,端口号为:8888 。移动设备设置:在手机上设置 WIFI 的 HTTP 代理。注意这里的前提是,Phone 和 Charles 代理设备链接的是同一网络(同一个ip地址和端口号)。正常情况下,Charles 是不能截取Https的网络包的,这涉及到 Https 的证书问题。Charles抓包:
https://mp.weixin.qq.com/s/kqMUbHl59V75w8xBxHbXkA第一步,客户端向服务器发起HTTPS请求,charles截获客户端发送给服务器的HTTPS请求,charles伪装成客户端向服务器发送请求进行握手 。第二步,服务器发回相应,charles获取到服务器的CA证书,用根证书(这里的根证书是CA认证中心给自己颁发的证书)公钥进行解密,验证服务器数据签名,获取到服务器CA证书公钥。然后charles伪造自己的CA证书(这里的CA证书,也是根证书,只不过是charles伪造的根证书),冒充服务器证书传递给客户端浏览器。第三步,与普通过程中客户端的操作相同,客户端根据返回的数据进行证书校验、生成密码Pre_master、用charles伪造的证书公钥加密,并生成HTTPS通信用的对称密钥enc_key。第四步,客户端将重要信息传递给服务器,又被charles截获。charles将截获的密文用自己伪造证书的私钥解开,获得并计算得到HTTPS通信用的对称密钥enc_key。charles将对称密钥用服务器证书公钥加密传递给服务器。第五步,与普通过程中服务器端的操作相同,服务器用私钥解开后建立信任,然后再发送加密的握手消息给客户端。第六步,charles截获服务器发送的密文,用对称密钥解开,再用自己伪造证书的私钥加密传给客户端。第七步,客户端拿到加密信息后,用公钥解开,验证HASH。握手过程正式完成,客户端与服务器端就这样建立了”信任“。• 在之后的正常加密通信过程中,charles如何在服务器与客户端之间充当第三者呢?服务器—>客户端:charles接收到服务器发送的密文,用对称密钥解开,获得服务器发送的明文。再次加密, 发送给客户端。客户端—>服务端:客户端用对称密钥加密,被charles截获后,解密获得明文。再次加密,发送给服务器端。由于charles一直拥有通信用对称密钥enc_key,所以在整个HTTPS通信过程中信息对其透明。
3.1 先看如何抓包
- 1.电脑上需要安装证书。这个主要是让Charles充当中间人,颁布自己的CA证书。
- 2.手机上需要安装证书。这个是访问Charles获取手机证书,然后安装即可。
- 3.Android项目代码设置兼容。Google 推出更加严格的安全机制,应用默认不信任用户证书(手机里自己安装证书),自己的app可以通过配置解决,相当于信任证书的一种操作!
- 第一点:必须链接代理,且跟Charles要具有相同ip。思路:客户端是否可以判断网络是否被代理了。
- 第二点:CA证书,这一块避免使用黑科技hook证书校验代码,或者拥有修改CA证书权限。思路:集中在可以判断是否挂载。
- 第三点:冒充中间人CA证书,在客户端client和服务端server之间篡改拦截数据。思路:可以做CA证书校验。
- 第四点:为了可以在7.0上抓包,App往往配置清单文件networkSecurityConfig。思路:线上环境去掉该配置。
3.2 设置配置文件
- debug包为了能够抓包,需要配置networkSecurityConfig清单文件的system和user权限,只有这样才会信任用户证书。
- 不论是权威机构颁发的证书还是自签名的,打包一份到 app 内部,比如存放在 asset 里。然后用这个KeyStore去引导生成的TrustManager来提供证书验证。
- Android允许开发者重定义证书验证方法,使用HostnameVerifier类检查证书中的主机名与使用该证书的服务器的主机名是否一致。
- 如果重写的HostnameVerifier不对服务器的主机名进行验证,即验证失败时也继续与服务器建立通信链接,存在发生“中间人攻击”的风险。
3.3 数据加密处理
- 为了项目数据安全性,对请求体和响应体加密,那肯定要知道请求体或响应体在哪里,然后才能加密,其实都一样不论是加密url里面的query内容还是加密body体里面的都一样。
- 目前对数据返回的data进行加解密。那么如何做数据加密呢?目前项目中采用RC4加密和解密数据。
- 有的APP为了防止抓取,在返回的内容上做了层加密,所以从Charles上看到的内容是乱码。这种情况下也只能反编译APP,研究其加密解密算法进行解密。难度极大!
3.4 避免黑科技抓包
- 这种方式可以检查是否有Xposed环境,大概的思路是使用ClassLoader去加载固定包名的xp类,或者手动抛出异常然后捕获去判断是否包含Xposed环境。
- 基于VirtualApp挂载App突破证书访问权限
4.1 App安全配置
- android:networkSecurityConfig="@xml/network_security_config"
- 配置networkSecurityConfig抓包说明:
- 中间人代理之所有能够获取到加密密钥就是因为我们手机上安装并信任了其代理证书,这类证书安装后都会被归结到用户证书一类,而不是系统证书。
- 那我们可以选择只信任系统内置的系统证书,而屏蔽掉用户证书(Android7.0以后就默认是只信任系统证书了),就可以防止数据被解密了。
- 一种是Android官方提供的网络安全配置;另一种也可以通过设置网络框架实现(以okhttp为例)。
- 第一种:具体可以看清单配置文件,相当于base-config标签下去掉 这组标签。
- 第二种:需要给okhttpClient配置 X509TrustManager 来监听校验服务端证书有效性。遍历设备上信任的证书,通过证书别名将用户证书(别名中含有user字段)过滤掉,只将系统证书添加到验证列表中。
- 优点:network_security_config配置简单,对整个app网络生效,无需修改代码;代码实现对通过该网络框架请求的生效,能兼容7.0以前系统。
- 缺陷:network_security_config配置方式,7.0以前的系统配置不生效,依然可以通过代理工具进行抓包。okhttp配置的方式只能对使用该网络框架进行数据传输的接口生效,并不能对整个app生效。
- 破解:将手机进行root,然后将代理证书放置到系统证书列表内,就可以绕过代码或配置检查了。
4.2 关闭代理
OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY)
.build()
- no_proxy实际上就是type属性为direct的一个proxy对象,这个type有三种:
- direct,http,socks。这样因为是直连,所以不走代理。所以charles等工具就抓不到包了,这样一定程度上保证了数据的安全,这种方式只是通过代理抓不到包。
- 通常情况下上述的办法有用,但是无法防住使用 VPN 导流进行的抓包:
- 使用VPN抓包的原理是,先将手机请求导到VPN,再对VPN的网络进行Charles的代理,绕过了对App的代理。
- 缺陷:该方案比较粗暴,将一切代理都切断了,对于有合理诉求需要使用网络代理的场景无法满足。
- 破解:使用ProxyDroid全局代理工具通过iptables对请求进行强制转发,可以有效绕过代理检测。
4.3 证书校验(单向认证)
- 为了防止上面方案可能导致的“中间人攻击”,可以下载服务器端公钥证书,然后将公钥证书编译到Android应用中一般在assets文件夹保存,由应用在交互过程中去验证证书的合法性。
- 通过OkHttp的API方法 sslSocketFactory(sslSocketFactory,trustManager) 设置SSL证书校验。
- 通过OkHttp的API方法 hostnameVerifier(hostnameVerifier) 设置域名合法性校验。
- 按CA证书去验证的,若不是CA可信任的证书,则无法通过验证。
- 优点:安全性比较高,单向认证校验证书在代码中是方便的,安全性相对较高。
- 破解:证书锁定破解比较复杂,比如老牌的JustTrustMe插件,通过hook各网络框架的证书校验方法,替换原有逻辑,使校验失效。
4.4 双向认证
- SSL/TLS 协议提供了双向认证的功能,即除了 Client 需要校验 Server 的真实性,Server 也需要校验 Client 的真实性。
- 双向认证需要 Server 支持,Client 必须内置一套公钥证书 + 私钥。在 SSL/TLS 握手过程中,Server 端会向 Client 端请求证书,Client 端必须将内置的公钥证书发给 Server,Server 验证公钥证书的真实性。
- 用于双向认证的公钥证书和私钥代表了 Client 端身份,所以其是隐秘的,一般都是用 .p12 或者 .bks 文件 + 密钥进行存放。
- 双向校验就是自定义生成客户端证书,保存在服务端和客户端,当客户端发起请求时在服务端也校验客户端的证书合法性,如果不是可信任的客户端发送的请求,则拒绝响应。
- 服务端根据自身使用语言和网络框架配置相应证书校验机制即可。
- 缺陷:服务端需要存储客户端证书,一般服务端会对应多个客户端,就需要分别存储和校验客户端证书,增加校验成本,降低响应速度。该方案比较适合对安全等级要求比较高的业务(如金融类业务)。
- 破解:由于在服务端也做校验,在服务端安全的情况下很难被攻破。
4.5 防止挂载抓包
- Xposed + JustTrustMe 可以破解绕过校验CA证书。那么这样CA证书的校验就形同虚设了,对App的危险性也很大。
- 多开App的原理类似,都是以新进程运行被多开的App,并hook各类系统函数,使被多开的App认为自己是一个正常的App在运行。
- 一种是从多开App中直接加载被多开的App,如平行空间、VirtualApp等,另一种是让用户新安装一个App,但这个App本质上就是一个壳,用来加载被多开的App。
- 它破坏了Android 系统本身的隔离措施,可以进行免root hook和其他黑科技操作,你可以用这个做很多在原来APP里做不到事情,于此同时Virtual App的安全威胁也不言而喻。
- 第一种方式:获取当前设备所有运行的APP,根据安装包名对应用进行检测判断是否有Xposed环境。
- 第二种方式:通过自造异常来检测堆栈信息,判断异常堆栈中是否包含Xposed等字符串。
- 第三种方式:通过ClassLoader检查是否已经加载了XposedBridge类和XposedHelpers类来检测。
- 第四种方式:获取DEX加载列表,判断其中是否包含XposedBridge.jar等字符串。
- 第五种方式:检测Xposed相关文件,通过读取/proc/self/maps文件,查找Xposed相关jar或者so文件来检测。
- 第一种方式:通过检测app私有目录,多开后的应用路径会包含多开软件的包名。还有一种思路遍历应用列表如果出现同样的包名,则被认为双开了。
- 第二种方式:如果同一uid下有两个进程对应的包名,在"/data/data"下有两个私有目录,则该应用被多开了。
- 判断了具有xposed或者多开环境怎么处理App:
- 目前使用VirtualApp挂载,或者Xposed黑科技去hook,前期可以先用埋点统计。测试学而思App发现挂载在VA上是推出App。
4.6 数据加解密
- 目前在网络请求类里添加拦截器,然后在拦截器中处理request请求和response响应数据的加密和解密操作。
- 在request请求数据阶段,如果是get请求加密url数据,如果是post请求则加密url数据和requestBody数据。
- 第一步:获取请求的数据。主要是获取请求url和requestBody,这一块需要对数据一块处理。
- 第三步:根据不同的请求方式构造新的request。使用 key 和 result 生成新的 RequestBody 发起网络请求。
- 第一步:常规解析得到 result ,然后使用RC4工具,传入key去解密数据得到解密后的字符串。
- 第二步:将解密的字符串组装成ResponseBody数据传入到body对象中。
- 第三步:利用response对象去构造新的response,然后最后返回给App。
4.7 证书锁定
- 证书锁定是Google官方比较推荐的一种校验方式:
- 原理是在客户端中预先设置好证书信息,握手时与服务端返回的证书进行比较,以确保证书的真实性和有效性。
- 有两种实现方式:一种通过network_security_config.xml配置,另一种通过代码设置;
//第一种方式:配置文件
api.zuoyebang.cn
38JpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhK90=
9k1a0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM90K=
//第二种方式:代码设置
fun sslPinning(): OkHttpClient {
val builder = OkHttpClient.Builder()
val pinners = CertificatePinner.Builder()
.add("api.zuoyebang.cn", "sha256//89KpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRh00L=")
.add("api.zuoyebang.com", "sha256//a8za0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1o=09")
.build()
builder.apply {
certificatePinner(pinners)
}
return builder.build()
}
- 优点:安全性高,配置方式也比较简单,并能实现动态更新配置。
- 缺陷:网络安全配置无法实现证书证书的动态更新,另外该配置也受Android系统影响,对7.0以前的系统不支持。代码配置相对灵活些。
- 破解:证书锁定破解比较复杂,比如老牌的JustTrustMe插件,通过hook各网络框架的证书校验方法,替换原有逻辑,使校验失效
4.8 Sign签名
- 请求来源(身份)是否合法?请求参数被篡改?请求的唯一性(不可复制)?
- 解决方案:为了保证数据在通信时的安全性,我们可以采用参数签名的方式来进行相关验证。
- 调用接口之前需要验证签名和有效时间,要生成一个sign签名。先拼接-后转码-再加密-再发请求!
- 这样请求的时候就需要合法正确签名sign才可以获取数据。这样就解决了身份验证和防止参数篡改问题,如果请求参数被人拿走,没事,他们永远也拿不到secret,因为secret是不传递的。再也无法伪造合法的请求。
5.1 整体架构设计
5.2 稳定性设计
- 在网络上交换数据(网络请求数据)时,可能会遇到不可见字符,不同的设备对字符的处理方式有一些不同。
- Base64对数据内容进行编码来适合传输。准确说是把一些二进制数转成普通字符用于网络传输。统统变成可见字符,这样出错的可能性就大降低了。
5.3 降级设计
.setMonitorToggle(object : IMonitorToggle {
override fun isOpen(): Boolean {
//todo 是否降级,如果降级,则不使用该功能。留给AB测试开关
return false
}
})
5.4 异常设计说明
- Android 有自带的Base64实现,flag要选Base64.NO_WRAP,不然末尾会有换行影响服务端解码。导致解码失败。
5.5 Api文档
NotCaptureHelper.getInstance().config = CaptureConfig.builder()
//设置debug模式
.setDebug(true)
//设置是否禁用代理
.setProxy(false)
//设置是否进行数据加密和解密,
.setEncrypt(true)
//设置cer证书路径
.setCerPath("")
//设置是否进行CA证书校验
.setCaVerify(false)
//设置加密和解密key
.setEncryptKey(key)
//设置参数
.setReservedQueryParam(OkHttpBuilder.RESERVED_QUERY_PARAM_NAMES)
.setMonitorToggle(object : IMonitorToggle {
override fun isOpen(): Boolean {
//todo 是否降级,如果降级,则不使用该功能。留给AB测试开关
return false
}
})
.build()
NotCaptureHelper.getInstance().setOkHttp(app,okHttpBuilder)
NotCaptureHelper.getInstance().encryptDecryptListener = object : EncryptDecryptListener {
/**
* 外部实现自定义加密数据
*/
override fun encryptData(key: String, data: String): String {
LoggerReporter.report("NotCaptureHelper", "encryptData data : $data")
val str = data.encryptWithRC4(key) ?: ""
LoggerReporter.report("NotCaptureHelper", "encryptData str : $str")
return str
}
/**
* 外部实现自定义解密数据
*/
override fun decryptData(key: String, data: String): String {
LoggerReporter.report("NotCaptureHelper", "decryptData data : $data")
val str = data.decryptWithRC4(key) ?: ""
LoggerReporter.report("NotCaptureHelper", "decryptData str : $str")
return str
}
}
5.6 防抓包功能自测
- 单向认证测试:进行网络请求,会提示SSLHandshakeException即ssl握手失败的错误提示,即表示app端的单向认证成功。
- 数据加解密:进行网络请求,看一下请求参数和响应body数据是否加密,如果看不到实际json实体则表示加密成功。
防抓包库:
https://github.com/yangchong211/YCToolLib
综合库:
https://github.com/yangchong211/YCAppTool
视频播放器:
https://github.com/yangchong211/YCVideoPlayer
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
![]()
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!