# -*- coding: utf-8 -*-
# Copyright (C) 2016 Fabio Falcinelli, Maximilian Hils
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
import subprocess
import sys
from ctypes import byref, c_uint64, c_uint, c_char, c_char_p
from pydivert import windivert_dll
from pydivert.consts import Layer, Direction, Flag
from pydivert.packet import Packet
from pydivert.util import PY2
DEFAULT_PACKET_BUFFER_SIZE = 1500
class WinDivert(object):
"""
A WinDivert handle that can be used to capture packets.
The main methods are `.open()`, `.recv()`, `.send()` and `.close()`.
Use it like so::
with pydivert.WinDivert() as w:
for packet in w:
print(packet)
w.send(packet)
"""
def __init__(self, filter="true", layer=Layer.NETWORK, priority=0, flags=Flag.DEFAULT):
self._handle = None
self._filter = filter.encode()
self._layer = layer
self._priority = priority
self._flags = flags
def __repr__(self):
return ''.format(
"open" if self._handle is not None else "closed",
self._filter.decode(),
self._layer,
self._priority,
self._flags
)
def __enter__(self):
self.open()
return self
def __exit__(self, *args):
self.close()
def __iter__(self):
return self
def __next__(self):
return self.recv()
if sys.version_info < (3, 0):
next = __next__
@staticmethod
def register():
"""
An utility method to register the service the first time.
It is usually not required to call this function, as WinDivert will register itself when opening a handle.
"""
with WinDivert("false"):
pass
@staticmethod
def is_registered():
"""
Check if the WinDivert service is currently installed on the system.
"""
return subprocess.call("sc query WinDivert1.3", stdout=subprocess.PIPE,
stderr=subprocess.PIPE) == 0
@staticmethod
def unregister():
"""
Unregisters the WinDivert service.
This function only requests a service stop, which may not be processed immediately if there are still open
handles.
"""
subprocess.check_call("sc stop WinDivert1.3", stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@staticmethod
def check_filter(filter, layer=Layer.NETWORK):
"""
Checks if the given packet filter string is valid with respect to the filter language.
The remapped function is WinDivertHelperCheckFilter::
BOOL WinDivertHelperCheckFilter(
__in const char *filter,
__in WINDIVERT_LAYER layer,
__out_opt const char **errorStr,
__out_opt UINT *errorPos
);
See: https://reqrypt.org/windivert-doc.html#divert_helper_check_filter
:return: A tuple (res, pos, msg) with check result in 'res' human readable description of the error in 'msg' and the error's position in 'pos'.
"""
res, pos, msg = False, c_uint(), c_char_p()
try:
res = windivert_dll.WinDivertHelperCheckFilter(filter.encode(), layer, byref(msg), byref(pos))
except OSError:
pass
return res, pos.value, msg.value.decode()
def open(self):
"""
Opens a WinDivert handle for the given filter.
Unless otherwise specified by flags, any packet that matches the filter will be diverted to the handle.
Diverted packets can be read by the application with receive().
The remapped function is WinDivertOpen::
HANDLE WinDivertOpen(
__in const char *filter,
__in WINDIVERT_LAYER layer,
__in INT16 priority,
__in UINT64 flags
);
For more info on the C call visit: http://reqrypt.org/windivert-doc.html#divert_open
"""
if self.is_open:
raise RuntimeError("WinDivert handle is already open.")
self._handle = windivert_dll.WinDivertOpen(self._filter, self._layer, self._priority,
self._flags)
@property
def is_open(self):
"""
Indicates if there is currently an open handle.
"""
return bool(self._handle)
def close(self):
"""
Closes the handle opened by open().
The remapped function is WinDivertClose::
BOOL WinDivertClose(
__in HANDLE handle
);
For more info on the C call visit: http://reqrypt.org/windivert-doc.html#divert_close
"""
if not self.is_open:
raise RuntimeError("WinDivert handle is not open.")
windivert_dll.WinDivertClose(self._handle)
self._handle = None
def recv(self, bufsize=DEFAULT_PACKET_BUFFER_SIZE):
"""
Receives a diverted packet that matched the filter.
The remapped function is WinDivertRecv::
BOOL WinDivertRecv(
__in HANDLE handle,
__out PVOID pPacket,
__in UINT packetLen,
__out_opt PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *recvLen
);
For more info on the C call visit: http://reqrypt.org/windivert-doc.html#divert_recv
:return: The return value is a `pydivert.Packet`.
"""
if self._handle is None:
raise RuntimeError("WinDivert handle is not open")
packet = bytearray(bufsize)
packet_ = (c_char * bufsize).from_buffer(packet)
address = windivert_dll.WinDivertAddress()
recv_len = c_uint(0)
windivert_dll.WinDivertRecv(self._handle, packet_, bufsize, byref(address), byref(recv_len))
return Packet(
memoryview(packet)[:recv_len.value],
(address.IfIdx, address.SubIfIdx),
Direction(address.Direction)
)
def send(self, packet, recalculate_checksum=True):
"""
Injects a packet into the network stack.
Recalculates the checksum before sending unless recalculate_checksum=False is passed.
The injected packet may be one received from recv(), or a modified version, or a completely new packet.
Injected packets can be captured and diverted again by other WinDivert handles with lower priorities.
The remapped function is WinDivertSend::
BOOL WinDivertSend(
__in HANDLE handle,
__in PVOID pPacket,
__in UINT packetLen,
__in PWINDIVERT_ADDRESS pAddr,
__out_opt UINT *sendLen
);
For more info on the C call visit: http://reqrypt.org/windivert-doc.html#divert_send
:return: The return value is the number of bytes actually sent.
"""
if recalculate_checksum:
packet.recalculate_checksums()
send_len = c_uint(0)
if PY2:
# .from_buffer(memoryview) does not work on PY2
buff = bytearray(packet.raw)
else:
buff = packet.raw
buff = (c_char * len(packet.raw)).from_buffer(buff)
windivert_dll.WinDivertSend(self._handle, buff, len(packet.raw), byref(packet.wd_addr),
byref(send_len))
return send_len
def get_param(self, name):
"""
Get a WinDivert parameter. See pydivert.Param for the list of parameters.
The remapped function is WinDivertGetParam::
BOOL WinDivertGetParam(
__in HANDLE handle,
__in WINDIVERT_PARAM param,
__out UINT64 *pValue
);
For more info on the C call visit: http://reqrypt.org/windivert-doc.html#divert_get_param
:return: The parameter value.
"""
value = c_uint64(0)
windivert_dll.WinDivertGetParam(self._handle, name, byref(value))
return value.value
def set_param(self, name, value):
"""
Set a WinDivert parameter. See pydivert.Param for the list of parameters.
The remapped function is DivertSetParam::
BOOL WinDivertSetParam(
__in HANDLE handle,
__in WINDIVERT_PARAM param,
__in UINT64 value
);
For more info on the C call visit: http://reqrypt.org/windivert-doc.html#divert_set_param
"""
return windivert_dll.WinDivertSetParam(self._handle, name, value)