WinDivert in C#

三世轮回 提交于 2019-12-23 03:31:17

问题


I want to call 5 WinDivert functions in a C# code, first reflex : PInvoke here are the signatures :

        internal enum WINDIVERT_LAYER
        {
            WINDIVERT_LAYER_NETWORK = 0,        /* Network layer. */
            WINDIVERT_LAYER_NETWORK_FORWARD = 1 /* Network layer (forwarded packets) */
        }

        internal const UInt64 WINDIVERT_FLAG_SNIFF = 1;

        /*
         * typedef struct
            {
                UINT32 IfIdx;
                UINT32 SubIfIdx;
                UINT8  Direction;
            } WINDIVERT_ADDRESS, *PWINDIVERT_ADDRESS;
         * */
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal struct WINDIVERT_ADDRESS
        {
            UInt32 IfIdx;
            UInt32 SubIfIdx;
            byte  Direction;
        }


        /*
         * typedef struct
            {
                UINT8  HdrLength:4;
                UINT8  Version:4;
                UINT8  TOS;
                UINT16 Length;
                UINT16 Id;
                UINT16 FragOff0;
                UINT8  TTL;
                UINT8  Protocol;
                UINT16 Checksum;
                UINT32 SrcAddr;
                UINT32 DstAddr;
            } WINDIVERT_IPHDR, *PWINDIVERT_IPHDR;
         * */

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal struct WINDIVERT_IPHDR
        {
            byte  HdrLengthAndVersion; 
            byte  TOS;
            UInt16 Length;
            UInt16 Id;
            UInt16 FragOff0;
            byte  TTL;
            byte  Protocol;
            UInt16 Checksum;
            UInt32 SrcAddr;
            UInt32 DstAddr;
        }


        /*
         * typedef struct
            {
                UINT16 SrcPort;
                UINT16 DstPort;
                UINT16 Length;
                UINT16 Checksum;
            } WINDIVERT_UDPHDR, *PWINDIVERT_UDPHDR;
         * */
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal struct WINDIVERT_UDPHDR
        {
            UInt16 SrcPort;
            UInt16 DstPort;
            UInt16 Length;
            UInt16 Checksum;
        }

        /*
         * HANDLE WinDivertOpen(
                __in const char *filter,
                __in WINDIVERT_LAYER layer,
                __in INT16 priority,
                __in UINT64 flags
            );
         * */
        [DllImport("WinDivert.dll", EntryPoint = "WinDivertOpen", SetLastError = true, CharSet = CharSet.Auto, 
            ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        internal static extern IntPtr WinDivertOpen(
            [MarshalAs(UnmanagedType.LPStr)] string filter, 
            WINDIVERT_LAYER layer,
            Int16 priority,
            UInt64 flags);


        /*
         * BOOL WinDivertClose(
                __in HANDLE handle
            );
         * */
        [DllImport("WinDivert.dll", EntryPoint = "WinDivertClose", SetLastError = true, CharSet = CharSet.Auto,
            ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        [return:MarshalAs(UnmanagedType.Bool)]
        internal static extern bool WinDivertClose(IntPtr handle);

        /*
         * BOOL WinDivertRecv(
                __in HANDLE handle,
                __out PVOID pPacket,
                __in UINT packetLen,
                __out_opt PWINDIVERT_ADDRESS pAddr,
                __out_opt UINT *recvLen
            );
         * */
        [DllImport("WinDivert.dll", EntryPoint = "WinDivertRecv", SetLastError = true, CharSet = CharSet.Auto, 
            ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool WinDivertRecv(IntPtr handle, out IntPtr pPacket, uint packetLen, [Optional] out IntPtr pAddr, [Optional] out uint readLen);



        /*
         * 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
            );
         * */
        [DllImport("WinDivert.dll", EntryPoint = "WinDivertHelperParsePacket", SetLastError = true, CharSet = CharSet.Auto,
            ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool WinDivertHelperParsePacket(IntPtr pPacket, uint packetLen, [Optional] out IntPtr ppIpHdr, [Optional] out IntPtr ppIpv6Hdr,
           [Optional] out IntPtr ppIcmpHdr, [Optional] out IntPtr ppTcpHdr, [Optional] out IntPtr ppUdpHdr, [Optional] out IntPtr ppData, 
            [Optional]out uint pDataLen);

        /*
         * BOOL WinDivertSend(
                __in HANDLE handle,
                __in PVOID pPacket,
                __in UINT packetLen,
                __in PWINDIVERT_ADDRESS pAddr,
                __out_opt UINT *sendLen
            );
         * */
        [DllImport("WinDivert.dll", EntryPoint = "WinDivertSend", SetLastError = true, CharSet = CharSet.Auto,
            ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool WinDivertSend(IntPtr handle, IntPtr pPacket, uint packetLen, IntPtr pAddr, [Optional] out uint sendLen);

I have managed to avoid (87 = ERROR_INVALID_PARAMETER), (998 = ERROR_NOACCESS) errors on calls to WinDivertOpen(), WinDivertClose() and WinDivertRecv() but I'm still getting System.AccessViolationException : Attempted to read or write protected memory. This is often an indication that other memory has been corrupted and (998 = ERROR_NOACCESS) when trying to call WinDivertHelperParsePacket().

Here's the code :

static void Main(string[] args)
    {
        const uint MAXBUF = 0xFFFF;
        IntPtr handle;
        IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
        IntPtr packet = Marshal.AllocHGlobal((int)MAXBUF);
        uint packetLen;
        IntPtr ip_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_IPHDR)));
        IntPtr udp_header = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_UDPHDR)));
        IntPtr payload;
        uint payload_len;
        uint sendLen;
        IntPtr opt_param = IntPtr.Zero;
                    byte[] managedPacket;
        IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
        handle = NativeMethods.WinDivertOpen("udp.DstPort == 53", NativeMethods.WINDIVERT_LAYER.WINDIVERT_LAYER_NETWORK, 404, NativeMethods.WINDIVERT_FLAG_SNIFF);
        if (handle == INVALID_HANDLE_VALUE) Console.WriteLine("open error:" + Marshal.GetLastWin32Error());
        else
        {
            while (true)
            {
                if (!NativeMethods.WinDivertRecv(handle, out packet, MAXBUF, out addr, out packetLen))
                {
                    Console.WriteLine("Recv error:" + Marshal.GetLastWin32Error());
                    continue;
                }
                try
                {
                    managedPacket = new byte[(int)packetLen];
                    Marshal.Copy(packet, managedPacket, 0, (int)packetLen); // causes AccessViolationException

                    Console.WriteLine("---------------------------------");

                    /*for (int i = 0; i < packetLen; i++)
                    {
                        Console.Write("{0:X}", managedPacket[i]);
                    }*/
                    Console.WriteLine("---------------------------------");
                }
                catch(Exception ex)
                {
                    Console.WriteLine("copy error :" + ex.Message);
                }
                    if (!NativeMethods.WinDivertHelperParsePacket(packet, packetLen, out ip_header, out opt_param, out opt_param, out opt_param, out udp_header, out payload, out payload_len)) // causes AccessViolationException
                    {
                        Console.WriteLine("Parse error:" + Marshal.GetLastWin32Error());
                        //continue;
                    }

                if (!NativeMethods.WinDivertSend(handle, packet, packetLen, addr, out sendLen))
                {
                    Console.WriteLine("Send error:" + Marshal.GetLastWin32Error());
                    continue;
                }
            }
            /*if (!NativeMethods.WinDivertClose(handle))
                Console.WriteLine("close error:" + Marshal.GetLastWin32Error());*/
        }



        Console.ReadKey();
    }

My boss told me that it's better/easier to write a COM object in C++ that wraps the C calls and expose it to C# to avoid the marshaling and memory handling pain. Should I stick to PInvoke or go the COM way ?

EDIT : Updates

I have tried two different ways of allocating unmanaged memory and both failed (unsafe code allowed) :

 byte[] managedPacket = new byte[(int)packetLen];     
 NativeMethods.WINDIVERT_ADDRESS windivertAddr = new NativeMethods.WINDIVERT_ADDRESS();
 GCHandle managedPacketHandle = GCHandle.Alloc(managedPacket, GCHandleType.Pinned);
 IntPtr managedPacketPointer = managedPacketHandle.AddrOfPinnedObject();
 GCHandle windivertAddrHandle = GCHandle.Alloc(windivertAddr, GCHandleType.Pinned);
 IntPtr windivertAddrPointer = managedPacketHandle.AddrOfPinnedObject();
 NativeMethods.WinDivertRecv(handle, out managedPacketPointer, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out windivertAddrPointer , out readLen);
 // output of managed array and struct fields = 0 and it still causes unhandled AccessViolationException even inside a try/catch
 managedPacketHandle.Free();
 windivertAddrPointer.Free(); 

and :

IntPtr packet = Marshal.AllocHGlobal((int)packetLen * Marshal.SizeOf(typeof(System.Byte)));
IntPtr addr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NativeMethods.WINDIVERT_ADDRESS)));
NativeMethods.WinDivertRecv(handle, out packet, (uint)(packetLen * Marshal.SizeOf(typeof(System.Byte))), out addr, out readLen)
byte[] managedPacket = new byte[(int)packetLen];
Marshal.Copy(packet, managedPacket, 0, (int)readLen);
NativeMethods.WINDIVERT_ADDRESS windivertAddr = (NativeMethods.WINDIVERT_ADDRESS)Marshal.PtrToStructure(addr, typeof(NativeMethods.WINDIVERT_ADDRESS));
// no output of managed array and struct fields and the same unhandled AccessViolationException 
Marshal.FreeHGlobal(addr);
Marshal.FreeHGlobal(packet);

Also sometimes inside a loop, WinDivRecv fails with LastWin32Error : 6 = INVALID_HANDLE_VALUE is it because the GC is messing with the handle ? I tried GC.KeepAlive(handle) and it didn't change anything.

C++\CLI wrapper : (bridge between unmanaged C DLL and managed C# code)

[Suggested option in the comments below]

I followed these steps :

  1. Create a C++/CLI library project
  2. Create a Native C++ class that wraps the C functions
  3. Create a managed C++/CLI class with a field pointing to a native class instance and wraps all the unmanaged methods and do the necessary marshalling.
  4. Try to build ==> fails with the famous LNK2019 and LNK2028

Should I add the WinDivert DLL as a reference or only WinDivert.h ? What about the kernel driver .sys files ? And honestly it's not easier than PInvoke but a lot worse, I still have to do the same marshalling of non-blittable data types and struct/enum definitions !

来源:https://stackoverflow.com/questions/23296106/windivert-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!