0
点赞
收藏
分享

微信扫一扫

WinDivert+MITM实现https流量透明

1.背景

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

WinDivert+MITM实现https流量透明_#include

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

WinDivert+MITM实现https流量透明_数据_02

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

WinDivert+MITM实现https流量透明_代理服务器_03

这里自己假装去找一下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的降级),这里不谈,主要是自己水平有限。

最后成功透明化了流量。

WinDivert+MITM实现https流量透明_数据_04

 



免责声明:全网优质文章转载,以作为收藏留档之用,文章均不代表本人立场!
请尊重原创作者,如需转载请标注原创作者链接



举报

相关推荐

0 条评论