随着移动安全攻防环境的不断升级,App渗透测试已成为网络安全领域的重要环节。本次我们将分享一次针对Flutter开发的Android应用的实战抓包过程。
应用初探与问题定位
在一次移动端渗透测试任务中,测试人员在常规抓包环节遇到了阻碍。应用功能正常,但在设备上设置了HTTP代理或VPN后,不仅无法抓到任何数据包,应用界面也变为一片空白,所有功能均无法访问。
初步判断,这很可能是因为应用进行了代理检测或VPN检测。然而,应用并未给出任何明确的提示信息,这促使我们进一步分析应用本身。
通过查看APK文件结构,在 AndroidManifest.xml 配置文件中发现了关键线索:io.flutter.embedding.android.FlutterActivity。这是Flutter 2.0的标准入口Activity,表明此应用很可能基于Flutter框架开发。
进一步的验证方法是检查应用的 lib 目录。如果存在 libapp.so 与 libflutter.so 这两个核心库文件,即可基本确认其为Flutter应用。经确认,目标应用符合此特征。
Flutter应用抓包难点分析
对于Flutter开发的App,常规代理工具(如Burp Suite)抓不到HTTPS包的原因通常有以下几点:
- 不走系统代理:Flutter引擎的网络请求可能默认不遵循系统的代理设置。
- 不信任系统证书:即使流量经过代理,Flutter可能也不信任用户手动安装到系统根证书目录的Burp等代理CA证书。
- 内置SSL Pinning:证书校验逻辑可能被编译到原生库(
.so文件)中,导致基于Xposed框架的通用绕过模块(如JustTrustMe、SSLUnpinning)失效。
因此,针对Flutter应用的抓包,通常需要借助Frida这样的动态Android Hook框架,从Java层和Native(原生)层同时绕过其证书校验机制。
使用Frida绕过多层SSL Pinning
以下Frida脚本尝试从多个层面(包括OkHttp、系统TrustManager、WebView以及原生BoringSSL/OpenSSL)绕过SSL Pinning,适用于大多数情况。
/* flutter_full_bypass.js
Multi-layer SSL pinning bypass for Flutter apps:
- Java layer: OkHttp CertificatePinner, TrustManager, HttpsURLConnection, WebView
- Native layer: try to override common BoringSSL/OpenSSL verify functions (best-effort)
Usage:
frida -U -f com.package.name -l flutter_full_bypass.js --no-pause*/
setTimeout(function () {
Java.perform(function () {
console.log("***** Frida: Java layer hooking start *****");
// ========== OkHttp3 CertificatePinner ==========
try {
var CertPinner = Java.use("okhttp3.CertificatePinner");
CertPinner.check.overload('java.lang.String', 'java.util.List').implementation = function (host, pins) {
console.log("[bypass] okhttp3.CertificatePinner.check => SKIP for " + host);
return;
};
console.log("[ok] hooked okhttp3.CertificatePinner");
} catch (e) {
console.log("[no] okhttp3.CertificatePinner: " + e.message);
}
// ========== OkHttp (old) ==========
try {
var CertPinnerOld = Java.use("com.squareup.okhttp.CertificatePinner");
CertPinnerOld.check.overload('java.lang.String', 'java.util.List').implementation = function (host, pins) {
console.log("[bypass] okhttp.CertificatePinner.check => SKIP for " + host);
return;
};
console.log("[ok] hooked com.squareup.okhttp.CertificatePinner");
} catch (e) {
console.log("[no] com.squareup.okhttp.CertificatePinner: " + e.message);
}
// ========== X509TrustManager / SSLContext ==========
try {
var X509TrustManager = Java.use("javax.net.ssl.X509TrustManager");
var SSLContext = Java.use("javax.net.ssl.SSLContext");
var TrustManagerImpl = Java.registerClass({
name: "org.frida.TrustManagerImpl",
implements: [X509TrustManager],
methods: {
checkClientTrusted: function (chain, authType) {
// no-op
},
checkServerTrusted: function (chain, authType) {
console.log("[bypass] TrustManagerImpl.checkServerTrusted");
},
getAcceptedIssuers: function () {
return [];
}
}
});
SSLContext.init.overload(
'[Ljavax.net.ssl.KeyManager;',
'[Ljavax.net.ssl.TrustManager;',
'java.security.SecureRandom'
).implementation = function (km, tm, sr) {
console.log("[bypass] SSLContext.init -> replace TrustManagers");
var tmArr = Java.array("javax.net.ssl.TrustManager", [TrustManagerImpl.$new()]);
return this.init(km, tmArr, sr);
};
console.log("[ok] hooked SSLContext.init and installed TrustManagerImpl");
} catch (e) {
console.log("[no] SSLContext / TrustManager hook: " + e.message);
}
// ========== HttpsURLConnection ==========
try {
var HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection");
HttpsURLConnection.setDefaultHostnameVerifier.implementation = function (v) {
console.log("[bypass] HttpsURLConnection.setDefaultHostnameVerifier called -> ignored");
return;
};
console.log("[ok] hooked HttpsURLConnection.setDefaultHostnameVerifier");
} catch (e) {
console.log("[no] HttpsURLConnection hook: " + e.message);
}
// ========== WebView SSL ==========
try {
var WebViewClient = Java.use("android.webkit.WebViewClient");
WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {
console.log("[bypass] WebView SSL error ignored");
handler.proceed();
};
console.log("[ok] hooked WebViewClient.onReceivedSslError");
} catch (e) {
console.log("[no] WebViewClient hook: " + e.message);
}
// ========== okhttp3.OkHttpClient.Builder.sslSocketFactory ==========
try {
var OkBuilder = Java.use("okhttp3.OkHttpClient$Builder");
OkBuilder.sslSocketFactory.overload('javax.net.ssl.SSLSocketFactory', 'javax.net.ssl.X509TrustManager').implementation = function (sslsf, trustManager) {
console.log("[bypass] OkHttpClient.Builder.sslSocketFactory called -> pass-through");
return this.sslSocketFactory(sslsf, trustManager);
};
console.log("[ok] hooked OkHttpClient.Builder.sslSocketFactory (if present)");
} catch (e) {
console.log("[no] OkHttp builder hook: " + e.message);
}
console.log("***** Frida: Java layer hooking finished *****");
});
// 延迟后开始Native层Hook
setTimeout(function () {
try {
console.log("***** native layer hooking start *****");
// 尝试Hook常见的SSL验证函数(尽力而为,效果因设备/Android版本而异)
var nativeCandidates = [
"SSL_get_verify_result",
"SSL_get_peer_certificate",
"SSL_get_peer_cert_chain",
"SSL_CTX_set_verify",
"X509_verify_cert",
"SSL_verify_client_post_handshake"
];
nativeCandidates.forEach(function (sym) {
try {
var addr = Module.findExportByName(null, sym);
if (addr) {
Interceptor.attach(addr, {
onEnter: function (args) {
console.log("[native] hit " + sym + " at " + addr);
},
onLeave: function (retval) {
// 如果函数返回整数类型的验证结果,强制改为成功 (0)
try {
if (retval !== undefined) {
retval.replace(0);
}
} catch (e) {}
}
});
console.log("[ok] attached to native " + sym);
}
} catch (e) {}
});
// Hook常见的BoringSSL函数名(尽力而为)
var tryNames = ["SSL_read", "SSL_write", "SSL_connect", "SSL_accept"];
tryNames.forEach(function (n) {
try {
var a = Module.findExportByName(null, n);
if (a) {
Interceptor.attach(a, {
onEnter: function (args) {
// 轻量级日志,避免过多输出
},
onLeave: function (ret) {
}
});
console.log("[ok] attached to " + n);
}
} catch (e) {}
});
console.log("***** native layer hooking finished *****");
} catch (err) {
console.log("native hooks failed: " + err);
}
}, 500);
}, 1000);
实战抓包与测试结果
目标应用未对Frida进行检测,因此可以顺利注入上述脚本。在Frida脚本成功运行后,同时开启设备的Wi-Fi代理并指向抓包工具(如Burp Suite)。
此时,应用的所有网络流量已被成功捕获,之前空白的功能页面也恢复了正常。通过对拦截到的HTTP/HTTPS请求进行分析,发现其请求参数并未进行额外的加密处理,这使得后续的漏洞挖掘工作得以顺利进行,并最终成功发现了高危安全漏洞。
通过本次Flutter App渗透测试实战可以看出,对于采用了强证书校验机制的应用,结合Frida框架进行动态Hook是突破SSL Pinning防御的有效手段。