返回列表 发新帖

《安卓逆向这档事》二十一、抓包学得好,牢饭吃得饱(中)

  [复制链接]

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-9-27 19:34:22 | 显示全部楼层 | 阅读模式  来自 福建
本帖最后由 正己 于 2024-9-28 09:02 编辑



一、课程目标
1.了解代理以及VPN检测与对抗
2.了解SSL Pinning
3.了解双向认证

二、工具

1.教程Demo(更新)
2.Charles
3.Reqable
三、课程内容

1.代理检测
`定义`
代理检测是用于检测设备是否设置了网络代理。这种检测的目的是识别出设备是否尝试通过代理服务器(如抓包工具)来转发网络流量,从而可能截获和分析App的网络通信。
`原理`
App会检查系统设置或网络配置,以确定是否有代理服务器被设置为转发流量。例如,它可能会检查系统属性或调用特定的网络信息API来获取当前的网络代理状态。
  1. java
  2. return System.getProperty("http.proxyHost") == null && System.getProperty("http.proxyPort")  == null

  3. Port跟设置有关,例如Charles默认是8888
复制代码


`强制不走代理`
  1. java
  2. connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);

  3. OkHttpClient.Builder()  
  4.     .proxy(Proxy.NO_PROXY)  
  5.     .build()
复制代码

`Charles安装与配置`
下载地址:https://www.charlesproxy.com/download/


放一组key:

  1. Registered Name:        52pojie
  2. License Key:        d43c11e6697bbe07a8
复制代码

详细安装与配置

`anti脚本:`
  1. js
  2. function anti_proxy() {
  3.     var GetProperty = Java.use("java.lang.System");
  4.     GetProperty.getProperty.overload("java.lang.String").implementation = function(getprop) {
  5.         if (getprop.indexOf("http.proxyHost") >= 0 || getprop.indexOf("http.proxyPort") >= 0) {
  6.             return null;
  7.         }
  8.         return this.getProperty(getprop);
  9.     }
  10. }
复制代码

`透明代理`
透明代理(Transparent Proxy)是一种特殊的代理服务类型,它可以在客户端(如浏览器或应用程序)不知道的情况下拦截、转发和处理网络请求。与传统的代理服务不同,透明代理不需要客户端进行任何配置就能工作。
[[Clash版]安卓上基于透明代理实现热点抓包](https://blog.seeflower.dev/archives/296/)
安卓上基于透明代理对特定APP抓包
2.VPN检测
`定义`
VPN检测是指应用程序或系统检查用户是否正在使用虚拟专用网络(Virtual Private Network, VPN)的一种技术。当用户使用VPN时,他们的网络流量会被加密并通过一个远程服务器路由,这可以隐藏用户的实际IP地址和位置信息,同时保护数据的安全性和隐私。
`原理`
当客户端运行VPN虚拟隧道协议时,会在当前节点创建基于`eth`之上的`tun0`接口或`ppp0`接口。这些接口是用于建立虚拟网络连接的特殊网络接口。
根据OSI七层模型,二者分别支持的协议:


VPN 协议大多是作用在 OSI 的第二层和第三层之间,由此可见VPN能抓到代理方式的所有的包

  1. kotlin
  2. public final boolean Check_Vpn1() {
  3.         try {
  4.             Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
  5.             if (networkInterfaces == null) {
  6.                 return false;
  7.             }
  8.             Iterator it = Collections.list(networkInterfaces).iterator();
  9.             while (it.hasNext()) {
  10.                 NetworkInterface networkInterface = (NetworkInterface) it.next();
  11.                 if (networkInterface.isUp() && !networkInterface.getInterfaceAddresses().isEmpty()) {
  12.                     Log.d("zj595", "isVpn NetworkInterface Name: " + networkInterface.getName());
  13.                     if (Intrinsics.areEqual(networkInterface.getName(), "tun0") || Intrinsics.areEqual(networkInterface.getName(), "ppp0") || Intrinsics.areEqual(networkInterface.getName(), "p2p0") || Intrinsics.areEqual(networkInterface.getName(), "ccmni0")) {
  14.                         return true;
  15.                     }
  16.                 }
  17.             }
  18.             return false;
  19.         } catch (Throwable th) {
  20.             th.printStackTrace();
  21.             return false;
  22.         }
  23.     }

  24. public final boolean Check_Vpn2() {
  25.         boolean z;
  26.         String networkCapabilities;
  27.         try {
  28.             Object systemService = getApplicationContext().getSystemService("connectivity");
  29.             Intrinsics.checkNotNull(systemService, "null cannot be cast to non-null type android.net.ConnectivityManager");
  30.             ConnectivityManager connectivityManager = (ConnectivityManager) systemService;
  31.             NetworkCapabilities networkCapabilities2 = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
  32.             Log.i("zj595", "networkCapabilities -> " + networkCapabilities2);
  33.             boolean z2 = networkCapabilities2 != null && networkCapabilities2.hasTransport(4);
  34.             // 检查网络能力是否包含 "WIFI|VPN"
  35.             if (networkCapabilities2 != null && (networkCapabilities = networkCapabilities2.toString()) != null) {
  36.                 if (StringsKt.contains$default((CharSequence) networkCapabilities, (CharSequence) "WIFI|VPN", false, 2, (Object) null)) {
  37.                     z = true;
  38.                     return !z || z2;
  39.                 }
  40.             }
  41.             z = false;
  42.             if (z) {
  43.             }
  44.         } catch (Exception e) {
  45.             e.printStackTrace();
  46.             return false;
  47.         }
  48.     }




复制代码


`anti`
  1. js
  2. function hook_vpn() {
  3.     Java.perform(function () {
  4.         var NetworkInterface = Java.use("java.net.NetworkInterface");
  5.         NetworkInterface.getName.implementation = function () {
  6.             var name = this.getName();  //hook java层的getName方法
  7.             console.log("name: " + name);
  8.             if (name === "tun0" || name === "ppp0") {
  9.                 return "rmnet_data0";
  10.             } else {
  11.                 return name;
  12.             }
  13.         }

  14.         var NetworkCapabilities = Java.use("android.net.NetworkCapabilities");
  15.         NetworkCapabilities.hasTransport.implementation = function () {
  16.             return false;
  17.         }

  18.         NetworkCapabilities.appendStringRepresentationOfBitMaskToStringBuilder.implementation = function (sb, bitMask, nameFetcher, separator) {
  19.             if (bitMask == 18) {
  20.                 console.log("bitMask", bitMask);
  21.                 sb.append("WIFI");
  22.             } else {
  23.                 console.log(sb, bitMask);
  24.                 this.appendStringRepresentationOfBitMaskToStringBuilder(sb, bitMask, nameFetcher, separator);
  25.             }
  26.         }


  27.     })
  28. }
复制代码


3.SSL Pinning
`SSL Pinning` 也称为证书锁定,是Google官方推荐的检验方式,意思是将服务器提供的SSL/TLS证书内置到移动客户端,当客户端发起请求的时候,通过对比内置的证书与服务器的证书是否一致,来确认这个连接的合法性。
PS:这里还要提到一个概念:`单向校验`,本质上二者没区别,`SSL Pinning`可以理解为加强版的`单向校验`


  1. 1.客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
  2. 2.服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
  3. 3.客户端使用服务端返回的信息验证服务器的合法性,包括:
  4.     (1)证书是否过期
  5.     (2)发型服务器证书的CA是否可靠
  6.     (3)返回的公钥是否能正确解开返回证书中的数字签名
  7.     (4)服务器证书上的域名是否和服务器的实际域名相匹配、验证通过后,将继续进行通信,否则,终止通信
  8. 4.客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
  9. 5.服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。
  10. 6.服务器将选择好的加密方案通过明文方式返回给客户端
  11. 7.客户端接收服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器
  12. 8.服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥。在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全
复制代码

`SSL Pinning`主流的三套方案:`公钥校验`、`证书校验`、`Host校验`
因为是客户端做的校验,所以可以在本地进行hook对抗,参考以下的两个项目:
JustTrustMesslunpining
1.指纹校验
在网站中我们可以看到网站的证书相关信息,其中就包含了指纹信息

`常见安卓网络开发框架`

OkHttp和Retrofit是非常流行的组合,被广泛应用于现代Android应用开发中

`原理(以okhttp框架为例):`
在CertificatePinner类里有一个check方法
  1. java
  2. /**
  3. * 检查指定主机名的证书链是否符合预设的哈希值(证书固定)。
  4. * @param hostname         要验证的主机名。
  5. * @param peerCertificates 待验证的证书列表。
  6. * @throws SSLPeerUnverifiedException 如果证书不符合预设的哈希值,则抛出此异常。
  7. */
  8. public void check(String hostname, List<Certificate> peerCertificates)
  9.       throws SSLPeerUnverifiedException {
  10.     // 查找与主机名匹配的哈希值列表(证书固定列表)。
  11.     List<Pin> pins = findMatchingPins(hostname);
  12.     // 如果没有找到任何匹配的哈希值,则直接返回,表示无需进一步检查。
  13.     if (pins.isEmpty()) return;

  14.     // 如果存在证书链清理器,则先清理证书链中的冗余证书。
  15.     if (certificateChainCleaner != null) {
  16.       peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
  17.     }
  18.     // 遍历每一个证书进行检查。
  19.     for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
  20.       // 获取当前证书。
  21.       X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
  22.       // 懒加载计算每个证书的SHA-1和SHA-256哈希值。
  23.       ByteString sha1 = null;
  24.       ByteString sha256 = null;
  25.       // 遍历预设的哈希值列表。
  26.       for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
  27.         Pin pin = pins.get(p);
  28.         // 根据预设的哈希算法进行检查。
  29.         if (pin.hashAlgorithm.equals("sha256/")) {
  30.           // 如果尚未计算SHA-256哈希值,则进行计算。
  31.           if (sha256 == null) sha256 = sha256(x509Certificate);
  32.           // 如果证书的SHA-256哈希值与预设值相同,则返回成功。
  33.           if (pin.hash.equals(sha256)) return;
  34.         } else if (pin.hashAlgorithm.equals("sha1/")) {
  35.           // 如果尚未计算SHA-1哈希值,则进行计算。
  36.           if (sha1 == null) sha1 = sha1(x509Certificate);
  37.           // 如果证书的SHA-1哈希值与预设值相同,则返回成功。
  38.           if (pin.hash.equals(sha1)) return;
  39.         } else {
  40.           // 如果遇到不支持的哈希算法,则抛出错误。
  41.           throw new AssertionError("unsupported hashAlgorithm: " + pin.hashAlgorithm);
  42.         }
  43.       }
  44.     }
  45.     // 如果遍历完所有证书和哈希值都没有匹配,则抛出异常。
  46.     throw new SSLPeerUnverifiedException("No matching certificate found.");
  47. }
复制代码

`实现方案:`
  1. kotlin
  2. //指纹检测  
  3. fun check_SSL_PINNING_key() {
  4.     // 使用CoroutineScope和Dispatchers.IO在后台线程中执行网络操作
  5.     CoroutineScope(Dispatchers.IO).launch {
  6.         // 定义需要固定证书的域名
  7.         val caDomain = "www.52pojie.cn"
  8.         // 使用CertificatePinner.Builder构建一个证书固定器
  9.         // 添加一个sha256哈希值,这个哈希值是服务器证书的指纹
  10.         // 这个值需要通过openssl工具获取
  11.         val pinner = CertificatePinner.Builder()
  12.             .add(caDomain, "sha256/WnsD5UGdP5/a65xO1rpH8ru2EjyxkmPEaiNtKixhJLU=") // 添加证书指纹
  13.             .build() // 构建完成证书固定器
  14.         // 使用newBuilder创建一个新的OkHttpClient实例,用于覆盖默认的客户端配置
  15.         val pClient = client.newBuilder()
  16.             .certificatePinner(pinner) // 设置证书固定器
  17.             .build() // 构建新的OkHttpClient实例
  18.         // 构建一个网络请求,访问指定的URL
  19.         val request = Request.Builder()
  20.             .url("https://www.52pojie.cn/?q=SSLPinningCode") // 设置请求的URL
  21.             .build() // 构建请求对象
  22.         try {
  23.             // 执行网络请求,并获取响应
  24.             val response = pClient.newCall(request).execute()
  25.             // 如果响应状态码是200,表示指纹检测通过
  26.             Log.d(TAG, "指纹检测通过,状态码:${response.code()}")
  27.         } catch (e: IOException) {
  28.             // 如果发生IOException,表示指纹检测不通过
  29.             Log.d(TAG, "指纹检测不通过")
  30.             e.printStackTrace() // 打印异常堆栈信息,用于调试
  31.         }
  32.     }
  33. }

复制代码

安装openssl,OpenSSL Windows 版本

配置环境变量

cmd窗口输入以下命令获取

  1. openssl s_client -connect [url]www.52pojie.cn:443[/url] -servername [url]www.52pojie.cn[/url] | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
复制代码


`anti脚本`
  1. js
  2. function anti_ssl_key() {
  3.         //check方法置空即可
  4.     var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner');   
  5.     okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {                             
  6.     console.log('[+] Bypassing SSL key pinning: ' + a);
  7.     return;
  8. }}
复制代码

已有4人评分好评 金币 理由
忆梦玖安 + 1
wwb66668 + 1 + 1 很给力!
下雨了 + 1
彼岸花开奈何桥 + 1 + 1

查看全部评分 总评分:好评 +4  金币 +2 

回复

使用道具 举报

91

主题

3807

回帖

2万

积分

博士后

萌新

Rank: 8Rank: 8

金币
10363
好评
335
信誉
278

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员

QQ
发表于 2024-9-27 19:36:18 | 显示全部楼层  来自 福建
内容太多了,剩下放这里
2.证书校验
`原理:`
通过`trustManager` 类实现的checkServerTrusted接口,核心在于验证服务器证书的公钥。具体步骤包括:获取服务器返回的证书,将其公钥编码为 Base64 字符串;同时从本地资源加载预存的可信客户端证书,并将其公钥也编码为 Base64 字符串。然后,比较这两个公钥是否匹配,以此确认服务器的身份是否合法。最后,使用自定义的 `SSLSocketFactory` 发起 HTTPS 请求,确保通信过程中只信任预定义的服务器证书,从而有效抵御中间人攻击。
`实现方案:`
  1. kotlin
  2. // 定义一个函数用于检查SSL证书
  3. private fun check_SSL_PINNING_CA() {
  4.     // 创建一个X509TrustManager的匿名对象,用于自定义信任管理器
  5.     val trustManager: X509TrustManager = object : X509TrustManager {
  6.         // 客户端证书信任检查,这里不实现任何逻辑
  7.         @Throws(CertificateException::class)
  8.         override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
  9.         }
  10.         // 服务器证书信任检查
  11.         @Throws(CertificateException::class)
  12.         override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String?) {
  13.             // 获取服务器返回的第一个证书
  14.             val cf: X509Certificate = chain[0]
  15.             // 将服务器证书的公钥编码为Base64字符串
  16.             val ServerPubkey: String = Base64.encodeToString(cf.publicKey.encoded, 0)
  17.             Log.e(TAG, "服务器端返回的证书:" + ServerPubkey)
  18.             // 从客户端资源中读取证书
  19.             val client_input = resources.openRawResource(R.raw.wuai)
  20.             val certificateFactory = CertificateFactory.getInstance("X.509")
  21.             // 生成客户端证书
  22.             val realCertificate: X509Certificate = certificateFactory.generateCertificate(client_input) as X509Certificate
  23.             // 将客户端证书的公钥编码为Base64字符串
  24.             val realPubkey: String = Base64.encodeToString(realCertificate.publicKey.encoded, 0)
  25.             Log.e(TAG, "客户端资源目录中的证书:" + realPubkey)
  26.             // 检查证书有效期
  27.             cf.checkValidity()
  28.             // 比较服务器证书和客户端证书的公钥是否相同
  29.             val expected = realPubkey.equals(ServerPubkey, ignoreCase = true)
  30.             if (!expected) {
  31.                 Log.e(TAG, "证书检测不通过")
  32.             } else {
  33.                 Log.e(TAG, "证书检测通过")
  34.             }
  35.         }
  36.         // 返回受信任的CA证书数组,这里返回空数组
  37.         override fun getAcceptedIssuers(): Array<X509Certificate?> {
  38.             return arrayOfNulls<X509Certificate>(0)
  39.         }
  40.     }
  41.     // 声明SSLSocketFactory变量
  42.     var factory: SSLSocketFactory? = null
  43.     try {
  44.         // 获取SSL上下文
  45.         val sslContext = SSLContext.getInstance("SSL")
  46.         // 初始化SSL上下文,使用自定义的信任管理器
  47.         sslContext.init(null, arrayOf<TrustManager>(trustManager), SecureRandom())
  48.         // 获取SSLSocketFactory
  49.         factory = sslContext.socketFactory
  50.     } catch (e: java.lang.Exception) {
  51.         e.printStackTrace()
  52.     }
  53.     // 确保factory不为空
  54.     val finalFactory: SSLSocketFactory? = factory
  55.     // 创建并启动一个新线程来执行网络请求
  56.     object : Thread() {
  57.         override fun run() {
  58.             try {
  59.                 // 使用自定义的SSLSocketFactory创建OkHttpClient
  60.                 val client =
  61.                     OkHttpClient.Builder().sslSocketFactory(finalFactory, trustManager).build()
  62.                 // 构建请求
  63.                 val req = Request.Builder().url("https://www.52pojie.cn/forum.php").build()
  64.                 // 发送请求并获取响应
  65.                 val call: okhttp3.Call = client.newCall(req)
  66.                 val res: Response = call.execute()
  67.                 // 打印响应状态码
  68.                 Log.e("请求发送成功", "状态码:" + res.code())
  69.             } catch (e: IOException) {
  70.                 // 打印网络异常信息
  71.                 Log.e("请求发送失败", "网络异常$e")
  72.             }
  73.         }
  74.     }.start()
  75. }

复制代码

cmd窗口输入以下命令获取证书信息

  1. openssl s_client -connect 52pojie.cn:443 -servername 52pojie.cn | openssl x509 -out wuai.pem
复制代码


`证书信知识补充:`

| 名词            | 含义                                                                      |     |
| ------------- | ----------------------------------------------------------------------- | --- |
| X.509         | 一种通用的证书格式,包含证书持有人的公钥、加密算法等信息                                            |     |
| PKCS1~PKCS12  | 公钥加密(非对称加密)的一系列标准(Public Key Cryptography Standards),.p12 是包含证书和密钥的封装格式 |     |
| *.der         | 证书的二进制存储格式(不常用)                                                         |     |
| *.pem         | 证书或密钥的 Base64 文本存储格式,可以单独存放证书或密钥,也可以同时存放证书和密钥                           |     |
| *.key         | 单独存放的 pem 格式的私钥文件,一般保存为 *.key                                           |     |
| *.cer / *.crt | 两者指的都是证书,Linux 下叫 crt,Windows 下叫 cer;存储格式可以是 pem,也可以是 der               |     |
| *.csr         | 证书签名请求(Certificate Signing Request),包含证书持有人的信息,如:国家、邮件、域名等              |     |
| *.pfx         | 微软 IIS 的实现,包含证书和私钥                                                      |     |
有的证书内容是只包含公钥(服务器的公钥),如.crt、.cer、.pem
有的证书既包含公钥又包含私钥(服务器的私钥),如.pfx、.p12
另外有些app的证书不走寻常路,不是上面所罗列到的格式,它有可能伪装成png等其他格式



`anti脚本`
思路:实例化一个trustManager类,然后里面什么都不写,当上面两处调用到这个类时hook这两个地方,把自己定义的空trustManager类放进去
  1. js
  2. function anti_ssl_cert() {
  3.         // 使用Frida获取Java类X509TrustManager的引用
  4.     var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');
  5.     // 使用Frida获取Java类SSLContext的引用
  6.     var SSLContext = Java.use('javax.net.ssl.SSLContext');
  7.     // 注册一个自定义的TrustManager类
  8.     var TrustManager = Java.registerClass({
  9.         // 指定自定义TrustManager的全名
  10.         name: 'dev.asd.test.TrustManager',
  11.         // 指定自定义TrustManager实现的接口
  12.         implements: [X509TrustManager],
  13.         // 定义自定义TrustManager的方法实现
  14.         methods: {
  15.             // 客户端证书信任检查,这里不实现任何逻辑
  16.             checkClientTrusted: function(chain, authType) {},
  17.             // 服务器证书信任检查,这里不实现任何逻辑
  18.             checkServerTrusted: function(chain, authType) {},
  19.             // 返回受信任的CA证书数组,这里返回空数组
  20.             getAcceptedIssuers: function() {return []; }
  21.         }
  22.     });
  23.     // 准备一个TrustManager数组,用于传递给SSLContext.init()方法
  24.     var TrustManagers = [TrustManager.$new()];
  25.     // 获取SSLContext.init()方法的引用,该方法用于初始化SSL上下文
  26.     var SSLContext_init = SSLContext.init.overload(
  27.         '[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom'
  28.     );
  29.     try {
  30.         // 覆盖init方法的实现,指定使用自定义的TrustManager
  31.         SSLContext_init.implementation = function(keyManager, trustManager, secureRandom) {
  32.             console.log('[+] Bypassing Trustmanager (Android < 7) pinner');
  33.             // 调用原始的init方法,并使用自定义的TrustManager数组
  34.             SSLContext_init.call(this, keyManager, TrustManagers, secureRandom);
  35.         };
  36.     } catch (err) {
  37.         // 如果覆盖init方法失败,打印错误信息
  38.         console.log('[-] TrustManager (Android < 7) pinner not found');
  39.         console.log(err); // 可以取消注释来打印异常的详细信息
  40.     }
  41. }

复制代码


4.双向认证
双向验证,顾名思义就是客户端验证服务器端证书的正确性,服务器端也验证客户端的证书正确性


  1. 1.客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
  2. 2.服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
  3. 3.客户端使用服务端返回的信息验证服务器的合法性,包括:
  4.     (1)证书是否过期
  5.     (2)发型服务器证书的CA是否可靠
  6.     (3)返回的公钥是否能正确解开返回证书中的数字签名
  7.     (4)服务器证书上的域名是否和服务器的实际域名相匹配、验证通过后,将继续进行通信,否则,终止通信
  8. 4.服务端要求客户端发送客户端的证书,客户端会将自己的证书发送至服务端
  9. 5.验证客户端的证书,通过验证后,会获得客户端的公钥
  10. 6.客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
  11. 7.服务器端在客户端提供的加密方案中选择加密程度最高的加密方式
  12. 8.将加密方案通过使用之前获取到的公钥进行加密,返回给客户端
  13. 9.客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端
  14. 10.服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。
复制代码

`实现方案:`
1.首先借助openssl生成**服务端证书**

  1. [b]生成CA私钥 [/b]
  2. openssl genrsa -out ca.key 2048

  3. [b]生成CA自签名证书 [/b]
  4. openssl req -x509 -new -nodes -key ca.key -sha256 -days 1024 -out ca.crt
复制代码

2.生成**服务端证书**
  1. shell
  2. openssl genrsa -out server.key 2048
复制代码

这个指令生成一个2048位的RSA私钥,并将其保存到名为`server.key`的文件中
  1. shell
  2. openssl req -new -key server.key -out server.csr -config server_cert.conf
复制代码

这个指令基于第一步生成的私钥创建一个新的证书签名请求(CSR)。CSR包含了公钥和一些身份信息,这些信息在证书颁发过程中用于识别证书持有者。`-out server.csr`指定了CSR的输出文件名。
执行这个指令时,系统会提示你输入一些身份信息,如国家代码、组织名等,这些信息将被包含在CSR中。(我们这边测试直接全部按回车键默认即可)

`-config server_cert.conf`创建一个OpenSSL配置文件(如 `server_cert.conf`)并指定IP地址,具体的ip地址可以由ipconfig获取

  1. [req]
  2. distinguished_name = req_distinguished_name
  3. req_extensions = v3_req
  4. prompt = no

  5. [req_distinguished_name]
  6. CN = 192.168.199.108

  7. [v3_req]
  8. subjectAltName = @alt_names

  9. [alt_names]
  10. IP.1 = 192.168.199.108

复制代码

  1. shell
  2. openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -extfile server_cert.conf -extensions v3_req
  3. openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.cer
复制代码

使用CA证书签发服务器证书。
生成cer证书供服务端验证。

**客户端证书:**
  1. shell
  2. openssl genrsa -out client.key 2048
  3. openssl req -new -out client.csr -key client.key
  4. openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 500 -sha256
复制代码

生成客户端带密码的p12证书(这步很重要,双向认证的话,浏览器访问时候要导入该证书才行;可能某些Android系统版本请求的时候需要把它转成bks来请求双向认证):
  1. shell
  2. openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt
复制代码

到这一步的时候,设置密码和验证密码光标不会显示,直接输入即可

#环境配置
PS:因为双向认证是本地搭建,所以需要完成几个前置条件:
1.确保电脑和手机处于同一wifi连接下
2.重打包替换生成的server.cer(路径在res/raw),替换ssl_verify方法里的ip地址以及res/xml/network_config.xml的ip地址(通过ipconfig获取实际的ipv4地址)
3.运行服务端代码,然后再请求看看是否能正常输出

**服务端代码:**
  1. python
  2. from flask import Flask, jsonify  
  3. import ssl  

  4. app = Flask(__name__)

  5. [b]ssl 证书校验  [/b]
  6. @app.route('/ca')  
  7. def ssl_verify():  
  8.     return jsonify({"message": "HTTPS server with mutual SSL verification started."})  
  9.   
  10. [b]配置ssl上下文,关键函数  [/b]
  11. def get_ssl_context():  
  12.     [b]CA根证书路径  [/b]
  13.     ca_crt_path = 'certs/ca.crt'  
  14.     [b]服务端证书和密钥路径  [/b]
  15.     server_crt_path = 'certs/server.crt'  
  16.     server_key_path = 'certs/server.key'  
  17.     [b]创建SSL上下文,使用TLS服务器模式  [/b]
  18.     ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)  
  19.     [b]设置验证模式为需要客户端证书  [/b]
  20.     ssl_context.verify_mode = ssl.CERT_REQUIRED  
  21.     [b]启用主机名检查(根据需要设置)  [/b]
  22.     ssl_context.check_hostname = False  
  23.     [b]设置加密套件  [/b]
  24.     ssl_context.set_ciphers("HIGH:!SSLv3:!TLSv1:!aNULL:@STRENGTH")  
  25.     [b]加载CA根证书,用于验证客户端证书  [/b]
  26.     ssl_context.load_verify_locations(cafile=ca_crt_path)  
  27.     [b]加载服务端证书和私钥  [/b]
  28.     ssl_context.load_cert_chain(certfile=server_crt_path, keyfile=server_key_path)  
  29.     return ssl_context  
  30.   
  31. if __name__ == '__main__':  
  32.     ssl_context = get_ssl_context()  
  33.     app.run(host="192.168.124.21", port=8088,ssl_context=ssl_context)

复制代码

**客户端代码:**
  1. kotlin
  2. fun ssl_verify() = Thread {  
  3. // 初始化一个用于信任管理的对象
  4.     var trustManager: X509TrustManager? = null
  5.     try {
  6.         // 获取默认的信任管理工厂实例
  7.         val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
  8.         // 初始化信任管理工厂,传入null表示使用系统默认的信任存储
  9.         trustManagerFactory.init(null as KeyStore?)
  10.         // 获取信任管理器列表
  11.         val trustManagers = trustManagerFactory.trustManagers
  12.         // 检查信任管理器列表是否只有一个X509TrustManager类型的对象
  13.         if (trustManagers.size != 1 || trustManagers[0] !is X509TrustManager) {
  14.             // 如果不符合预期,则抛出异常
  15.             throw IllegalStateException("Unexpected default trust managers: ${trustManagers.contentToString()}")
  16.         }
  17.         // 赋值信任管理器
  18.         trustManager = trustManagers[0] as X509TrustManager
  19.     } catch (e: Exception) {
  20.         // 捕获异常并打印堆栈跟踪信息
  21.         e.printStackTrace()
  22.     }
  23.     // 创建 OkHttpClient 实例并配置SSL套接字工厂和主机名验证器
  24.     val client = OkHttpClient.Builder()
  25.         .sslSocketFactory(
  26.             // 使用应用程序上下文获取自定义的SSL套接字工厂
  27.             ClientSSLSocketFactory.getSocketFactory(applicationContext),
  28.             // 设置信任管理器,如果为null则抛出异常
  29.             trustManager ?: throw IllegalStateException("TrustManager is null")
  30.         )
  31.                 .hostnameVerifier { hostname, session -> true }
  32.         .build()
  33.     // 构建请求
  34.     val request = Request.Builder()
  35.         .url("https://192.168.124.21:8088/ca") // 设置请求的URL
  36.         .build()
  37.     try {
  38.         // 发送HTTP请求并获取响应
  39.         val response = client.newCall(request).execute()
  40.         // 打印响应的状态码,表明HTTPS双向认证成功
  41.         Log.d(TAG, "双向检测通过:${response.code()}")
  42.     } catch (e: IOException) {
  43.         // 如果请求失败,打印错误信息,并记录堆栈跟踪
  44.         Log.d(TAG, "双向检测不通过")
  45.         e.printStackTrace()
  46.     }
  47. }.start()

复制代码


`dump内置证书:`
  1. js
  2. function hook_KeyStore_load() {
  3.     Java.perform(function () {
  4.         var ByteString = Java.use("com.android.okhttp.okio.ByteString");
  5.         var myArray=new Array(1024);
  6.         var i = 0
  7.         for (i = 0; i < myArray.length; i++) {
  8.             myArray[i]= 0x0;
  9.          }
  10.         var buffer = Java.array('byte',myArray);
  11.         var StringClass = Java.use("java.lang.String");
  12.         var KeyStore = Java.use("java.security.KeyStore");
  13.         KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) {
  14.             console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
  15.             console.log("KeyStore.load1:", arg0);
  16.             this.load(arg0);
  17.         };
  18.         KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {
  19.             console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
  20.             console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);
  21.             if (arg0){
  22.                 var file =  Java.use("java.io.File").$new("/data/user/0/com.zj.wuaipojie/files/client"+".p12");
  23.                 var out = Java.use("java.io.FileOutputStream").$new(file);
  24.                 var r;
  25.                 while( (r = arg0.read(buffer)) > 0){
  26.                     out.write(buffer,0,r)
  27.                 }
  28.                 console.log("证书保存成功!")
  29.                 out.close()
  30.             }
  31.             this.load(arg0, arg1);
  32.         };
  33.     });
  34. }
复制代码

六、视频及课件地址


百度云
阿里云
哔哩哔哩
教程开源地址
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压
回复

使用道具 举报

11

主题

6715

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
3675
好评
18
信誉
259

MT论坛最佳新人MT论坛帅哥考神MT论坛新人MT论坛活跃会员

发表于 2024-9-27 19:34:45 来自手机  | 显示全部楼层  来自 福建
这个是沙发么
回复

使用道具 举报

5

主题

1万

回帖

3万

积分

博士后

萌新

Rank: 8Rank: 8

金币
3419
好评
51
信誉
101

考神MT论坛最佳新人MT论坛帅哥MT论坛新人MT论坛活跃会员

发表于 2024-9-27 19:36:07 来自手机  | 显示全部楼层  来自 广东
感谢分享
回复

使用道具 举报

1

主题

200

回帖

787

积分

初中生

Rank: 3Rank: 3

金币
459
好评
0
信誉
100

考神

发表于 2024-9-27 19:36:29 来自手机  | 显示全部楼层  来自 江西
学习学习
回复

使用道具 举报

172

主题

4652

回帖

1万

积分

博士生

水怪

Rank: 7Rank: 7Rank: 7

金币
4517
好评
23
信誉
99

MT论坛灌水老大MT论坛活跃会员

发表于 2024-9-27 19:39:17 来自手机  | 显示全部楼层  来自 福建
感谢分享
回复

使用道具 举报

688

主题

1万

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
3411
好评
164
信誉
119

考神MT论坛新人MT论坛活跃会员MT论坛帅哥MT论坛灌水老大MT论坛侠客MT论坛最佳新人

发表于 2024-9-27 19:40:19 来自手机  | 显示全部楼层  来自 广东
谢谢分享
回复

使用道具 举报

23

主题

4709

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
5490
好评
2
信誉
107

MT论坛新人MT论坛最佳新人MT论坛帅哥考神

发表于 2024-9-27 19:40:46 来自手机  | 显示全部楼层  来自 广西
感谢楼主分享的内容
回复

使用道具 举报

19

主题

3754

回帖

9555

积分

硕士生

Rank: 6Rank: 6

金币
1518
好评
21
信誉
143
发表于 2024-9-27 19:48:13 来自手机  | 显示全部楼层  来自 广西
感谢分享
回复

使用道具 举报

52

主题

4718

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
3649
好评
97
信誉
1072

考神MT论坛新人

发表于 2024-9-27 19:48:22 来自手机  | 显示全部楼层  来自 新西兰
谢谢分享
回复

使用道具 举报

85

主题

7249

回帖

2万

积分

博士后

Rank: 8Rank: 8

金币
777
好评
96
信誉
147

考神MT论坛活跃会员MT论坛新人MT论坛最佳新人MT论坛帅哥MT论坛侠客挂机大佬

发表于 2024-9-27 19:48:43 来自手机  | 显示全部楼层  来自 新疆–库尔勒
看看教程
回复

使用道具 举报

5

主题

2099

回帖

7246

积分

硕士生

Rank: 6Rank: 6

金币
2690
好评
16
信誉
100

考神MT论坛最佳新人MT论坛帅哥MT论坛新人

发表于 2024-9-27 19:50:37 来自手机  | 显示全部楼层  来自 河南
看看
回复

使用道具 举报

12

主题

2989

回帖

9145

积分

硕士生

Rank: 6Rank: 6

金币
179
好评
4
信誉
82
发表于 2024-9-27 19:50:48 来自手机  | 显示全部楼层  来自 广东
看看隐藏
回复

使用道具 举报

122

主题

3289

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
2979
好评
4
信誉
102
发表于 2024-9-27 19:54:42 来自手机  | 显示全部楼层  来自 贵州
感谢分享
回复

使用道具 举报

13

主题

4015

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
3074
好评
16
信誉
168

考神

发表于 2024-9-27 19:55:13 来自手机  | 显示全部楼层  来自 山东
感谢分享
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表