App抓包工具教程汇总

xiaoeryu Lv5

HTTPS抓包详解

  • HTTP + SSL + 认证 + 完整性保护 = HTTPS
  • 预共享证书的非对称加解密技术
  • HTTPS通信完整流程
  • 中间人抓包核心原理
  • Charles、Burp Suite开启SSL抓包

把Charles和burp的证书安装在手机里面是为了通过HTTPS的证书校验

认证机关的公开密钥必须安全的转交给客户端。使用通信方式时,如何安全转交是一件很困难的事情。因此,多数浏览器开发商发布版本时,会事先在内部植入常用认证机关的公开密钥

中间人抓包流程

  • 在这种情况下,app本质上是和Charles通信,服务器本质上也是跟Charles通信
  • 所以在这个时候需要把Charles的证书放在手机里面
  • 在把证书放到手机信任凭据的根目录之后,App和抓包工具之间以及抓包工具与服务器之间的校验就没有问题了

HTTPS到底是什么

HTTP作为一种被广泛使用的传输协议,也存在一些缺点:

  1. 无状态(可以通过Cookie或Session解决)
  2. 明文传输
  3. 不安全

为了解决“明文”和“不安全“两个问题,就产生了HTTPS。HTTPS不是一种单独的协议,它是由HTTP + SSL/TLS组成

一图讲解单向认证和双向认证

抓包工具抓HTTP包的原理

  1. 首先抓包工具会提供出代理服务,客户端需要连接该代理
  2. 客户端发出HTTP请求时,会经过抓包工具,抓包工具将请求的原文进行展示
  3. 抓包工具使用该原文将请求发送给服务器
  4. 服务器返回结果给抓包工具,抓包工具将返回结果进行展示
  5. 抓包工具将服务器返回的结果原样返回给客户端

抓包工具就相当于一个透明的中间人,数据经过的时候它一手接收数据,另一手奖数据传出去

抓包工具抓HTTPS包的原理

这个时候抓包工具对客户端来说相当于服务器,对服务器来说相当于客户端。在这个传输过程中,客户端会以为它就是目标服务器,服务器也会以为它就是请求发起的客户端

  1. 客户端连接抓包工具提供的代理服务
  2. 客户端需要安装抓包工具的根证书
  3. 客户端发出HTTPS请求,抓包工具模拟服务器与客户端进行TLS握手交换密钥等流程
  4. 抓包工具发送一个HTTPS请求给客户端请求的目标服务器,并与目标服务器进行TLS握手交换密钥等流程
  5. 客户端使用与抓包工具协定好的密钥加密数据后发送给抓包工具
  6. 抓包工具使用与客户端协定好的密钥解密数据,并将结果进行展示
  7. 抓包工具将解密后的客户端数据,使用与服务器协定好的密钥进行加密后发送给目标服务器
  8. 服务器解密数据后,做对应的逻辑处理,然后将返回结果使用与抓包工具协定好的密钥进行加密发送给抓包工具
  9. 抓包工具将服务器返回的结果,用与服务器协定好的密钥解密,并将结果进行展示
  10. 抓包工具将解密后的服务器返回数据,使用与客户端协定好的密钥进行加密后发送给客户端
  11. 客户端解密数据

VPN抓包

环境

Pixel XL Android8.1、已经root

Postern: 3.1.3

Charles: 4.6.5

Burpsuite: 2023.6

在没有防止中间人抓包的情况下,我们在之前安装配置抓包环境的文章中已经尝试过了。

但是如果App为了防止中间人抓包,特意设置了不走代理这个选项,那我们单独使用Fiddler、BurpSuite这些工具是抓不到包的。这种情况下可以尝试使用VPN抓包,例如:使用Postern + Charles这个组合,是因为Charles没有直接监听到App,Charles是监听到了Postern上,Postern是VPN,它通过VPN将所有流量转发到Charles的socks代理,再打开Charles的External Proxy Server –(外部代理服务器)转发到Burpsuite,从而实施中间人抓包。

如果有服务器对客户端的校验的话,可以尝试使用VPN代理抓包。这样VPN会代理所有的流量,开启VPN会添加一个网卡这时候等于是我们从应用层的抓包变为了网络层抓包。能抓到手机通过网卡发出去的所有包。这个模式burp不支持,可以使用Charles + Postern进行

这个时候我们需要使用socket来抓包,因为HTTPS协议是运行在应用层为应用程序提供服务。而socket是位于传输层的,可以使用这种方式来绕过应用层的检测。

配置socket

Charles打开socket端口

手机Wi-Fi连接socket端口

Postern配置代理

配置代理

删除默认的代理并添加自己的代理服务器

配置规则

删除掉其它的规则防止干扰,只保留当前要使用的规则

打开/关闭Postern
抓包测试

如果打不开网页的话,重启手机试试

Postern + Charles + Burpsuite对App抓包

Postern还是跟上面一样配置不用修改

打开Charles 勾选 Proxy $\rightarrow$ External Proxy Settings就是要将Charles作为手机端的代理,再由Charles将包转发给Burpsuite。这种情况下对于Charles来说,Burpsuite就成了一个External Proxy Server(外部代理服务器)

  • 勾选HTTP和HTTPS代理,这里HTTP和HTTPS的代理服务器地址都是127.0.0.1:8080(这里的配置与Burpsuite代理一致)

已经设置了外部代理了,就必须打开 burp(当然了主要是要打开 burp 中对 127.0.0.1:8080 的监听),否则就会出现 连不上网了 的现象。在 burp 的菜单栏中的 Proxy 选项下的 Options 中打开 127.0.0.1:8080 的代理监听(默认应该是已经打开的),在 Intercept 中关闭请求拦截。

配置完成之后在手机上搜索内容,就可以抓到数据包,并且数据包也从Charles转发到Burpsuite了

VPN抓包的对抗 – VPN检测

现在随着使用VPN抓包变成常用手段,很多App会增加一些对抗措施。例如App增加了对VPN是否开启的检测

  1. java.net.NetworkInterface.getName()是检测VPN的API

    Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
    while (networkInterfaces.hasMoreElements()) {
        NetworkInterface next = networkInterfaces.nextElement();
        logOutPut("getName获得网络设备名称=" + next.getName());
        logOutPut("getDisplayName获得网络设备显示名称=" + next.getDisplayName());
        logOutPut("getIndex获得网络接口的索引=" + next.getIndex());
        logOutPut("isUp是否已经开启并运行=" + next.isUp());
        logOutPut("isBoopback是否为回调接口=" + next.isLoopback());
    }
    

    可以通过检测返回值否等于tun0ppp0来判断是否开启VPN,如果开启的话就hook java.net.NetworkInterface.getName()绕过VPN检测

    function hook_vpn(){
       Java.perform(function() {
           var NetworkInterface = Java.use("java.net.NetworkInterface");
           NetworkInterface.getName.implementation = function() {
               var name = this.getName();
               console.log("name: " + name);
               if(name == "tun0" || name == "ppp0"){
                   return "rmnet_data0";
               }else {
                   return name;
               }
           }
       })
    }
    

    如果开启VPN,NetworkCapabilities.hasTransport 会返回 true。通过hook,修改其返回值为false

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

上面的方法可以过掉大部分VPN检测,如果还有少数VPN检测过不掉,可以通过Objection Hook的方式,大范围hook java库中的网络或系统函数,找出监测点,进行绕过。

服务端校验客户端绕过

如果服务器要对客户端进行证书校验的话,因为抓包工具本身没有App的证书,所以是无法通过校验的。

这时候我们就要找到证书,然后把证书导入到抓包工具中再进行抓包

举例测试

便利蜂这个App正好有服务端对客户端证书的校验,接下来我们以这个App为例进行实战测试

方式一

直接使用r0capture脚本来获取证书和证书密码

环境

frida-server:12.8

Python:3.8.0

Android:8.1

给App打开文件存储的权限

使用r0capture脚本获取证书信息

frida -U -f com.bianlifeng.customer.android -l script.js --no-pause -o r0capture.txt

  • 在手机路径下查找dump下来的证书文件

    • 这里dump下来了非常多,随便拿一个就行了
导入证书

拿到之后,把证书导入到抓包工具中

  • 导入p12文件,密码刚刚也获取到了是r0ysue
  • 这里的HostPort可以填通配符也可以只填写400访问失败的地址

导入证书之后再重新抓取登录包

  • 成功通过校验抓到了数据包
方式二

手动分析找到证书的位置并将其保存成证书文件

原理

参考这篇文章 可知我们可以通过系统中的keyStrore.load拿到证书的名字和密码

  • PS:在8.1系统中还生效,在Android10.0的源码中不生效。可能是没有load()这个方法了

在Android8.1上可以通过Hook **keyStore.load()**这个接口拿到证书密码

拿到之后,因为有壳所以先对便利蜂App进行脱壳然后分析

脱壳

使用frida自带的脱壳工具

frida-dexdump -UF

  • 不用指定参数,把App打开放在前台就行
定位

grep命令搜索一下关键字,看主要逻辑在哪个dex文件中

  • 根据搜索的结果classes07.dex每次都会出现,那主要逻辑应该就在这个文件中了,分析一下这个文件
  • getKeyStore是一个native函数,那就需要去native中去分析了

    分析so之前先定位要分析的函数在哪个so文件中缩小分析范围,可以使用脚本 来辅助进行定位(不管静态注册还是动态注册,最终都要走RegisterNative,那此时就可以枚举符号找到符号的地址,通过此地址再结合Frida ModuleMap api可以反推出so的名称)

    脚本报错:

    • 原因是因为这个方法在我们使用的frida12里面没有,在frida14之后才有。所以需要切换一下frida和Python版本

      重新执行脚本frida -U -f com.bianlifeng.customer.android -l HookRegisterNative.js --runtime=v8

      定位到了getKeyStore()libbreakpad.so中,offset=>0x6f58

用IDA分析libbreakpad.so
  • 打开后直接搜索getKeyStore搜索到之后按F5,然后如果参数类型不对的话,先把参数类型改了然后其中的一些JNI函数就能识别出来了。

  • 分析反编译出来的代码,发现其中的FindClass()的参数v2是通过sub_7B7C获取到的,那就先hook这个函数看**FindClass()**都加载了什么类

    function hook_dlopen(module_name, fun) {
        var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
    
        if (android_dlopen_ext) {
            Interceptor.attach(android_dlopen_ext, {
                onEnter: function (args) {
                    var pathptr = args[0];
                    if (pathptr) {
                        this.path = (pathptr).readCString();
                        if (this.path.indexOf(module_name) >= 0) {
                            this.canhook = true;
                            console.log("android_dlopen_ext:", this.path);
                        }
                    }
                },
                onLeave: function (retval) {
                    if (this.canhook) {
                        fun();
                    }
                }
            });
        }
        var dlopen = Module.findExportByName(null, "dlopen");
        if (dlopen) {
            Interceptor.attach(dlopen, {
                onEnter: function (args) {
                    var pathptr = args[0];
                    if (pathptr) {
                        this.path = (pathptr).readCString();
                        if (this.path.indexOf(module_name) >= 0) {
                            this.canhook = true;
                            console.log("dlopen:", this.path);
                        }
                    }
                },
                onLeave: function (retval) {
                    if (this.canhook) {
                        fun();
                    }
                }
            });
        }
        console.log("android_dlopen_ext:", android_dlopen_ext, "dlopen:", dlopen);
    }
    
    
    function hook7B7C(){
        var breakpad = Process.findModuleByName("libbreakpad.so")
        Interceptor.attach(breakpad.base.add('0x7b7c'),{
            onEnter:function(args){
                console.log("entering 7B7C...")
            }, onLeave:function(ret){
                console.log("leaving 7b7c:", ret, ret.readCString())
            }
        })
    }
    
    function main() {
        hook_dlopen("libbreakpad.so", hook7B7C);
    }
    
    setImmediate(main);
    

    执行hook脚本:frida -U -f com.bianlifeng.customer.android -l hookartMethod.js --runtime=v8

    • 根据返回结果可以猜测证书是一个P12类型的,加密使用的base64,参数类型是string和int返回值是byte数组,证书密码是blibee
解密

继续分析IDA反编译的代码,猜测这段最长的加密字符串就是证书文件

根据上一个脚本执行的结果猜测这串字符是base64加密,接下来再写一个脚本hook android.util.Base64.decode的所有输出,拿到解密后的字符串

function hookBase64decode(){
    Java.perform(function(){
        Java.use("android/util/Base64").decode.overload('java.lang.String', 'int').implementation = function(str,i){
            var result = this.decode(str,i)
            var ByteString = Java.use("com.android.okhttp.okio.ByteString")
            console.log("str: " + str + "\n" + "i: " + i + "\n" + "result: " + ByteString.of(result).hex())
            return result
        }
    })
}

function main() {
    // hook_dlopen("libbreakpad.so", hook7B7C);
    hookBase64decode();
}

setImmediate(main);

执行:frida -U -f com.bianlifeng.customer.android -l hookartMethod.js --runtime=v8 --no-pause

保存为证书

把这段result的结果拷贝下来保存为**.p12**文件

打开保存的文件,密码是blibee

混淆后的SSL Pinning解绑定

SSL Pinning(SSL 证书固定)是一种安全机制,旨在防止中间人攻击和伪造证书的攻击。它通过将预定义的服务器证书或公钥嵌入到客户端应用程序中,确保客户端只信任特定的证书或公钥,即使是证书颁发机构(CA)被攻破或信任链出现问题,客户端也能安全地与服务器通信。

环境

Android: 8.1

frida-server: 14.2.8

pyenv: 3.9.0

frida: 14.2.8

frida-tools: 8.1.2

使用postern开起了vpn抓包,在开始抓包之前可以先拿酷安的App点击登录试一下看抓包环境是否畅通

案例App:滴答清单

  • 这里是在对SSL Pinning的App进行抓包的时候,因为证书校验不通过,发送登录账号的请求被拒绝了
  • 这种情况说明App做了SSL证书的绑定
什么是SSL证书绑定

Google搜索一下“okhttp3 certificatePinner”,看看 它是怎么实现的

  • 官网上写解释了它是用来防止中间人攻击的,例如我们通常情况下的抓包是把一段请求拆分成了两段,那么他的证书肯定是变了的
  • 示例代码中也介绍了它的实现通过提前配置好的域名,在进行握手的过程中验证客户端/服务器的证书与预定证书指纹是否相匹配,如果这时候请求的证书是charles的那么校验肯定是失败了的

github 看一下它的校验函数

fun check(
    hostname: String,
    peerCertificates: List<Certificate>,
  ) {
    return check(hostname) {
      (certificateChainCleaner?.clean(peerCertificates, hostname) ?: peerCertificates)
        .map { it as X509Certificate }
    }
  }

  internal fun check(
    hostname: String,
    cleanedPeerCertificatesFn: () -> List<X509Certificate>,
  ) {
    val pins = findMatchingPins(hostname)
    if (pins.isEmpty()) return

    val peerCertificates = cleanedPeerCertificatesFn()

    for (peerCertificate in peerCertificates) {
      // Lazily compute the hashes for each certificate.
      var sha1: ByteString? = null
      var sha256: ByteString? = null

      for (pin in pins) {
        when (pin.hashAlgorithm) {
          "sha256" -> {
            if (sha256 == null) sha256 = peerCertificate.sha256Hash()
            if (pin.hash == sha256) return // Success!
          }
          "sha1" -> {
            if (sha1 == null) sha1 = peerCertificate.sha1Hash()
            if (pin.hash == sha1) return // Success!
          }
          else -> throw AssertionError("unsupported hashAlgorithm: ${pin.hashAlgorithm}")
        }
      }
    }

    // If we couldn't find a matching pin, format a nice exception.
    val message =
      buildString {
        append("Certificate pinning failure!")
        append("\n  Peer certificate chain:")
        for (element in peerCertificates) {
          append("\n    ")
          append(pin(element))
          append(": ")
          append(element.subjectDN.name)
        }
        append("\n  Pinned certificates for ")
        append(hostname)
        append(":")
        for (pin in pins) {
          append("\n    ")
          append(pin)
        }
      }
    throw SSLPeerUnverifiedException(message)
  }
  • 通过校验确保了只有预定义的证书才能通过验证
标准SSL Pinning的解绑定

比较常用的就是objection

  • objection中hook的就是用于校验的check函数
解绑定

有一些对各种种类的证书绑定比较全面的脚本 ,拷贝下来试一下

这里用spwn模式运行frida脚本,因为这里hook的okhttp的时机是在创建的时候,所以如果错过了这个点就不生效了

frida -U -f cn.ticktick.task -l StandardSSLpinningBypass.js --no-pause

  • 脚本执行成功了可以看到经过了很多hook的点,再试试抓包是否能成功

    • 还是一样的,没有通过
    • 如果没能成功的话,就怀疑这个证书是不是混淆了
混淆证书的解绑定

怎么对混淆的证书解绑定呢,首先从证书校验的原理考虑,它打开文件的过程肯定是必须的(因为要打开证书文件进行校验)。所以可以考虑使用frida去hook所有打开文件的动作,这里使用r0tracer (这个frida脚本是基于frida16的,那我们切换为frida16的环境)

切换环境

frida-server: 16.2.1

pyenv: 3.12.0

frida: 16.2.1

执行脚本frida -U -f cn.ticktick.task -l r0tracer.js -o dida.txt

  • 因为文件操作非常多,所以多等一会儿
  • 结束了之后,去刚刚输出的dida.txt文件中查找关键字看能不能找到

这里尝试直接搜索我们前面分析查看okhttp代码中间里面的certificatePinner

  • 这里直接找到 了,那用GDA打开apk看一下找到的这个函数是什么

    • 找到了这个函数,看到它的参数跟okhttp源码中的check函数是相同的

    • 另外包括它结尾校验失败抛出的异常也是跟源码中相同的

      • 所以这个函数大概率就是check函数了,那接下来尝试直接hook它

写一个简单的hook脚本直接hook这个函数

Java.perform(function() {
    // 获取目标类
    var TargetClass = Java.use("z1.g");
    // Hook目标方法并重写其实现
    TargetClass.a.implementation = function(x, y) {
        // 打印调用信息
        console.log('z1.g.a called with arguments: ' + x + ', ' + y);
    };
});

执行脚本frida -U -f cn.ticktick.task -l didaSSLbypass.js

然后再点击登录就成功的抓到了数据包

flutter库的SSL Pinning解绑定

暂时没找到合适的案例

如果在尝试的时候也无法抓到登录包,包括使用r0capture等脚本也无法抓到包的话。

可以通过反编译查看源码,看主要逻辑是否被flutter包裹了

尝试使用一些flutter解绑定的脚本

暂时参考一些帖子吧,找到合适的案例再补上

flutter抓包绕过-Android安全-看雪-安全社区|安全招聘|kanxue.com

[原创]一种基于frida和drony的针对flutter抓包的方法-Android安全-看雪-安全社区|安全招聘|kanxue.com

horangi-cyops/flutter-ssl-pinning-bypass: Horangi tools for Android penetration testing (github.com)

flutter抓包

  • 标题: App抓包工具教程汇总
  • 作者: xiaoeryu
  • 创建于 : 2024-05-26 23:08:29
  • 更新于 : 2024-08-01 18:10:31
  • 链接: https://github.com/xiaoeryu/2024/05/26/App抓包教程汇总/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论