1. 环境准备
1.1 Ubuntu 主机
安装基础工具:
bash
sudo apt update
sudo apt install -y mitmproxy adb tcpdump tshark sqlite3 openssl
确认工具可用:
bash
mitmdump --version
adb version
tshark --version
openssl version
mitmproxy 安装包提供:
mitmproxy:交互式 UImitmdump:命令行 / 脚本化抓包mitmweb:Web UI
1.2 Android 真机
前置条件:
- 已开启 USB 调试,
adb devices能看到设备。 - 需要修改系统 CA 或配置网络转发时,设备必须有 root。
- 抓自身应用流量时,先确认目标应用 UID:
bash
adb shell dumpsys package <package_name> | grep -E 'uid=|appId=' | head
示例:
bash
adb shell dumpsys package com.android.vending | grep -E 'uid=|appId=' | head
2. 启动 mitmdump
2.1 普通代理模式
适合应用遵循系统代理的场景:
bash
mitmdump --listen-host 127.0.0.1 --listen-port 18080 \
--set block_global=false \
-w /tmp/capture.flows
2.2 反向代理模式
适合不使用系统代理、只观察指定域名的场景:
bash
mitmdump --mode reverse:https://example.com \
--listen-host 127.0.0.1 --listen-port 18080 \
--set block_global=false \
-w /tmp/capture.flows
2.3 保持前台运行
后台 nohup 进程可能被会话清理。调试链路时建议保持前台运行:
bash
mitmdump --mode reverse:https://example.com \
--listen-host 127.0.0.1 --listen-port 18080 \
--set block_global=false \
-w /tmp/capture.flows
3. 手机流量导入电脑
3.1 adb reverse
主机和手机不在同一网段时,使用 adb reverse 最简单:
bash
adb reverse tcp:18080 tcp:18080
手机访问 127.0.0.1:18080 时,流量会转发到电脑的 127.0.0.1:18080。
验证:
bash
adb shell curl -k -sS -o /dev/null \
-w '%{http_code} %{remote_ip}\n' \
https://127.0.0.1:18080/
清理:
bash
adb reverse --remove tcp:18080
3.2 系统代理
适合普通应用:
bash
adb shell settings put global http_proxy <host>:18080
清理:
bash
adb shell settings put global http_proxy :0
注意:系统代理影响整机流量,且部分系统应用或 native 网络库可能不走代理。
3.3 iptables 转发
适合不遵循代理的应用。以下示例只拦截指定 UID 和目标 IP:
bash
adb shell su 0 sh -c '
iptables -t nat -A OUTPUT \
-m owner --uid-owner <uid> \
-p tcp -d <target_ip> --dport 443 \
-j REDIRECT --to-ports 18080
'
查看规则:
bash
adb shell su 0 sh -c 'iptables -t nat -L OUTPUT -v -n --line-numbers'
删除规则:
bash
adb shell su 0 sh -c '
iptables -t nat -D OUTPUT \
-m owner --uid-owner <uid> \
-p tcp -d <target_ip> --dport 443 \
-j REDIRECT --to-ports 18080
'
如果应用使用 QUIC,可临时拒绝该 UID 的 UDP/443,使其回退 TCP:
bash
adb shell su 0 sh -c '
iptables -I OUTPUT 1 \
-m owner --uid-owner <uid> \
-p udp --dport 443 \
-j REJECT
'
清理:
bash
adb shell su 0 sh -c '
iptables -D OUTPUT \
-m owner --uid-owner <uid> \
-p udp --dport 443 \
-j REJECT
'
注意:DNAT --to-destination 127.0.0.1:18080 在部分设备上受 route_localnet 限制;REDIRECT --to-ports 更可用。
4. 替换系统 CA
4.1 生成 mitmproxy CA
首次运行 mitmdump 后,CA 会生成在 ~/.mitmproxy/:
bash
mitmdump --listen-host 127.0.0.1 --listen-port 18080
确认 CA:
bash
openssl x509 -in ~/.mitmproxy/mitmproxy-ca-cert.pem \
-noout -subject -issuer -dates
计算 Android 证书文件名:
bash
openssl x509 -in ~/.mitmproxy/mitmproxy-ca-cert.pem \
-noout -subject_hash_old
假设输出为 c8750f0d,推送到设备:
bash
adb push ~/.mitmproxy/mitmproxy-ca-cert.pem /data/local/tmp/c8750f0d.0
4.2 临时 bind mount 系统 CA 目录
Android 15 的 /system/etc/security/cacerts 只读。可复制一份到 /data/local/tmp,加入 mitmproxy CA 后临时 bind mount:
bash
adb shell su 0 sh -c '
rm -rf /data/local/tmp/cacerts-mitm
mkdir -p /data/local/tmp/cacerts-mitm
cp /system/etc/security/cacerts/* /data/local/tmp/cacerts-mitm/
cp /data/local/tmp/c8750f0d.0 /data/local/tmp/cacerts-mitm/
chown root:root /data/local/tmp/cacerts-mitm/*
chmod 644 /data/local/tmp/cacerts-mitm/*
chcon u:object_r:system_security_cacerts_file:s0 \
/data/local/tmp/cacerts-mitm/*
chcon u:object_r:system_security_cacerts_file:s0 \
/data/local/tmp/cacerts-mitm
mount --bind /data/local/tmp/cacerts-mitm \
/system/etc/security/cacerts
'
验证:
bash
adb shell su 0 sh -c '
ls -lZ /system/etc/security/cacerts/c8750f0d.0
'
4.3 卸载临时 CA
bash
adb shell su 0 sh -c '
umount /system/etc/security/cacerts
ls /system/etc/security/cacerts/c8750f0d.0 2>&1
'
ls 返回 No such file or directory 表示已恢复原系统 CA 目录。
注意两个坑:
cp /system/etc/security/cacerts/.会报Cross-device link且不会复制任何证书 ,必须用通配符cp /system/etc/security/cacerts/*。- 除了证书文件,挂载目录本身的 SELinux 上下文也必须改成
system_security_cacerts_file;只改文件不改目录,应用会读不到信任库(ls -ldZ /system/etc/security/cacerts挂载后应为system_security_cacerts_file)。
4.4 证书校验失败
部分系统应用(如 Finsky)会额外校验证书链或使用自己的网络栈。即使系统 CA 目录已包含 mitmproxy CA,仍可能出现:
text
Client TLS handshake failed. The client does not trust the proxy's certificate
(OpenSSL Error([('SSL routines', '', 'sslv3 alert certificate unknown')]))
此时流量已到达 mitmdump,但 TLS 握手失败,无法观察 HTTP 层内容。
5. 导出抓包文件到电脑
mitmdump -w 写入的是 mitmproxy flow 文件,通常直接保存在电脑上:
bash
mitmdump --mode reverse:https://example.com \
--listen-host 127.0.0.1 --listen-port 18080 \
--set block_global=false \
-w /tmp/capture.flows
如果文件在手机上,例如 tcpdump pcap:
bash
adb pull /data/local/tmp/capture.pcap /tmp/capture.pcap
6. 在电脑上分析流量
6.1 mitmdump 查看 flow
bash
mitmdump -nr /tmp/capture.flows
增加详情:
bash
mitmdump -nr /tmp/capture.flows --set flow_detail=2
只看指定 host:
bash
mitmdump -nr /tmp/capture.flows \
--set flow_detail=1 \
--view-filter 'host == "example.com"'
导出为文本:
bash
mitmdump -nr /tmp/capture.flows \
--set flow_detail=3 > /tmp/capture.txt
6.2 mitmproxy 交互式分析
bash
mitmproxy -r /tmp/capture.flows
常用快捷键:
Enter:进入 flow 详情q:返回 / 退出Tab:切换请求 / 响应f:编辑过滤表达式
6.3 tshark 分析 pcap
查看 DNS 和 TLS SNI:
bash
tshark -r /tmp/capture.pcap \
-Y 'dns.qry.name || tls.handshake.extensions_server_name' \
-T fields \
-e dns.qry.name \
-e tls.handshake.extensions_server_name
去重:
bash
tshark -r /tmp/capture.pcap \
-Y 'dns.qry.name || tls.handshake.extensions_server_name' \
-T fields \
-e dns.qry.name \
-e tls.handshake.extensions_server_name \
| sed 's/[[:space:]]*$//' | sort -u
查看 HTTP:
bash
tshark -r /tmp/capture.pcap \
-Y 'http' \
-T fields \
-e frame.time \
-e ip.src \
-e ip.dst \
-e http.request.method \
-e http.host \
-e http.request.uri \
-e http.response.code \
-e http.location
6.4 常用排查点
- 确认 mitmdump 正在监听:
bash
ss -ltnp | grep 18080
- 确认 adb reverse:
bash
adb reverse --list
- 确认 iptables 规则计数是否增长:
bash
adb shell su 0 sh -c \
'iptables -t nat -L OUTPUT -v -n --line-numbers'
- 查看目标应用实时连接:
bash
adb shell su 0 sh -c 'ss -tnp | grep <package_or_uid>'
7. 清理现场
bash
adb reverse --remove tcp:18080
adb shell su 0 sh -c '
iptables -t nat -D OUTPUT \
-m owner --uid-owner <uid> \
-p tcp -d <target_ip> --dport 443 \
-j REDIRECT --to-ports 18080
iptables -D OUTPUT \
-m owner --uid-owner <uid> \
-p udp --dport 443 \
-j REJECT
umount /system/etc/security/cacerts
'
adb shell settings put global http_proxy :0
最终确认:
bash
adb reverse --list
adb shell su 0 sh -c 'iptables -t nat -S OUTPUT'
adb shell su 0 sh -c 'iptables -S OUTPUT | head'
adb shell su 0 sh -c \
'ls /system/etc/security/cacerts/c8750f0d.0 2>&1'
adb shell settings get global http_proxy