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】-【修改网络】手动设置代理
针对不走系统代理的情况有如下三种应对:
- 使用基于VPN模式的Postern
- 使用基于iptables的ProxyDroid
- 使用frida hook掉相关检测代码
使用基础VPN模式的软件
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出现抓包闪退等问题,先逆向,查看源码,在通信处仔细进行分析,再针对检测代码进行绕过,才是正解。
免责声明
免责声明:本博客的内容仅供合法、正当、健康的用途,切勿将其用于违反法律法规的行为。如因此导致任何法律责任或纠纷,本博客概不负责。谢谢您的理解与配合!