This is a C# utility for reconstructing sniffer captured TCP sessions (even incomplete). This is based on libnids and a translated part of WireShark. Not being able to find such a solution, I had to build one myself.
I was looking for some tools which could reconstruct a TCP session from a Pcap file. The tools I have found were mostly for Linux, or robust GUI tools like WireShark. So, I decided to build my own tool. It is time that the C# community will have a TCP session reconstruction tool.
Contents
"A sniffer is a piece of software that grabs all of the traffic flowing into and out of a computer attached to a network. They are available for several platforms in both commercial and open-source variations. Some of the simplest packages are actually quite easy to implement in C or Perl; use a command line interface and dump the captured data to the screen. More complex projects use a GUI, graph traffic statistics, track multiple sessions, and offer several configuration options. Sniffers are also the engines for other programs. Intrusion Detection Systems (IDS) use sniffers to match packets against a rule-set designed to flag anything malicious or strange. Network utilization and monitoring programs often use sniffers to gather data necessary for metrics and analysis. Law enforcement agencies that need to monitor emails during investigations likely employ a sniffer designed to capture very specific traffic."
Excerpt from - Sniffers: What They Are and How to Protect Yourself
A sniffer lets you capture packets on your network, but the packets come in various shapes and colors, and they are often in disorder. TCP reconstruction is the reordering of the session data back to its original state.
In a sentence, it lets you watch the application layer data. Let's say, you have a capture file obtained from a program such as tcpdump or WireShark. Now, you want to observe the actual sessions that you captured, for example, a full HTTP session between a server and a client. You can reconstruct the page requests and the HTTP server responses. Yes, this means you can also actually reconstruct emails and attachments that are sent over your network.
I use it in a security tool I'm working on. You can use this kind of functionality to do good or evil, but it is a powerful functionality that is missing.
libnids is a library designed by Rafal Wojtczuk. It emulates the IP stack of Linux 2.0.x. libnids offers IP defragmentation, TCP stream assembly, and TCP port scan detection. There are some ports of the library to Win32, the most recent one I have found is for version 1.19 (the newest Linux version is 1.22). You will also need the WinPcap library in order to build it. The solution here is to wrap this library with managed C++ code and provide two managed callbacks, one for the server data and one for the client data.
How to work with libnids:
- The first thing we have to do is to configure the library
nids_params
struct. I needed to work on a capture file, this is the place to pass the file name. You can set a lot of options here, including a filter which can be very useful if you have a big data file, or you decide to run live on the wire. - Call
nids_init()
. - Register a local non managed callback for the library.
- Setup the two managed callbacks.
- Call
nids_run()
.
IntPtr p = Marshal::StringToHGlobalAnsi(filename);
char *szFile = (char*)p.ToPointer();
nids_params.filename = szFile;
.
.
.
managedLibnids::LibnidsWrapper::m_clientCallback = clientCallback;
managedLibnids::LibnidsWrapper::m_serverCallback = serverCallback;
if (!nids_init ())
{
return;
}
nids_register_tcp (tcp_callback);
nids_run ();
Simple as that. Of course, now you need to marshal data off to the managed callbacks, but besides that, you are basically done.
This is the interesting part of the local callback function:
void managedLibnids::tcp_callback (struct tcp_stream *a_tcp,
void ** this_time_not_needed)
{
if (a_tcp->nids_state == NIDS_JUST_EST)
{
a_tcp->client.collect++;
a_tcp->server.collect++;
a_tcp->server.collect_urg++;
a_tcp->client.collect_urg++;
return;
}
.
.
.
if (a_tcp->nids_state == NIDS_DATA)
{
struct half_stream *hlf;
if (a_tcp->client.count_new)
{
hlf = &a_tcp->client;
}
else
{
hlf = &a_tcp->server;
}
int len = hlf->count_new;
array[byte]^ data = gcnew array[byte](len);
Marshal::Copy((IntPtr)hlf->data,data,0, len);
if (a_tcp->client.count_new)
LibnidsWrapper::m_clientCallback(data,a_tcp->addr.saddr,
a_tcp->addr.source,a_tcp->addr.daddr,a_tcp->addr.dest,false);
else
LibnidsWrapper::m_serverCallback(data,a_tcp->addr.saddr,
a_tcp->addr.source,a_tcp->addr.daddr,a_tcp->addr.dest,false);
}
return ;
}
I have discovered that only complete or nearly complete sessions get reassembled with libnids. This is usually enough for most people, but I need to check incomplete sessions as well. I usually work with WireShark on specific streams, and it has the ability to follow TCP streams, even incomplete streams. This is the ability I want to have as well.
Luckily, WireShark is an open source project. I stripped it open, and searched for the code that reconstructs the TCP session, and Walla! From here, all I had to do was to translate the ANSI C code to pure C#. The code was very intuitive and easy to follow, thanks for the guys at WireShark that did a good job.
This is the main function that reconstructs the TCP session:
private void reassemble_tcp( ulong sequence, ulong length, byte[] data,
ulong data_length, bool synflag, long net_src,
long net_dst, uint srcport, uint dstport)
{
long srcx, dstx;
int src_index, j;
bool first = false;
ulong newseq;
tcp_frag tmp_frag;
src_index = -1;
srcx = net_src;
dstx = net_dst;
for( j=0; j<2; j++ ) {
if (src_addr[j] == srcx && src_port[j] == srcport ) {
src_index = j;
}
}
if( src_index < 0 ) {
for( j=0; j<2; j++ ) {
if( src_port[j] == 0 ) {
src_addr[j] = srcx;
src_port[j] = srcport;
src_index = j;
first = true;
break;
}
}
}
if( src_index < 0 ) {
throw new Exception("ERROR in reassemble_tcp: Too many addresses!");
}
if( data_length < length ) {
incomplete_tcp_stream = true;
}
if( first ) {
seq[src_index] = sequence + length;
if( synflag ) {
seq[src_index]++;
}
write_packet_data( src_index, data );
return;
}
if( sequence < seq[src_index] ) {
newseq = sequence + length;
if( newseq > seq[src_index] ) {
ulong new_len;
new_len = seq[src_index] - sequence;
if ( data_length <= new_len ) {
data = null;
data_length = 0;
incomplete_tcp_stream = true;
} else {
data_length -= new_len;
byte[] tmpData = new byte[data_length];
for(ulong i=0; i<data_length; /> 0 && sequence > seq[src_index] ) {
tmp_frag = new tcp_frag();
tmp_frag.data = data;
tmp_frag.seq = sequence;
tmp_frag.len = length;
tmp_frag.data_len = data_length;
if( frags[src_index] != null ) {
tmp_frag.next = frags[src_index];
} else {
tmp_frag.next = null;
}
frags[src_index] = tmp_frag;
}
}
}
O.K. We can reconstruct a session; now, all we need is to capture the packets and store each session separately.
For this, I have used a great library I have found here at CodeProject, which is called SharpPcap, written by Tamir Gal. For more info, you can check out this link on how to work with SharpPcap.
Here is the code I have used to capture TCP packets:
try
{
device = SharpPcap.GetPcapOfflineDevice(capFile);
device.PcapOpen();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return;
}
device.PcapOnPacketArrival +=
new SharpPcap.PacketArrivalEvent(device_PcapOnPacketArrival);
device.PcapSetFilter("tcp");
device.PcapCapture(SharpPcap.INFINITE);
device.PcapClose();
The design I used is a dictionary object that holds pairs of (Connection, TcpRecon)
objects. TcpRecon
holds a FileStream
and the state of the TCP connection. It is responsible for the actual reconstruction of a TCP session and the storing of the session in its FileStream
. Connection
holds session information such as source IP, destination IP, source port, destination port, etc.
The SharpPcap library callback searches for a matching connection in the dictionary. Next, the packet is fed to the correct TcpRecon
, which in turn reconstructs the session.
private static void device_PcapOnPacketArrival(object sender, Packet packet)
{
TCPPacket tcpPacket = (TCPPacket)packet;
Connection c = new Connection(tcpPacket);
if (!sharpPcapDict.ContainsKey(c))
{
string fileName = c.getFileName(path);
TcpRecon tcpRecon = new TcpRecon(fileName);
sharpPcapDict.Add(c, tcpRecon);
}
sharpPcapDict[c].ReassemblePacket(tcpPacket);
}
Note that you must have WinPcap installed in order to run TcpRecon!
TcpRecon <capture file name> <-nids>
The -nids flag is used to activate the libnids reconstruction
instead of the built in functionality.
The tool will create session files of the form:
<server_ip>.<server_port>-<client_ip>.<client_port>.data
I would like to thank all the wonderful people behind the projects I've used to build this tool. Thank you for sharing.
- Version 1 - First Submission
- Version 1.01 - Fixed handling of none TCP packets.