https无法明文抓包
Android P版本开始强制App使用Https协议,否则访问崩溃如下所示错误:
java.lang.ClassCastException:
com.android.okhttp.internal.huc.HttpURLConnectionImpl cannot be cast to javax.net.ssl.HttpsURLConnection
可参阅:
Android 9.0强制使用https,会阻塞http请求,如果app使用的第三方sdk有http,将全部被阻塞
因此在Android P基本很少能能抓取http请求,多数以https为主。
但是你在参考网上https抓包配置后发现还是无法解密内容:
Android 7
以上系统默认会让App不信任默认用户证书,只信用系统证书。
默认情况我们只能导入用户证书,系统证书导入需要一些root权限。如下图所示导入的charles
证书。
如果你是App所有用户可以参阅下面文章操作:
Android 7及以上信任用户证书
如果你有root权限直接导入系统证书即可:
(1) 首先安装代理的证书(此时会将会自动将证书转化为特定格式),用户证书安装目录:
/data/misc/user/0/cacerts-added
如笔者次目录下的用户证书
(2) 将证书文件拷贝到系统证书目录/system/etc/security/cacerts
举例命令
cp 97fa6c80.0 /system/etc/security/cacerts
如果出现 Read-only file system
,重新挂载分区(android 8以上版本请关闭dm-verity以及avb在尝试网上重新挂载命令)。
//针对ROM是userdebug版本可以使用下面的命令
adb disable-verity
adb reboot
adb root
adb remount
对于root手机可以考虑使用mask的一个模块:
movecert_iyue
这个模块原理非常简单,和新源码如下
mv -f /data/misc/user/0/cacerts-added/* $MODDIR/system/etc/security/cacerts/
就是将用户证书移动mask规范下的system目录(会被映射到真实的/system)
安装后重启即可看到系统证书:
抓包效果图可以明显看到成功解析了数据:
app不走代理
很多网络框架可以自主选择是否选择路由代理。如下代码:
//
val httpHost = System.getProperties().getProperty("http.proxyHost");
val httpPort = System.getProperties().getProperty("http.proxyPort");
val httpsHost = System.getProperties().getProperty("https.proxyHost");
val httpsPort = System.getProperties().getProperty("https.proxyPort");
//输出代理信息
Log.e( "test", "httpHost ${httpHost} httpPort ${httpPort} \r\n httpsHost ${httpsHost} httpsPort ${httpsPort} ")
System.getProperties().remove("http.proxyHost");
System.getProperties().remove("http.proxyPort");
System.getProperties().remove("https.proxyHost");
System.getProperties().remove("https.proxyPort");
val url = URL("https://www.google.com")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.connect()
val readBytes = urlConnection.inputStream.readBytes()
Log.e("test", "${String(readBytes)}")
上面的这种方式会迫使wifi手动设置代理抓包方式失效
解决方案:
- 电脑开个热点,然后抓电脑无线网卡即可(软路由差不多,比较简单不做举例)
- 手机开代理软件转发流量到代理
- 手机刷Nethunter 直接当电脑玩,抓手机网卡即可(我现在还想不到不走网卡上网的app)
举例代理流量 (2)
VPN方式转发流量可以通杀市面大多数的APP,但是部分app会检测,但是免root。proxydroid
需要root但基本可以做到市面上百分之99%的应用无感知透明代理
我们以charles
+proxydroid
举例
charles
开启socks代理
流量转发VPN
Charles+postern抓包教程 转载
流量转发 proxydroid
举例(3) 刷入Nethunter内核
刷入后你将是一个完整linux系统,通过vnc链接手机桌面,然后开启wireshark解析数据包
当然wireshare比较适合做自定义协议的分析,https还是建议在新的内核系统再装一个charles在通过iptable转发流量。
SSL Pinning(客户端固定证书)
客户端可以强制只信任某一证书,那么代理抓包的中间人证书将失效并且可以风控异常设备进行上报。
我们首先用keytool生产一个PKCS12文件(里面包含密钥以及证书)
keytool -genkeypair --ext SAN=IP:192.168.38.70 -alias baeldung -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore baeldung.p12 -validity 3650
其中注意上面需要改成自己的IP
生产的文件会交付给我们的Demo
服务端使用,然后我们需要再提起里面的证书给客户端
//生产处一个证书,但是这个证书不是我们需要的格式
openssl pkcs12 -in baeldung.p12 -out your-cert.pem -nokeys
//将证书转化成Android 客户端证书格式
openssl x509 -outform der -in your-cert.pem -out your-cert.crt
我们服务端使用spring boot作为案例:
//UserControl.kt
//接口如下:
@Controller
@RequestMapping("test")
class UserControl {
@RequestMapping("xx")
@ResponseBody
fun myout(): String {
return "hello"
}
}
最后是我们的ssl配置如下(即可拷贝证书到工程的keystore/baeldung.p12)
#application.properties 文件
# The format used for the keystore. It could be set to JKS in case it is a JKS file
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate
server.ssl.key-store=classpath:keystore/baeldung.p12
# The password used to generate the certificate
server.ssl.key-store-password=123456
# The alias mapped to the certificate
server.ssl.key-alias= baeldung
最后就是我们的客户端案例:
fun test(){
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val caInput: InputStream = BufferedInputStream(this.resources.assets.open("your-cert.crt"))
val ca: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
System.out.println("ca=" + ca.subjectDN)
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
load(null, null)
setCertificateEntry("baeldung", ca)
}
// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
// Create an SSLContext that uses our TrustManager
val context: SSLContext = SSLContext.getInstance("TLS").apply {
init(null, tmf.trustManagers, null)
}
// Tell the URLConnection to use a SocketFactory from our SSLContext
val url = URL("https://192.168.38.70:8080/test/xx")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.sslSocketFactory = context.socketFactory
urlConnection.connect()
val inputStream: InputStream = urlConnection.inputStream
Log.e("test", "${String(inputStream.readBytes())}")
}
开启代理抓包后:
解决方案1:
对于客户端固定证书个人推荐推荐frida hook方式进行解绑
这里给出一个开源的解决脚本
frida-android-unpinning
举例clone 上面的脚本执行后
frida -U -f com.example.learnssldemo -l frida-script.js
输出:
JackdeMacBook-Pro:frida-android-unpinning-main jack$ frida -U -f com.example.learnssldemo -l frida-script.js
____
/ _ | Frida 16.0.8 - 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 (id=19291FDF6006PD)
Spawned `com.example.learnssldemo`. Resuming main thread!
[Pixel 6::com.example.learnssldemo ]-> ---
Unpinning Android app...
[+] SSLPeerUnverifiedException auto-patcher
[+] HttpsURLConnection (setDefaultHostnameVerifier)
[+] HttpsURLConnection (setSSLSocketFactory)
[+] HttpsURLConnection (setHostnameVerifier)
[+] SSLContext
[+] TrustManagerImpl
[ ] OkHTTPv3 (list)
[ ] OkHTTPv3 (cert)
[ ] OkHTTPv3 (cert array)
[ ] OkHTTPv3 ($okhttp)
[ ] Trustkit OkHostnameVerifier(SSLSession)
[ ] Trustkit OkHostnameVerifier(cert)
[ ] Trustkit PinningTrustManager
[ ] Appcelerator PinningTrustManager
[ ] OpenSSLSocketImpl Conscrypt
[ ] OpenSSLEngineSocketImpl Conscrypt
[ ] OpenSSLSocketImpl Apache Harmony
[ ] PhoneGap sslCertificateChecker
[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string)
[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string array)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)
[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)
[ ] Conscrypt CertPinManager
[ ] CWAC-Netsecurity CertPinManager
[ ] Worklight Androidgap WLCertificatePinningPlugin
[ ] Netty FingerprintTrustManagerFactory
[ ] Squareup CertificatePinner (cert)
[ ] Squareup CertificatePinner (list)
[ ] Squareup OkHostnameVerifier (cert)
[ ] Squareup OkHostnameVerifier (SSLSession)
[+] Android WebViewClient (SslErrorHandler)
[ ] Android WebViewClient (WebResourceError)
[ ] Apache Cordova WebViewClient
[ ] Boye AbstractVerifier
[ ] Appmattus (Transparency)
Unpinning setup completed
---
解决方案2:
如果你是App所有者那么你只需要在如下地方配置即可
HTTPS using Self-Signed Certificate in Spring Boot
通过 HTTPS 和 SSL 确保安全
mutual authentication
如法炮制,首先是生产一个客户端使用的证书
keytool -genkeypair -alias baeldung -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore cbaeldung.p12 -validity 3650
生产的p12文件格式Android 无法直接使用需要转化为bks格式,这里使用portecle 进行转化。
此时我们还需要导出一个jks给服务端使用,教程如下:
首先执行命令导出客户端p12的证书
openssl pkcs12 -in cbaeldung.p12 -out OUTFILE.crt -nodes
然后重新打开portecle
然后执行如下操作将证书放入jks格式的一个封装文件中,
首先我们编写服务端代码,只需要将上文证书固定代码稍微增加即可
# application.properties
# The format used for the keystore. It could be set to JKS in case it is a JKS file
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate
server.ssl.key-store=classpath:keystore/baeldung.p12
# The password used to generate the certificate
server.ssl.key-store-password=123456
# The alias mapped to the certificate
server.ssl.key-alias= baeldung
spring.rsocket.server.ssl.key-store-type=PKCS12
#开启验证客户端证书
server.ssl.client-auth=need
server.ssl.trust-store=classpath:keystore/clienttrus.jks
server.ssl.trust-store-password=123456
server.ssl.trust-store-type=JKS
客户端代码:
fun test() {
//服务端证书固定代码
val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
val caInput: InputStream = BufferedInputStream(this.resources.assets.open("your-cert.crt"))
val ca: X509Certificate = caInput.use {
cf.generateCertificate(it) as X509Certificate
}
System.out.println("ca=" + ca.subjectDN)
// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
load(null, null)
setCertificateEntry("baeldung", ca)
}
// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm: String = TrustManagerFactory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
init(keyStore)
}
//客户端证书导入
val keyStore2 = KeyStore.getInstance("BKS");
val ksIn = this.resources.getAssets().open("cbaeldung.bks");
ksIn.use {ksIn->
keyStore2.load(ksIn, KEY_STORE_PASSWORD.toCharArray())
}
val keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore2, KEY_STORE_PASSWORD.toCharArray());
// Create an SSLContext that uses our TrustManager
val context: SSLContext = SSLContext.getInstance("TLS").apply {
init(
keyManagerFactory.keyManagers,//开启客户端证书
tmf.trustManagers, //服务端证书固定
null)
}
// Tell the URLConnection to use a SocketFactory from our SSLContext
val url = URL("https://192.168.38.70:8080/test/xx")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.sslSocketFactory = context.socketFactory
urlConnection.connect()
val inputStream: InputStream = urlConnection.inputStream
Log.e("test", "${String(inputStream.readBytes())}")
}
不要忘记将bks文件拷贝到Android工程哦
当你开启抓包软件后你会发现链接失败。。.。
解决方案:
如果你不是app拥有者,首先先用上网的Frida解绑,然后再逆向App取出客户端bks文件以及密码,然后导入chales。
本节服务端代码
本节客户端
参阅文章1 开源app-从0到1实现(四)Android端自制https证书实现双向认证.md
🍶 为什么你的 Charles 会抓包失败?
Hook通用抓包
= =无视所谓的证书绑定,双向认证1232
一个firda 脚本
原理看了下非常简单,可参阅我的另一篇博文Android hook方式抓包