背景
导师布置一个主动测量的小任务:
- 在windows是实现主动修改某特定进程发出的IP数据包的IPID字段。
这个任务,说简单不简单,说复杂不复杂。主要是解决两个问题:
- 如何确定特定进程的数据包?简单起见能确定到传输层就行。
- 如何修改特定的数据包?
关键在于如何拦截ip层的数据包,并修改它。因为修改是对流的“主动”行为,winpcap类似的东西是做不来的。而且因为需要修改的是IP层的东西,因此常规的钩子程序也不好处理,因为钩子一般是去hook系统的socket函数,hook这些函数 只能修改应用层的内容。目前已有的技术手段有使用WFP框架的,或者类似的NIDS框架的。但是这个东西开发起来不是很顺手呀。
后面,得知有windivert 这个神器!!!!它是基于WFP开发的,但是提供很好使的API !它可以支持对IP数据包的capture和修改,以及发送。
2021.6.23修改: 最新版本的windivert已经支持按照processID过滤数据包。
windivert的官网:https://reqrypt.org/windivert-doc.html
看看它的特点吧:
- capture network packets
- filter/drop network packets
- sniff network packets
- (re)inject network packets
- modify network packets
还是很棒的呀!
windivert 快速学习
0.vs2013安装windivert
步骤:
- 下载windivert的二进制版本 https://reqrypt.org/download/WinDivert-1.4.3-A.zip ,并解压。
- 新建vs项目,设置项目属性,在项目的VC++ Directories里面,在包含目录添加windivert的include目录。在库目录添加windivert的x86_64目录。
- 在链接器——输入 ——添加新的依赖,增加WinDivert.lib
- 测试一波。
测试代码见文章末尾。
1. 几个重要的 api
- WinDivertOpen
HANDLE WinDivertOpen(
__in const char *filter,
__in WINDIVERT_LAYER layer,
__in INT16 priority,
__in UINT64 flags
);
打开一个windivert对象,返回一个对象指针。打开的过程中,需要制定 过滤规则,过滤层,过滤器的优先级,以及windivert对象的工作模式。
过滤规则的编写:参见https://reqrypt.org/windivert-doc.html 第7部分
过滤层参数说明:
Layer | Description |
---|---|
WINDIVERT_LAYER_NETWORK = 0 | The network layer. This is the default. 网络层 |
WINDIVERT_LAYER_NETWORK_FORWARD | The network layer (forwarded packets).转发层 |
优先级的说明:其实这里说明的就是指明过滤规则的优先级。值越大,优先级越大。 对于一个数据包,如果它同时匹配多个Windivert对象的规则,那么,它依次被这些对象按照优先级从高到低的次序匹配, 直到遇到匹配的规则。
详细见文档解释:Different WinDivert handles can be assigned different priorities by the priority parameter. Packets are diverted to higher priority handles before lower priority handles. Packets injected by a handle are then diverted to the next priority handle, and so on, provided the packet matches the handle’s filter. A packet is only diverted once per priority level, so handles should not share priority levels unless they use mutually exclusive filters. Otherwise it is not defined which handle will receive the packet first. Higher priority values represent higher priorities, with WINDIVERT_PRIORITY_HIGHEST being the highest priority, 0 the middle (and a good default) priority, and WINDIVERT_PRIORITY_LOWEST the lowest priority.
过滤FLAG :该参数指明Windivert对象到底是用于监听、丢包、还是修改包模式。
Flag | Description |
---|---|
WINDIVERT_FLAG_SNIFF | WinDivert进入监听模式,只是被动监听,其功能等同于Winpcap |
WINDIVERT_FLAG_DROP | WinDivert单纯地把满足过滤条件的包丢弃,此模式下不能读取包内容 |
WINDIVERT_FLAG_DEBUG | WinDivert把满足过滤的条件的包扣下来,等待被修改然后重新注入 |
- WinDivertRecv
BOOL WinDivertRecv(
__in HANDLE handle,
__out PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *recvLen
);
接收特定WinDivert对象的捕获的包的函数。
handle: WinDivertOpen的返回值
pPacket : 用于存储包的buffer,缓冲区,这个是用户提供的。
packetLen: 缓冲区的大小;如果包的实际长度大于此值,则截取packetLen个字节到pPacket。
pAddr :包性质说明结构。该结构体,指明目前抓取到的包的性质。说明如下:
typedef struct
{
INT64 Timestamp;
UINT32 IfIdx;
UINT32 SubIfIdx;
UINT8 Direction:1;
UINT8 Loopback:1;
UINT8 Impostor:1;
UINT8 PseudoIPChecksum:1;
UINT8 PseudoTCPChecksum:1;
UINT8 PseudoUDPChecksum:1;
} WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS;
字段说明:
Timestamp: WinDivert捕获到该包的时间戳
IfIdx: 该包所在的网卡序号
SubIfIdx: 网卡子序号
Direction: 包的方向,主要有入包和出包之分。
值 | 含义 |
---|---|
WINDIVERT_DIRECTION_OUTBOUND | with value 0 for outbound packets. |
WINDIVERT_DIRECTION_INBOUND | with value 1 for inbound packets. |
Loopback: 是否是回环包。Set to 1 for loopback packets, 0 otherwise
Impostor: 是否是已经修改的包。Set to 1 for impostor packets, 0 otherwise. 注意,如果是修改后的包,这个值会被值为1。这个值主要是为了防止出现同一个包先被WinDivert捕获到,然后WinDivertSend后,又被捕获到。
PseudoIPChecksum: Set to 1 for packets with a pseudo IPv4 checksum, 0 otherwise.
PseudoTCPChecksum: Set to 1 for packets with a pseudo TCP checksum, 0 otherwise.
PseudoTCPChecksum: Set to 1 for packets with a pseudo UDP checksum, 0 otherwise.
recvlen:实际拷贝到pPacket缓冲区的字节数。可能为0.
!!!注意:WinDivertRecv() should not be used on any WinDivert handle created with the WINDIVERT_FLAG_DROP set. !!!
- WinDivertSend
BOOL WinDivertSend(
__in HANDLE handle,
__in PVOID pPacket,
__in UINT packetLen,
__in PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *sendLen
);
handle: WinDivertOpen()返回值.
pPacket: 待发包的内容.
packetLen: pPacket的总长度.
pAddr: 待发包的性质 WINDIVERT_ADDRESS.
sendLen: The total number of bytes injected. Can be NULL if this information is not required.
注意:发包之前 ,一定要让包有正确的校验值。!!!
- WinDivertClose 这个没啥好说的
BOOL WinDivertClose(
__in HANDLE handle
);
2.一些辅助api
主要是一些包的解析、校验和的计算api.
- WinDivertHelperParsePacket 头部解析api
BOOL WinDivertHelperParsePacket(
__in PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_IPHDR *ppIpHdr,
__out_opt PWINDIVERT_IPV6HDR *ppIpv6Hdr,
__out_opt PWINDIVERT_ICMPHDR *ppIcmpHdr,
__out_opt PWINDIVERT_ICMPV6HDR *ppIcmpv6Hdr,
__out_opt PWINDIVERT_TCPHDR *ppTcpHdr,
__out_opt PWINDIVERT_UDPHDR *ppUdpHdr,
__out_opt PVOID *ppData,
__out_opt UINT *pDataLen
);
- WinDivertHelperParseIPv4Address ip地址转换api ,点分十进制转INT。
BOOL WinDivertHelperParseIPv4Address(
__in const char *addrStr,
__out_opt UINT32 *pAddr
);
- WinDivertHelperCalcChecksums 计算校验值
UINT WinDivertHelperCalcChecksums(
__inout PVOID pPacket,
__in UINT packetLen,
__in_opt PWINDIVERT_ADDRESS pAddr,
__in UINT64 flags
);
!!!!注意!这个函数会直接在pPacket缓冲区中计算各个校验值。返回值是表明计算了多少个校验值。!!!
3. 基本流程
Windivert的基本流程贼好使,就三大步。
- 1.打开一个windivert对象,设置好过滤规则。
- 2.编写一个循环:抓包——>修改包——>计算校验和——>发送注入后的包
- 3.循环结束后,关闭windivert对象。
HANDLE handle; // WinDivert handle
WINDIVERT_ADDRESS addr; // Packet address
char packet[MAXBUF]; // Packet buffer
UINT packetLen;
handle = WinDivertOpen("...", 0, 0, 0); // Open some filter
if (handle == INVALID_HANDLE_VALUE)
{
// Handle error
exit(1);
}
// Main capture-modify-inject loop:
while (TRUE)
{
if (!WinDivertRecv(handle, packet, sizeof(packet), &addr, &packetLen))
{
// Handle recv error
continue;
}
// Modify packet.
WinDivertHelperCalcChecksums(packet, packetLen, &addr, 0);
if (!WinDivertSend(handle, packet, packetLen, &addr, NULL))
{
// Handle send error
continue;
}
}
WinDivertClose(handle);
示例
#include <winsock2.h>
#include <windows.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include "windivert.h"
#define MAXBUF 0xFFFF
#define PROXY_PORT 34010
#define ALT_PORT 43010
#define MAX_LINE 65
/*
* Proxy server configuration.
*/
typedef struct
{
UINT16 proxy_port;
UINT16 alt_port;
} PROXY_CONFIG, *PPROXY_CONFIG;
typedef struct
{
SOCKET s;
UINT16 alt_port;
struct in_addr dest;
} PROXY_CONNECTION_CONFIG, *PPROXY_CONNECTION_CONFIG;
typedef struct
{
BOOL inbound;
SOCKET s;
SOCKET t;
} PROXY_TRANSFER_CONFIG, *PPROXY_TRANSFER_CONFIG;
/*
* Lock to sync output.
*/
static HANDLE lock;
/*
* Prototypes.
*/
static DWORD proxy(LPVOID arg);
static DWORD proxy_connection_handler(LPVOID arg);
static DWORD proxy_transfer_handler(LPVOID arg);
/*
* Error handling.
*/
static void message(const char *msg, ...)
{
va_list args;
va_start(args, msg);
WaitForSingleObject(lock, INFINITE);
vfprintf(stderr, msg, args);
putc('\n', stderr);
ReleaseMutex(lock);
va_end(args);
}
#define error(msg, ...) \
do { \
message("error: " msg, ## __VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (FALSE)
#define warning(msg, ...) \
message("warning: " msg, ## __VA_ARGS__)
/*
* Cleanup completed I/O requests.
*/
static void cleanup(HANDLE ioport, OVERLAPPED *ignore)
{
OVERLAPPED *overlapped;
DWORD iolen;
ULONG_PTR iokey = 0;
while (GetQueuedCompletionStatus(ioport, &iolen, &iokey, &overlapped, 0))
{
if (overlapped != ignore)
{
free(overlapped);
}
}
}
/*
* Entry.
*/
int __cdecl main(int argc, char **argv)
{
HANDLE handle, thread;
UINT16 port, proxy_port, alt_port;
int r;
char filter[256];
INT16 priority = 123; // Arbitrary.
PPROXY_CONFIG config;
unsigned char packet[MAXBUF];
UINT packet_len;
WINDIVERT_ADDRESS addr;
PWINDIVERT_IPHDR ip_header;
PWINDIVERT_TCPHDR tcp_header;
OVERLAPPED *poverlapped;
OVERLAPPED overlapped;
HANDLE ioport, event;
DWORD len;
// Init.
if (argc != 2)
{
fprintf(stderr, "usage: %s dest-port\n", argv[0]);
exit(EXIT_FAILURE);
}
port = (UINT16)atoi(argv[1]);
if (port < 0 || port > 0xFFFF)
{
fprintf(stderr, "error: invalid port number (%d)\n", port);
exit(EXIT_FAILURE);
}
proxy_port = (port == PROXY_PORT? PROXY_PORT+1: PROXY_PORT);
alt_port = (port == ALT_PORT? ALT_PORT+1: ALT_PORT);
lock = CreateMutex(NULL, FALSE, NULL);
if (lock == NULL)
{
fprintf(stderr, "error: failed to create mutex (%d)\n",
GetLastError());
exit(EXIT_FAILURE);
}
ioport = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (ioport == NULL)
{
error("failed to create I/O completion port (%d)", GetLastError());
}
event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (event == NULL)
{
error("failed to create event (%d)", GetLastError());
}
// Divert all traffic to/from `port', `proxy_port' and `alt_port'.
r = snprintf(filter, sizeof(filter),
"tcp and "
"(tcp.DstPort == %d or tcp.DstPort == %d or tcp.DstPort == %d or "
"tcp.SrcPort == %d or tcp.SrcPort == %d or tcp.SrcPort == %d)",
port, proxy_port, alt_port, port, proxy_port, alt_port);
if (r < 0 || r >= sizeof(filter))
{
error("failed to create filter string");
}
handle = WinDivertOpen(filter, WINDIVERT_LAYER_NETWORK, priority, 0);
if (handle == INVALID_HANDLE_VALUE)
{
error("failed to open the WinDivert device (%d)", GetLastError());
}
if (CreateIoCompletionPort(handle, ioport, 0, 0) == NULL)
{
error("failed to associate I/O completion port (%d)", GetLastError());
}
// Spawn proxy thread,
config = (PPROXY_CONFIG)malloc(sizeof(PROXY_CONFIG));
if (config == NULL)
{
error("failed to allocate memory");
}
config->proxy_port = proxy_port;
config->alt_port = alt_port;
thread = CreateThread(NULL, 1, (LPTHREAD_START_ROUTINE)proxy,
(LPVOID)config, 0, NULL);
if (thread == NULL)
{
error("failed to create thread (%d)", GetLastError());
}
CloseHandle(thread);
// Main loop:
while (TRUE)
{
memset(&overlapped, 0, sizeof(overlapped));
ResetEvent(event);
overlapped.hEvent = event;
if (!WinDivertRecvEx(handle, packet, sizeof(packet), 0, &addr,
&packet_len, &overlapped))
{
if (GetLastError() != ERROR_IO_PENDING)
{
read_failed:
warning("failed to read packet (%d)", GetLastError());
continue;
}
// Timeout = 1s
while (WaitForSingleObject(event, 1000) == WAIT_TIMEOUT)
{
cleanup(ioport, &overlapped);
}
if (!GetOverlappedResult(handle, &overlapped, &len, FALSE))
{
goto read_failed;
}
packet_len = len;
}
cleanup(ioport, &overlapped);
if (!WinDivertHelperParsePacket(packet, packet_len, &ip_header, NULL,
NULL, NULL, &tcp_header, NULL, NULL, NULL))
{
warning("failed to parse packet (%d)", GetLastError());
continue;
}
switch (addr.Direction)
{
case WINDIVERT_DIRECTION_OUTBOUND:
if (tcp_header->DstPort == htons(port))
{
// Reflect: PORT ---> PROXY
UINT32 dst_addr = ip_header->DstAddr;
tcp_header->DstPort = htons(proxy_port);
ip_header->DstAddr = ip_header->SrcAddr;
ip_header->SrcAddr = dst_addr;
addr.Direction = WINDIVERT_DIRECTION_INBOUND;
}
else if (tcp_header->SrcPort == htons(proxy_port))
{
// Reflect: PROXY ---> PORT
UINT32 dst_addr = ip_header->DstAddr;
tcp_header->SrcPort = htons(port);
ip_header->DstAddr = ip_header->SrcAddr;
ip_header->SrcAddr = dst_addr;
addr.Direction = WINDIVERT_DIRECTION_INBOUND;
}
else if (tcp_header->DstPort == htons(alt_port))
{
// Redirect: ALT ---> PORT
tcp_header->DstPort = htons(port);
}
break;
case WINDIVERT_DIRECTION_INBOUND:
if (tcp_header->SrcPort == htons(port))
{
// Redirect: PORT ---> ALT
tcp_header->SrcPort = htons(alt_port);
}
break;
}
WinDivertHelperCalcChecksums(packet, packet_len, &addr, 0);
poverlapped = (OVERLAPPED *)malloc(sizeof(OVERLAPPED));
if (poverlapped == NULL)
{
error("failed to allocate memory");
}
memset(poverlapped, 0, sizeof(OVERLAPPED));
if (WinDivertSendEx(handle, packet, packet_len, 0, &addr, NULL,
poverlapped))
{
continue;
}
if (GetLastError() != ERROR_IO_PENDING)
{
warning("failed to send packet (%d)", GetLastError());
continue;
}
}
return 0;
}
/*
* Proxy server thread.
*/
static DWORD proxy(LPVOID arg)
{
PPROXY_CONFIG config = (PPROXY_CONFIG)arg;
UINT16 proxy_port = config->proxy_port;
UINT16 alt_port = config->alt_port;
int on = 1;
WSADATA wsa_data;
WORD wsa_version = MAKEWORD(2, 2);
struct sockaddr_in addr;
SOCKET s;
HANDLE thread;
free(config);
if (WSAStartup(wsa_version, &wsa_data) != 0)
{
error("failed to start WSA (%d)", GetLastError());
}
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
error("failed to create socket (%d)", WSAGetLastError());
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(int))
== SOCKET_ERROR)
{
error("failed to re-use address (%d)", GetLastError());
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(proxy_port);
if (bind(s, (SOCKADDR *)&addr, sizeof(addr)) == SOCKET_ERROR)
{
error("failed to bind socket (%d)", WSAGetLastError());
}
if (listen(s, 16) == SOCKET_ERROR)
{
error("failed to listen socket (%d)", WSAGetLastError());
}
while (TRUE)
{
// Wait for a new connection.
PPROXY_CONNECTION_CONFIG config;
int size = sizeof(addr);
SOCKET t = accept(s, (SOCKADDR *)&addr, &size);
if (t == INVALID_SOCKET)
{
warning("failed to accept socket (%d)", WSAGetLastError());
continue;
}
// Spawn proxy connection handler thread.
config = (PPROXY_CONNECTION_CONFIG)
malloc(sizeof(PROXY_CONNECTION_CONFIG));
if (config == NULL)
{
error("failed to allocate memory");
}
config->s = t;
config->alt_port = alt_port;
config->dest = addr.sin_addr;
thread = CreateThread(NULL, 1,
(LPTHREAD_START_ROUTINE)proxy_connection_handler,
(LPVOID)config, 0, NULL);
if (thread == NULL)
{
warning("failed to create thread (%d)", GetLastError());
closesocket(t);
free(config);
continue;
}
CloseHandle(thread);
}
}
/*
* Proxy connection handler thread.
*/
static DWORD proxy_connection_handler(LPVOID arg)
{
PPROXY_TRANSFER_CONFIG config1, config2;
HANDLE thread;
PPROXY_CONNECTION_CONFIG config = (PPROXY_CONNECTION_CONFIG)arg;
SOCKET s = config->s, t;
UINT16 alt_port = config->alt_port;
struct in_addr dest = config->dest;
struct sockaddr_in addr;
free(config);
t = socket(AF_INET, SOCK_STREAM, 0);
if (t == INVALID_SOCKET)
{
warning("failed to create socket (%d)", WSAGetLastError());
closesocket(s);
return 0;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(alt_port);
addr.sin_addr = dest;
if (connect(t, (SOCKADDR *)&addr, sizeof(addr)) == SOCKET_ERROR)
{
warning("failed to connect socket (%d)", WSAGetLastError());
closesocket(s);
closesocket(t);
return 0;
}
config1 = (PPROXY_TRANSFER_CONFIG)malloc(sizeof(PROXY_TRANSFER_CONFIG));
config2 = (PPROXY_TRANSFER_CONFIG)malloc(sizeof(PROXY_TRANSFER_CONFIG));
if (config1 == NULL || config2 == NULL)
{
error("failed to allocate memory");
}
config1->inbound = FALSE;
config2->inbound = TRUE;
config2->t = config1->s = s;
config2->s = config1->t = t;
thread = CreateThread(NULL, 1,
(LPTHREAD_START_ROUTINE)proxy_transfer_handler, (LPVOID)config1, 0,
NULL);
if (thread == NULL)
{
warning("failed to create thread (%d)", GetLastError());
closesocket(s);
closesocket(t);
free(config1);
free(config2);
return 0;
}
proxy_transfer_handler((LPVOID)config2);
WaitForSingleObject(thread, INFINITE);
CloseHandle(thread);
closesocket(s);
closesocket(t);
return 0;
}
/*
* Handle the transfer of data from one socket to another.
*/
static DWORD proxy_transfer_handler(LPVOID arg)
{
PPROXY_TRANSFER_CONFIG config = (PPROXY_TRANSFER_CONFIG)arg;
BOOL inbound = config->inbound;
SOCKET s = config->s, t = config->t;
char buf[8192];
int len, len2, i;
HANDLE console;
free(config);
while (TRUE)
{
// Read data from s.
len = recv(s, buf, sizeof(buf), 0);
if (len == SOCKET_ERROR)
{
warning("failed to recv from socket (%d)", WSAGetLastError());
shutdown(s, SD_BOTH);
shutdown(t, SD_BOTH);
return 0;
}
if (len == 0)
{
shutdown(s, SD_RECEIVE);
shutdown(t, SD_SEND);
return 0;
}
// Dump stream information to the screen.
console = GetStdHandle(STD_OUTPUT_HANDLE);
WaitForSingleObject(lock, INFINITE);
printf("[%.4d] ", len);
SetConsoleTextAttribute(console,
(inbound? FOREGROUND_RED: FOREGROUND_GREEN));
for (i = 0; i < len && i < MAX_LINE; i++)
{
putchar((isprint(buf[i])? buf[i]: '.'));
}
SetConsoleTextAttribute(console,
FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
printf("%s\n", (len > MAX_LINE? "...": ""));
ReleaseMutex(lock);
// Send data to t.
for (i = 0; i < len; )
{
len2 = send(t, buf+i, len-i, 0);
if (len2 == SOCKET_ERROR)
{
warning("failed to send to socket (%d)", WSAGetLastError());
shutdown(s, SD_BOTH);
shutdown(t, SD_BOTH);
return 0;
}
i += len2;
}
}
return 0;
}