We code a crazy passive sniffer in the form of a module for Python



    I must say right away: what we will be doing here does not pretend, say, to any industrial applicability. Moreover, I admit that my code in this example can be horrible, scary and unnecessary. Nevertheless - why not intercept packets in the middle of the week? So, lightly.

    So, today we’ll conjure something up:

    1. We will implement the simplest passive packet interceptor for TCP and UDP
    2. Put it into the C-library as an extension for Python
    3. We will attach an iterator interface to it so that the bytes are poured from a cornucopia
    4 . ...
    5. PROFIT!

    Why is all this necessary?

    Why do we need sniffers? To sniff network traffic. No, I'm not joking, the English verb “to sniff” is translated as “sniff”. Okay, okay, we’ll be more scientific - with their help, you can and should analyze network packets passing through the computer’s network card.

    Sniffers are passive and active. A passive sniffer does exactly and only what is required of it - it intercepts for analysis traffic that passes through the network card of the computer on which it is installed. It would seem much cooler? However, the active sniffer not only monitors the network card, but also tries in every way to get to traffic that walks on the local network and is not intended for prying eyes. He does this, for example, using ARP-Spoofingand all sorts of other dirty tricks. By the way, in non-switched networks (based on repeaters and hubs), a passive sniffer can suddenly detect superpowers in itself, because in the absence of switching, all packets in such networks are sent to all hosts. Of course, only recipients should accept them, but ... :)

    But in order to receive them, you must first make the network card fire its personal secretary, who filters the correspondence, and start reading everything at once. Scientifically, this is called " Promiscuous mode ", that is, "Inaudible mode." And there is an ambush:

    image

    Yes, root permissions are required to put the card in promiscuous mode. Therefore, our script will ask them to run. So it goes.

    So what?

    As one famous movie character said, "We need to go deeper." That is, to dive deeper into the OSI layers, at the link layer. To do this, when creating a socket, you must specify the constant PF_PACKET instead of PF_INET :

        int s_sock;
        struct ifreq ifr;
        strcpy(ifr.ifr_name, IFACE);
        if ( (s_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
            perror("Error creating socket");
            exit(-1);
        }
        ifr.ifr_flags |= IFF_PROMISC;
        if (ioctl(s_sock, SIOCGIFFLAGS, &ifr) < 0) {
          perror("Unable to set promiscious mode for device");
          close(s_sock);
          exit(-1);
        }
    


    Usually, when you open such a socket, a specific protocol is indicated, according to which the interception takes place. If you specify the constant ETH_P_ALL - everything will be intercepted. The ifreq structure is used in Linux to drive the network interface at a low level via ioctl () , and IFACE in this case is just a string with the name of the interface looking at the network, for example, “eth0” .

    Actually, now all that remains for us is to read the data from the socket in the loop and see what happens:
        int n = 0;
        char buf[MTU];
        n = recvfrom(s_sock, buf, sizeof(buf), 0, 0, 0);
    

    The MTU here is easiest to set equal to 1500 - this is the maximum packet size defined by the standard on Ethernet networks . For networks built according to another standard, for example, FDDI , the value may be different.

    Since we work with Ethernet , the data on the headers of received packets is easiest to write to the kernel structure specially designed for this purpose - ethhdr . For different basic protocols down to the transport layer, there are similar structures that, no matter how hard to believe, are named like iphdr , tcphdr or even udphdr(as they say, “I know a great joke about UDP, but not the fact that it will reach you”). As expected in the good old C, it does something like this:
      struct ethhdr eth;
      memcpy((char *) ð, data, sizeof(struct ethhdr));
    


    Hmm ... And how do you put this in Python?

    Everyone knows that in python we all have an object. In the case of the generator / iterator, there will also be no exception - we must create an object that has the __iter __ () method and can next () . The object has a bunch of fields, most of which we don't need, so be careful - there are a bunch of zeros ahead.
    PyTypeObject PyPacketGenerator_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "packgen",                       /* tp_name */
        sizeof(PacketGeneratorState),            /* tp_basicsize */
        0,                              /* tp_itemsize */
        (destructor)packgen_dealloc,     /* tp_dealloc */
        0,                              /* tp_print */
        0,                              /* tp_getattr */
        0,                              /* tp_setattr */
        0,                              /* tp_reserved */
        0,                              /* tp_repr */
        0,                              /* tp_as_number */
        0,                              /* tp_as_sequence */
        0,                              /* tp_as_mapping */
        0,                              /* tp_hash */
        0,                              /* tp_call */
        0,                              /* tp_str */
        0,                              /* tp_getattro */
        0,                              /* tp_setattro */
        0,                              /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT,             /* tp_flags */
        0,                              /* tp_doc */
        0,                              /* tp_traverse */
        0,                              /* tp_clear */
        0,                              /* tp_richcompare */
        0,                              /* tp_weaklistoffset */
        PyObject_SelfIter,              /* tp_iter */
        (iternextfunc)packgen_next,      /* tp_iternext */
        0,                              /* tp_methods */
        0,                              /* tp_members */
        0,                              /* tp_getset */
        0,                              /* tp_base */
        0,                              /* tp_dict */
        0,                              /* tp_descr_get */
        0,                              /* tp_descr_set */
        0,                              /* tp_dictoffset */
        0,                              /* tp_init */
        PyType_GenericAlloc,            /* tp_alloc */
        packgen_new,                     /* tp_new */
    };
    

    This announcement shows that the working methods of our future object are packgen_new () , packgen_next (), and packgen_dealloc () . The latter is a destructor and, in general, you can do without it if you really want and there is no extra data in memory. In addition, we need a structure in which we will store data about the state of the object at the current iteration. Since the only thing we need to store is a socket, we’ll declare it:
    typedef struct {
        PyObject_HEAD
        int s_sock;
    } PacketGeneratorState;
    


    As I said above, you need to open the socket and configure the network card in spy mode. The easiest way to do this is directly in the object initialization method.

    static PyObject *
    packgen_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
    {
        int s_sock;
        struct ifreq ifr;
        strcpy(ifr.ifr_name, IFACE);
        if ( (s_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
            perror("Error creating socket");
            exit(-1);
        }
        ifr.ifr_flags |= IFF_PROMISC;
        if (ioctl(s_sock, SIOCGIFFLAGS, &ifr) < 0) {
          perror("Unable to set promiscious mode for device");
          close(s_sock);
          exit(-1);
        }
        PacketGeneratorState *pkstate = (PacketGeneratorState *)type->tp_alloc(type, 0);
        if (!pkstate)
            return NULL;
        pkstate->s_sock = s_sock;
        return (PyObject *)pkstate;
    }
    


    In principle, it would be quite realistic to make the return of packages from the module in the form of classes, but I was too lazy to decide to make it easier and return the dictionary (and if someone remakes in his own way, it will be great). All that remains to be done is to determine that we have IP at the network level, and by the IP-header, in turn, determine the overlying protocol (if anyone does not remember, the protocols are indicated by numbers and are written in / etc / protocols ). This all works because our packages are similar to the creations of Burger King cooks - each higher level appends its title to the lower level. Take the good old brothers - TCP and UDP .

    PyObject *packet;
    packet = PyDict_New();
    if (!packet)
          return NULL;
    if (ntohs(eth.h_proto) == ETH_P_IP) {
          ip = (struct iphdr *)(data + sizeof(struct ethhdr));
          PyDict_SetItemString(packet, "ip_source", PyString_FromFormat("%s", inet_ntoa(ip->saddr)));
          ...
          if ((ip->protocol) == IPPROTO_TCP) {
                tcp = (struct tcphdr *)(data + sizeof(struct ethhdr) + sizeof(struct iphdr));
                PyDict_SetItemString(packet, "tcp_source_port", PyString_FromFormat("%d", ntohs(tcp->source)));
                ...
          }
          if ((ip->protocol) == IPPROTO_UDP) {  
            udp = (struct udphdr *)(data + sizeof(struct ethhdr) + sizeof(struct iphdr));
            PyDict_SetItemString(packet, "udp_source_port", PyString_FromFormat("%d", ntohs(udp->source)));
            ...
          }
    } 
    return packet;
    


    Voila!

    It all looks like this: For the sake of laughter, I created a repository on github , what if I want to develop an idea? Have a nice sniffing, don't get caught!
    Python 2.7.3 (default, Apr 20 2012, 22:44:07)
    [GCC 4.6.3] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import pysniff
    >>> for i in pysniff.packgen():
    ... print i
    ...
    {'ip_destination': '192.168.1.111', 'tcp_seq': '10972', 'ip_source': '173.194.32.53', 'tcp_offset': '8', 'tcp_source_port': '443', 'tcp_dest_port': '44021'}
    {'ip_destination': '173.194.32.53', 'tcp_seq': '47475', 'ip_source': '192.168.1.111', 'tcp_offset': '8', 'tcp_source_port': '44021', 'tcp_dest_port': '443'}
    {'ip_destination': '192.168.1.111', 'tcp_seq': '10972', 'ip_source': '173.194.32.53', 'tcp_offset': '8', 'tcp_source_port': '443', 'tcp_dest_port': '44021'}
    {'ip_destination': '173.194.32.53', 'tcp_seq': '47475', 'ip_source': '192.168.1.111', 'tcp_offset': '8', 'tcp_source_port': '44021', 'tcp_dest_port': '443'}



    Also popular now: