11.4. Combining libnet and libpcapThe ability to create arbitrary packets can be very powerful in a tool. When this is combined with the ability to capture packets from the network you can create powerful tools to manipulate traffic on the network. In this section we will create such a tool: a simple half-open port scanner called SYNplescan. 11.4.1. Overview of SYNplescanHalf-open (or SYN scanning) works by taking advantage of the three-way handshaking process the TCP protocol uses to establish a connection. The three-way handshaking process, as shown in Figure 11-3, involves the system initiating the connection to send a TCP packet with the SYN flag set. If the port the system is attempting to connect to is accepting connections, the destination system responds with a TCP packet with the SYN and ACK flags set. To complete the connection, the initiating system sends a TCP packet back with the ACK flag set. Figure 11-3. TCP three-way handshakeThis is in contrast to the situation shown in Figure 11-4, in which the initiating system is attempting to connect to a TCP port that is closed. In this case the destination host responds with a TCP packet with the SYN and RST flags set. Figure 11-4. Attempted TCP connection to closed portWhether connecting to an open port or a closed port, only two packets are required to determine whether the port is open or closed. In addition, many operating systems do not log incoming connections if the full three-way handshaking process has not completed. Half-open scanning relies on the ability to create a TCP packet with the SYN packet set, and on capturing return traffic from the destination system to determine if the SYN and ACK flags have been set, signaling that the port is open or, if the SYN and RST flags are set, indicating that the port is closed. 11.4.2. Creating the SYN PacketBecause SYNplescan works at the TCP layer, we can open the libnet context for raw sockets mode as follows: l = libnet_init (LIBNET_RAW4, device, libnet_errbuf); To create the outgoing SYN packet, we are going to start with the libnet_build_tcp() function, as this is the highest-level protocol in this example. This is shown in Example 11-7. Example 11-7. Creating the TCP headerlibnet_ptag_t tcp = 0; /* libnet protocol block */ tcp = libnet_build_tcp (libnet_get_prand (LIBNET_PRu16), /* src port */ ports[i], /* destination port */ libnet_get_prand (LIBNET_PRu16), /* sequence number */ 0, /* acknowledgement */ TH_SYN, /* control flags */ 7, /* window */ 0, /* checksum - 0 = autofill */ 0, /* urgent */ LIBNET_TCP_H, /* header length */ NULL, /* payload */ 0, /* payload length */ l, /* libnet context */ tcp); /* protocol tag */ if (tcp == -1) { fprintf (stderr, "Unable to build TCP header: %s\n", libnet_geterror (l)); exit (1); } libnet_build_tcp( ) specifies the field values for the TCP header. In this case we are specifying that the TCP packet has the SYN flag set (using the TH_SYN value; this is a constant supplied in the tcp.h include file), that the TCP packet is empty (there is no pointer to a payload), and that the payload length is 0. Also note that the value 0 has been provided as the checksum for the TCP header. By default, if 0 is provided as a value for a packet header's checksum, libnet calculates the correct value and inserts it into the header for you. You can modify this behavior using libnet_toggle_checksum() if you want to deliberately create invalid packets. For this packet, we are using random source ports and sequence numbers, which we've obtained using the libnet_get_prand( ) function. You can use this function to generate pseudorandom numbers from 2 bits to 32 bits for build functions. Also, we are passing the libnet_ptag_t value tcp to this function because we are going to be sending several packets with differing destination port values to test a series of ports. Therefore, for efficiency, we modify the existing protocol block rather than create a new protocol block each time. Once the TCP header is created, we can build the IP packet, as shown in Example 11-8. Because we are using the LIBNET_RAW4 injection type, this is the last packet header we need to create, as the operating system is used to send this packet out to its destination. Example 11-8. Creating the IP headerlibnet_ptag_t ipv4 = 0; /* libnet protocol block */ ipv4 = libnet_build_ipv4 (LIBNET_TCP_H + LIBNET_IPV4_H, /* length */ 0, /* TOS */ libnet_get_prand (LIBNET_PRu16), /* IP ID */ 0, /* frag offset */ 127, /* TTL */ IPPROTO_TCP, /* upper layer protocol */ 0, /* checksum, 0=autofill */ myipaddr, /* src IP */ ipaddr, /* dest IP */ NULL, /* payload */ 0, /* payload len */ l, /* libnet context */ ipv4); /* protocol tag */ if (ipv4 == -1) { fprintf (stderr, "Unable to build IPv4 header: %s\n", libnet_geterror (l)); exit (1); } We used the libnet_build_ipv4() function to build the IP header for our SYN packet. The checksum value for this packet is also specified as 0; however; it is worth noting that the operating system calculates this value regardless of what we specify here if we are using a LIBNET_RAW4 injection type (which we are). 11.4.3. Capturing the ResponsesTo capture the responding packets, SYNplescan uses the libpcap library. libpcap is covered in detail in Chapter 10. To capture packets with the SYN and ACK flags set, as well as packets with the SYN and RST flags set, SYNplescan uses the following tcpdump -style filter to specify packets to capture from the wire: char *filter = "(tcp[13] == 0x14) || (tcp[13] == 0x12)"; The tcp[13] value refers to the TCP flags value within the TCP header. In this case we are comparing these to the hardcoded values 0x14 (SYN and RST are set) and 0x12 (SYN and ACK are set). Then these values are used to provide output to the user on ports that are open or closed, as follows: if (tcp->th_flags == 0x14) { printf ("Port %d appears to be closed\n", ntohs (tcp->th_sport)); answer = 0; } else { if (tcp->th_flags == 0x12) { printf ("Port %d appears to be open\n", ntohs (tcp->th_sport)); answer = 0; } } In addition to these cases, the SYNplescan tool also handles situations in which no response is obtained from the destination system. In these cases the initial SYN packets or the response packets might be filtered by a firewall. SYNplescan therefore assumes any port that doesn't respond in a timeout period is filtered. 11.4.4. The SYNplescan Tool Source CodeExample 11-9 provides the full source code to the SYNplescan tool. This should compile on most Linux distributions as follows: gcc -o synplescan synplescan.c -lnet -lpcap If that does not work, libnet provides a tool called libnet-config that contains definitions and library references that might be required for your libnet installation. You can use this with back quotes as follows: gcc -o synplescan synplescan.c `libnet-config -defines` \ `libnet-config -libs` `libnet-config -cflags` -lpcap This tool was written on Gentoo Linux. It should work on most Linux installations; however, some tweaking might be necessary to get this working on other Unix and Unix-like environments. Example 11-9. Source code to the SYNplescan tool#define _BSD_SOURCE 1 #include <stdio.h> #include <unistd.h> #include <time.h> #include <libnet.h> #include <pcap.h> int answer = 0; /* flag for scan timeout */ /* usage */ void usage (char *name) { printf ("%s - Simple SYN scan\n", name); printf ("Usage: %s -i ip_address\n", name); printf (" -i IP address to scan\n"); exit (1); } void packet_handler (u_char * user, const struct pcap_pkthdr *header, const u_char * packet) { struct tcphdr *tcp = (struct tcphdr *) (packet + LIBNET_IPV4_H + LIBNET_ETH_H); if (tcp->th_flags == 0x14) { printf ("Port %d appears to be closed\n", ntohs (tcp->th_sport)); answer = 0; } else { if (tcp->th_flags == 0x12) { printf ("Port %d appears to be open\n", ntohs (tcp->th_sport)); answer = 0; } } } int main (int argc, char *argv[]) { char *device = NULL; /* device for sniffing/sending */ char o; /* for option processing */ in_addr_t ipaddr; /* ip address to scan */ u_int32_t myipaddr; /* ip address of this host */ libnet_t *l; /* libnet context */ libnet_ptag_t tcp = 0, ipv4 = 0; /* libnet protocol blocks */ char libnet_errbuf[LIBNET_ERRBUF_SIZE]; /* libnet error messages */ char libpcap_errbuf[PCAP_ERRBUF_SIZE]; /* pcap error messages */ pcap_t *handle; /* libpcap handle */ bpf_u_int32 netp, maskp; /* netmask and ip */ char *filter = "(tcp[13] == 0x14) || (tcp[13] == 0x12)"; /* if the SYN and RST or ACK flags are set */ struct bpf_program fp; /* compiled filter */ int ports[] = { 21, 22, 23, 25, 53, 79, 80, 110, 139, 443, 445, 0 }; /* ports to scan */ int i; time_t tv; if (argc != 3) usage (argv[0]); /* open context */ l = libnet_init (LIBNET_RAW4, device, libnet_errbuf); if (l == NULL) { fprintf (stderr, "Error opening context: %s", libnet_errbuf); exit (1); } while ((o = getopt (argc, argv, "i:")) > 0) { switch (o) { case 'i': if ((ipaddr = libnet_name2addr4 (l, optarg, LIBNET_RESOLVE)) == -1) { fprintf (stderr, "Invalid address: %s\n", libnet_geterror (l)); usage (argv[0]); } break; default: usage (argv[0]); break; } } /* get the ip address of the device */ if ((myipaddr = libnet_get_ipaddr4 (l)) == -1) { fprintf (stderr, "Error getting IP: %s", libnet_geterror (l)); exit (1); } printf ("IP: %s\n", libnet_addr2name4 (ipaddr, LIBNET_DONT_RESOLVE)); /* get the device we are using for libpcap */ if ((device = libnet_getdevice (l)) == NULL) { fprintf (stderr, "Device is NULL. Packet capture may be broken\n"); } /* open the device with pcap */ if ((handle = pcap_open_live (device, 1500, 0, 2000, libpcap_errbuf)) == NULL) { fprintf (stderr, "Error opening pcap: %s\n", libpcap_errbuf); exit (1); } if ((pcap_setnonblock (handle, 1, libnet_errbuf)) == -1) { fprintf (stderr, "Error setting nonblocking: %s\n", libpcap_errbuf); exit (1); } /* set the capture filter */ if (pcap_lookupnet (device, &netp, &maskp, libpcap_errbuf) == -1) { fprintf (stderr, "Net lookup error: %s\n", libpcap_errbuf); exit (1); } if (pcap_compile (handle, &fp, filter, 0, maskp) == -1) { fprintf (stderr, "BPF error: %s\n", pcap_geterr (handle)); exit (1); } if (pcap_setfilter (handle, &fp) == -1) { fprintf (stderr, "Error setting BPF: %s\n", pcap_geterr (handle)); exit (1); } pcap_freecode (&fp); /* seed the pseudo random number generator */ libnet_seed_prand (l); for (i = 0; ports[i] != 0; i++) { /* build the TCP header */ tcp = libnet_build_tcp (libnet_get_prand (LIBNET_PRu16), /* src port */ ports[i], /* destination port */ libnet_get_prand (LIBNET_PRu16), /* sequence number */ 0, /* acknowledgement */ TH_SYN, /* control flags */ 7, /* window */ 0, /* checksum - 0 = autofill */ 0, /* urgent */ LIBNET_TCP_H, /* header length */ NULL, /* payload */ 0, /* payload length */ l, /* libnet context */ tcp); /* protocol tag */ if (tcp == -1) { fprintf (stderr, "Unable to build TCP header: %s\n", libnet_geterror (l)); exit (1); } /* build the IP header */ ipv4 = libnet_build_ipv4 (LIBNET_TCP_H + LIBNET_IPV4_H, /* length */ 0, /* TOS */ libnet_get_prand (LIBNET_PRu16), /* IP ID */ 0, /* frag offset */ 127, /* TTL */ IPPROTO_TCP, /* upper layer protocol */ 0, /* checksum, 0=autofill */ myipaddr, /* src IP */ ipaddr, /* dest IP */ NULL, /* payload */ 0, /* payload len */ l, /* libnet context */ ipv4); /* protocol tag */ if (ipv4 == -1) { fprintf (stderr, "Unable to build IPv4 header: %s\n", libnet_geterror (l)); exit (1); } /* write the packet */ if ((libnet_write (l)) == -1) { fprintf (stderr, "Unable to send packet: %s\n", libnet_geterror (l)); exit (1); } /* set variables for flag/counter */ answer = 1; tv = time (NULL); /* capture the reply */ while (answer) { pcap_dispatch (handle, -1, packet_handler, NULL); if ((time (NULL) - tv) > 2) { answer = 0; /* timed out */ printf ("Port %d appears to be filtered\n", ports[i]); } } } /* exit cleanly */ libnet_destroy (l); return 0; } |