WinDivert+MITM实现https流量透明

1.背景

自己想了解一下Https如何透明,于是找到了这个项目,但是需要自己把流量定位到8080端口。windows是支持代理流量的,常见的代理方式是修改internet setting(某不愿透露姓名的哥儿说SSR也是通过这种方式代理本地的流量),如下图所示。

接着简单分析一下这个代理服务器是如何实现。通过ProMon不难发现,iexplore是通过修改注册表的方式实现的。

那么我们怎么通过API来指定自己的代理服务器,这里通过栈回溯,发现使用的是WININET.dll下的InternetSetOptionW。

这里自己假装去找一下MSDN的定义,了解一下怎么使用这个API

2.如何自己转发流量

上述的方式可以代理当前计算机产生的流量,mitmproxy也能成功的实现https的透明化,但是有几个问题需要了解和注意:

1、修改windows代理的方式比较简单,但是也容易被其他人修改。

2、https透明需要本地安装根证书的。

3、怎么让流量走到我们设置的代理服务器。

在对上述问题有一个基本了解之后,开始查找资料。

3.使用WinDivert进行端口重定向

对https有点了解的朋友都知道,https使用的是443端口,那么我们只需要转发自己的443端口到代理服务器的端口不就行了吗?是的就是这么简单。下面是r3使用WinDivert转发端口的代码

#include <winsock2.h>
#include <windows.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <map>
#include "windivert.h"

#define MAXBUF          WINDIVERT_MTU_MAX

//结构体使用的是 https://github.com/zliu-fd/WinDivertProxy
struct EndPoint {
	UINT32 addr;
	USHORT port;
	bool operator<(const EndPoint& ep) const { return (addr < ep.addr || (addr == ep.addr && port < ep.port)); }
	bool operator==(const EndPoint& ep) const { return (addr == ep.addr && port == ep.port); }
	bool operator>(const EndPoint& ep) const { return (addr > ep.addr || (addr == ep.addr && port > ep.port)); }
	EndPoint() { }
	EndPoint(UINT32 _addr, USHORT _port)
	{
		addr = _addr;
		port = _port;
	}
};

std::string ConvertIP(UINT32 addr)
{
	in_addr in_addr;
	in_addr.S_un.S_addr = addr;
	char* pAddr = inet_ntoa(in_addr);
	std::string ipaddr(pAddr);
	return ipaddr;
}

void LogRedirect(UINT32 srcAddr, USHORT srcPort, UINT32 proxyAddr, USHORT proxyPort, UINT32 dstAddr, USHORT dstPort, int direction) 
{
	if (direction == 0)
	{
		std::cout << "O Redirect ";
		std::cout << "[" << ConvertIP(srcAddr) << ":" << ntohs(srcPort) << "  " << ConvertIP(dstAddr) << ":" << ntohs(dstPort) << "]";
		std::cout << " -> [" << ConvertIP(srcAddr) << ":" << ntohs(srcPort) << "  " << ConvertIP(proxyAddr) << ":" << ntohs(proxyPort) << "]" << std::endl;
	}
	else if (direction == 1)
	{
		std::cout << "I Received ";
		std::cout << "[" << ConvertIP(proxyAddr) << ":" << ntohs(proxyPort) << "  " << ConvertIP(dstAddr) << ":" << ntohs(dstPort) << "]";
		std::cout << " -> [" << ConvertIP(srcAddr) << ":" << ntohs(srcPort) << "  " << ConvertIP(dstAddr) << ":" << ntohs(dstPort) << "]" << std::endl;
	}
	else 
	{
		std::cout << "X Error ";
	}
}

std::map<EndPoint, EndPoint> ClientToServerMap;

int main()
{
	PVOID payload = NULL;
	UINT payload_len = NULL;
	UINT packet_len = NULL;
	unsigned char packet[MAXBUF];
	WINDIVERT_ADDRESS addr = { 0 };
	PWINDIVERT_IPHDR ip_header = NULL;
	PWINDIVERT_TCPHDR tcp_header = NULL;

	//代理服务器,不支持本地
	UINT32 ProxyAddr = inet_addr("");
	USHORT ProxyPort = htons(8080);

	//发往443端口和从8080端口返回的数据都会被拦截下来
	HANDLE handle = WinDivertOpen(
		"(outbound and tcp.DstPort == 443) or (inbound and tcp.SrcPort == 8080)",
		WINDIVERT_LAYER_NETWORK, 0, 0);

	if (handle == INVALID_HANDLE_VALUE)
		return 0;

	while (1)
	{
		//这里一直在接收数据包
		if (!WinDivertRecv(handle, packet, sizeof(packet), &packet_len, &addr))
		{
			printf("failed to read packet (%d) \n", GetLastError());
			continue;
		}

		WinDivertHelperParsePacket(packet, packet_len, &ip_header, NULL, NULL, NULL, NULL, &tcp_header, NULL, &payload, &payload_len, NULL, NULL);
		if (ip_header == NULL || tcp_header == NULL)
		{
			printf("failed to parse packet (%d) \n", GetLastError());
			continue;
		}

		if (addr.Outbound)//出去的数据
		{
			if(tcp_header->DstPort == htons(443))
			{
				EndPoint srcEndPoint(ip_header->SrcAddr, tcp_header->SrcPort);
				EndPoint dstEndPoint(ip_header->DstAddr, tcp_header->DstPort);
				ClientToServerMap[srcEndPoint] = dstEndPoint;

				LogRedirect(ip_header->SrcAddr, tcp_header->SrcPort, ProxyAddr, ProxyPort, ip_header->DstAddr, tcp_header->DstPort, 0);

				//指定到代理服务器
				ip_header->DstAddr = ProxyAddr;
				tcp_header->DstPort = ProxyPort;
			}
		}
		else//进来的数据
		{
			if (tcp_header->SrcPort == ProxyPort)
			{
				//接收到代理服务器发送的数据
				EndPoint dstEndPoint(ip_header->DstAddr, tcp_header->DstPort);

				if (ClientToServerMap.find(dstEndPoint) != ClientToServerMap.end())
				{
					EndPoint originalDstEP = ClientToServerMap[dstEndPoint];
					ip_header->SrcAddr = originalDstEP.addr;
					tcp_header->SrcPort = originalDstEP.port;

					LogRedirect(ip_header->SrcAddr, tcp_header->SrcPort, ProxyAddr, ProxyPort, ip_header->DstAddr, tcp_header->DstPort, 1);
				}

			}
		}

		WinDivertHelperCalcChecksums(packet, packet_len, &addr, 0);
		UINT writeLen = NULL;
		if (!WinDivertSend(handle, packet, packet_len, &writeLen, &addr))
		{
			printf("failed to send packet (%d) \n", GetLastError());
			continue;
		}

	}

	return 0;
}

这里需要注意的是不能使用本地的IP地址,因为443端口的数据会被全部转发到8080,导致本地的代理服务器的https请求也发不出去,形成一个闭环。

还有就是根证书问题,需要在需要https透明的主机上安装,建议使用静默安装。

证书管理器工具 (Certmgr.exe) | Microsoft Docs

当然https还有很多需要注意的细节(比如为什么要安装CA、https通信又是如何保证安全的、Http1和Http2又有说明区别、MITM又是怎么实现中间人的、SSLStrip如何实现https的降级),这里不谈,主要是自己水平有限。

最后成功透明化了流量。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
要拦截 Windows 系统上所有进入或出去的 TCP 和 HTTP 请求,您可以使用 Python 的第三方库 WinDivert 或 PyDivert。这些库允许您在 Windows 系统上捕获和操作网络数据包。 以下是一些可能的步骤来实现这个目标: 1. 安装 WinDivert 或 PyDivert 库。您可以使用 pip 命令来安装 PyDivert: ``` pip install pydivert ``` 而 WinDivert 库需要手动下载并安装。 2. 使用 WinDivert 或 PyDivert 库创建一个 Divert 对象。Divert 对象允许您捕获和操作网络数据包。您可以使用以下代码创建一个 Divert 对象: ``` from pydivert import WinDivert, WinDivertLayer, WinDivertFlags with WinDivert("tcp.DstPort == 80", layer=WinDivertLayer.Network, flags=WinDivertFlags.SNIFF) as w: for packet in w: # 处理网络数据包 ``` 以上代码将创建一个 Divert 对象,用于捕获所有目标端口为 80 的 TCP 流量。您可以将此条件更改为其他条件来捕获其他类型的流量。 3. 在 Divert 对象的循环中,处理捕获的网络数据包。您可以使用第三方库,如 dpkt 或 scapy,来解析和操作网络数据包。例如,以下代码使用 dpkt 库解析 TCP 流量,获取源和目标 IP 地址和端口: ``` import dpkt with WinDivert("tcp.DstPort == 80", layer=WinDivertLayer.Network, flags=WinDivertFlags.SNIFF) as w: for packet in w: ip = dpkt.ip.IP(packet.raw) tcp = ip.tcp src_ip = dpkt.inet_ntoa(ip.src) dst_ip = dpkt.inet_ntoa(ip.dst) src_port = tcp.sport dst_port = tcp.dport # 处理网络数据包 ``` 您可以使用类似的代码来处理 HTTP 流量。例如,您可以检查 TCP 流量的目标端口是否为 80 或 443,并解析 HTTP 请求和响应。 请注意,拦截和操作网络数据包可能会对系统性能产生影响,并可能需要管理员权限。因此,请小心操作,并确保您的程序不会干扰网络通信或泄露敏感信息。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值