App防抓包的 8 个实践
本文作者
作者:杨充
链接:
https://juejin.cn/post/7175325220109025339
本文由作者授权发布。
1.1 项目背景
通讯安全是App安全检测过程中非常重要的一项。 针对该项的主要检测手段就是使用中间人代理机制对网络传输数据进行抓包、拦截和篡改,以检验App在核心链路上是否有安全漏洞。 保证数据安全。 通过charles等工具可以对app的网络请求进行抓包,这样这些信息就会被清除的提取出来,会被不法分子进行利用。 不想被竞争对手逆向抓包。 不想自身App的数据被别人轻而易举地抓包获取到,从而进行类似业务或数据分析、爬虫或网络攻击等破坏性行为。
1.2 思考问题
开发项目的时候,都需要抓包,很多情况下即使是Https也能正常抓包正常。那么问题来了: 抓包的原理是?任何Https的 app 都能抓的到吗?如果不能,哪些情况下可以抓取,哪些情况下抓取不到? 什么叫做中间人攻击? 使用HTTPS协议进行通信时,客户端需要对服务器身份进行完整性校验,以确认服务器是真实合法的目标服务器。 如果没有校验,客户端可能与仿冒的服务器建立通信链接,即“中间人攻击”。
1.3 设计目标
防止App被各种方式抓包。 做好各种防抓包安全措施,避免各种黑科技抓包。 沉淀为技术库复用。 目前只是针对App端有需要做防抓包措施,后期其他业务线可能也有这个需要。因此下沉为工具库,傻瓜式调用很有必要。 该库终极设计目标如下所示。 第一点:必须是低入侵性,对原有代码改动少,最简单的加入是一行代码设置即可。完全解耦合。 第二点:可以动态灵活配置,支持配置禁止代理,支持配置是否证书校验,支持配置域名合法性过滤,支持拦截器加解密数据。 第三点:可以检测App是否在双开,挂载,Xposed攻击环境。 第四点:可以灵活设置加解密的key,可以灵活替换加解密方式,比如目前采用RC4,另一个项目想用DES,可以灵活更换。
1.4 收益分析
抓包库收益。 提高产品App的数据安全,必须对数据传输做好安全保护措施和完整性校验,以防止自身数据在网络传输中裸奔,甚至是被三方恶意利用或攻击。 技能的收益。 下沉为功能基础库,可以方便各个产品线使用,提高开发的效率。避免跟业务解耦合。傻瓜式调用,低成本接入!
2.1 Https三要素
2.2 抓包核心原理
HTTPS抓包原理。 Fiddler、Charles等抓包工具,其实都是采用了中间人攻击的方案:将客户端的网络流量代理到MITM(中间人)主机,再通过一系列的面板或工具将网络请求结构化地呈现出来。 抓包Https有两个突破点。 CA证书校验是否合法;数据传递过程中的加密和解密。如果是要抓包,则需要突破这两点的技术,无非就是MITM(中间人)伪造证书和使用自己的加解密方式。 抓包的工作流程如下: 中间人截获客户端向发起的HTTPS请求,佯装客户端,向真实的服务器发起请求; 中间人截获真实服务器的返回,佯装真实服务器,向客户端发送数据; 中间人获取了用来加密服务器公钥的非对称秘钥和用来加密数据的对称秘钥,处理数据加解密。
2.3 搞定CA证书
Https抓包核心CA证书。 HTTPS抓包的原理还是挺简单的,简单来说,就是Charles作为“中间人代理”,拿到了服务器证书公钥和HTTPS连接的对称密钥。 前提是客户端选择信任并安装Charles的CA证书,否则客户端就会“报警”并中止连接。这样看来,HTTPS还是很安全的。 安装CA证书到手机中必须洗白。 抓包应用内置的 CA 证书要洗白,必须安装到系统中。而 Android 系统将 CA 证书又分为两种:用户 CA 证书和系统 CA 证书(必要Root权限)。 Android从7.0开始限制CA证书。 只有系统(system)证书才会被信任。用户(user)导入的Charles根证书是不被信任的。相当于可以理解Android系统增加了安全校验! 如何绕过CA证书这种限制呢?已知有以下四种方式: 第一种方式:AndroidManifest 中配置 networkSecurityConfig,App 信任用户 CA 证书,让系统对用户 CA 证书的校验给予通过。 第二种方式:调低 targetSdkVersion < 24,不过这种方式谷歌市场有限制,意味着抓 HTTPS 的包越来越难操作。 第三种方式:挂载App抓包,VirtualApp 这种多开应用可以作为宿主系统来运行其它应用,利用xposed避开CA证书校验。 第四种方式:Root手机,把 CA 证书安装到系统 CA 证书目录中,那这个假 CA 证书就是真正洗白了,难度较大。
2.4 突破CA证书校验
第二种:基于 VirtualApp 的 Hook 机制破解证书固定。在 VirtualApp 中加入 Hook 代码,然后利用 VirtualApp 打开目标应用进行抓包。具体看:VirtualHook。
Charles抓包:
客户端—>服务端:客户端用对称密钥加密,被charles截获后,解密获得明文。再次加密,发送给服务器端。由于charles一直拥有通信用对称密钥enc_key,所以在整个HTTPS通信过程中信息对其透明。
3.1 先看如何抓包
使用Charles需要做哪些操作: 1.电脑上需要安装证书。这个主要是让Charles充当中间人,颁布自己的CA证书。 2.手机上需要安装证书。这个是访问Charles获取手机证书,然后安装即可。 3.Android项目代码设置兼容。Google 推出更加严格的安全机制,应用默认不信任用户证书(手机里自己安装证书),自己的app可以通过配置解决,相当于信任证书的一种操作! 尤其可知抓包的突破口集中以下几点: 第一点:必须链接代理,且跟Charles要具有相同ip。思路:客户端是否可以判断网络是否被代理了。 第二点:CA证书,这一块避免使用黑科技hook证书校验代码,或者拥有修改CA证书权限。思路:集中在可以判断是否挂载。 第三点:冒充中间人CA证书,在客户端client和服务端server之间篡改拦截数据。思路:可以做CA证书校验。 第四点:为了可以在7.0上抓包,App往往配置清单文件networkSecurityConfig。思路:线上环境去掉该配置。
3.2 设置配置文件
一个是CA证书配置文件 debug包为了能够抓包,需要配置networkSecurityConfig清单文件的system和user权限,只有这样才会信任用户证书。 一个是检验证书配置 不论是权威机构颁发的证书还是自签名的,打包一份到 app 内部,比如存放在 asset 里。然后用这个KeyStore去引导生成的TrustManager来提供证书验证。 一个是检验域名合法性 Android允许开发者重定义证书验证方法,使用HostnameVerifier类检查证书中的主机名与使用该证书的服务器的主机名是否一致。 如果重写的HostnameVerifier不对服务器的主机名进行验证,即验证失败时也继续与服务器建立通信链接,存在发生“中间人攻击”的风险。 如何查看CA证书的数据 证书验证网站 ;SSL配置检查网站
3.3 数据加密处理
网络数据加密的需求 为了项目数据安全性,对请求体和响应体加密,那肯定要知道请求体或响应体在哪里,然后才能加密,其实都一样不论是加密url里面的query内容还是加密body体里面的都一样。 对数据哪里进行加密和解密 目前对数据返回的data进行加解密。那么如何做数据加密呢?目前项目中采用RC4加密和解密数据。 抓取到的内容为乱码 有的APP为了防止抓取,在返回的内容上做了层加密,所以从Charles上看到的内容是乱码。这种情况下也只能反编译APP,研究其加密解密算法进行解密。难度极大!
3.4 避免黑科技抓包
基于Xposed(或者)黑科技破解证书校验 这种方式可以检查是否有Xposed环境,大概的思路是使用ClassLoader去加载固定包名的xp类,或者手动抛出异常然后捕获去判断是否包含Xposed环境。 基于VirtualApp挂载App突破证书访问权限 这个VirtualApp相当于是一个宿主App(可以把它想像成桌面级App),它突破证书校验。然后再实现挂载App的抓包。判断是否是双开环境!
4.1 App安全配置
添加配置文件: android:networkSecurityConfig="@xml/network_security_config" 配置networkSecurityConfig抓包说明: 中间人代理之所有能够获取到加密密钥就是因为我们手机上安装并信任了其代理证书,这类证书安装后都会被归结到用户证书一类,而不是系统证书。 那我们可以选择只信任系统内置的系统证书,而屏蔽掉用户证书(Android7.0以后就默认是只信任系统证书了),就可以防止数据被解密了。 实现App防抓包安全配置方式有两种: 一种是Android官方提供的网络安全配置;另一种也可以通过设置网络框架实现(以okhttp为例)。 第一种:具体可以看清单配置文件,相当于base-config标签下去掉 这组标签。 第二种:需要给okhttpClient配置 X509TrustManager 来监听校验服务端证书有效性。遍历设备上信任的证书,通过证书别名将用户证书(别名中含有user字段)过滤掉,只将系统证书添加到验证列表中。 该方案优点和缺点分析说明: 优点:network_security_config配置简单,对整个app网络生效,无需修改代码;代码实现对通过该网络框架请求的生效,能兼容7.0以前系统。 缺陷:network_security_config配置方式,7.0以前的系统配置不生效,依然可以通过代理工具进行抓包。okhttp配置的方式只能对使用该网络框架进行数据传输的接口生效,并不能对整个app生效。 破解:将手机进行root,然后将代理证书放置到系统证书列表内,就可以绕过代码或配置检查了。
4.2 关闭代理
charles 和 fiddler 都使用代理来进行抓包,对网络客户端使用无代理模式即可防止抓包,如:
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可信任的证书,则无法通过验证。 单向认证流程图: 该方案优点和缺点分析说明: 优点:安全性比较高,单向认证校验证书在代码中是方便的,安全性相对较高。 缺陷: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是一个牛逼的黑科技: Xposed + JustTrustMe 可以破解绕过校验CA证书。那么这样CA证书的校验就形同虚设了,对App的危险性也很大。 App多开运行在多个环境上: 多开App的原理类似,都是以新进程运行被多开的App,并hook各类系统函数,使被多开的App认为自己是一个正常的App在运行。 一种是从多开App中直接加载被多开的App,如平行空间、VirtualApp等,另一种是让用户新安装一个App,但这个App本质上就是一个壳,用来加载被多开的App。 VirtualApp是一个牛逼的黑科技: 它破坏了Android 系统本身的隔离措施,可以进行免root hook和其他黑科技操作,你可以用这个做很多在原来APP里做不到事情,于此同时Virtual App的安全威胁也不言而喻。 如何判断是否具有Xposed环境: 第一种方式:获取当前设备所有运行的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数据。 在response响应数据阶段。 如何进行加密:发起请求(加密) 第一步:获取请求的数据。主要是获取请求url和requestBody,这一块需要对数据一块处理。 第二步:对请求数据进行加密。采用RC4加密数据。 第三步:根据不同的请求方式构造新的request。使用 key 和 result 生成新的 RequestBody 发起网络请求。 如何进行解密:接收返回(解密) 第一步:常规解析得到 result ,然后使用RC4工具,传入key去解密数据得到解密后的字符串。 第二步:将解密的字符串组装成ResponseBody数据传入到body对象中。 第三步:利用response对象去构造新的response,然后最后返回给App。
4.7 证书锁定
证书锁定是Google官方比较推荐的一种校验方式: 原理是在客户端中预先设置好证书信息,握手时与服务端返回的证书进行比较,以确保证书的真实性和有效性。 如何实现证书锁定: 有两种实现方式:一种通过network_security_config.xml配置,另一种通过代码设置; 该方案优点和缺点分析说明: 优点:安全性高,配置方式也比较简单,并能实现动态更新配置。 缺陷:网络安全配置无法实现证书证书的动态更新,另外该配置也受Android系统影响,对7.0以前的系统不支持。代码配置相对灵活些。 破解:证书锁定破解比较复杂,比如老牌的JustTrustMe插件,通过hook各网络框架的证书校验方法,替换原有逻辑,使校验失效
//第一种方式:配置文件
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()
}
4.8 Sign签名
先说一下背景和问题: api.test.com/getbanner?k…
http://api.test.com/getbanner?key1=value1&key2=value2&key3=value3
这种方式简单粗暴,通过调用getbanner方法即可获取轮播图列表信息,但是这样的方式会存在很严重的安全性问题,没有进行任何的验证,大家都可以通过这个方法获取到数据,导致产品信息泄露。 在写开放的API接口时是如何保证数据的安全性的? 请求来源(身份)是否合法?请求参数被篡改?请求的唯一性(不可复制)? 问题的解决方案设想: 解决方案:为了保证数据在通信时的安全性,我们可以采用参数签名的方式来进行相关验证。 最终决定的解决方案: 调用接口之前需要验证签名和有效时间,要生成一个sign签名。先拼接-后转码-再加密-再发请求! sign签名校验实践: 需要对请求参数进行签名验证,签名方式如下:key1=value1&key2=value2&key3=value3&secret=yc 。对这个字符串进行md5一下。 然后被sign后的接口就变成了:api.test.com/getbanner?k…
http://api.test.com/getbanner?key1=value1&key2=value2&key3=value3&sign=xxx
为什么在获取sign的时候建议使用secret参数?secret仅作加密使用,添加在参数中主要是md5,为了保证数据安全请不要在请求参数中使用。 服务端对sign校验: 这样请求的时候就需要合法正确签名sign才可以获取数据。这样就解决了身份验证和防止参数篡改问题,如果请求参数被人拿走,没事,他们永远也拿不到secret,因为secret是不传递的。再也无法伪造合法的请求。 如何保证请求的唯一性: api.test.com/getbanner?k…
http://api.test.com/getbanner?key1=value1&key2=value2&key3=value3&sign=xxx&stamp=201803261407
通过stamp时间戳用来验证请求是否过期。这样就算被人拿走完整的请求链接也是无效的。 Sign签名安全性分析: 通过上面的案例,安全的关键在于参与签名的secret,整个过程中secret是不参与通信的,所以只要保证secret不泄露,请求就不会被伪造。
5.1 整体架构设计
5.2 稳定性设计
对于请求和响应的数据加解密要注意: 在网络上交换数据(网络请求数据)时,可能会遇到不可见字符,不同的设备对字符的处理方式有一些不同。 Base64对数据内容进行编码来适合传输。准确说是把一些二进制数转成普通字符用于网络传输。统统变成可见字符,这样出错的可能性就大降低了。
5.3 降级设计
.setMonitorToggle(object : IMonitorToggle {
override fun isOpen(): Boolean {
//todo 是否降级,如果降级,则不使用该功能。留给AB测试开关
return false
}
})
5.4 异常设计说明
base64加密和解密导致错误问题: 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 防抓包功能自测
网络请求测试: 正常请求,测试网络功能是否正常。 抓包测试: 配置fiddler,charles等工具。 手机上设置代理。 手机上安装证书。 单向认证测试:进行网络请求,会提示SSLHandshakeException即ssl握手失败的错误提示,即表示app端的单向认证成功。 数据加解密:进行网络请求,看一下请求参数和响应body数据是否加密,如果看不到实际json实体则表示加密成功。
防抓包库:
https://github.com/yangchong211/YCToolLib
综合库:
https://github.com/yangchong211/YCAppTool
视频播放器:
https://github.com/yangchong211/YCVideoPlayer
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!