序章:当抓包工具遇上"铜墙铁壁"
本章字数:约5500字 阅读时间:约18分钟 难度等级:★★☆☆☆
声明:本文中的公司名称、包名、API地址、密钥等均已脱敏处理,使用虚构名称替代。"梦想世界"、"dreamworld"等均为虚构名称,与任何真实公司无关。
引言
2026年1月5日,深夜23:15。
实验室里只有显示器的微光和咖啡机偶尔发出的咕噜声。我坐在工位前,手指在键盘上轻轻敲击,屏幕上的Charles Proxy正在等待连接。作为一名有十年经验的安全研究员,我已经习惯了深夜的工作节奏,也习惯了各种APP的安全防护措施。
但今晚,我即将遇到一个让我整整困扰三周的对手。
这款APP来自国内某知名新能源车企,内部代号为"dw_mall_shop",对外显示为"梦想世界",当前版本v8.x.x。我之所以选择这款APP进行安全测试,是因为近年来车企APP的数据安全问题日益突出。
用户的车辆数据、行驶轨迹、个人信息都通过这类APP进行传输,其安全防护水平直接关系到用户的隐私和财产安全。作为安全研究员,评估这类APP的安全性是我们的职责所在。
1.1 第一次尝试:常规抓包
按照常规流程,我的第一步是进行抓包分析,了解APP与服务器之间的通信协议和数据格式。这是逆向工程的基础------只有了解了数据是如何传输的,才能进一步分析数据是如何生成的。
我熟练地完成了抓包环境的配置:
1.1.1 环境准备
测试设备:
- 手机:Pixel 6 Pro,Android 13
- 电脑:MacBook Pro M2,macOS Ventura
- 抓包工具:Charles Proxy 4.6.4
配置步骤:
bash
# 1. 启动Charles Proxy
# 默认监听端口:8888
# 2. 在手机上配置WiFi代理
# 设置 -> WiFi -> 当前网络 -> 代理 -> 手动
# 服务器:电脑IP地址
# 端口:8888
# 3. 安装Charles根证书
# 在手机浏览器访问 chls.pro/ssl
# 下载并安装证书
# 设置 -> 安全 -> 加密与凭据 -> 安装证书
验证配置:
为了确认代理配置正确,我先用手机浏览器访问了几个网站:
arduino
✓ https://www.baidu.com - Charles成功捕获
✓ https://www.google.com - Charles成功捕获
✓ https://api.github.com - Charles成功捕获
一切正常。代理配置没有问题。
1.1.2 启动目标APP
深吸一口气,我点击了"梦想世界"的图标。
APP正常启动了。启动画面、加载动画、主界面------一切看起来都很正常。我开始在APP中进行各种操作:
- 浏览商城首页
- 查看商品详情
- 搜索商品
- 查看个人中心
APP响应流畅,数据加载正常,图片显示清晰。从用户体验的角度来看,这是一款非常优秀的APP。
然而,当我切换到Charles窗口时,我愣住了。
Charles的请求列表------空空如也。
没有任何HTTP请求。没有任何HTTPS请求。什么都没有。
我以为是自己眼花了,揉了揉眼睛,仔细看了看屏幕。确实是空的。
1.1.3 排查问题
"不可能啊,"我自言自语道,"APP明明在正常加载数据,怎么可能没有网络请求?"
我开始排查可能的问题:
检查1:代理配置
bash
# 在手机上确认代理设置
设置 -> WiFi -> 当前网络 -> 代理
# 显示:手动,服务器:192.168.1.100,端口:8888
# 配置正确 ✓
检查2:Charles监听状态
bash
Charles -> Proxy -> Proxy Settings
# 端口:8888
# 启用透明代理:是
# 状态正常 ✓
检查3:证书安装
bash
# 在手机上确认证书
设置 -> 安全 -> 加密与凭据 -> 用户凭据
# 显示:Charles Proxy CA
# 证书已安装 ✓
检查4:其他APP测试
我打开了手机上的其他几个APP进行测试:
✓ 微信 - Charles成功捕获请求
✓ 淘宝 - Charles成功捕获请求
✓ 抖音 - Charles成功捕获请求
其他APP都能正常抓包,只有"梦想世界"抓不到。
这说明问题出在APP本身,而不是我的抓包环境。
1.2 深入分析:为什么抓不到?
1.2.1 排除SSL Pinning
我的第一反应是:这可能是SSL Pinning导致的。
SSL Pinning(证书锁定)是一种常见的安全措施,APP会验证服务器证书是否与预期的证书匹配。如果不匹配(比如使用了Charles的代理证书),就会拒绝连接。
但是,SSL Pinning通常会导致以下现象:
- APP显示网络错误
- APP提示"无法连接到服务器"
- Charles中显示SSL握手失败
而我观察到的现象是:
- APP正常运行
- 数据正常加载
- Charles中什么都没有
这不像是SSL Pinning的表现。
1.2.2 一个关键发现
就在我百思不得其解的时候,我想起了一件事。
前几天,我在这台手机上安装了一款科学上网工具(类似VPN的代理软件),用于访问一些国外的技术文档。这款工具使用的是SOCKS5代理协议。
我突然好奇:如果开启这个科学上网工具,"梦想世界"APP还能正常使用吗?
我打开了科学上网工具,然后启动"梦想世界"APP。
APP完全正常!
数据加载正常,功能使用正常,没有任何异常提示。
这个发现让我陷入了沉思。
1.2.3 代理类型的差异
让我们来分析一下不同代理类型的工作原理:
HTTP代理(Charles使用):
markdown
┌─────────┐ HTTP请求 ┌─────────┐ HTTP请求 ┌─────────┐
│ APP │ ────────────> │ Charles │ ────────────> │ 服务器 │
└─────────┘ └─────────┘ └─────────┘
│
解析HTTP协议
记录请求内容
SOCKS5代理(科学上网工具使用):
markdown
┌─────────┐ TCP连接 ┌─────────┐ TCP连接 ┌─────────┐
│ APP │ ────────────> │ SOCKS5 │ ────────────> │ 服务器 │
└─────────┘ └─────────┘ └─────────┘
│
只转发TCP数据
不解析应用层协议
关键区别:
- HTTP代理工作在应用层(第7层),需要解析HTTP协议
- SOCKS5代理工作在会话层(第5层),只负责转发TCP数据
这意味着什么?
APP可能实现了这样的逻辑:
- 检测系统是否设置了HTTP代理
- 如果检测到HTTP代理,就绕过系统网络栈,使用自定义的网络通信方式
- 如果没有检测到HTTP代理(或者是SOCKS代理),就正常通信
这是一种精准打击的策略:
- 针对安全研究员的抓包工具(HTTP代理):完全失效
- 针对普通用户的科学上网需求(SOCKS代理):正常使用
太精妙了!
1.3 验证假设:代理检测机制
1.3.1 系统代理检测
在Android系统中,HTTP代理设置可以通过以下方式获取:
java
// 方法1:通过System.getProperty获取
String proxyHost = System.getProperty("http.proxyHost");
String proxyPort = System.getProperty("http.proxyPort");
// 方法2:通过ProxySelector获取
ProxySelector selector = ProxySelector.getDefault();
List<Proxy> proxies = selector.select(new URI("https://api.example.com"));
// 方法3:通过ConnectivityManager获取(Android特有)
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
ProxyInfo proxyInfo = cm.getDefaultProxy();
如果APP在发起网络请求之前检测这些值,就能知道系统是否设置了HTTP代理。
1.3.2 WiFi代理检测
除了系统级别的代理设置,Android还支持针对特定WiFi网络设置代理:
java
// 获取当前WiFi配置
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
WifiConfiguration config = getWifiConfiguration(wifiInfo.getNetworkId());
// 检查代理设置
if (config.getProxySettings() == ProxySettings.STATIC) {
ProxyInfo proxyInfo = config.getHttpProxy();
String host = proxyInfo.getHost();
int port = proxyInfo.getPort();
// 检测到代理!
}
1.3.3 绕过代理的方法
一旦检测到代理,APP可以使用以下方法绕过:
方法1:使用NO_PROXY
java
// 创建不使用代理的OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY) // 明确指定不使用代理
.build();
方法2:自定义SocketFactory
java
// 创建直连的Socket,绕过系统代理
public class DirectSocketFactory extends SocketFactory {
@Override
public Socket createSocket() throws IOException {
Socket socket = new Socket();
// 直接连接,不经过代理
return socket;
}
@Override
public Socket createSocket(String host, int port) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port));
return socket;
}
}
方法3:使用自定义DNS
java
// 绕过系统DNS,直接使用IP地址
public class DirectDns implements Dns {
@Override
public List<InetAddress> lookup(String hostname) throws UnknownHostException {
// 使用硬编码的IP地址,或者自定义DNS服务器
if (hostname.equals("api.dreamworld.com")) {
return Arrays.asList(InetAddress.getByName("1.2.3.4"));
}
return Dns.SYSTEM.lookup(hostname);
}
}
1.4 更多尝试:寻找突破口
1.4.1 VPN抓包
既然HTTP代理不行,我尝试使用VPN方式抓包。
工具:HttpCanary
HttpCanary是一款Android平台的抓包工具,它通过创建本地VPN来捕获所有网络流量。
markdown
┌─────────┐ 所有流量 ┌─────────────┐ 转发 ┌─────────┐
│ APP │ ────────────> │ HttpCanary │ ────────> │ 服务器 │
└─────────┘ │ (VPN) │ └─────────┘
└─────────────┘
│
捕获所有流量
测试结果:
安装HttpCanary,启动VPN,然后打开"梦想世界"APP。
依然抓不到任何数据。
APP正常运行,但HttpCanary中没有任何来自"梦想世界"的请求记录。
1.4.2 iptables流量转发
在Root设备上,我尝试使用iptables强制转发所有流量:
bash
# 将所有443端口的流量转发到本地代理
iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port 8888
# 将所有80端口的流量转发到本地代理
iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8888
测试结果:
还是抓不到。
APP似乎使用了非标准端口,或者有其他方式绑过iptables规则。
1.4.3 Wireshark抓包
作为最后的尝试,我使用Wireshark在路由器层面抓包:
bash
# 在路由器上运行tcpdump
tcpdump -i eth0 host 手机IP -w capture.pcap
测试结果:
终于抓到了一些数据!但是:
- 所有数据都是加密的(TLS 1.3)
- 无法看到HTTP请求的具体内容
- 只能看到连接的目标IP和端口
这证实了APP确实在进行网络通信,只是我们无法通过常规手段看到通信内容。
1.5 阶段性总结
经过一个晚上的尝试,我对这款APP的网络防护有了初步了解:
1.5.1 防护特点
| 特点 | 说明 |
|---|---|
| 精准检测 | 只检测HTTP代理,不影响SOCKS代理 |
| 静默处理 | 检测到代理后不崩溃,而是切换通信方式 |
| 用户友好 | 普通用户完全感知不到异常 |
| 多层防护 | HTTP代理、VPN、iptables都无法突破 |
1.5.2 技术推测
基于观察到的现象,我推测APP可能使用了以下技术:
- 代理检测:在应用启动时检测系统代理设置
- 通信切换:检测到代理后,切换到直连模式
- 自定义网络栈:使用自定义的Socket实现,绕过系统网络层
- 非标准端口:可能使用非80/443的端口进行通信
1.5.3 下一步计划
既然网络层的抓包行不通,我需要换一个思路:
- 静态分析:反编译APK,分析代码逻辑
- 动态调试:使用Frida等工具Hook网络请求
- 模拟执行:如果动态调试也被阻止,考虑使用Unidbg
1.6 意外发现:Frida的命运
在结束这个晚上的工作之前,我决定快速尝试一下Frida。
Frida是一款强大的动态插桩工具,可以在运行时注入代码到目标进程中。如果能用Frida Hook住网络请求相关的函数,就能看到APP发送的数据。
1.6.1 准备Frida环境
bash
# 安装Frida
pip install frida-tools
# 在手机上启动frida-server
adb push frida-server /data/local/tmp/
adb shell chmod +x /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &
1.6.2 编写Hook脚本
javascript
// hook_network.js
Java.perform(function() {
console.log("[*] Starting network hook...");
// Hook OkHttp的请求方法
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
var Request = Java.use('okhttp3.Request');
OkHttpClient.newCall.implementation = function(request) {
console.log("[*] ========== New Request ==========");
console.log("[*] URL: " + request.url().toString());
console.log("[*] Method: " + request.method());
console.log("[*] Headers: " + request.headers().toString());
return this.newCall(request);
};
console.log("[*] Hook installed!");
});
1.6.3 运行Frida
bash
frida -U -f com.dreamworld.app -l hook_network.js --no-pause
结果:
bash
____
/ _ | Frida 16.1.4 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to Pixel 6 Pro (id=192.168.1.101:5555)
Spawning `com.dreamworld.app`...
Process crashed: Segmentation fault
APP直接崩溃了!
Frida注入后,APP立即发生段错误(Segmentation fault)并退出。
这说明APP实现了反Frida检测机制。一旦检测到Frida的存在,就会触发崩溃,阻止任何动态调试。
1.6.4 这意味着什么?
这款APP的安全防护不仅仅是网络层的:
| 防护层 | 防护措施 | 效果 |
|---|---|---|
| 网络层 | 代理检测 + 通信切换 | 抓包工具失效 |
| 运行时 | Frida检测 | 动态调试失败 |
| ??? | ??? | 待发现 |
看来,这将是一场持久战。
1.7 本章小结
1.7.1 关键发现
- 抓包完全失效:Charles、HttpCanary、iptables都无法捕获APP的网络请求
- 精准代理检测:APP只检测HTTP代理,SOCKS代理可以正常使用
- 静默通信切换:检测到代理后不崩溃,而是切换到直连模式
- Frida被检测:动态调试工具注入后APP直接崩溃
1.7.2 技术启示
这款APP展示了现代移动应用安全防护的一个重要趋势:精准打击 + 用户友好。
- 精准打击:只针对安全研究员的工具(HTTP代理、Frida),不影响普通用户
- 用户友好:检测到威胁后不是简单崩溃,而是静默处理,保证用户体验
这种设计思路值得所有移动开发者学习。
1.7.3 下一步
既然网络抓包和动态调试都行不通,我需要:
- 静态分析:反编译APK,从代码层面理解APP的工作原理
- 寻找突破口:分析代理检测和Frida检测的具体实现
- 探索新工具:考虑使用Unidbg等离线模拟执行工具
本章思考题
-
为什么APP选择"静默切换通信方式"而不是"直接崩溃"?这种设计有什么优缺点?
-
如果你是这款APP的安全工程师,你会如何改进现有的代理检测机制?
-
除了HTTP代理检测,还有哪些方法可以阻止抓包分析?
下一章:第一章 - 静态分析的艺术
章节附录
A. 本章涉及的工具
| 工具 | 用途 | 官网 |
|---|---|---|
| Charles Proxy | HTTP/HTTPS抓包 | www.charlesproxy.com/ |
| HttpCanary | Android VPN抓包 | httpcanary.com/ |
| Frida | 动态插桩 | frida.re/ |
| Wireshark | 网络协议分析 | www.wireshark.org/ |
B. 本章关键代码
代理检测示例:
java
public static boolean isProxySet() {
String proxyHost = System.getProperty("http.proxyHost");
return proxyHost != null && !proxyHost.isEmpty();
}
绕过代理示例:
java
OkHttpClient client = new OkHttpClient.Builder()
.proxy(Proxy.NO_PROXY)
.build();
C. 参考资料
- Android Proxy Settings: developer.android.com/reference/a...
- OkHttp Proxy Configuration: square.github.io/okhttp/
- Frida Documentation: frida.re/docs/
本章完