11.4. Combining libnet and libpcap
The 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 SYNplescan
Half-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 handshake
This 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 port
Whether 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 Packet
Because 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 header
libnet_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 header
libnet_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 Responses
To
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 Code
Example 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;
}