正己 发表于 2024-9-26 12:26

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

本帖最后由 正己 于 2024-9-28 09:01 编辑

https://www.bilibili.com/video/BV18asZeeEpY
![|600](https://pic.rmb.bdstatic.com/bjh/240717/c64c33e20b330e6fa3c40a816a2381b77908.png)
# 一、课程目标
1.了解代理以及VPN检测与对抗
2.了解SSL Pinning
3.了解双向认证

# 二、工具

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

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

Port跟设置有关,例如Charles默认是8888
```

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

OkHttpClient.Builder()
    .proxy(Proxy.NO_PROXY)
    .build()
```
`Charles安装与配置`
下载地址:https://www.charlesproxy.com/download/
![图片](https://pic.rmb.bdstatic.com/bjh/240925/690785bc58639169df752ca001d82e038823.png)
![图片](https://pic.rmb.bdstatic.com/bjh/240925/b4cacbf211c796ba7ccd9ca8fdfae8905990.png)
放一组key:
```
Registered Name:      52pojie
License Key:      d43c11e6697bbe07a8
```
[详细安装与配置](https://blog.csdn.net/qq_45005145/article/details/141132846)

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

| VPN | OpvenVPN、IPsec、IKEv2、PPTP、L2TP、WireGuard等 |
| --- | ----------------------------------------- |
| 代理| HTTP、HTTPS、SOCKS、FTP、RTSP等                |
VPN 协议大多是作用在 OSI 的第二层和第三层之间,由此可见VPN能抓到代理方式的所有的包

```kotlin
public final boolean Check_Vpn1() {
      try {
            Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
            if (networkInterfaces == null) {
                return false;
            }
            Iterator it = Collections.list(networkInterfaces).iterator();
            while (it.hasNext()) {
                NetworkInterface networkInterface = (NetworkInterface) it.next();
                if (networkInterface.isUp() && !networkInterface.getInterfaceAddresses().isEmpty()) {
                  Log.d("zj595", "isVpn NetworkInterface Name: " + networkInterface.getName());
                  if (Intrinsics.areEqual(networkInterface.getName(), "tun0") || Intrinsics.areEqual(networkInterface.getName(), "ppp0") || Intrinsics.areEqual(networkInterface.getName(), "p2p0") || Intrinsics.areEqual(networkInterface.getName(), "ccmni0")) {
                        return true;
                  }
                }
            }
            return false;
      } catch (Throwable th) {
            th.printStackTrace();
            return false;
      }
    }

public final boolean Check_Vpn2() {
      boolean z;
      String networkCapabilities;
      try {
            Object systemService = getApplicationContext().getSystemService("connectivity");
            Intrinsics.checkNotNull(systemService, "null cannot be cast to non-null type android.net.ConnectivityManager");
            ConnectivityManager connectivityManager = (ConnectivityManager) systemService;
            NetworkCapabilities networkCapabilities2 = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
            Log.i("zj595", "networkCapabilities -> " + networkCapabilities2);
            boolean z2 = networkCapabilities2 != null && networkCapabilities2.hasTransport(4);
            // 检查网络能力是否包含 "WIFI|VPN"
            if (networkCapabilities2 != null && (networkCapabilities = networkCapabilities2.toString()) != null) {
                if (StringsKt.contains$default((CharSequence) networkCapabilities, (CharSequence) "WIFI|VPN", false, 2, (Object) null)) {
                  z = true;
                  return !z || z2;
                }
            }
            z = false;
            if (z) {
            }
      } catch (Exception e) {
            e.printStackTrace();
            return false;
      }
    }




```

`anti`
```js
function hook_vpn() {
    Java.perform(function () {
      var NetworkInterface = Java.use("java.net.NetworkInterface");
      NetworkInterface.getName.implementation = function () {
            var name = this.getName();//hook java层的getName方法
            console.log("name: " + name);
            if (name === "tun0" || name === "ppp0") {
                return "rmnet_data0";
            } else {
                return name;
            }
      }

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

      NetworkCapabilities.appendStringRepresentationOfBitMaskToStringBuilder.implementation = function (sb, bitMask, nameFetcher, separator) {
            if (bitMask == 18) {
                console.log("bitMask", bitMask);
                sb.append("WIFI");
            } else {
                console.log(sb, bitMask);
                this.appendStringRepresentationOfBitMaskToStringBuilder(sb, bitMask, nameFetcher, separator);
            }
      }


    })
}
```

## 3.SSL Pinning
`SSL Pinning` 也称为证书锁定,是Google官方推荐的检验方式,意思是将服务器提供的SSL/TLS证书内置到移动客户端,当客户端发起请求的时候,通过对比内置的证书与服务器的证书是否一致,来确认这个连接的合法性。
PS:这里还要提到一个概念:`单向校验`,本质上二者没区别,`SSL Pinning`可以理解为加强版的`单向校验`
![图片](https://pic.rmb.bdstatic.com/bjh/240818/3e7d68353ecaa9c62720955d1d230ea61665.png)
```
1.客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
2.服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
3.客户端使用服务端返回的信息验证服务器的合法性,包括:
    (1)证书是否过期
    (2)发型服务器证书的CA是否可靠
    (3)返回的公钥是否能正确解开返回证书中的数字签名
    (4)服务器证书上的域名是否和服务器的实际域名相匹配、验证通过后,将继续进行通信,否则,终止通信
4.客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
5.服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。
6.服务器将选择好的加密方案通过明文方式返回给客户端
7.客户端接收服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器
8.服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥。在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全
```
`SSL Pinning`主流的三套方案:`公钥校验`、`证书校验`、`Host校验`
因为是客户端做的校验,所以可以在本地进行hook对抗,参考以下的两个项目:
(https://github.com/Fuzion24/JustTrustMe)、(https://github.com/ac-pm/SSLUnpinning_Xposed)
### 1.指纹校验
在网站中我们可以看到网站的证书相关信息,其中就包含了指纹信息
![图片](https://pic.rmb.bdstatic.com/bjh/240822/fa47478f32c76bf2adee8ad73e206b295695.png)
`常见安卓网络开发框架`

| 框架名称               | 描述                                                                        | GitHub 地址                                                                                                            |
| ------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Volley             | 由Google开源的轻量级网络库,支持网络请求处理、小图片的异步加载和缓存等功能                                  | (https://github.com/google/volley)                                                 |
| Android-async-http | 基于Apache HttpClient的一个异步网络请求处理库                                           | (https://github.com/android-async-http/android-async-http) |
| xUtils             | 类似于Afinal,但被认为是Afinal的一个升级版,提供了HTTP请求的支持                                  | (https://github.com/wyouflf/xUtils3)                                             |
| OkHttp             | 一个高性能的网络框架,已经被Google官方认可,在Android 6.0中底层源码已经使用了OkHttp来替代HttpURLConnection | (https://github.com/square/okhttp)                                                 |
| Retrofit         | 提供了一种类型安全的HTTP客户端接口,简化了HTTP请求的编写,通常与OkHttp配合使用                            | (https://github.com/square/retrofit)                                             |
OkHttp和Retrofit是非常流行的组合,被广泛应用于现代Android应用开发中

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

    // 如果存在证书链清理器,则先清理证书链中的冗余证书。
    if (certificateChainCleaner != null) {
      peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
    }
    // 遍历每一个证书进行检查。
    for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
      // 获取当前证书。
      X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
      // 懒加载计算每个证书的SHA-1和SHA-256哈希值。
      ByteString sha1 = null;
      ByteString sha256 = null;
      // 遍历预设的哈希值列表。
      for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
      Pin pin = pins.get(p);
      // 根据预设的哈希算法进行检查。
      if (pin.hashAlgorithm.equals("sha256/")) {
          // 如果尚未计算SHA-256哈希值,则进行计算。
          if (sha256 == null) sha256 = sha256(x509Certificate);
          // 如果证书的SHA-256哈希值与预设值相同,则返回成功。
          if (pin.hash.equals(sha256)) return;
      } else if (pin.hashAlgorithm.equals("sha1/")) {
          // 如果尚未计算SHA-1哈希值,则进行计算。
          if (sha1 == null) sha1 = sha1(x509Certificate);
          // 如果证书的SHA-1哈希值与预设值相同,则返回成功。
          if (pin.hash.equals(sha1)) return;
      } else {
          // 如果遇到不支持的哈希算法,则抛出错误。
          throw new AssertionError("unsupported hashAlgorithm: " + pin.hashAlgorithm);
      }
      }
    }
    // 如果遍历完所有证书和哈希值都没有匹配,则抛出异常。
    throw new SSLPeerUnverifiedException("No matching certificate found.");
}
```
`实现方案:`
```kotlin
//指纹检测
fun check_SSL_PINNING_key() {
    // 使用CoroutineScope和Dispatchers.IO在后台线程中执行网络操作
    CoroutineScope(Dispatchers.IO).launch {
      // 定义需要固定证书的域名
      val caDomain = "www.52pojie.cn"
      // 使用CertificatePinner.Builder构建一个证书固定器
      // 添加一个sha256哈希值,这个哈希值是服务器证书的指纹
      // 这个值需要通过openssl工具获取
      val pinner = CertificatePinner.Builder()
            .add(caDomain, "sha256/WnsD5UGdP5/a65xO1rpH8ru2EjyxkmPEaiNtKixhJLU=") // 添加证书指纹
            .build() // 构建完成证书固定器
      // 使用newBuilder创建一个新的OkHttpClient实例,用于覆盖默认的客户端配置
      val pClient = client.newBuilder()
            .certificatePinner(pinner) // 设置证书固定器
            .build() // 构建新的OkHttpClient实例
      // 构建一个网络请求,访问指定的URL
      val request = Request.Builder()
            .url("https://www.52pojie.cn/?q=SSLPinningCode") // 设置请求的URL
            .build() // 构建请求对象
      try {
            // 执行网络请求,并获取响应
            val response = pClient.newCall(request).execute()
            // 如果响应状态码是200,表示指纹检测通过
            Log.d(TAG, "指纹检测通过,状态码:${response.code()}")
      } catch (e: IOException) {
            // 如果发生IOException,表示指纹检测不通过
            Log.d(TAG, "指纹检测不通过")
            e.printStackTrace() // 打印异常堆栈信息,用于调试
      }
    }
}

```
安装openssl,(https://slproweb.com/products/Win32OpenSSL.html?spm=5176.28103460.0.0.3a32572c9fMUi9)
![图片](https://pic.rmb.bdstatic.com/bjh/240824/860cb5670ca1da633d4f7cd69a9b024a8599.png)
配置环境变量
![图片](https://pic.rmb.bdstatic.com/bjh/240824/b66485d2d34c392a8f599b496a59f0fc3877.png)
cmd窗口输入以下命令获取
```
openssl s_client -connect www.52pojie.cn:443 -servername www.52pojie.cn | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```
![图片](https://pic.rmb.bdstatic.com/bjh/240824/693f228eb6a69046a950dd8b35992c6f3820.png)
`anti脚本`
```js
function anti_ssl_key() {
      //check方法置空即可
    var okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner');   
    okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function(a, b) {                           
    console.log('[+] Bypassing SSL key pinning: ' + a);
    return;
}}
```
### 2.证书校验
`原理:`
通过`trustManager` 类实现的checkServerTrusted接口,核心在于验证服务器证书的公钥。具体步骤包括:获取服务器返回的证书,将其公钥编码为 Base64 字符串;同时从本地资源加载预存的可信客户端证书,并将其公钥也编码为 Base64 字符串。然后,比较这两个公钥是否匹配,以此确认服务器的身份是否合法。最后,使用自定义的 `SSLSocketFactory` 发起 HTTPS 请求,确保通信过程中只信任预定义的服务器证书,从而有效抵御中间人攻击。
`实现方案:`
```kotlin
// 定义一个函数用于检查SSL证书
private fun check_SSL_PINNING_CA() {
    // 创建一个X509TrustManager的匿名对象,用于自定义信任管理器
    val trustManager: X509TrustManager = object : X509TrustManager {
      // 客户端证书信任检查,这里不实现任何逻辑
      @Throws(CertificateException::class)
      override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) {
      }
      // 服务器证书信任检查
      @Throws(CertificateException::class)
      override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String?) {
            // 获取服务器返回的第一个证书
            val cf: X509Certificate = chain
            // 将服务器证书的公钥编码为Base64字符串
            val ServerPubkey: String = Base64.encodeToString(cf.publicKey.encoded, 0)
            Log.e(TAG, "服务器端返回的证书:" + ServerPubkey)
            // 从客户端资源中读取证书
            val client_input = resources.openRawResource(R.raw.wuai)
            val certificateFactory = CertificateFactory.getInstance("X.509")
            // 生成客户端证书
            val realCertificate: X509Certificate = certificateFactory.generateCertificate(client_input) as X509Certificate
            // 将客户端证书的公钥编码为Base64字符串
            val realPubkey: String = Base64.encodeToString(realCertificate.publicKey.encoded, 0)
            Log.e(TAG, "客户端资源目录中的证书:" + realPubkey)
            // 检查证书有效期
            cf.checkValidity()
            // 比较服务器证书和客户端证书的公钥是否相同
            val expected = realPubkey.equals(ServerPubkey, ignoreCase = true)
            if (!expected) {
                Log.e(TAG, "证书检测不通过")
            } else {
                Log.e(TAG, "证书检测通过")
            }
      }
      // 返回受信任的CA证书数组,这里返回空数组
      override fun getAcceptedIssuers(): Array<X509Certificate?> {
            return arrayOfNulls<X509Certificate>(0)
      }
    }
    // 声明SSLSocketFactory变量
    var factory: SSLSocketFactory? = null
    try {
      // 获取SSL上下文
      val sslContext = SSLContext.getInstance("SSL")
      // 初始化SSL上下文,使用自定义的信任管理器
      sslContext.init(null, arrayOf<TrustManager>(trustManager), SecureRandom())
      // 获取SSLSocketFactory
      factory = sslContext.socketFactory
    } catch (e: java.lang.Exception) {
      e.printStackTrace()
    }
    // 确保factory不为空
    val finalFactory: SSLSocketFactory? = factory
    // 创建并启动一个新线程来执行网络请求
    object : Thread() {
      override fun run() {
            try {
                // 使用自定义的SSLSocketFactory创建OkHttpClient
                val client =
                  OkHttpClient.Builder().sslSocketFactory(finalFactory, trustManager).build()
                // 构建请求
                val req = Request.Builder().url("https://www.52pojie.cn/forum.php").build()
                // 发送请求并获取响应
                val call: okhttp3.Call = client.newCall(req)
                val res: Response = call.execute()
                // 打印响应状态码
                Log.e("请求发送成功", "状态码:" + res.code())
            } catch (e: IOException) {
                // 打印网络异常信息
                Log.e("请求发送失败", "网络异常$e")
            }
      }
    }.start()
}

```
cmd窗口输入以下命令获取证书信息
```
openssl s_client -connect 52pojie.cn:443 -servername 52pojie.cn | openssl x509 -out wuai.pem
```
![图片](https://pic.rmb.bdstatic.com/bjh/240824/616433f141a1a49e76ff6be600dfcde46951.png)
`证书信知识补充:`

| 名词            | 含义                                                                      |   |
| ------------- | ----------------------------------------------------------------------- | --- |
| 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等其他格式
![图片](https://pic.rmb.bdstatic.com/bjh/240824/08bd74f1969f4ad3e07239687aace37d1767.png)


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

```

## 4.双向认证
双向验证,顾名思义就是客户端验证服务器端证书的正确性,服务器端也验证客户端的证书正确性
![图片](https://pic.rmb.bdstatic.com/bjh/240825/3f61fa813672068bbdd42947d4d6d6907655.png)
```
1.客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
2.服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
3.客户端使用服务端返回的信息验证服务器的合法性,包括:
    (1)证书是否过期
    (2)发型服务器证书的CA是否可靠
    (3)返回的公钥是否能正确解开返回证书中的数字签名
    (4)服务器证书上的域名是否和服务器的实际域名相匹配、验证通过后,将继续进行通信,否则,终止通信
4.服务端要求客户端发送客户端的证书,客户端会将自己的证书发送至服务端
5.验证客户端的证书,通过验证后,会获得客户端的公钥
6.客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
7.服务器端在客户端提供的加密方案中选择加密程度最高的加密方式
8.将加密方案通过使用之前获取到的公钥进行加密,返回给客户端
9.客户端收到服务端返回的加密方案密文后,使用自己的私钥进行解密,获取具体加密方式,而后,产生该加密方式的随机码,用作加密过程中的密钥,使用之前从服务端证书中获取到的公钥进行加密后,发送给服务端
10.服务端收到客户端发送的消息后,使用自己的私钥进行解密,获取对称加密的密钥,在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全。
```
`实现方案:`
1.首先借助openssl生成**服务端证书**
```
# 生成CA私钥
openssl genrsa -out ca.key 2048

# 生成CA自签名证书
openssl req -x509 -new -nodes -key ca.key -sha256 -days 1024 -out ca.crt
```
2.生成**服务端证书**
```shell
openssl genrsa -out server.key 2048
```
这个指令生成一个2048位的RSA私钥,并将其保存到名为`server.key`的文件中
```shell
openssl req -new -key server.key -out server.csr -config server_cert.conf
```
这个指令基于第一步生成的私钥创建一个新的证书签名请求(CSR)。CSR包含了公钥和一些身份信息,这些信息在证书颁发过程中用于识别证书持有者。`-out server.csr`指定了CSR的输出文件名。
执行这个指令时,系统会提示你输入一些身份信息,如国家代码、组织名等,这些信息将被包含在CSR中。(我们这边测试直接全部按回车键默认即可)

| 字段名称                         | 描述                                                         | 默认值/示例值    | 是否必填 |
|----------------------------------|----------------------------------|-------------------|----------|
| Country Name (2 letter code)   | 国家代码,两位字母代码。                                 | AU               | 否       |
| State or Province Name          | 州或省份的全名。                                           |                  | 否       |
| Locality Name (eg, city)         | 城市或地区名称。                                           |                  | 否       |
| Organization Name                | 组织名称,通常是公司或机构的名称。                         |                  | 否       |
| Organizational Unit Name (eg, section) | 组织单位名称,可以是部门或团队的名称。               |                  | 否       |
| Common Name (CN)                | 完全限定的域名(FQDN)或个人名称,用于标识证书持有者。 |                  | 是       |
| Email Address                   | 与证书持有者关联的电子邮件地址。                           |                  | 否       |
| Challenge Password            | 挑战密码,用于CSR的额外安全措施。                         |                  | 否       |
| Optional Company Name         | 可选的公司名称字段。                                       |                  | 否       |

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

distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no


CN = 192.168.199.108


subjectAltName = @alt_names


IP.1 = 192.168.199.108

```

```shell
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
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.cer
```
使用CA证书签发服务器证书。
生成cer证书供服务端验证。

**客户端证书:**
```shell
openssl genrsa -out client.key 2048
openssl req -new -out client.csr -key client.key
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 500 -sha256
```
生成客户端带密码的p12证书(这步很重要,双向认证的话,浏览器访问时候要导入该证书才行;可能某些Android系统版本请求的时候需要把它转成bks来请求双向认证):
```shell
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt
```
到这一步的时候,设置密码和验证密码光标不会显示,直接输入即可
![图片](https://pic.rmb.bdstatic.com/bjh/240825/9008c362571f7ceff9b68a57858707423332.png)
#### 环境配置
PS:因为双向认证是本地搭建,所以需要完成几个前置条件:
1.确保电脑和手机处于同一wifi连接下
2.重打包替换生成的server.cer(路径在res/raw),替换ssl_verify方法里的ip地址以及res/xml/network_config.xml的ip地址(通过ipconfig获取实际的ipv4地址)
3.运行服务端代码,然后再请求看看是否能正常输出

**服务端代码:**
```python
from flask import Flask, jsonify
import ssl

app = Flask(__name__)

# ssl 证书校验
@app.route('/ca')
def ssl_verify():
    return jsonify({"message": "HTTPS server with mutual SSL verification started."})

# 配置ssl上下文,关键函数
def get_ssl_context():
    # CA根证书路径
    ca_crt_path = 'certs/ca.crt'
    # 服务端证书和密钥路径
    server_crt_path = 'certs/server.crt'
    server_key_path = 'certs/server.key'
    # 创建SSL上下文,使用TLS服务器模式
    ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    # 设置验证模式为需要客户端证书
    ssl_context.verify_mode = ssl.CERT_REQUIRED
    # 启用主机名检查(根据需要设置)
    ssl_context.check_hostname = False
    # 设置加密套件
    ssl_context.set_ciphers("HIGH:!SSLv3:!TLSv1:!aNULL:@STRENGTH")
    # 加载CA根证书,用于验证客户端证书
    ssl_context.load_verify_locations(cafile=ca_crt_path)
    # 加载服务端证书和私钥
    ssl_context.load_cert_chain(certfile=server_crt_path, keyfile=server_key_path)
    return ssl_context

if __name__ == '__main__':
    ssl_context = get_ssl_context()
    app.run(host="192.168.124.21", port=8088,ssl_context=ssl_context)

```
**客户端代码:**
```kotlin
fun ssl_verify() = Thread {
// 初始化一个用于信任管理的对象
    var trustManager: X509TrustManager? = null
    try {
      // 获取默认的信任管理工厂实例
      val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
      // 初始化信任管理工厂,传入null表示使用系统默认的信任存储
      trustManagerFactory.init(null as KeyStore?)
      // 获取信任管理器列表
      val trustManagers = trustManagerFactory.trustManagers
      // 检查信任管理器列表是否只有一个X509TrustManager类型的对象
      if (trustManagers.size != 1 || trustManagers !is X509TrustManager) {
            // 如果不符合预期,则抛出异常
            throw IllegalStateException("Unexpected default trust managers: ${trustManagers.contentToString()}")
      }
      // 赋值信任管理器
      trustManager = trustManagers as X509TrustManager
    } catch (e: Exception) {
      // 捕获异常并打印堆栈跟踪信息
      e.printStackTrace()
    }
    // 创建 OkHttpClient 实例并配置SSL套接字工厂和主机名验证器
    val client = OkHttpClient.Builder()
      .sslSocketFactory(
            // 使用应用程序上下文获取自定义的SSL套接字工厂
            ClientSSLSocketFactory.getSocketFactory(applicationContext),
            // 设置信任管理器,如果为null则抛出异常
            trustManager ?: throw IllegalStateException("TrustManager is null")
      )
                .hostnameVerifier { hostname, session -> true }
      .build()
    // 构建请求
    val request = Request.Builder()
      .url("https://192.168.124.21:8088/ca") // 设置请求的URL
      .build()
    try {
      // 发送HTTP请求并获取响应
      val response = client.newCall(request).execute()
      // 打印响应的状态码,表明HTTPS双向认证成功
      Log.d(TAG, "双向检测通过:${response.code()}")
    } catch (e: IOException) {
      // 如果请求失败,打印错误信息,并记录堆栈跟踪
      Log.d(TAG, "双向检测不通过")
      e.printStackTrace()
    }
}.start()

```

`dump内置证书:`
```js
function hook_KeyStore_load() {
    Java.perform(function () {
      var ByteString = Java.use("com.android.okhttp.okio.ByteString");
      var myArray=new Array(1024);
      var i = 0
      for (i = 0; i < myArray.length; i++) {
            myArray= 0x0;
         }
      var buffer = Java.array('byte',myArray);
      var StringClass = Java.use("java.lang.String");
      var KeyStore = Java.use("java.security.KeyStore");
      KeyStore.load.overload('java.security.KeyStore$LoadStoreParameter').implementation = function (arg0) {
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            console.log("KeyStore.load1:", arg0);
            this.load(arg0);
      };
      KeyStore.load.overload('java.io.InputStream', '[C').implementation = function (arg0, arg1) {
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            console.log("KeyStore.load2:", arg0, arg1 ? StringClass.$new(arg1) : null);
            if (arg0){
                var file =Java.use("java.io.File").$new("/data/user/0/com.zj.wuaipojie/files/client"+".p12");
                var out = Java.use("java.io.FileOutputStream").$new(file);
                var r;
                while( (r = arg0.read(buffer)) > 0){
                  out.write(buffer,0,r)
                }
                console.log("证书保存成功!")
                out.close()
            }
            this.load(arg0, arg1);
      };
    });
}
```
# 四、请作者喝杯咖啡


# 六、视频及课件地址


[百度云](https://pan.baidu.com/s/1cFWTLn14jeWfpXxlx3syYw?pwd=nqu9)
[阿里云](https://www.aliyundrive.com/s/TJoKMK6du6x)
[哔哩哔哩](https://www.bilibili.com/video/BV1wT411N7sV/?spm_id_from=333.788&vd_source=6dde16dc6479f00694baaf73a2225452)
[教程开源地址](https://github.com/ZJ595/AndroidReverse)
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压

# 七、其他章节
[《安卓逆向这档事》一、模拟器环境搭建](https://www.52pojie.cn/thread-1695141-1-1.html)
[《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修改](https://www.52pojie.cn/thread-1695796-1-1.html)
[《安卓逆向这档事》三、初识smail,vip终结者](https://www.52pojie.cn/thread-1701353-1-1.html)
[《安卓逆向这档事》四、恭喜你获得广告&弹窗静默卡](https://www.52pojie.cn/thread-1706691-1-1.html)
[《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩](https://www.52pojie.cn/thread-1714727-1-1.html)
[《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代{过}{滤}理、IO重定向](https://www.52pojie.cn/thread-1731181-1-1.html)
[《安卓逆向这档事》七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写,常用Api](https://www.52pojie.cn/thread-1740944-1-1.html)
[《安卓逆向这档事》八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook](https://www.52pojie.cn/thread-1748081-1-1.html)
[《安卓逆向这档事》九、密码学基础、算法自吐、非标准加密对抗](https://www.52pojie.cn/thread-1762225-1-1.html)
[《安卓逆向这档事》十、不是我说,有了IDA还要什么女朋友?](https://www.52pojie.cn/thread-1787667-1-1.html)
[《安卓逆向这档事》十二、大佬帮我分析一下](https://www.52pojie.cn/thread-1809646-1-1.html)
[《安卓逆向这档事》番外实战篇1-某电影视全家桶](https://www.52pojie.cn/thread-1814917-1-1.html)
[《安卓逆向这档事》十三、是时候学习一下Frida一把梭了(上)](https://www.52pojie.cn/thread-1823118-1-1.html)
[《安卓逆向这档事》十四、是时候学习一下Frida一把梭了(中)](https://www.52pojie.cn/thread-1838539-1-1.html)
[《安卓逆向这档事》十五、是时候学习一下Frida一把梭了(下)](https://www.52pojie.cn/thread-1840174-1-1.html)
[《安卓逆向这档事》十六、是时候学习一下Frida一把梭了(终)](https://www.52pojie.cn/thread-1859820-1-1.html#/)
[《安卓逆向这档事》十七、你的RPCvs佬的RPC](https://www.52pojie.cn/thread-1892127-1-1.html#/)
[《安卓逆向这档事》番外实战篇2-【2024春节】解题领红包活动,启动!](https://www.52pojie.cn/thread-1893708-1-1.html#/)
[《安卓逆向这档事》十八、表哥,你也不想你的Frida被检测吧!(上)](https://www.52pojie.cn/thread-1921073-1-1.html)
[《安卓逆向这档事》十九、表哥,你也不想你的Frida被检测吧!(下)](https://www.52pojie.cn/thread-1938862-1-1.html)
[《安卓逆向这档事》二十、抓包学得好,牢饭吃得饱(上)](https://www.52pojie.cn/thread-1945285-1-1.html)
[《安卓逆向这档事》番外实战篇3-拨云见日之浅谈Flutter逆向](https://www.52pojie.cn/thread-1951619-1-1.html)

# 八、参考文档
[炒冷饭汇总抓包姿势-上](https://bbs.kanxue.com/thread-278142.htm)
[[原创]android抓包学习的整理和归纳](https://bbs.kanxue.com/thread-267940.htm)
[浅析APP代理检测对抗](https://xz.aliyun.com/t/11398?time__1311=Cq0xR7D%3DiQDQitD%2F82c0DIgjbDkzN7Ka4D)
[[原创]Android APP漏洞之战(6)——HTTP/HTTPs通信漏洞详解](https://bbs.kanxue.com/thread-270634.htm)
(https://curz0n.github.io/2020/08/15/android-ssl-and-intercept/#6-%E4%BB%A3%E7%90%86%E6%A3%80%E6%B5%8B)
(https://github.com/WooyunDota/DroidDrops/blob/master/2018/Frida.Android.Practice.md#fridaandroidpractice-ssl-unpinning)
(https://ch3nye.top/Android-HTTPS%E8%AE%A4%E8%AF%81%E7%9A%84N%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%92%8C%E5%AF%B9%E6%8A%97%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93/#android-https%E8%AE%A4%E8%AF%81%E7%9A%84n%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%92%8C%E5%AF%B9%E6%8A%97%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93)

zeusho 发表于 2024-9-26 12:41

表哥带带我 手撕瑞数 脚踢akamai

风子09 发表于 2024-10-15 17:29

有抓包能抓到,找不到app对应位置,开始学习到现在,跟着一个app,学了很多,发现自己不会的更多,已经放弃。
app作者不断改进,之前是可以抓到包,现在包都抓不到了!

正己 发表于 2024-9-26 12:37

视频晚点上传

bigqyng 发表于 2024-9-26 12:48

收下膝盖666

沉默的小嘉 发表于 2024-9-26 12:49

想学一下小程序 / app 的 sign 分析

uuwatch 发表于 2024-9-26 13:06

四是认真的吗?,五也是被吃掉了{:301_1010:}

st0rm 发表于 2024-9-26 13:44

相当详细,确实有用

iwxiao 发表于 2024-9-26 13:45

66666请加大尺度

快乐的小跳蛙 发表于 2024-9-26 14:37

前排膜拜大佬

涛少 发表于 2024-9-26 16:15

膜拜大佬,学习一波,虽然看不懂
页: [1] 2 3 4 5 6 7
查看完整版本: 《安卓逆向这档事》第二十一课、抓包学得好,牢饭吃得饱(中)