不知从什么版本后,对快手进行简单抓包似乎“不可行”了。表现就是使用常规的 HTTP 正向代理抓包工具(charles、mitmproxy、fiddler 之类)并且把自签名证书种到系统证书里后,数据依然能刷出来,也能抓到一些零星的报文,但是关键出数据的那些接口报文都没有。
一般来说,常规方法无法抓安卓应用的 https 包,通常有以下几种可能:
当前的现象是数据能刷出来,那就说明并不是证书信任相关的问题。接下来就需要验证它究竟是使用了什么样的传输方式,对症下药。
最稳妥的验证方式当然是白盒测试看源码,不过快手反编译的代码看起来也费劲,于是考虑直接当成黑盒来测试看看。以下验证方式均以 快手 8.2.31.17191 版本为例。
需要在 Linux 主机上使用 openssl 工具生成一波证书。当然,这一步可以忽略,直接使用mitmproxy生成的证书。只是手动配置一下能够加深一下对 openssl 的理解。
# 生成RSA私钥 cert.key
openssl genrsa -out cert.key 2048
# 生成有效期为15年的X509证书 cert.crt(DN信息随便填填似乎都行,但是不能全空)
openssl req -new -x509 -key cert.key -out -days 5480 cert.crt
# 私钥和证书放一起,构成PEM格式的 cert.pem
cat cert.key cert.crt > cert.pem
# 将之前生成的pem证书作为mitmproxy使用的根证书
cp cert.pem ~/.mitmproxy/mitmproxy-ca.pem
# 以普通正向代理模式启动在8000端口,并配合curl简单验证证书可用性
mitmproxy -p 8000
# 能正常访问没有SSL相关报错,就说明之前生成的自签名证书是OK的
curl -x localhost:8000 --cacert ~/.mitmproxy/mitmproxy-ca.pem https://www.baidu.com
# 从证书文件中计算出用于放置在安卓中的文件hash名,假设结果为 a5176621
openssl x509 -subject_hash_old -in cert.crt -noout
# 将cert.crt复制出一个上述hash结果的新文件
cp cert.crt a5176621.0
值得注意的是,不要尝试使用 mitmproxy --certs
来配置证书,这种方式只能配置 leaf 证书,而不能配置根 CA 证书。因此还是老老实实的把根证书放在默认路径下。
为了方便测试,我在 arm 服务器上使用 redroid 准备了一台安卓虚拟机。
docker run -itd --rm --memory-swappiness=0 \
--privileged --name redroid \
--mount type=bind,source=/home/myths/exp/a5176621.0,target=/system/etc/security/cacerts/a5176621.0 \
-p 5555:5555 \
redroid/redroid:11.0.0-arm64 \
redroid.width=720 \
redroid.height=1280 \
redroid.fps=15 \
redroid.gpu.mode=guest
其中 /home/myths/exp/a5176621.0 替换成实际文件所在路径。
然后在arm主机上用 adb 连接安卓的 tcpip 端口,下载并安装快手 8.2.31.17191 版本。
# 在arm服务器上用adb连接安卓虚拟机
adb connect localhost:5555
# 安装快手
adb install kuaishou.apk
为了方便远程操作,需要在本地机器上连接 arm 服务器上的安卓虚拟机,并用scrcpy操作。
# 在本地主机上连接远程arm服务器上的安卓虚拟机
adb connect <ip for arm server>:5555
# 启动scrcpy
scrcpy
到这一步骤时,可以检测安卓中的网络应该都已经是通的了。
先尝试使用传统正向代理的方式进行抓包。
# 在arm服务器上用正向代理启动mitmproxy
mitmproxy -p 8000
# 对安卓设置正向代理,其中 172.17.0.1 为安卓下访问arm主机的ip
adb shell settings put global http_proxy 172.17.0.1:8000
设置完成后,观察手机的网络状况,现象如下:
有数据但是抓不到包,怀疑应当是有些流量漏掉了,于是尝试把这些流量Ban掉看看效果。
# 首先需要打开内核的 ip_forward 功能
echo 1 > /proc/sys/net/ipv4/ip_forward
# 依然在arm服务器上用正向代理启动mitmproxy
mitmproxy -p 8000
# 继续在手机上配置http代理,其中172.17.0.1为安卓下访问arm主机的ip
adb shell settings put global http_proxy 172.17.0.1:8000
# 在 arm 服务器上配置iptables,将来源于安卓虚拟机但目的地不是arm服务器的流量重定向到一个无用端口。
# 其中 172.17.0.12 位安卓虚拟机的ip,1234为一个无用端口。
sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 ! -d 172.17.0.1 -j REDIRECT --to-ports 1234
# 如果上述操作报了 Couldn't load match `owner':No such file or directory 的错,那么需要在 arm 服务器上启动下相关模块。
sudo modprobe ipt_owner
# 记得测试完成后,将上面这个记录干掉
iptables -t nat -D PREROUTING 1
设置完成后,观察手机的网络状况,现象如下:
说明这里的确是有流量漏了,没有走正向代理,但是依然出了外网。
那么这些流量到底是私有的四层TCP流量、还是没走正向代理的80/443流量呢?因此尝试把非80/443的流量放开试试。
# 依然在arm服务器上用正向代理启动mitmproxy
mitmproxy -p 8000
# 继续在手机上配置http代理
adb shell settings put global http_proxy 172.17.0.1:8000
# 在 arm 服务器上配置iptables,将来源于安卓虚拟机但目的端口不是80/443的流量重定向到一个无用端口。
# 其中 172.17.0.12 位安卓虚拟机的ip,1234为一个无用端口。
sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 80 -j REDIRECT --to-ports 1234
sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 443 -j REDIRECT --to-ports 1234
# 记得测试完成后,将上面这两条记录干掉
iptables -t nat -D PREROUTING 1
iptables -t nat -D PREROUTING 1
设置完成后,观察手机的网络状况,现象如下:
这就说明,控制快手推荐页的流量并不是所谓私有流量,而就是走的80/443端口,只是没有走正向代理。
既然七层的代理配置会被忽略,那就尝试使用四层的透明代理,将流量强制转到透明代理服务器上即可。
# 在arm服务器上以透明代理模式启动mitmproxy
mitmproxy -p 8000 -m transparent
# 将手机上的http代理移除
adb shell settings put global http_proxy :0
# 在arm服务器上配置将来源于安卓虚拟机的的80/443流量直接路由到mitmproxy
# 其中 172.17.0.12 位安卓虚拟机的ip
sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 80 -j REDIRECT --to-ports 8000
sudo iptables -t nat -I PREROUTING -p tcp -s 172.17.0.12 --dport 443 -j REDIRECT --to-ports 8000
# 记得测试完成后,将上面这两个记录干掉
iptables -t nat -D PREROUTING 1
iptables -t nat -D PREROUTING 1
设置完成后,观察手机的网络状况,现象如下:
目前看来,快手并非像很多网上传的那样,大多数接口都走了 kquic 协议导致无法抓包。其实很多接口只是做了一个禁止走系统代理的设置,简单使用透明代理模式进行抓包即可轻松绕过。当然,不排除有些关键接口也做了SSL pinning、走私有协议之类的。。。
https://docs.mitmproxy.org/stable/concepts-certificates/
https://stackoverflow.com/questions/56830858
https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/-builder/proxy/