首页
社区
课程
招聘
6
[原创]《安卓逆向这档事》第二十一课、抓包学得好,牢饭吃得饱(中)
发表于: 2024-9-26 12:31 5688

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

2024-9-26 12:31
5688

1.了解代理以及VPN检测与对抗
2.了解SSL Pinning
3.了解双向认证

1.教程Demo(更新)
2.Charles
3.Reqable

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

强制不走代理

Charles安装与配置
下载地址:https://www.charlesproxy.com/download/
图片
图片
放一组key:

详细安装与配置

anti脚本:

透明代理
透明代理(Transparent Proxy)是一种特殊的代理服务类型,它可以在客户端(如浏览器或应用程序)不知道的情况下拦截、转发和处理网络请求。与传统的代理服务不同,透明代理不需要客户端进行任何配置就能工作。
[Clash版]安卓上基于透明代理实现热点抓包
安卓上基于透明代理对特定APP抓包

定义
VPN检测是指应用程序或系统检查用户是否正在使用虚拟专用网络(Virtual Private Network, VPN)的一种技术。当用户使用VPN时,他们的网络流量会被加密并通过一个远程服务器路由,这可以隐藏用户的实际IP地址和位置信息,同时保护数据的安全性和隐私。
原理
当客户端运行VPN虚拟隧道协议时,会在当前节点创建基于eth之上的tun0接口或ppp0接口。这些接口是用于建立虚拟网络连接的特殊网络接口。
根据OSI七层模型,二者分别支持的协议:

anti

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

SSL Pinning主流的三套方案:公钥校验证书校验Host校验
因为是客户端做的校验,所以可以在本地进行hook对抗,参考以下的两个项目:
JustTrustMesslunpining

在网站中我们可以看到网站的证书相关信息,其中就包含了指纹信息
图片
常见安卓网络开发框架

原理(以okhttp框架为例):
在CertificatePinner类里有一个check方法

实现方案:

安装openssl,OpenSSL Windows 版本
图片
配置环境变量
图片
cmd窗口输入以下命令获取

图片
anti脚本

原理:
通过trustManager 类实现的checkServerTrusted接口,核心在于验证服务器证书的公钥。具体步骤包括:获取服务器返回的证书,将其公钥编码为 Base64 字符串;同时从本地资源加载预存的可信客户端证书,并将其公钥也编码为 Base64 字符串。然后,比较这两个公钥是否匹配,以此确认服务器的身份是否合法。最后,使用自定义的 SSLSocketFactory 发起 HTTPS 请求,确保通信过程中只信任预定义的服务器证书,从而有效抵御中间人攻击。
实现方案:

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

图片
证书信知识补充:

anti脚本
思路:实例化一个trustManager类,然后里面什么都不写,当上面两处调用到这个类时hook这两个地方,把自己定义的空trustManager类放进去

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

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

2.生成服务端证书

这个指令生成一个2048位的RSA私钥,并将其保存到名为server.key的文件中

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

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

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

客户端证书:

生成客户端带密码的p12证书(这步很重要,双向认证的话,浏览器访问时候要导入该证书才行;可能某些Android系统版本请求的时候需要把它转成bks来请求双向认证):

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

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

服务端代码:

客户端代码:

dump内置证书:

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

炒冷饭汇总抓包姿势-上
[原创]android抓包学习的整理和归纳
浅析APP代理检测对抗
[原创]Android APP漏洞之战(6)——HTTP/HTTPs通信漏洞详解
Android HTTPS防抓包策略与对抗方法总结
Frida.Android.Practice (ssl unpinning)
Android HTTPS认证的N种方式和对抗方法总结

return System.getProperty("http.proxyHost") == null && System.getProperty("http.proxyPort")  == null
 
Port跟设置有关,例如Charles默认是8888
return System.getProperty("http.proxyHost") == null && System.getProperty("http.proxyPort")  == null
 
Port跟设置有关,例如Charles默认是8888
connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
 
OkHttpClient.Builder() 
    .proxy(Proxy.NO_PROXY) 
    .build()
connection = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY);
 
OkHttpClient.Builder() 
    .proxy(Proxy.NO_PROXY) 
    .build()
Registered Name:    52pojie
License Key:    d43c11e6697bbe07a8
Registered Name:    52pojie
License Key:    d43c11e6697bbe07a8
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);
    }
}
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);
    }
}
VPN OpvenVPN、IPsec、IKEv2、PPTP、L2TP、WireGuard等
代理 HTTP、HTTPS、SOCKS、FTP、RTSP等
VPN 协议大多是作用在 OSI 的第二层和第三层之间,由此可见VPN能抓到代理方式的所有的包
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);
            }
        }
 
 
    })
}
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);
            }
        }
 
 
    })
}
1.客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
2.服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
3.客户端使用服务端返回的信息验证服务器的合法性,包括:
    (1)证书是否过期
    (2)发型服务器证书的CA是否可靠
    (3)返回的公钥是否能正确解开返回证书中的数字签名
    (4)服务器证书上的域名是否和服务器的实际域名相匹配、验证通过后,将继续进行通信,否则,终止通信
4.客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
5.服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。
6.服务器将选择好的加密方案通过明文方式返回给客户端
7.客户端接收服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器
8.服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥。在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全
1.客户端向服务端发送SSL协议版本号、加密算法种类、随机数等信息。
2.服务端给客户端返回SSL协议版本号、加密算法种类、随机数等信息,同时也返回服务器端的证书,即公钥证书
3.客户端使用服务端返回的信息验证服务器的合法性,包括:
    (1)证书是否过期
    (2)发型服务器证书的CA是否可靠
    (3)返回的公钥是否能正确解开返回证书中的数字签名
    (4)服务器证书上的域名是否和服务器的实际域名相匹配、验证通过后,将继续进行通信,否则,终止通信
4.客户端向服务端发送自己所能支持的对称加密方案,供服务器端进行选择
5.服务器端在客户端提供的加密方案中选择加密程度最高的加密方式。
6.服务器将选择好的加密方案通过明文方式返回给客户端
7.客户端接收服务端返回的加密方式后,使用该加密方式生成产生随机码,用作通信过程中对称加密的密钥,使用服务端返回的公钥进行加密,将加密后的随机码发送至服务器
8.服务器收到客户端返回的加密信息后,使用自己的私钥进行解密,获取对称加密密钥。在接下来的会话中,服务器和客户端将会使用该密码进行对称加密,保证通信过程中信息的安全
框架名称 描述 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应用开发中
/**
 * 检查指定主机名的证书链是否符合预设的哈希值(证书固定)。
 * @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.");
}
/**
 * 检查指定主机名的证书链是否符合预设的哈希值(证书固定)。
 * @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.");
}
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
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
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;
}}
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;
}}
openssl s_client -connect 52pojie.cn:443 -servername 52pojie.cn | openssl x509 -out wuai.pem
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等其他格式
图片
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: [X509TrustManager],
        // 定义自定义TrustManager的方法实现
        methods: {
            // 客户端证书信任检查,这里不实现任何逻辑
            checkClientTrusted: function(chain, authType) {},
            // 服务器证书信任检查,这里不实现任何逻辑
            checkServerTrusted: function(chain, authType) {},
            // 返回受信任的CA证书数组,这里返回空数组
            getAcceptedIssuers: function() {return []; }
        }
    });
    // 准备一个TrustManager数组,用于传递给SSLContext.init()方法
    var TrustManagers = [TrustManager.$new()];
    // 获取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); // 可以取消注释来打印异常的详细信息
    }
}
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',

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
赞赏记录
参与人
雪币
留言
时间
mb_dfrfhvqw
期待更多优质内容的分享,论坛有你更精彩!
2025-3-3 08:23
MMD_DYOR
这个讨论对我很有帮助,谢谢!
2025-2-28 15:37
mb_zfqvurgb
非常支持你的观点!
2024-10-24 14:15
mb_eodvrytd
谢谢你的细致分析,受益匪浅!
2024-10-9 23:26
Arahat0
为你点赞!
2024-9-30 05:56
PLEBFE
+1
正己老师yyds!
2024-9-26 16:22
最新回复 (3)
雪    币: 15610
活跃值: (6893)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
证书效验目前还没有哪个代理能抓包?一般都需要hook或修改去掉程序中证书效验代码才能抓包。
2024-9-26 15:29
0
雪    币: 4109
活跃值: (2991)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
tDasm 证书效验目前还没有哪个代理能抓包?一般都需要hook或修改去掉程序中证书效验代码才能抓包。
是啊,视频里就是hook啊
2024-9-26 19:02
0
雪    币: 49
活跃值: (90)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
求雀神麻将脚本,
2025-2-28 04:28
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册