导航目录
网络&&网络抓包原理
HTTP 原理
HTTPS 原理
HTTPS 详解 点击跳转
PS: 其中包含整个加密套件的工作流程讲解
TLS 原理
不错的文档: 点击前往
网络抓包原理
一. 什么是抓包?
在应用的开发调试中,查看软件实际运行时HTTP/HTTPS通信的请求数据和返回数据,从而分析问题的过程就叫做抓包。 通常我们说的抓包主要是分为两种:
- 使用 Wireshark 抓取传输层的 TCP/UDP 通信包。
- 使用 Fiddler 或 Charles 抓取应用层的 HTTP/HTTPS 通信包。
在大部分场景下,我们只是需要抓取应用层的
HTTP/HTTPS
数据包也就是第二种方式。
二. 抓包的原理
抓包的原理其实很简单,例如:PC上的Fiddler监听一个端口(port: 8888),在Android测试机上连接同一个局域网,配置网络代理,指向该PC的8888端口,这样一来测试机的所有网络通信都会被转发到PC的8888端口,进而被Fiddler捕获,然后就可以对数据包进行分析。经常用的网络协议分为HTTP和HTTPS,HTTPS在HTTP上进行了加密操作,所以对这两种请求进行抓包也有不同。
对HTTP请求进行抓包
对于HTTP协议,因为本身就是明文传输
,所以可以直接看到数据报文,除非对这些明文在传输时进行二次加密,但那是另一种情况,这里暂不分析。
对HTTPS请求进行抓包
对于双向加密的HTTPS,正常情况下,即使能以中间人的方式拿到通信报文
,但是因为没有密钥,同样也不能看到具体的传输内容。基于HTTPS加密通信的建立过程,和密钥交换方式,如果在加密通信建立之前,截取服务端发送的包含证书的报文,伪装成服务端,把自己的证书发给客户端,然后拿到客户端返回的包含对称加密通信密钥的报文,以服务端自己的公钥加密后发给服务端
,这样一来,双向加密通信建立完成,而中间人实际拿到了通信的密钥,所以可以查看、修改HTTPS的通信报文,这就是典型的Man-in-the-middle attack即MITM中间人攻击
。
这样似乎看起来,HTTPS也不是那么安全,当然不能这么说了,实现MITM最关键的一点,是中间人要把服务端证书替换成自己的证书发给客户端,让客户端相信自己就是服务端,那么问题是,客户端为什么会信任而进行替换呢?
HTTPS之所以安全,是因为它用来建立加密通信的证书是由权威的CA机构签发
的,受信的CA机构的根证书都会被内嵌在Windows, Linux, macOS, Android, iOS这些操作系统里,用来验证服务端发来的证书是否是由CA签发的。CA机构当然不可能随便给一个中间人签发不属于它的域名证书,那么只有一个很明显的办法了,把中间人的根证书,导入到客户端的操作系统里,以此来完成建立加密通信时对中间人证书的验证。
所以,在一定的情况下,HTTPS通信是可以被监听的,抓包的实现基础,是Android测试机导入Fiddler或者Charles的根证书。
无论是fidder和charles都是充当了一个中间人代理的角色来对HTTPS进行抓包:
- 截获客户端向发起的HTTPS请求,佯装客户端,向真实的服务器发起请求。
- 截获真实服务器的返回,佯装真实服务器,向客户端发送数据。
- 获取了用来加密服务器公钥的非对称秘钥和用来加密数据的对称秘钥。
如图:
三. Android设备抓包问题
Android6.0 及以下系统
直接安装证书后, 可以直接使用FIddler进行抓包。
Android7.0 及以上系统
在Android 7.0及以上的设备上测试时,发现又抓不到HTTPS了! 原因是:Google在Android 7.0时更改了App对操作系统本地证书的信任机制,在Android 7.0之前,默认信任系统预置证书和用户自导入证书,在Android 7.0(API 24)之后,为了保障App的通信安全,避免被第三方抓包做了些改动,App默认只信任系统预置证书,而不再信任用户自导入证书(把MITM的ssl证书,安装到 受信任的凭据 -> 用户)抓出来的https的请求,都是加了密的,无法看到原文了.解决的方式有如下几种:
- 方式一: 修改App的AndroidManifest网络安全配置,信任用户自导入证书。
- 方式二: Root测试机或自编译系统,把Fiddler根证书设置为系统预置证书。
- 方式三: 在Android 7.0以下的测试机中抓包。
- 方式四: targetSDK版本设置为24以下。
方式一:
如果是自己公司开发的应用那么在 Android 工程目录的 res 底下创建一个 xml 文件夹,然后在内部创建一个名为 “network_security_config.xml” 的文件。
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" overridePins="true" />
<certificates src="user" overridePins="true" />
</trust-anchors>
</base-config>
</network-security-config>
在 AndroidManifest 里的标签中,添加代码:
android:networkSecurityConfig="@xml/network_security_config"
这种方式只适用于能自己修改源码的应用。
方式二
root手机;Root手机将用户下载的CA协议移动到系统信任的证书目录下。
非root手机可以VirtualXposed插件:
PS: 方式三和方式四用的比较少 • 略
抓包&&对抗
系统证书链校验
https网络协议:首先明确 android 8 开始系统默认使用根证书校验(系统证书链校验);
这也是为什么需要安装charles证书到根证书目录下
的原因。
SSL pinning(证书绑定)
原理:是在客户端中预先设置好证书信息,握手时与服务端返回的证书进行比较,以确保服务端返回的证书的真实性,实现方式有两种,一种是在代码层实现,一种是通过network_security_config.xml
配置文件完成。
对抗: 绕过SSL PINNING会稍微麻烦一点,需要使用到第三方插件,比如老牌的JustTrustMe模块,其绕过原理是通过Hook各网络框架的证书验证方法,替换其方法原有的逻辑,使校验失效。
JustTrustMe优点
:
- 转为解决ssl pinning 而生
- 对常见的网络框架检测点进行了hook
JustTrustMe的缺点
:
- 检测点 JustTrustMe 未覆盖
- 当app 存在混淆时, JustTrustMe可能hook失败
- 协议更底层(tcp)也会失效
为此而生的产品:
另外
的操作:
- HTTPS 修改为 HTTP ,解决了 SSLPinning 的问题
- 替换 app 中 https 的 url 为 http
- charles map remote 建⽴映射规则,将 http 映射为 https 发包
单向校验
ssl pinning 就是典型的单向校验,也称:客户端校验
同时,也可以先保存一个证书在APP客户端中,HTTPS协议握手时客户端把APP中保存的证书发送给服务端,由服务端校验
双向校验
无论是系统证书链校验还是SSL PINNING,其本质都是客户端在校验服务端的证书,即单向校验。如果先保存一个证书在APP客户端中,HTTPS协议握手时客户端把APP中保存的证书发送给服务端,这种客户端校验服务端证书,同时服务端也校验客户端证书的方式称为双向校验。
代理检测
// 检测接口
java.net.NetworkInterface.getName()
android.net.ConnectivityManager.getNetworkCapabilities(network)
/*
* 检测代理
* 直接绕过代理,网络正常但抓包工具无法抓包
*/
mCheckProxy = findViewById(R.id.checkProxy);
mCheckProxy.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked){
client = new OkHttpClient().newBuilder().proxy(Proxy.NO_PROXY).build();
}else{
client = new OkHttpClient();
}
}
});
// Proxy.NO_PROXY 就可以绕过代理
如图:
未开启代理前:
开启代理后:
对抗思路:
- hook 函数
- 对抗方法也比较简单,可以使用
iptables
对请求进行强制转发,ProxyDroid
全局代理工具就是通过iptables实现的,所以使用ProxyDroid开启代理,可以比较有效的绕过代理检测。 - proxydroid下载 点击前往
思考:
- 既然代理检测的点是http/https 那么我们使用sockes 协议是否就可以绕过
- socksdroid 点击下载
hook 系统底层(⽆视证书绑定,顺⼿解决了证书校验)的实现原理
我们根据源码一直往下跟会发现-发包-收包
有果必有因,深⼊系统底层,⽅能降维打击,才有更多可能
tcp-java
java.net.SocketOutputStream.socketWrite0('java.io.FileDescriptor', '[B', 'int', 'int')
java.net.SocketInputStream.socketRead0('java.io.FileDescriptor', '[B', 'int', 'int', 'int')
tcp-native
sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
udp-java
libcore.io.Linux.sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetAddressinetAddress, int port)
libcore.io.Linux.sendtoBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, SocketAddress
address)
libcore.io.Linux.recvfromBytes(FileDescriptor fd, Object buffer, int byteOffset, int byteCount, int flags, InetSocketAddress
srcAddress)
udp-native
sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t addr_len)
recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
udp-native和tcp-native底层都是sendto和recvfrom,但r0capture未选择该hook点
ssl-java
android 版本 > 8
com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream.write('[B', 'int', 'int')
com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read('[B', 'int', 'int')
android 版本 <= 8
com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream.write('[B', 'int', 'int')
com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream.read('[B', 'int', 'int')
ssl-native
- 明⽂ hook 点: 的 SSL_write 和 SSL_read
- 密⽂ hook 点: 的 write 和 read
从上延申的项目:r0capture:https://github.com/r0ysue/r0capture
r0capture的hook点选择的底层原理就是基于上面的逻辑进行的: 点击前往
另外的操作:
- HTTPS 修改为 HTTP ,解决了 SSLPinning 的问题
- 替换 app 中 https 的 url 为 http
- charles map remote 建⽴映射规则,将 http 映射为 https 发包
借鉴: