Introduction:
Libnet is a library that lets developers easily construct
and inject individual network packets into the network. Libnet hides most of
the low-level details of how to construct and inject packets allowing the
developer to easily create cross-platform security tools.
In this blog we will be showing how to use libnet (and also
libpcap) to develop various network security tools. The libnet library that we develop over the
next few posts will be used to develop these tools. While libnet can be used to write some
powerful network security tools it can also be used to write malicious tools as
well.
If you wish to use libnet to develop tools specifically for
Apple's OS X environment I would recommend reading the libnet chapter in my
book iOS and OS X Network Programming Cookbook which shows how to develop
applications with libnet and Objective-C.
In this series of libnet articles we will be developing a basic libnet
library in C that you can integrate in your applications. While you could use this C library to develop
applications for OS X as well, using an Objective-C library is preferred.
Libnet Installation:
Libnet was originally maintained at http://packetfactory.openwall.net however this site has not been updated since 2007. Since then a number of individuals have
forked the library in an attempt to maintain the development. The version that I use for my projects is the
libnet-dev project maintained on sourceforge. You can find the code at http://sourceforge.net/projects/libnet-dev/files/libnet-1.2-rc3.tar.gz/download
Once you download the code you will want to unzip and untar
the package. You can then install libnet
by running the following three commands within the directory that was created
when you untarred the libnet package.
./configure
make
sudo make install
These commands will install the libnet headers to
/usr/local/include and the libnet library files to /usr/local/lib. Remember to link libnet to your project when
you build it.
Creating a ICMP packet:
Before you begin creating network packets you need to
understand how a packet is constructed.
I wrote a brief introduction in this post: http://network-development.blogspot.com/2014/02/layers-of-internet-protocol-suite.html. While this introduction will give you the
information that you need to begin constructing packets, I would recommend
getting a packet capture utility like Wireshark and examining packets coming
into your system. You can learn a lot
about how packets are constructed and how protocols work by examining the
packets coming in and going out of your system.
When we write the functions to create ICMP, TCP and UDP
packets, we will need a couple of helper functions to create the IPv4 header
and to write the packet to the network.
If you are familiar with how packets are created, you may be asking
yourself about the Link Layer headers.
When we initiate the libnet context we will be setting the injecting
type to LIBNET_RAW4 which tells libnet to automatically generate the Link Layer
headers for us. We will briefly discuss
the injection types when we look at the code later in this post.
To see what fields are in the IPv4, ICMP Echo and ICMP Echo
Reply headers, you can refer to this post:
http://network-development.blogspot.com/2014/01/packet-headers.html. I am still unsure if that is the best way to
present the headers but it is the best I have been able to come up with. If anyone has any suggestions please let me
know.
libnet_lib header file:
Lets dive into my favorite part, the code. We will begin by creating a header file for
our libnet library. Lets create a file
called libnet_lib.h and add the following code to it.
#include <sys/types.h>
#include <stdbool.h>
#ifndef LIBNET_LIB_H
#define
LIBNET_LIB_H
bool sendICMPEcho(char *addr, char *payload, int
type, char *interface);
#endif // LIBNET_LIB_H
In this code we define one function which is the sendICMP()
function. This function accepts four
arguments:
char *addr: This is the address to send the packet too.
char *payload: This is any payload that you wish to send in
the ICMP packet.
int type: The ICMP packet type. Since this function sends a ICMP Echo packet
we would want to set this to ICMP_ECHO or ICMP_ECHOREPLY however libnet defines
the following ICMP types:
ICMP_ECHOREPLY
ICMP_UNREACH
ICMP_SOURCEQUENCH
ICMP_REDIRECT
ICMP_ECHO
ICMP_ROUTERADVERT
ICMP_ROUTERSOLICIT
ICMP_TIMXCEED
ICMP_PARAMPROB
ICMP_TSTAMP
ICMP_TSTAMPREPLY
ICMP_IREQ
ICMP_IREQREPLY
ICMP_MASKREQ
ICMP_MASKREPLY
char *interface: The interface to send the packet though. By setting this to NULL libnet will pick the
most appropriate interface to send the packet.
Unfortunately on some systems (like my Ubuntu laptop) this automatically
defaults to eth0 even if it is not connected to anything.
In future posts, as we add additional functions (UDP and
TCP) to our library, we will define those functions in this header file as
well.
sendICMPEcho():
The
sendICMPEcho() function will build and inject a ICMP echo packet into
the network. Lets take a look at the
code and then we can examine it:
bool sendICMPEcho(char *addr, char *payload, int
type, char *interface)
{
libnet_t *lnet;
u_int16_t
id,seq;
char errbuf[LIBNET_ERRBUF_SIZE];
lnet =
libnet_init(LIBNET_RAW4, interface, errbuf);
if ( lnet
== NULL ) {
printf("Error with libnet_init():
%s\n", errbuf);
return false
}
/*
Generating a random id */
libnet_seed_prand (lnet);
id =
(u_int16_t)libnet_get_prand(LIBNET_PR16);
/*
Building ICMP header */
seq = 1;
if
(libnet_build_icmpv4_echo(type,
0,
0,
id,
seq,
(u_int8_t*)payload,
sizeof(payload),
lnet,
0) == -1)
{
printf("Error building UDP header: %s\n",libnet_geterror(lnet));
libnet_destroy(lnet);
return false;
}
if
(!build_ipv4 (addr, IPPROTO_ICMP, sizeof(payload) + LIBNET_ICMPV4_ECHO_H,
lnet))
return false;
bool
success = write_packet (lnet);
libnet_destroy(lnet);
return
success;
}
We begin the sendICMPEcho() function by using the libnet_init()
function to initiate the libnet context.
We need to initiate this context prior to calling any other libnet
functions. The prototype for the libnet_init
function is: libnet_init(int
injection_type, const char *device, char *err_buf); As you can see, this function takes three
arguments which are:
int injection_type: This is the libnet injection type. I almost exclusively use LIBNET_RAW4
which tells libnet to automatically create the link layer headers for me. Some of the other possible values are: LIBNET_LINK , LIBNET_LINK_ADV , LIBNET_RAW4 ,
LIBNET_
RAW4_ADV , LIBNET_RAW6 , and
LIBNET_RAW6_ADV.
const char *device: This is the name of the interface to
use. This can be set to NULL to let
libnet choose the interface.
char *err_buf: This buffer will contain any errors that
occur if something goes wrong with the request.
After we initiate the libnet context we then verify that it
was properly initiated. If it was not
properly initiated we return false to let the calling function know that
the packet was not sent out.
Next we need to create a random number to be used as the
identifier. We use the libnet_seed_prand()
function to seed the pseudo-random number generator and then the libnet_get_prand()
function to retrieve a random number.
The LIBNET_PR16 constant specifies a number between 0 and 32767.
Next we build the ICMP header. To create a echo request we use the libnet_build_icmpv4_echo()
function which has a prototype of: libnet_ptag_t
libnet_build_icmpv4_echo(uint8_t type, uint8_t code, uint16_t sum,uint16_t id,
uint16_t seq, const uint8_t* payload, uint32_t payload_s, libnet_t *l,
libnet_ptag_t ptag);. As you can see
this function accepts nine arguments:
uint8_t type: This is the ICMP type that we passed to the
function. Since we are using the
libnet_build_icmpv4_echo() funciton, this value should be either ICMP_ECHO or ICMP_ECHOREPLY.
uint8_t code: We set this to 0, it is not used for the
ICMP Echo request.
uint16_t sum: This is set to 0 to let libnet generate
the checksum
uint16_t id: We set this to the pseudo-random number that
we generated.
uint16_t seq: This is the sequence number for the packet.
const uint8_t* payload: This is our payload.
uint32_t payload_s: This is the size of the payload.
libnet_t *l: This is the libnet context.
libnet_ptag_t ptag: We set this to 0 to generate a new header.
There are several other libnet functions that can be used to
create other ICMP packet types. These
are: libnet_build_icmpv4_mask(),
libnet_build_icmpv4_unreach(), libnet_build_icmpv4_redirect(),
libnet_build_icmpv4_timeexceed() and libnet_build_icmpv4_timestamp(). For this example we will be using just the libnet_build_icmpv4_echo()
function.
If the libnet_build_icmpv4_echo() function returns
-1, there was a problem generating the header.
If this happens we return a boolean false to the calling function to let
it know that the packet was not sent out.
After we generate the the ICMPv4 header we call our
build_ipv4() function to build the IPv4 headers and our write_packet() function
to send the packet out. We will be
discussing these functions later in this post.
After the packet is sent we use the libnet_destroy()
function to release the memory used by the libnet context.
Build_ipv4():
The build_ipv4() function accepts four arguments,
they are:
char *addr: This is the IP address of the destination
device.
u_int8_t
proto: This is the
protocol that is being sent. In the
sendICMPEcho() function above we send IPPROTO_ICMP.
u_int32_t psize:
This
is the size of the ICMP header and the payload.
libnet_t
*lnet: This is the
libnet context that the IPv4 header will be added too.
Lets
look at the code for the build_ipv4() function:
void build_ipv4(char *addr, u_int8_t proto,
u_int32_t psize, libnet_t *lnet) {
u_int32_t
target, source;
u_int16_t id;
target = libnet_name2addr4(lnet, addr,
LIBNET_DONT_RESOLVE);
source =
libnet_get_ipaddr4(lnet);
if (
source == -1 ) {
printf("Error retrieving IP address:
%s\n",libnet_geterror(lnet));
libnet_destroy(lnet);
return false;
}
libnet_seed_prand (lnet);
id =
(u_int16_t)libnet_get_prand(LIBNET_PR16);
if(
libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + psize,
0,
id,
0,
64,
proto,
0,
source,
target,
NULL,
0,
lnet,
0) == -1)
{
printf("Error building IP header:
%s\n",libnet_geterror(lnet));
libnet_destroy(lnet);
return false;
}
return true;
}
We
begin this function by calling the libnet_name2addr4() function which
accepts a char array and returns a network-byte ordered IPv4
address. The libnet_name2addr4()
function accepts the following three arguments:
libnet_t *lnet: This is a
pointer to the libnet context to use.
char*
host_name:
This is a pointer to the char array containing the name.
uint8_t
use_name:
This can be either LIBNET_RESOLVE or LIBNET_DONT_RESOLVE . If the char array
contains a hostname such network-development.blogspot.com, we would want the libnet_name2addr()
function to perform a DNS lookup prior to creating the network-byte-ordered
IPv4 address; therefore, we would set this to LIBNET_RESOLVE constant. If the
hostname contained an IP address, we would not want libet_name2addr4()
to perform a DNS lookup, so we would use LIBNET_DONT_ RESOLVE constant. For this function we assume that the addr
char array contains an IP address.
The
libnet_get_ipaddr4() function is then used to retrieve the IP address of
our local device. This function accepts
one argument which is the libnet context.
Just
like in the sendICMPEcho() function, we use the libnet_seed_prand() and
libnet_get_prand() functions to create a pseudo-random number to use for an
ID.
We
use the libnet_build_ipv4() function to build our IPv4 header. This function takes the following thirteen
arguments:
uint16_t
ip_len: This is the size of the packet. We add the IPv4 header size with the size of
the headers that were created from the upper layers plus the payload (TCP, UDP
or ICMP header size plus the payload).
uint8_t
tos: This field, now known as
Differentiated Services Code Point (DSCP), may indicate a particular quality of
service needs. Libnet still refers to TOS and is usually set to 0
uint16_t
id: This field is primarily used to
uniquely identify fragments of an original packet.
uint16_t frag: This specifies the offset of a particular
fragment and is relative to the beginning of the original unfragmented packet.
uint8_t ttl: This indicates the number of hops the packet
can be routed. This number is decremented at each hop until it reaches its
destination or it reaches 0. If the Time
to Live reaches 0 before the packet reaches its destination, the packet is
discarded.
uint8_t prot: This is the IP Protocol ID.
uint16_t
sum: This maps to the Checksum field
and is set to 0 (zero) to have libnet auto-fill the checksum.
uint32_t
src: This is the IP address of the
sending device.
uint32_t dst,: This is the IP address of the device the
packet is going to.
const uint8_t*
payload: This is the
optional payload.
uint32_t
payload_s: This is the
size of the optional payload.
libnet_t *l: The libnet context to use for the IP header
libnet_ptag_t
ptag: We set this to 0 to generate a new header.
If
the libnet_build_ipv4() function returns -1, then there was a problem
creating the IPv4 header and we release
the libnet context and return false to the calling function. If the libnet_build_ipv4() function
was successful we return true.
write_packet():
The write_packet() function injects the packet that we
created into the network. This function
accepts one argument which is the libnet context that contains the newly
created packet. The write_packet()
function looks like this:
bool write_packet(libnet_t *lnet) {
int
bytes_written = libnet_write(lnet);
if (
bytes_written != -1 ) {
printf("%d bytes written to device %s.\n", bytes_written,
libnet_getdevice(lnet));
return true;
}
else {
printf("Error writing packet: %s\n",libnet_geterror(lnet));
return false;
}
}
The write_packet() function calls the libnet_write()
function to inject the packet. This
function accepts the libnet context that contains the packet as it's only
argument and returns the number of bytes written. If there was a problem injecting the packet,
the libnet_write() function returns -1.
Using the sendICMPEcho() function:
Lets look at how we would use the sendICMPEcho() function:
bool success = sendICMPEcho("10.0.1.4",
"Hello from libnet", ICMP_ECHO, "wlan0");
if
(success) {
printf("Success");
} else {
printf("Failed");
}
The sendICMPEcho() function accepts four arguments as
described earlier in tis post. In this
code snippet I am sending a ICMP Echo request to a device with an IP Address of
10.0.1.4 using the wlan0 interface. You
could set the interface to null to let libnet pick the appropriate interface
but on my Ubuntu laptop libnet defaults to eth0 which, depending where I am at,
is not always connected to a network.
You can experiment a little with ICMP packets. Try running Wireshark and seeing the request
and replies as you send the packet out.
You can also try to send out an ICMP_ECHOREPLY (rather than a request)
and seeing how different Operating systems respond. It is a lot more fun to experiment with TCP
packets but that will be for our next post when we cover creating both TCP and
UDP packets with libnet.
Part 2 of this post can be found here: http://network-development.blogspot.com/2014/04/libnet-library-part-2-injecting-tcp-and.html
No comments:
Post a Comment