Skip to content

8. Our first application example

Chema García edited this page Jan 11, 2016 · 1 revision

Source code template

Note: We will not to explain API description in this page. If you want it please refer to "API Description" Wiki page.

This time, we'll start from a source code template using libpcap, which only receives the capture source and the capture filter through parameters. Here you have the source code:

#include <stdio.h>
#include <signal.h>
#include <getopt.h>
#include <pcap.h>
#include <libntoh.h>

#define SIZE_ETHERNET 14

pcap_t *handle;

void shandler ( int s )
{
	if ( s != 0 )
		signal ( s , &shandler );
	
	pcap_close ( handle );
	fprintf ( stderr , "\n\n" );
	exit ( s );
}

int main ( int argc , char *argv[] )
{
	/* parameters parsing */
	int c;

	/* pcap */
	char 			errbuf[PCAP_ERRBUF_SIZE];
	struct bpf_program 	fp;
	char 			filter_exp[] = "ip";
	char 			*source = 0;
	char 			*filter = filter_exp;
	const unsigned char 	*packet = 0;
	struct pcap_pkthdr 	header;


	fprintf( stderr, "\n[i] libntoh version: %s\n", ntoh_version() );

	if ( argc < 3 )
	{
		fprintf( stderr, "\n[+] Usage: %s <options>\n", argv[0] );
		fprintf( stderr, "\n+ Options:" );
		fprintf( stderr, "\n\t-i | --iface <val> -----> Interface to read packets from" );
		fprintf( stderr, "\n\t-f | --file <val> ------> File path to read packets from" );
		fprintf( stderr, "\n\t-F | --filter <val> ----> Capture filter (must contain \"tcp\" or \"ip\")\n\n" );
		exit( 1 );
	}

	/* check parameters */
	while ( 1 )
	{
		int option_index = 0;
		static struct option long_options[] =
		{
		{ "iface" , 1 , 0 , 'i' } ,
		{ "file" , 1 , 0 , 'f' } ,
		{ "filter" , 1 , 0 , 'F' } ,
		{ 0 , 0 , 0 , 0 } };

		if ( ( c = getopt_long( argc, argv, "i:f:F:", long_options, &option_index ) ) < 0 )
			break;

		switch ( c )
		{
			case 'i':
				source = optarg;
				handle = pcap_open_live( optarg, 65535, 1, 0, errbuf );
				break;

			case 'f':
				source = optarg;
				handle = pcap_open_offline( optarg, errbuf );
				break;

			case 'F':
				filter = optarg;
				break;

			default:
				if ( handle != 0 )
					pcap_close ( handle );
				exit ( -1 );
		}
	}

	if ( !handle )
	{
		fprintf( stderr, "\n[e] Error loading %s: %s\n", source, errbuf );
		exit( -2 );
	}

	if ( pcap_compile( handle, &fp, filter, 0, 0 ) < 0 )
	{
		fprintf( stderr, "\n[e] Error compiling filter \"%s\": %s\n\n", filter, pcap_geterr( handle ) );
		pcap_close( handle );
		exit( -3 );
	}

	if ( pcap_setfilter( handle, &fp ) < 0 )
	{
		fprintf( stderr, "\n[e] Cannot set filter \"%s\": %s\n\n", filter, pcap_geterr( handle ) );
		pcap_close( handle );
		exit( -4 );
	}
	pcap_freecode( &fp );

	/* verify datalink */
	if ( pcap_datalink( handle ) != DLT_EN10MB )
	{
		fprintf ( stderr , "\n[e] libntoh is independent from link layer, but this example only works with ethernet link layer\n");
		pcap_close ( handle );
		exit ( -5 );
	}

	signal ( SIGINT , &shandler );

	/* capture starts */
	while ( ( packet = pcap_next( handle, &header ) ) != 0 )
	{
		fprintf ( stderr , "\nGot a packet!");
	}

	shandler(0);
	//dummy return
	return 0;
}

Now try to compile it (no errors should appear):

$ gcc example.c -o example -Wall -lpcap $(pkg-config ntoh --cflags --libs)

Let's try it:

$ sudo ./example -i eth0 
[sudo] password for sch3m4: 

[i] libntoh version: 0.3a

Got a packet!
Got a packet!
Got a packet!
Got a packet!
Got a packet!
Got a packet!^C

$ 

Here starts the "libntoh Development Lesson" ;-)


Preface

The main difference between libntoh and others, is the concept of "session". A session can be understood has a "container" to store streams. Here you can be asking why a session and not a global data structure to store the streams, right? Well, the principal motivation to introduce this concept is that different sessions will have different parameters. The relation between sessions and streams is an "1...n" relation.

Session ---- 1........n ---- Streams

Q: Differents sessions can have differents parameters?

A: I mean the parameters explained in "Custom compilation" page in this wiki. And yes, you can specify them at compilation time and in execution time!

IMPORTANT: We will always require at least ONE session

Once you have a session, you will be able do the following actions through libntoh:

  • IPv4/6 Fragments/Flows:

    • Create a flow
    • Find a flow
    • Delete a flow
    • Add a fragment to his flow
    • Count flows
    • Get defragmented IPv4 datagrams
  • TCP Segments/Streams:

    • Create a stream
    • Find a stream
    • Delete a stream
    • Add a segment to his stream
    • Count streams
    • Get the status of a stream
    • Get the offset of a segment in the stream
    • Set the correct offset of a file descriptor to write the segment
    • Get reassembled TCP streams

Creating a TCP Session and identifying TCP streams

Now, we have to initialize libntoh and create a new TCP Session. We'll do it using "ntoh_init()" and "ntoh_tcp_new_session" APIs:

Note: "ntoh_init()" Will initialize TCP and IPv4/6 defragmentation. If we only want to use TCP reassembly, we could use "ntoh_tcp_init()". If we only want to use IPv4 defragmentation, then "ntoh_ipv4_init()" should be called. However, in this example we will use TCP and IPv4 so, initializes TCP and IPv4.

First, we declare two new variables:

    /* TCP processing */
    pntoh_tcp_session_t     tcpsession = 0;
    unsigned int            error = 0;

Add the following lines between "signal (...)" and capture loop:

    /* initializes libntoh (TCP,IPv4,IPv6) */
    ntoh_init();

    /* creates a new TCP session */
    if ( ! (tcpsession = ntoh_tcp_new_session ( 0 , 0 , &error )) )
    {
            fprintf ( stderr , "\n[e] Error %d creating the TCP session: %s" , error , ntoh_get_errdesc ( error ) );
            shandler ( 0 );
    }

And modify the "shandler" function to look like this:

    void shandler ( int s )
    {
	if ( s != 0 )
		signal ( s , &shandler );
	
            pcap_close ( handle );
            ntoh_exit();
            fprintf ( stderr , "\n\n" );
            exit ( s );
    }

This time, we don't need to declare "tcpsession" as global, because when libntoh exits it releases all stored sessions.

Now we have a TCP session, we can start playing! The next step is to identify TCP streams.

Libntoh uses a tuple5 structure to identify TCP streams with the following members:

  • Source IP
  • Destination IP
  • Source Port
  • Destination Port
  • Protocol

As we'll need to dissect IP and TCP headers, we should add the following variables:

    /* TCP processing */
    pntoh_tcp_session_t     tcpsession = 0;
    ntoh_tcp_tuple5_t       tcpt5 = {0};
    pntoh_tcp_stream_t      tcpstream = 0;
    unsigned int            error = 0;

    /* TCP and IP headers dissection */
    struct ip       *iphdr = 0;
    struct tcphdr   *tcphdr = 0;
    size_t          size_ip = 0;
    size_t          size_tcp = 0;

Also we'll need to define a callback function to receive the information of the new added streams:

void tcp_callback ( pntoh_tcp_stream_t stream , pntoh_tcp_peer_t orig , pntoh_tcp_peer_t dest , pntoh_tcp_segment_t seg , int reason , int extra )
{
        fprintf ( stderr , "\nSomething is happening here..." );

        return;
}

And finally, modify the capture loop to be able to add and look for streams:

/* capture starts */
while ( ( packet = pcap_next( handle, &header ) ) != 0 )
{
	/* check IP header */
	iphdr = (struct ip*) ( packet + SIZE_ETHERNET );
	if ( ( size_ip = iphdr->ip_hl * 4 ) < sizeof ( struct ip ) )
		continue;

	/* if it isn't a TCP segment */
	if ( iphdr->ip_p != IPPROTO_TCP )
		continue;

	/* check TCP header */
	tcphdr = (struct tcphdr*)((unsigned char*)iphdr + size_ip);
	if ( (size_tcp = tcphdr->th_off * 4) < sizeof(struct tcphdr) )
		continue;

	/* fill TCP tuple5 fields */
	ntoh_tcp_get_tuple5 ( (void*)iphdr , tcphdr , &tcpt5 );

	/* look for this TCP stream */
	if ( !(tcpstream = ntoh_tcp_find_stream ( tcpsession , &tcpt5 )) )
	{
		if ( ! ( tcpstream = ntoh_tcp_new_stream( tcpsession , &tcpt5, &tcp_callback , 0 , &error , 1 , 1 ) ) )
			fprintf ( stderr , "\n[e] Error %d creating new stream: %s" , error , ntoh_get_errdesc ( error ) );
		else{
			fprintf ( stderr , "\n[i] New stream added! %s:%d --> " , inet_ntoa ( *(struct in_addr*)&tcpt5.source ) , ntohs ( tcpt5.sport ) );
			fprintf ( stderr , "%s:%d" , inet_ntoa ( *(struct in_addr*)&tcpt5.destination ) , ntohs ( tcpt5.dport ) );
		}
	}
}

Note: At this step, we don't need a callback function because of we aren't adding segments, however it's required to create a new stream.

Now, try to compile:

$ gcc example.c -o example -Wall -lpcap $(pkg-config ntoh --cflags --libs)

To test it, I will open a terminal and type "nc -vvz 10.0.0.1 443" and in other terminal I will run this example. So, in the example terminal:

$ sudo ./example -i eth0 -F "tcp and host 10.0.0.1 and port 443"

[i] libntoh version: 0.3a

Now, the NetCat terminal:

$ nc -vvz 10.0.0.1 443
caronte.hell.lan [10.0.0.1] 443 (https) open
 sent 0, rcvd 0
$

Afterwards, in the example terminal we can see:

[i] New stream added! 10.0.0.33:50847 --> 10.0.0.1:443


Working with TCP segments

At this step, we're able to identify and create TCP streams. Now, we'll work with TCP segments.

This is a good moment to mention one of the more powerfull features of libntoh. Has you have realized if you have readed "API Description" Wiki page, you can link your custom data with TCP streams and segments (yes, with IPv4 too). So, you don't have to declare your own data structures and look for your custom data when a new segment/fragment arrives.

To give you an example, we'll work with TCP segments by adding new segments to his stream (libntoh will reasemble them), and after that linking custom data to streams and segments.

So, basically we only need make use of "ntoh_tcp_add_segment" API when a new segment arrives, and declares a new variable to get the total length of the packet and another variable to get the return code of "ntoh_tcp_add_segment":

/* TCP processing */
pntoh_tcp_session_t     tcpsession = 0;
ntoh_tcp_tuple5_t       tcpt5 = {0};
pntoh_tcp_stream_t      tcpstream = 0;
unsigned int            error = 0;
int                     ret = 0;

/* TCP and IP headers dissection */
struct ip       *iphdr = 0;
struct tcphdr   *tcphdr = 0;
size_t          size_ip = 0;
size_t          size_tcp = 0;
size_t          size_total = 0;

Add this line after TCP header checks:

size_total = ntohs(iphdr->ip_len);

Add this lines after looking for/create the TCP stream:

/* add this segment to the stream */
switch ( ( ret = ntoh_tcp_add_segment( tcpsession , tcpstream, (void*)iphdr, size_total , 0 ) ) )
{
    case NTOH_OK:
    case NTOH_SYNCHRONIZING:
        break;

    default:
        fprintf( stderr, "\n[e] Error %d adding segment: %s", ret, ntoh_get_retval_desc( ret ) );
        break;
}

Now, repeating the above NetCat example, we'll see:

$ sudo ./example -i eth0 -F "tcp and host 10.0.0.1 and port 443"

[i] libntoh version: 0.3a

[i] New stream added! 10.0.0.33:51225 --> 10.0.0.1:443
Something is happening here...
Something is happening here...
Something is happening here...
Something is happening here...
Something is happening here...
Something is happening here...^C

$

Looks great! Now, let's try to track TCP connections by adding the following lines to TCP callback function:

switch ( reason )
{
        /* connection sinchronization */
        case NTOH_REASON_SYNC:
                switch ( extra )
                {
                        case NTOH_REASON_ESTABLISHED:
                        case NTOH_REASON_TIMEDOUT:
                                fprintf ( stderr , "\n[i] %s/%s - %s | %s:%d --> " , ntoh_get_reason ( reason ) , ntoh_get_reason ( extra ) , ntoh_tcp_get_status ( stream->status ) , inet_ntoa( *(struct in_addr*) &orig->addr ) , ntohs(orig->port) );
                                fprintf ( stderr , "%s:%d" , inet_ntoa( *(struct in_addr*) &dest->addr ) , ntohs(dest->port) );
                                break;

                        case NTOH_REASON_CLOSED:
                                fprintf ( stderr , "\n[i] %s/%s - %s | Connection closed by %s (%s)" , ntoh_get_reason ( reason ) , ntoh_get_reason ( extra ) , ntoh_tcp_get_status ( stream->status ) , stream->closedby == NTOH_CLOSEDBY_CLIENT ? "Client" : "Server" , inet_ntoa( *(struct in_addr*) &(stream->client.addr) ) );
                                break;
                }
                break;
}

This little modification should give us the following output:

[i] New stream added! 10.0.0.33:51745 --> 10.0.0.1:443
[i] Synchronization/Established - Established | 10.0.0.33:51745 --> 10.0.0.1:443
[i] Synchronization/Closed - Closed | Connection closed by Client (10.0.0.33)

If you change the above code snippet by this:

    fprintf ( stderr , "\n[%s] %s:%d (%s) ---> " , ntoh_tcp_get_status ( stream->status ) , inet_ntoa( *(struct in_addr*) &orig->addr ) , ntohs(orig->port) , ntoh_tcp_get_status ( orig->status ) );
    fprintf ( stderr , "%s:%d (%s)\n\t" , inet_ntoa( *(struct in_addr*) &dest->addr ) , ntohs(dest->port) , ntoh_tcp_get_status ( dest->status ) );

You will get the following output:

[i] New stream added! 10.0.0.33:51764 --> 10.0.0.1:443
[SYN Sent] 10.0.0.33:51764 (SYN Sent) ---> 10.0.0.1:443 (Listen)
	
[SYN Rcv] 10.0.0.1:443 (SYN Rcv) ---> 10.0.0.33:51764 (SYN Sent)
	
[Established] 10.0.0.33:51764 (Established) ---> 10.0.0.1:443 (Established)
	
[Closing] 10.0.0.33:51764 (Fin Wait1) ---> 10.0.0.1:443 (Close Wait)
	
[Closing] 10.0.0.1:443 (Close Wait) ---> 10.0.0.33:51764 (Fin Wait2)
	
[Closing] 10.0.0.1:443 (Last ACK) ---> 10.0.0.33:51764 (Time Wait)
	
[Closed] 10.0.0.33:51764 (Time Wait) ---> 10.0.0.1:443 (Closed)

Now, we'll merge the above code snippets, and we'll link a data structure to the session, to be able to count how many bytes are sent by each peer.

First of all, we declare the following data structure:

typedef struct
{
    unsigned long client;
    unsigned long server;
} byte_count_t , *pbyte_count_t;

Next, declare a variable of the above typedef as following:

/* TCP processing */
pntoh_tcp_session_t     tcpsession = 0;
ntoh_tcp_tuple5_t       tcpt5 = {0};
pntoh_tcp_stream_t      tcpstream = 0;
unsigned int            error = 0;
int                     ret = 0;
pbyte_count_t           btcount = 0;

Next, modify the lines looking for the stream as following:

/* look for this TCP stream */
if ( !(tcpstream = ntoh_tcp_find_stream ( tcpsession , &tcpt5 )) )
{
	btcount = (pbyte_count_t) calloc ( 1 , sizeof ( byte_count_t ) );

	if ( ! ( tcpstream = ntoh_tcp_new_stream( tcpsession , &tcpt5, &tcp_callback , (void*)btcount , &error , 1 , 1 ) ) )
	        fprintf ( stderr , "\n[e] Error %d creating new stream: %s" , error , ntoh_get_errdesc ( error ) );
	else{
	        fprintf ( stderr , "\n[i] New stream added! %s:%d --> " , inet_ntoa ( *(struct in_addr*)&tcpt5.source ) , ntohs ( tcpt5.sport ) );
	        fprintf ( stderr , "%s:%d" , inet_ntoa ( *(struct in_addr*)&tcpt5.destination ) , ntohs ( tcpt5.dport ) );
	}
}

And finally, modify TCP callback function as follows:

pbyte_count_t btcount = (pbyte_count_t)(stream->udata);

fprintf ( stderr , "\n[%s] %s:%d (%s) ---> " , ntoh_tcp_get_status ( stream->status ) , inet_ntoa( *(struct in_addr*) &orig->addr ) , ntohs(orig->port) , ntoh_tcp_get_status ( orig->status ) );
    fprintf ( stderr , "%s:%d (%s)" , inet_ntoa( *(struct in_addr*) &dest->addr ) , ntohs(dest->port) , ntoh_tcp_get_status ( dest->status ) );

switch ( reason )
{
	/* connection sinchronization */
	case NTOH_REASON_SYNC:
		switch ( extra )
		{
			case NTOH_REASON_MAX_SYN_RETRIES_REACHED:
			case NTOH_REASON_MAX_SYNACK_RETRIES_REACHED:
			case NTOH_REASON_HSFAILED:
			case NTOH_REASON_EXIT:
			case NTOH_REASON_TIMEDOUT:
			case NTOH_REASON_CLOSED:
				if ( extra == NTOH_REASON_CLOSED )
					fprintf ( stderr , "\n\t+ Connection closed by %s (%s)" , stream->closedby == NTOH_CLOSEDBY_CLIENT ? "Client" : "Server" , inet_ntoa( *(struct in_addr*) &(stream->client.addr) ) );
				else
					fprintf ( stderr , "\n\t+ %s/%s - %s" , ntoh_get_reason ( reason ) , ntoh_get_reason ( extra ) , ntoh_tcp_get_status ( stream->status ) );

				fprintf ( stderr , "\n\t[i] Total transfered data:");
				fprintf ( stderr , "\n\t\t- Client: %lu bytes" , btcount->client );
				fprintf ( stderr , "\n\t\t- Server: %lu bytes\n" , btcount->server );

				free ( btcount );

				break;
		}

		break;

	case NTOH_REASON_DATA:
		fprintf ( stderr , "\n\t+ Segment payload len: %i" , seg->payload_len );
		if ( orig == &(stream->client) )
			btcount->client += seg->payload_len;
		else
			btcount->server += seg->payload_len;

		break;
}

With all of modifications made, you should get the following output (continuing with NetCat example):

[i] New stream added! 10.0.0.33:52015 --> 10.0.0.1:443
[SYN Sent] 10.0.0.33:52015 (SYN Sent) ---> 10.0.0.1:443 (Listen)
[SYN Rcv] 10.0.0.1:443 (SYN Rcv) ---> 10.0.0.33:52015 (SYN Sent)
[Established] 10.0.0.33:52015 (Established) ---> 10.0.0.1:443 (Established)
[Closing] 10.0.0.33:52015 (Fin Wait1) ---> 10.0.0.1:443 (Close Wait)
[Closing] 10.0.0.1:443 (Last ACK) ---> 10.0.0.33:52015 (Closing)
[Closed] 10.0.0.33:52015 (Time Wait) ---> 10.0.0.1:443 (Closed)
+ Connection closed by Client (10.0.0.33)
[i] Total transfered data:
	- Client: 0 bytes
	- Server: 0 bytes

No bytes transfered, remember we're launching NetCat with "-vvz" options. So, let's try the following:

$ ssh 10.0.0.1 'exit'

[i] New stream added! 10.0.0.33:50933 --> 10.0.0.1:22
[SYN Sent] 10.0.0.33:50933 (SYN Sent) ---> 10.0.0.1:22 (Listen)
[SYN Rcv] 10.0.0.1:22 (SYN Rcv) ---> 10.0.0.33:50933 (SYN Sent)
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 41
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 32
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 1272
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 784
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 24
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 152
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 144
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 720
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 16
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 48
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 48
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 64
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 1520
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 368
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 320
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 640
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 32
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 128
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 48
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 112
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 80
[Established] 10.0.0.1:22 (Established) ---> 10.0.0.33:50933 (Established)
	+ Segment payload len: 192
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 32
[Established] 10.0.0.33:50933 (Established) ---> 10.0.0.1:22 (Established)
	+ Segment payload len: 64
[Closing] 10.0.0.33:50933 (Fin Wait1) ---> 10.0.0.1:22 (Close Wait)
[Closing] 10.0.0.1:22 (Last ACK) ---> 10.0.0.33:50933 (Closing)
[Closed] 10.0.0.33:50933 (Time Wait) ---> 10.0.0.1:22 (Closed)
	+ Connection closed by Client (10.0.0.33)
	[i] Total transfered data:
		- Client: 2944 bytes
		- Server: 3937 bytes

That looks really great!!


Final Words

Once you have learned the above concepts about how to use libntoh, you are ready to read and understand the source code example given in the repository.

On the example above, we had not worked with segment payloads. To do it, you must link each segment payload with his TCP segment (using ntoh_tcp_add_segment function) in the same way we've done before with bytes count.

You can see this feature and more in the example attached with libntoh in the source code repository.

Have fun!