2014年7月9日 星期三

Using WinPcap in C# (Packet capture)

WinPcap is a very useful tool which can enables users to capture windows packets. On their website they offer a development pack but only for C/C++ programmers. So this is why I decided to write my own (simple) class for capturing packets in C# importing the wpcap.dll . Before showing the code, you must know that in order for your program to run as it should in Windows Vista/7 you must run it as administrator, otherwise it won’t even find the (network) devices.
I
n order to import the functions we must first define some structures:
        [StructLayout(LayoutKind.Sequential)]
        struct pcap_if
        {
            public IntPtr next;
            public string name; //name of device
            public string description; //description of device
            public pcap_addr addresses;
            public int flags;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct pcap_addr
        {
            public IntPtr next;
            public IntPtr addr;
            public IntPtr netmask;
            public IntPtr broadaddr;
            public IntPtr dstaddr;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct sockaddr
        {
            public Int16 sa_family;
            public string sa_data;
        }

        [StructLayout(LayoutKind.Sequential)]
        struct pcap_pkthdr
        {
            public timeval ts;
            public int caplen; //captured length
            public int len; //packet length
        }

        [StructLayout(LayoutKind.Sequential)]
        struct timeval
        {
            public int tv_sec;
            public int tv_usec;
        }

        //some credentials if we want to capture packets remotely
        //CAN BE VERY USEFUL!!
        [StructLayout(LayoutKind.Sequential)]
        struct pcap_rmtauth
        {
            public int type;
            public string username; 
            public string password;
        };
The following step we must do is defining three constants (there are more which are supported, but we don’t need them for this program):
        const int PCAP_ERRBUF_SIZE = 256;
        const int PCAP_OPENFLAG_PROMISCUOUS = 1;
        const string PCAP_SRC_IF_STRING = "rpcap://";
Now we must also define the functions we will use from wpcap.dll :
        [DllImport("wpcap.dll")]
        static extern IntPtr pcap_open(string source, int snaplen, int flags, int read_timeout, IntPtr auth, ref IntPtr errbuff);
        [DllImport("wpcap.dll")]
        static extern void pcap_freealldevs(IntPtr alldevs);
        //auth is a managed pointer to a structure of type pcap_rmtauth
        [DllImport("wpcap.dll")]
        static extern int pcap_findalldevs_ex(string source, IntPtr auth, ref IntPtr alldevs, ref IntPtr errbuff);
        [DllImport("wpcap.dll")]
        static extern int pcap_next_ex(IntPtr conn, ref IntPtr header, ref IntPtr packetdata);
        [DllImport("wpcap.dll")]
        static extern void pcap_close(IntPtr conn);
In order to get all the devices you can use this function I created:
        public List<deviceDetails> getDeviceList()
        {
            try
            {
                List ret = new List();
                IntPtr ip = allDevices;
                while (ip != IntPtr.Zero)
                {
                    pcap_if dev = (pcap_if)Marshal.PtrToStructure(ip, typeof(pcap_if));
                    deviceDetails dd = new deviceDetails();
                    dd.name = dev.name;
                    dd.description = dev.description;
                    ret.Add(dd);
                    ip = dev.next;
                }
                if (ret.Count == 0)
                    OnErrorEvent("No devices found!");
                return ret;
            }
            catch (Exception ex)
            {
                OnErrorEvent(ex.Message);
                return null;
            }
        }
where deviceDetails is a structure with two fields (name and description).
You can also used the class Tuple which is available in C# 4.0 like this:
        public List<Tuple<string,string>> getDeviceList()
        {
            try
            {
                List ret = new List();
                IntPtr ip = allDevices;
                while (ip != IntPtr.Zero)
                {
                    pcap_if dev = (pcap_if)Marshal.PtrToStructure(ip, typeof(pcap_if));
                    Tuple<string,string> dd = new Tuple<string,string>(dev.name,dev.description);
                    ret.Add(dd);
                    ip = dev.next;
                }
                if (ret.Count == 0)
                    OnErrorEvent("No devices found!");
                return ret;
            }
            catch (Exception ex)
            {
                OnErrorEvent(ex.Message);
                return null;
            }
        }
To open and capture packets from devices the next two functions could be used:
        public IntPtr openDevice(deviceDetails dev)
        {
            IntPtr fp = IntPtr.Zero;
            IntPtr referrbuff = IntPtr.Zero;
            fp = pcap_open(dev.name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, IntPtr.Zero, ref referrbuff); 
            if (fp == IntPtr.Zero)
            {
                OnErrorEvent(PtrToString(referrbuff, PCAP_ERRBUF_SIZE));
                return IntPtr.Zero;
            }
            return fp;
        }

        //fp refers to the returned pointer of the previous function.
        private void startCapture(IntPtr fp,AsyncOperation async) 
        {
            try
            {
                IntPtr hdr = IntPtr.Zero;
                IntPtr data = IntPtr.Zero;
                int res;
                pcap_pkthdr header = new pcap_pkthdr();
                while ((res = pcap_next_ex(fp, ref hdr, ref data)) >= 0)
                {
                    if (res == 0)
                        continue;
                    header = (pcap_pkthdr)Marshal.PtrToStructure(hdr, typeof(pcap_pkthdr));
                    //asynchronously calls the function OnPacketCaptured with the arguments: data (the captured data) and header.caplen (the length of the data).
                    async.Post (
                        delegate (object e) {
                            object[] inputdata = (object[])e;
                            OnPacketCapturedEvent((int)inputdata[0], PtrToString((IntPtr)inputdata[1], (int)inputdata[0]));
                        },
                        (object)(new object[] {header.caplen,data}
                    ));
                    hdr = IntPtr.Zero;
                    data = IntPtr.Zero;
                }
            }
            catch (Exception ex)
            {
                OnErrorEvent(ex.Message);
                stopCapture(fp);
            }
        }
I think the code is straight forward so as you to understand how to use winpcap in C#. HOWEVER, NOTICE that I didn’t define some functions in this post (e.g. : OnErrorEvent, PtrToString, stopCapture, etc.) as well as I didn’t tell you how to call startCapture as you need an AsyncOperation parameter.
This should be for you to exercise!!
If you still struggle to understand some of the functions/parameters in winpcap I recommend you download the developer pack and look through the C/C++ source codes as they are fully documented using comments.
PS: In order to use DllImport, the Marshal class etc. you should import System.Runtime.InteropServices in your code. (using System.Runtime.InteropServices;)

Using WinPcap in your programs

Creating an application that uses wpcap.dll

To create an application that uses wpcap.dll with Microsoft Visual C++, follow these steps:
  • Include the file pcap.h at the beginning of every source file that uses the functions exported by library.
  • If your program uses Win32 specific functions of WinPcap, remember to include WPCAP among the preprocessor definitions.
  • If your program uses the remote capture capabilities of WinPcap, add HAVE_REMOTE among the preprocessor definitions. Do not include remote-ext.h directly in your source files.
  • Set the options of the linker to include the wpcap.lib library file specific for your target (x86 or x64). wpcap.lib for x86 can be found in the \lib folder of the WinPcap developer's pack, wpcap.lib for x64 can be found in the \lib\x64 folder.
  • Set the options of the linker to include the winsock library file ws2_32.lib. This file is distributed with the C compiler and contains the socket functions for Windows. It is needed by some functions used by the samples in the tutorial.
How to properly set Microsoft Visual Studio
Visual Studio 6
  • To add a preprocessor definition, you must select Settings from the Project menu, then select C/C++ from the tab control, and under the category General, you must add the definition under the Preprocessor Definitions text box.
  • To add a new library to the project with Microsoft Visual C++, you must select Settings from the Project menu, then select Link from the tab control, and then add the name of the new library in the Object/library modules editbox.
  • To add a new path where Microsoft Visual C++ will look for the libraries, you must select Options from the Tools menu, then Directories from the tab control, Library files from the Show directories for combobox, and the add the path in the Directories box.
  • To add a new path where Microsoft Visual C++ will look for include files, you must select Options from the Tools menu, then Directories from the tab control, Include files from the Show directories for combobox, and the add the path in the Directories box.
Visual Studio 2005 (needed to compile x64 applications)
  • To add a preprocessor definition, you must select Properties from the Project menu, then select C/C++ from the list control on the left, and under the category Preprocessor, you must add the definition under the Preprocessor Definitions text box.
  • To add a new library to the project, you must select Properties from the Project menu, then select Linker from the list control on the left, and under the category Input add the name of the new library in the Additional Dependencies text box.
  • To add a new path where Microsoft Visual Studio will look for the libraries, you must select Options from the Tools menu, then Project and Solutions from the list control on the left, VC++ Directories, then choose Library Files in the Show directories for combobox, and the add the path in the box below.
  • To add a new path where Microsoft Visual Studio will look for the include files, you must select Options from the Tools menu, then Project and Solutions from the list control on the left, VC++ Directories, then choose Include Files in the Show directories for combobox, and the add the path in the box below.

Sample programs

A couple of sample programs are provided to show the usage of the WinPcap API. The source of the examples, along with all the files needed to compile and run them, can be found in the Developer's Pack.  For didactic purpose we provide here a browsable version of the code: it is possible to click on the variables and functions to jump the documentation of each of them. For a more complete set of samples, try WinPcap Tutorial Section.

Packet Dump

This program reads packets from a file or a network adapter, depending on a command line switch. If a source is not provided, the program shows a list of available adapters, one of which can be selected. Once the capture is started, the program prints the timestamp, the length and the raw contents of the packets. Once compiled, it will run on all the Win32 platforms. It can be compiled to run on Unix as well (the makefile is provided).
/*
 * Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy)
 * Copyright (c) 2005 - 2006 CACE Technologies, Davis (California)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Politecnico di Torino, CACE Technologies 
 * nor the names of its contributors may be used to endorse or promote 
 * products derived from this software without specific prior written 
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


#include <stdlib.h>
#include <stdio.h>

//
// NOTE: remember to include WPCAP and HAVE_REMOTE among your
// preprocessor definitions.
//

#include <pcap.h>

#define LINE_LEN 16

int main(int argc, char **argv)
{   
pcap_if_t *alldevs, *d;
pcap_t *fp;
u_int inum, i=0;
char errbuf[PCAP_ERRBUF_SIZE];
int res;
struct pcap_pkthdr *header;
const u_char *pkt_data;

    printf("pktdump_ex: prints the packets of the network using WinPcap.\n");
    printf("   Usage: pktdump_ex [-s source]\n\n"
           "   Examples:\n"
           "      pktdump_ex -s file://c:/temp/file.acp\n"
           "      pktdump_ex -s rpcap://\\Device\\NPF_{C8736017-F3C3-4373-94AC-9A34B7DAD998}\n\n");

    if(argc < 3)
    {

        printf("\nNo adapter selected: printing the device list:\n");
        /* The user didn't provide a packet source: Retrieve the local device list */
        if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1)
        {
            fprintf(stderr,"Error in pcap_findalldevs_ex: %s\n", errbuf);
            return -1;
        }
        
        /* Print the list */
        for(d=alldevs; d; d=d->next)
        {
            printf("%d. %s\n    ", ++i, d->name);

            if (d->description)
                printf(" (%s)\n", d->description);
            else
                printf(" (No description available)\n");
        }
        
        if (i==0)
        {
            fprintf(stderr,"No interfaces found! Exiting.\n");
            return -1;
        }
        
        printf("Enter the interface number (1-%d):",i);
        scanf_s("%d", &inum);
        
        if (inum < 1 || inum > i)
        {
            printf("\nInterface number out of range.\n");

            /* Free the device list */
            pcap_freealldevs(alldevs);
            return -1;
        }
        
        /* Jump to the selected adapter */
        for (d=alldevs, i=0; i< inum-1 ;d=d->next, i++);
        
        /* Open the device */
        if ( (fp= pcap_open(d->name,
                            100 /*snaplen*/,
                            PCAP_OPENFLAG_PROMISCUOUS /*flags*/,
                            20 /*read timeout*/,
                            NULL /* remote authentication */,
                            errbuf)
                            ) == NULL)
        {
            fprintf(stderr,"\nError opening adapter\n");
            return -1;
        }
    }
    else 
    {
        // Do not check for the switch type ('-s')
        if ( (fp= pcap_open(argv[2],
                            100 /*snaplen*/,
                            PCAP_OPENFLAG_PROMISCUOUS /*flags*/,
                            20 /*read timeout*/,
                            NULL /* remote authentication */,
                            errbuf)
                            ) == NULL)
        {
            fprintf(stderr,"\nError opening source: %s\n", errbuf);
            return -1;
        }
    }

    /* Read the packets */
    while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
    {

        if(res == 0)
            /* Timeout elapsed */
            continue;

        /* print pkt timestamp and pkt len */
        printf("%ld:%ld (%ld)\n", header->ts.tv_sec, header->ts.tv_usec, header->len);          
        
        /* Print the packet */
        for (i=1; (i < header->caplen + 1 ) ; i++)
        {
            printf("%.2x ", pkt_data[i-1]);
            if ( (i % LINE_LEN) == 0) printf("\n");
        }
        
        printf("\n\n");     
    }

    if(res == -1)
    {
        fprintf(stderr, "Error reading the packets: %s\n", pcap_geterr(fp));
        return -1;
    }

    return 0;
}

Packet Filter

This is a more complete example of libpcap usage. It shows, among other things, how to create and set filters and how to save a capture to disk. It can be compiled under Win32 or Unix (projects and makefiles are provided). Pcap_filter (pf.exe) is a general-purpose packet filtering application: its input parameters are a source of packets (it can be a physical interface or a file), a filter and an output file. It takes packets from the source until CTRL+C is pressed or the whole file is processed, applies the filter to the incoming packets and saves them to the output file if they satisfy the filter. Pcap_filter can be used to dump network data according to a particular filter, but also to extract a set of packets from a previously saved file. The format of both input and output files is the format used by libpcap, i.e. same of WinDump, tcpdump and many other network tools.
/*
 * Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy)
 * Copyright (c) 2005 - 2006 CACE Technologies, Davis (California)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Politecnico di Torino, CACE Technologies 
 * nor the names of its contributors may be used to endorse or promote 
 * products derived from this software without specific prior written 
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */


#include <stdlib.h>
#include <stdio.h>

#include <pcap.h>

#define MAX_PRINT 80
#define MAX_LINE 16


void usage();


void main(int argc, char **argv)
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
char *source=NULL;
char *ofilename=NULL;
char *filter=NULL;
int i;
pcap_dumper_t *dumpfile;
struct bpf_program fcode;
bpf_u_int32 NetMask;
int res;
struct pcap_pkthdr *header;
const u_char *pkt_data;

    if (argc == 1)
    {
        usage();
        return;
    }

    for(i=1;i < argc; i+= 2)
    {

        switch (argv[i] [1])
        {
            case 's':
            {
                source=argv[i+1];
            };
            break;

            case 'o':
            {
                ofilename=argv[i+1];
            };
            break;

            case 'f':
            {
                filter=argv[i+1];
            };
            break;
        }
    }

    // open a capture from the network
    if (source != NULL)
    {
        if ( (fp= pcap_open(source,
                            1514 /*snaplen*/,
                            PCAP_OPENFLAG_PROMISCUOUS /*flags*/,
                            20 /*read timeout*/,
                            NULL /* remote authentication */,
                            errbuf)
                            ) == NULL)
        {
            fprintf(stderr,"\nUnable to open the adapter.\n");
            return;
        }
    }

    else usage();

    if (filter != NULL)
    {
        // We should loop through the adapters returned by the pcap_findalldevs_ex()
        // in order to locate the correct one.
        //
        // Let's do things simpler: we suppose to be in a C class network ;-)
        NetMask=0xffffff;

        //compile the filter
        if(pcap_compile(fp, &fcode, filter, 1, NetMask) < 0)
        {
            fprintf(stderr,"\nError compiling filter: wrong syntax.\n");
            return;
        }

        //set the filter
        if(pcap_setfilter(fp, &fcode)<0)
        {
            fprintf(stderr,"\nError setting the filter\n");
            return;
        }

    }

    //open the dump file
    if (ofilename != NULL)
    {
        dumpfile= pcap_dump_open(fp, ofilename);

        if (dumpfile == NULL)
        {
            fprintf(stderr,"\nError opening output file\n");
            return;
        }
    }
    else usage();

    //start the capture
    while((res = pcap_next_ex( fp, &header, &pkt_data)) >= 0)
    {

        if(res == 0)
        /* Timeout elapsed */
        continue;

        //save the packet on the dump file
        pcap_dump((unsigned char *) dumpfile, header, pkt_data);

    }
}


void usage()
{

    printf("\npf - Generic Packet Filter.\n");
    printf("\nUsage:\npf -s source -o output_file_name [-f filter_string]\n\n");
    exit(0);
}

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。