24
2011
Code a network packet sniffer in PHP
Example of a packet sniffer is Wireshark. Packet sniffers pick up the packets going in and out of a system and analyse them and present them to user for further analysis.
In this post we are going to code a simple packet sniffer in php. The basic theory this packet sniffer is that , raw packets can sniff without much effort if they are put into receiving mode. This will work only on a Linux system , since we cannot create raw sockets on windows.
So the steps are :
1. Create a raw socket
2. Receive on it.
3. See what you received.
Code
<?php
/**
Packet sniffer in PHP
Will run only on Linux
Needs root privileges , so use sudo !!
*/
error_reporting(~E_ALL);
//Create a RAW socket
$socket = socket_create(AF_INET , SOCK_RAW , SOL_TCP);
if($socket)
{
echo "Starting sniffing...\n";
while(true)
{
//Start receiving on the raw socket
socket_recv ( $socket , &$buf , 65536 , 0 );
//Process the packet
process_packet($buf);
}
}
//Some error - check that you used sudo !!
else
{
$error_code = socket_last_error();
$error_message = socket_strerror($error_code);
echo "Could not create socket : [$error_code] $error_message";
}
/**
Process the captured packet.
*/
function process_packet($packet)
{
//IP Header
$ip_header_fmt = 'Cip_ver_len/'
.'Ctos/'
.'ntot_len/'
.'nidentification/'
.'nfrag_off/'
.'Cttl/'
.'Cprotocol/nheader_checksum/Nsource_add/Ndest_add/';
//Unpack the IP header
$ip_header = unpack($ip_header_fmt , $packet);
if($ip_header['protocol'] == '6')
{
print_tcp_packet($packet);
}
}
/*
Process a TCP Packet :)
*/
function print_tcp_packet($packet)
{
$ip_header_fmt = 'Cip_ver_len/'
.'Ctos/'
.'ntot_len/';
$p = unpack($ip_header_fmt , $packet);
$ip_len = ($p['ip_ver_len'] & 0x0F);
if($ip_len == 5)
{
//IP Header format for unpack
$ip_header_fmt = 'Cip_ver_len/'
.'Ctos/'
.'ntot_len/'
.'nidentification/'
.'nfrag_off/'
.'Cttl/'
.'Cprotocol/'
.'nip_checksum/'
.'Nsource_add/'
.'Ndest_add/';
}
else if ($ip_len == 6)
{
//IP Header format for unpack
$ip_header_fmt = 'Cip_ver_len/'
.'Ctos/'
.'ntot_len/'
.'nidentification/'
.'nfrag_off/'
.'Cttl/'
.'Cprotocol/'
.'nip_checksum/'
.'Nsource_add/'
.'Ndest_add/'
.'Noptions_padding/';
}
$tcp_header_fmt = 'nsource_port/'
.'ndest_port/'
.'Nsequence_number/'
.'Nacknowledgement_number/'
.'Coffset_reserved/';
//total packet unpack format
$total_packet = $ip_header_fmt.$tcp_header_fmt.'H*data';
$p = unpack($total_packet , $packet);
$tcp_header_len = ($p['offset_reserved'] >> 4);
if($tcp_header_len == 5)
{
//TCP Header Format for unpack
$tcp_header_fmt = 'nsource_port/'
.'ndest_port/'
.'Nsequence_number/'
.'Nacknowledgement_number/'
.'Coffset_reserved/'
.'Ctcp_flags/'
.'nwindow_size/'
.'nchecksum/'
.'nurgent_pointer/';
}
else if($tcp_header_len == 6)
{
//TCP Header Format for unpack
$tcp_header_fmt = 'nsource_port/'
.'ndest_port/'
.'Nsequence_number/'
.'Nacknowledgement_number/'
.'Coffset_reserved/'
.'Ctcp_flags/'
.'nwindow_size/'
.'nchecksum/'
.'nurgent_pointer/'
.'Ntcp_options_padding/';
}
//total packet unpack format
$total_packet = $ip_header_fmt.$tcp_header_fmt.'H*data';
//unpack the packet finally
$packet = unpack($total_packet , $packet);
//prepare the unpacked data
$sniff = array(
'ip_header' => array(
'ip_ver' => ($packet['ip_ver_len'] >> 4) ,
'ip_len' => ($packet['ip_ver_len'] & 0x0F) ,
'tos' => $packet['tos'] ,
'tot_len' => $packet['tot_len'] ,
'identification' => $packet['identification'] ,
'frag_off' => $packet['frag_off'] ,
'ttl' => $packet['ttl'] ,
'protocol' => $packet['protocol'] ,
'checksum' => $packet['ip_checksum'] ,
'source_add' => long2ip($packet['source_add']) ,
'dest_add' => long2ip($packet['dest_add']) ,
) ,
'tcp_header' => array(
'source_port' => $packet['source_port'] ,
'dest_port' => $packet['dest_port'] ,
'sequence_number' => $packet['sequence_number'] ,
'acknowledgement_number' => $packet['acknowledgement_number'] ,
'tcp_header_length' => ($packet['offset_reserved'] >> 4) ,
'tcp_flags' => array(
'cwr' => (($packet['tcp_flags'] & 0x80) >> 7) ,
'ecn' => (($packet['tcp_flags'] & 0x40) >> 6) ,
'urgent' => (($packet['tcp_flags'] & 0x20) >> 5 ) ,
'ack' => (($packet['tcp_flags'] & 0x10) >>4) ,
'push' => (($packet['tcp_flags'] & 0x08)>>3) ,
'reset' => (($packet['tcp_flags'] & 0x04)>>2) ,
'syn' => (($packet['tcp_flags'] & 0x02)>>1) ,
'fin' => (($packet['tcp_flags'] & 0x01)) ,
) ,
'window_size' => $packet['window_size'] ,
'checksum' => $packet['checksum'] . ' [0x'.dechex($packet['checksum']).']',
) ,
'data' => hex_to_str($packet['data'])
);
//print the unpacked data
print_r($sniff);
}
/*
idea taken from http://ditio.net/2008/11/04/php-string-to-hex-and-hex-to-string-functions/
modified a bit to show non alphanumeric characters as dot.
*/
function hex_to_str($hex)
{
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2)
{
$d = hexdec($hex[$i].$hex[$i+1]);
//Show only if number of alphabet
if( ($d >= 48 and $d <= 57) or ($d >= 65 and $d <= 90) or ($d >= 97 and $d <= 122) )
{
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
else
{
$string .= '.';
}
}
return $string;
}
The script needs to run with root privileges. So on ubuntu you can run like this :
sudo php sniffer.php
Analysis
This is what creates a raw socket
$socket = socket_create(AF_INET , SOCK_RAW , SOL_TCP);
SOCK_RAW means raw.
No start receiving on the raw socket.
while(true)
{
//Start receiving on the raw socket
socket_recv ( $socket , &$buf , 65536 , 0 );
//Process the packet
process_packet($buf);
}
The socket_recv receives some passing by packet in $buf. Then process_packet will analyse the packet which is in $buf.
The structure of a typical TCP packet is like this : IP Header + TCP Header + Data
So the packet needs to be broken down into the relevant parts.
IP Header
According to RFC 791 the IP headers looks like this :
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 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The IHL (IP Header Length) contains the length of the ip header / 4. So if its value is 5 then header length is 20 bytes. Generally header length is 5 and options and padding is not there. If options and padding is present then length would be 6. This has to be checked first before moving to the next part that is TCP header.
Since the $buf contains data in binary format , it needs to be unpacked using the unpack function of php. The unpack string should be:
//IP Header format for unpack $ip_header_fmt = 'Cip_ver_len/' .'Ctos/' .'ntot_len/' .'nidentification/' .'nfrag_off/' .'Cttl/' .'Cprotocol/' .'nip_checksum/' .'Nsource_add/' .'Ndest_add/';
For TCP packets the value of protocol is 6.
TCP headers look like this :
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 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The TCP header also has an options and padding part. The TCP header length is present in the Data Offset part and needs to be multiplied by 4 to get the length in bytes.
They can be unpacked with this string :
//TCP Header Format for unpack $tcp_header_fmt = 'nsource_port/' .'ndest_port/' .'Nsequence_number/' .'Nacknowledgement_number/' .'Coffset_reserved/' .'Ctcp_flags/' .'nwindow_size/' .'nchecksum/' .'nurgent_pointer/';
The print_tcp_packet breaks down the tcp packet into its parts and saves them in a array with proper key names for analysis.
//total packet unpack format $total_packet = $ip_header_fmt.$tcp_header_fmt.'H*data'; //unpack the packet finally $packet = unpack($total_packet , $packet); //prepare the unpacked data $sniff = array( 'ip_header' => array( 'ip_ver' => ($packet['ip_ver_len'] >> 4) , 'ip_len' => ($packet['ip_ver_len'] & 0x0F) , 'tos' => $packet['tos'] , 'tot_len' => $packet['tot_len'] , 'identification' => $packet['identification'] , 'frag_off' => $packet['frag_off'] , 'ttl' => $packet['ttl'] , 'protocol' => $packet['protocol'] , 'checksum' => $packet['ip_checksum'] , 'source_add' => long2ip($packet['source_add']) , 'dest_add' => long2ip($packet['dest_add']) , ) , 'tcp_header' => array( 'source_port' => $packet['source_port'] , 'dest_port' => $packet['dest_port'] , 'sequence_number' => $packet['sequence_number'] , 'acknowledgement_number' => $packet['acknowledgement_number'] , 'tcp_header_length' => ($packet['offset_reserved'] >> 4) , 'tcp_flags' => array( 'cwr' => (($packet['tcp_flags'] & 0x80) >> 7) , 'ecn' => (($packet['tcp_flags'] & 0x40) >> 6) , 'urgent' => (($packet['tcp_flags'] & 0x20) >> 5 ) , 'ack' => (($packet['tcp_flags'] & 0x10) >>4) , 'push' => (($packet['tcp_flags'] & 0x08)>>3) , 'reset' => (($packet['tcp_flags'] & 0x04)>>2) , 'syn' => (($packet['tcp_flags'] & 0x02)>>1) , 'fin' => (($packet['tcp_flags'] & 0x01)) , ) , 'window_size' => $packet['window_size'] , 'checksum' => $packet['checksum'] . ' [0x'.dechex($packet['checksum']).']', ) , 'data' => hex_to_str($packet['data']) );
Similar piece of code can be written for UDP packets , ICMP packets and so on.
The C version of the same code can be found here : http://www.binarytides.com/blog/packet-sniffer-code-in-c-using-linux-sockets-bsd/.
Popularity: 3% [?]
Related Posts
Subscribe
Recent Posts
- Login into phpmyadmin without username and password
- 10+ tips to localise your php application
- 40+ Techniques to enhance your php code – Part 3
- 40+ Techniques to enhance your php code – Part 2
- 40+ Techniques to enhance your php code – Part 1
- CSSDeck – Collection of Pure CSS Creations
- Execute shell commands in PHP
- Php get list of locales installed on system
- Sound cracking in Ubuntu 11.10
- PHP script to perform IP whois
An article by





Thanks for posting this!
I am trying to run it, but can’t create the socket. How do you specify to run it as sudo to view/run the script in a browser (I have it saved in: htdocs/sniff/sniffer.php)? Your suggestion of “sudo php sniffer.php” from the command line results in a command not found msg.
Could you please let me know how you are running this awesome script?
Thanks!
you need php cli to be installed.
Check it with the command “php -v”
It should show something like this :
PHP 5.3.5-1ubuntu7.4 with Suhosin-Patch (cli) (built: Dec 13 2011 18:30:11)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
with Xdebug v2.1.0, Copyright (c) 2002-2010, by Derick Rethans
If it says that command not found then its not installed.
Also this code will work only on Linux.
When running the script provide the full path like :
sudo php /var/htdocs/sniff/sniffer.php
or something similar