APP代理和VPN检测绕过对抗

在渗透测试过程中,遇到的APP总是会有代理或者VPN检测,导致抓取不了数据包。为更一步了解APP代理和VPN原理及检测机制,于是就从网络上借鉴一些文章,再结合近期实际渗透测试过程中遇到的问题进行补充优化,但愿此文能够帮助正在摸索的你。(本文不涉及ssl pinning相关内容,有需要可参考之前的文章绕过ssl pingning

OSI7层网络模型

下面是对GPT对OSI七层网络模型的简单解释:

  • 应用层:为用户提供网络服务,如电子邮件、文件传输和Web浏览器。
  • 表示层:处理数据的格式转换、加密和压缩,以确保数据能够正确解释。
  • 会话层:建立和管理会话,允许不同设备之间的通信和同步。
  • 传输层:提供端到端的数据传输,确保数据可靠和完整。
  • 网络层:处理数据包的路由和寻址,确定数据的最佳路径。
  • 数据链路层:在直接连接的设备之间传输数据帧,并处理错误检测和纠正。
  • 物理层:处理物理连接和传输介质,如电缆、网卡等。
    这些层次按照从底层到顶层的顺序进行,每个层次都负责不同的功能。

使用网络数据的传输离不开网络协议七层模型,通过理解每一层协议的分工,也就能对网络故障逐一排查,这样的思维逻辑在安卓应用中也同样适用。

OSI 7层模型各层功能及对应的协议、设备如下表所示:

知识点:HTTPS协议是HTTP+SSL

根据上表可知,SSL做数据加密是在表示层,也就是说,HTTPS实际上是建立在SSL之上的HTTP协议,而普通的HTTP协议是建立在TCP协议之上的。所以,当HTTPS访问URL时,由于URL在网络传送过程中最后是处于HTTP协议数据报头中,而HTTP协议位于SSL的上层,所以凡是HTTP协议所负责传输的数据就全部被加密了;但是IP地址并没加密,因为处理IP地址的协议(网络层)位于处理SSL协议(表示层)的下方。

额,说了这么多,就是要告诉你一个重要的关键点:数据的封装是自下而上的 !在网络数据处理方面,如果是上层做了检测处理,则需要在同层或下层进行逻辑绕过,这就是攻与防的关键了,偷家(底层)才是硬道理。

接下来,我们再理解一下代理与VPN。

代理与VPN

代理

代理(proxy) 也称网络代理,是一种特殊的网络服务,允许一个终端(一般为客户端)通过这个服务与另外一个终端(一般为服务器)进行非直接的连接。

一个完整的代理请求过程为:客户端首先根据代理服务器所使用的代理协议,与代理服务器创建连接,接着按照协议请求对目标服务器创建连接、或者获得目标服务器的指定资源。

VPN

VPN(virtual private network)(虚拟专用网络)是常用于连接中、大型企业或团体间私人网络的通讯方法。它利用隧道协议(Tunneling Protocol)来达到发送端认证、消息保密与准确性等功能。

代理和VPN的区别

从各自的定义,我们就能看出VPN的特点是采取隧道协议进行数据传输和保护;而代理使用的则是对应的代理协议。

下面是VPN和代理的常用协议:

协议名称
VPN OpvenVPN、IPsec、IKEv2、PPTP、L2TP、WireGuard等
代理 HTTP、HTTPS、SOCKS、FTP、RTSP等

VPN 协议大多是作用在 OSI 的第二层和第三层之间,所以使用 VPN 时,几乎能转发所有的流量。

而代理协议多作用在应用层,最高层

安卓代理检测

知道了代理与VPN的作用后,在APP中,如果开发人员在代码中添加了一些网络层的检测机制,而这些机制恰恰又是针对工作层协议进行的检测,那么只要分析出工作在IOS的哪一层,抢先一步在下层做出应对,那APP在上层无论怎么检测,都没有用。下面将对测试场景进行详细分析。

抓包的步骤:

  • 在客户端(手机)中设置代理服务器的地址
  • 开启代理服务器(burp)的代理功能

在Android中,可以使用以下几种方式来进行代理检测的代码:

  • 1.使用SystemProperties类
import android.os.SystemProperties;

String proxyHost = SystemProperties.get("http.proxyHost");
String proxyPort = SystemProperties.get("http.proxyPort");

if (proxyHost != null && proxyPort != null) {
    // 代理已设置
} else {
    // 未设置代理
}
  • 2.使用ConnectivityManager类:
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;

public boolean isProxySet(Context context) {
    ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo activeNetwork = cm.getActiveNetworkInfo();

    if (activeNetwork != null && activeNetwork.isConnected()) {
        if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
            // Wi-Fi连接,无需代理
            return false;
        } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
            String proxyHost = android.net.Proxy.getDefaultHost();
            return (proxyHost != null && proxyHost.length() > 0);
        }
    }

    return false; // 未设置代理
}
  • 3.使用Java的URL类:
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;

try {
    URL url = new URL("http://www.example.com");
    URLConnection connection = url.openConnection(Proxy.NO_PROXY);
    // 无代理设置
} catch (Exception e) {
    // 代理设置
}

第三种,NO_PROXY实际上就是type属性为DIRECT的一个Proxy对象,这个type有三种:

  • DIRECT
  • HTTP
  • SOCKS
    所以,Proxy.NO_PROXY的意思是connection的请求是直连。
    此时若通过系统进行代理,app对外请求会失效,也就是视觉上看到的卡死状态,就是不让走系统代理。

安卓手机上设置系统代理即是在【设置】-【WLAN】-【修改网络】手动设置代理

针对不走系统代理的情况有如下三种应对:

  1. 使用基于VPN模式的Postern
  2. 使用基于iptables的ProxyDroid
  3. 使用frida hook掉相关检测代码

使用基础VPN模式的软件

  • Postern
    下载地址
    这里先推荐使用的是Postern,因为这款软件传播比较宽泛上手较为简单,设置保存后默认开启

  • VProxid
    下载地址
    在其他场景下,我推荐使用的另外一款软件是VProxid

VProxid是Android平台上的Proxifier替代工具,VProxid允许不支持通过代理服务器工作的网络应用程序通过SOCKS或HTTP(S)代理进行操作。使用VProxid,可以轻松地将所选应用程序上的所有TCP连接隧道传输到不同的代理服务器,这意味着VProxid可以为每个应用程序设置不同的代理来抓取数据,配合Burp使用更佳

简单的来说,你可以理解为是Proxifier的手机版,可以勾选抓取响应应用的数据包。在某次案例中,我使用Postern无法抓取的数据包,但是通过VProxid能够抓取。

安卓VPN检测

安卓VPN检测的方案如下几种:
在Android中,可以使用以下几种方式来进行VPN检测的代码:

  • 1.使用ConnectivityManager类:
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Build;

public boolean isVpnConnected(Context context) {
    ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        Network network = cm.getActiveNetwork();

        if (network != null) {
            NetworkCapabilities capabilities = cm.getNetworkCapabilities(network);

            if (capabilities != null) {
                return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
            }
        }
    } else {
        NetworkInfo networkInfo = cm.getActiveNetworkInfo();

        if (networkInfo != null) {
            return networkInfo.getType() == ConnectivityManager.TYPE_VPN;
        }
    }

    return false; // 未连接VPN
}
  • 2.使用NetworkInterface类:
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

public boolean isVpnConnected() {
    try {
        Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();

        while (networkInterfaces.hasMoreElements()) {
            NetworkInterface networkInterface = networkInterfaces.nextElement();

            if (networkInterface.isUp() && networkInterface.isVirtual() && !networkInterface.isLoopback()) {
                return true; // 连接了VPN
            }
        }
    } catch (SocketException e) {
        e.printStackTrace();
    }

    return false; // 未连接VPN
}
  • 3.使用系统属性检测:
public boolean isVpnConnected() {
    String dnsServers = System.getProperty("net.dns1");

    if (dnsServers != null && dnsServers.contains("10.0.0.0")) {
        return true; // 连接了VPN
    }

    return false; // 未连接VPN
}

将上述代码放置在onClick和activity中,在检测到用户使用了VPN就直接退出。

绕过VPN检测

在针对上述代码的检测当中,可以使用frida hook掉相关检测代码:

function bypassVpn() {
    Java.perform(function () {
        var can_hook = false
        var ConnectivityManager = Java.use("android.net.ConnectivityManager");
        ConnectivityManager.getNetworkInfo.overload('int').implementation = function(){
            console.log("call getNetworkInfo function =========!!!")
            if(arguments[0] == 17){
                can_hook = true
            }
            var ret = this.getNetworkInfo(arguments[0])
            return ret
        }

        var NetworkInfo = Java.use("android.net.NetworkInfo")
        NetworkInfo.isConnected.implementation = function(){
            console.log("call isConnected ========= !!!")
            var ret = this.isConnected()
            if(can_hook){
                ret = false
                can_hook = false
                console.log("call isConnected function========= !!!")
            }
            return ret
        }

        var NetworkCapabilities = Java.use("android.net.NetworkCapabilities")
        NetworkCapabilities.hasTransport.implementation = function(){
            console.log("call hasTransport =========!!!")
            var ret = this.hasTransport(arguments[0])
            if(arguments[0] == 4){
                console.log("call hasTransport function =========!!!")
                ret = false
            }
            return ret
        }

        NetworkCapabilities.transportNameOf.overload('int').implementation = function(){
            console.log("call transportNameOf =========!!!")
            var ret = this.transportNameOf(arguments[0])
            if(ret.indexOf("VPN") >= 0){
                ret = "WIFI"
            }
            return ret;
        }

        var NetworkInterface = Java.use("java.net.NetworkInterface")
        NetworkInterface.getAll.implementation = function(){
            var nis = this.getAll()
            console.log("call getAll =========!!!")
            nis.forEach(function(ni){
                if (ni.name.value.indexOf("tun0")>=0 || ni.name.value.indexOf("ppp0")>=0 ){
                    ni.name.value = "xxxx"
                    ni.displayName.value = "xxxx"
                }
            })
            return nis
        }
    })
}

使用ProxyDroid代理

当前场景:APP同时开启了代理检测以及VPN检测

这时使用iptables进行数据转发的软件 ProxyDroid 进行测试,配置如下图所示:

burp成功获取到了请求,至此代理与VPN的应对方法均已实现。所以,iptables 竟然能从OSI的 2、3层下面走吗?(笔者用这个方法没有成功,原作者成功了,原因下面会讲)

iptables原理

我们都知道安卓使用的是linux内核,而linux内核提供的防火墙工具是Netfilter/Iptables

Netfilter是由linux内核集成的IP数据包过滤系统,其工作在内核内部,而Iptables则是让用户定义规则集的表结构。

也就是,iptables是一个命令行工具,位于用户空间,它真正操作的框架实现在内核当中。

Netfilter是一个数据包处理模块,它具有网络地址转换、数据包内容修改、数据包过滤等功能。 要使netfilter能够工作,就需要将所有的规则读入内存中。netfilter自己维护一个内存块,在此内存块中有4个表:filter表、NAT表、mangle表和raw表。在每个表中有相应的链,链中存放的是一条条的规则,规则就是过滤防火的语句或者其他功能的语句。也就是说表是链的容器,链是规则的容器。实际上,每个链都只是一个hook函数(钩子函数)而已。

Iptables主要工作在OSI七层的2.3.4层,好像也没比VPN的工作协议低,反而还有高的,但是测试结果证明,是我想错了,iptables不是由于协议低,而是没有出现tun0或者ppp0这两个关键的网卡特征,所以成功绕过了VPN的检测。

基于iptables这个流量转发,我还发现了一个新的名词,叫做“透明代理”,iptables的转发模式就是这种。

由此,延伸了一个新的代理模式,通过burp进行“透明代理”,网上的教程错综复杂,亲测使用过程如下。

总结

根据不同的代码检测,也会有不同的应对方法,所以,遇到APP出现抓包闪退等问题,先逆向,查看源码,在通信处仔细进行分析,再针对检测代码进行绕过,才是正解。

参考文章1 参考文章2 参考文章3

免责声明

免责声明:本博客的内容仅供合法、正当、健康的用途,切勿将其用于违反法律法规的行为。如因此导致任何法律责任或纠纷,本博客概不负责。谢谢您的理解与配合!

微信公众号

本文链接:

https://sanshiok.com/archive/8.html

# 最新文章