Scaperl is a portable, customizable packet creation and sending/sniffing tool written in Perl. It is based on PCAP and libdnet (and their respective Perl wrappers). It was tested on NetBSD, GNU/Linux and Windows XP and should theoretically work on some other platforms such as FreeBSD, OpenBSD, Mac OS X and proprietary Unixes.
The goal is to have a minimal, portable, generic, efficient (see section on performance) implementation of Scapy concepts (see Philippe Biondi's page at http://www.secdev.org/projects/scapy/), with readable, well commented code and good documentation. Yes, this is definitively a huge program...
You can contact me at sylvain.sarmejeanne.ml AT gmail.com
Important note: Scaperl 0.1 was just released as a proof of concept, it will not be maintained anymore. Give Scruby (Scapy in Ruby) a try.
$ ./scaperl.pl scaperl> help This is Scaperl, a portable, customizable packet creation, [...] See http://sylv1.tuxfamily.org/projects/scaperl.html for more information. With Scaperl, you can: [...] Available dissectors: Ether IP [...] Available functions: sd sendp [...]
scaperl> help 'sniff' This function captures packets on an interface. [...]In this kind of message, each available argument for the function is explained and its default value is given, as well as usage examples.
scaperl> print $conf default interface: eth0 gateway hwaddr: 00:07:cb:0c:67:a6 promiscuous mode: 1To override a setting:
scaperl> $conf->{iface} = "eth1"
scaperl> print $conf
default interface: eth1
gateway hwaddr: 00:07:cb:0c:67:a6
promiscuous mode: 1
scaperl> $p=IP()To display detailed information about your packet:
scaperl> $p->show ###[ IPv4 ]### version_ihl = 0x45 tos = 0x0 len = 20 id = 0x0 flags_offset = 0 ttl = 64 proto = 6 chksum = 0x0 src = 127.0.0.1 dst = 127.0.0.1To modify your packet:
scaperl> $p->{ttl} = 128
scaperl> $p->{dst} = "www.google.com"
This is the same as:
scaperl> $p=IP(ttl=>128, dst=>"www.google.com")If you just want to display non-default parameters:
scaperl> print $p <IPv4 ttl=128 dst=www.google.com |>To create a real packet with several layers, just use the "/" (division) operator to bind layers or raw data together:
scaperl> $p=IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n" scaperl> print $p <IPv4 dst=www.google.com |><TCP |><Raw load="GET / HTTP 1.0 " |> scaperl> $p->show ###[ IPv4 ]### version_ihl = 0x45 tos = 0x0 len = 20 id = 0x0 flags_offset = 0 ttl = 64 proto = 6 chksum = 0x0 src = 127.0.0.1 dst = www.google.com ###[ TCP ]### sport = 1024 dport = 80 seq = 0 ack = 0 dataofs_reserved = 80 flags = 0x2 window = 8192 chksum = 0x0 urgptr = 0 ###[ Raw ]### load = "GET / HTTP 1.0 "Note that unlike Scapy, binding layer B over A will not modify layer A, for performance reason. That is to say, the following is a incorrect ICMP packet:
scaperl> $p=IP()/ICMP()You have to write:
scaperl> $p=IP(proto=>1)/ICMP()
scaperl> IP()->show ###[ IPv4 ]### version_ihl = 0x45 tos = 0x0 len = 20 id = 0x0 flags_offset = 0 ttl = 64 proto = 6 chksum = 0x0 src = 127.0.0.1 dst = 127.0.0.1If your terminal supports it (see below), you can have TAB completion for functions and dissectors:
scaperl> $p=I[tab][tab] ICMP IP scaperl> s[tab][tab] sd sendp sniff strYou can also have an history:
scaperl> $p=IP() scaperl> $q=UDP() scaperl> [up]$q=UDP()[up]$p=IP()To make it work, install Term::Readline::Gnu from the CPAN if you are on a Unix system. On Windows, ActivePerl natively supports it.
scaperl> $p=IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n" scaperl> $s=str($p) scaperl> print "string=$s" string=E:@c\ BùUhPP ¤~GET / HTTP 1.0 scaperl> print "result=",IP($s) result=<IPv4 len=58 chksum=0x635c dst=66.249.85.104 |><TCP chksum=0xa47e |><Raw load="GET / HTTP 1.0" |>This mechanism is used to build Scaperl packets from bytes sniffed.
Note that if not enough bytes are passed to dissect the whole fields of the protocol, Scaperl dissects as much as it can:
scaperl> print IP("A")
<IPv4 version_ihl=0x41>
First of all, check that the default output interface is well configured. It is stored in $conf->{iface} (see configuration section above). Scaperl also needs to know the MAC address of your default gateway. This is stored in $conf->{gateway_hwaddr} :
scaperl> $conf->{gateway_hwaddr} = "00:11:22:33:44:55";
The function for sending at level 3 is sd (same as Scapy's send function):
scaperl> $p=IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n" scaperl> sd($p) Sent on eth0.The IP source field is automagically filled with the output IP address for the default interface if you don't specify one. Operations like checksum computation are done just before sending (this is also true for sending at layer 2, see below). After sending the packet:
scaperl> print $p <IPv4 len=96 chksum=0xc403 src=11.22.33.44.55 dst=www.google.com |><TCP chksum=0x54c |> <Raw load="GET / HTTP 1.0 " |>
scaperl> $p=Ether()/IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n" scaperl> sendp($p) Sent.As for sending at level 3, sending at level 2 fills source fields for Ethernet and IP according to the default output interface (here again, if you don't specify them and if Libdnet is available). Moreover, Ethernet destination field is filled with $conf{gateway_hwaddr} if nothing is specified. After sending:
scaperl> print $p <Ethernet dst=00:07:cb:0c:67:a6 src=00:50:70:34:88:b4 |> <IPv4 len=58 chksum=0xc429 src=11.22.33.44.55 dst=www.google.com |><TCP chksum=0x54c |> <Raw load="GET / HTTP 1.0 " |>
scaperl> sniff listening on eth0, link type is EN10MB (Ethernet) 1161032765.823136 <Ethernet dst=00:07:cb:0c:67:a6 src=00:50:70:34:88:b4 |> <IPv4 len=59 id=0x48f flags_offset=16384 proto=17 chksum=0xcd3 src=11.22.33.44.55 dst=212.27.54.252 |> <Raw load="5'JPawwwgooglefr" |> 1161032765.853398 <Ethernet dst=00:50:70:34:88:b4 src=00:07:cb:0c:67:a6 |> <IPv4 len=103 flags_offset=16384 ttl=59 proto=17 chksum=0x1636 src=212.27.54.252 dst=11.22.33.44.55 |> <Raw load="5ShawwwgooglefrÀ XwwwgooglecomÀ+XÑUc" |> [...]To stop sniffing, press ^C. sniff has some interesting options, see the reference guide below.
This may be one of the shortest sniffers ever:
#! /usr/bin/env perl package Scaperl; use Scaperl; sniffThe first two lines import everything from Scaperl (same as "from scapy import *" in Python). Then write your code as if you were using the interpreter.
What about this useless tiny IDS that detects incoming packets on TCP/445 (Microsoft's CIFS):
#! /usr/bin/env perl
package Scaperl;
use Scaperl;
use warnings;
use strict;
sub callback {
my ($linktype, $header, $packet) = @_;
# Ethernet
if($linktype == Net::Pcap::DLT_EN10MB) {
my $dissect = Ether($packet);
my $l3 = $dissect->{layers_list}[1];
my $l4 = $dissect->{layers_list}[2];
return if(not defined $l4);
return if(not ref $l4 eq "TCP");
return if(not $l4->{dport} == 445);
print "just received a packet to TCP/445: $dissect\n";
}
# Unknown link type
else {
warn "Unknown link type: $linktype\n";
}
}
sniff(prn=>'callback')
example> $p=IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n" example> sd($p) Sent.
example> $p=Ether()/IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n" example> sendp($p) Sent.
example> $p=IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n" example> print str($p) E:@é·gPP DGET / HTTP 1.0
example> sniff listening on eth0, link type is EN10MB (Ethernet) 1158608918.45960 <Ethernet dst=00:11:22:33:44:55 src=55:44:33:22:11:00 |> <IPv4 len=84 flags_offset=16384 proto=1 chksum=0x7c0f src=1.2.3.4 dst=4.3.2.1 |> <ICMP chksum=17905 id=16922 seq=1 |> 1158608918.124147 <Ethernet dst=55:44:33:22:11:00 src=00:11:22:33:44:55 |> <IPv4 len=84 flags_offset=16384 ttl=244 proto=1 chksum=0xc80e src=4.3.2.1 dst=1.2.3.4 |> <ICMP type=0 chksum=19953 id=16922 seq=1 |>The following arguments are available (with the default values between brackets):
example> sub my_prn {my ($linktype, $header, $packet) = @_; print "GOT ONE: raw=|$packet|\n"}
example> sniff(iface=>"eth1", prn=>"my_prn", lfilter=>"icmp")
listening on eth0, link type is EN10MB (Ethernet)
GOT ONE: raw=|Ë
g¦Pp4ET@@É·RïËIÑU¶\bú¢8Eº£
Note that by default (and unlike Scapy), packets captured are not stored in memory for performance reason. To stop sniffing, press ^C.
class ICMP(Packet):
name = "ICMP"
fields_desc = [ ByteEnumField("type",8, icmptypes),
ByteField("code",0),
XShortField("chksum", None),
XShortField("id",0),
XShortField("seq",0) ]
package ICMP;
our @ISA = qw(Layer);
sub init {
my $self = shift;
$self->{protocol} = "ICMP";
$self->{fields_desc} = [ ByteField("type", ICMP_TYPE_ECHO_REQUEST),
ByteField("code", 0),
XShortField("chksum", 0),
XShortField("id", 0),
XShortField("seq", 0) ];
}
And some specific functions follow (checkum computation, etc).
Let's say you want to implement MySuperProtocol (MSP). In Dissectors.pm, create a new package, inherit from Layer, describe your protocol (name and fields) and write specific functions if needed:
package MSP;
our @ISA = qw(Layer);
*AUTOLOAD = \&Scaperl::AUTOLOAD;
# Global definition (name and fields)
sub init {
my $self = shift;
$self->{protocol} = "MySuperProtocol";
$self->{fields_desc} = [ ByteField("foo", 0),
StrField("bar", 0),
XShortField("chksum", 0)];
}
sub pre_send {
my $self = shift;
my ($underlayer, $payload) = @_;
$self->{chksum} = 0;
$self->{chksum} = $self->checksum($self->tonet().$payload);
}
pre_send is a special method you can implement. It will be called just before sending packets and can be used for any purpose, e.g. checksum computation. Have a look at Dissectors.pm for real-life examples. In this example, the checksum needs to be aware of the upper layers. For performance reason, this payload is not passed to all dissectors; add the name of the new protocol to the condition in Packet.pm, function tonet to make it work.
Here is the list of fields that can be used to build dissectors:
When implementing a new protocol, you may need to implement a new field. Imagine you want to implement a field for little endian short integers, with hexa display:
package XLEShortField;
our @ISA = qw(Field);
sub init {
my $self = shift;
$self->{format} = "v";
}
That's all for the general definition of the format. Looking at Field.pm, you will see that there are 3 functions in the Field package that you can implement in you own fields:
sub tohuman {
my $self = shift;
my ($value) = @_;
my $result = sprintf("0x%x", $value);
return $result;
}
In real life, you will first want to write a field for LEShortField, and then write another field for XLEShortField inheriting from LEShortField (have a look at XByteField for an example of this).
In Layer.pm, you will notice a special hash name "layer_bounds". There are stored the links between layers and field values. For Ethernet, this reads "if Ether type is 0x800, then the upper layer is IP":
Ether => [
["type", 0x800, "IP"],
],
When adding a new protocol, don't forget to add information to this hash so as to be able to dissect the new protocol.
I wrote 2 small test (yes, there is not much to test in Scaperl yet :):
$ cat test1.py
#! /usr/bin/env python
from scapy import *
s = "\x00\x07\xcb\x0c\x67\xa6\x00\x50\x70\x34\x88\xb4\x08\x00\x45\x00\x02\x62\x9d\x23\x40"
s += "\x00\x40\x06\x84\xe8\x52\xef\xcb\x49\x40\xe9\xb7\x68\xbf\xe9\x00\x50\xfc\xec\x17"
s += "\x4e\x50\x75\x81\x49\x50\x18\x7f\xff\xf7\x7d\x00\x00\x47\x45\x54\x20\x2f\x20\x48"
s += "\x54\x54\x50\x2f\x31\x2e\x31\x0d\x0a\x0d\x0a"
for i in range(10000):
p=Ether(s+str(i))
$ time ./test1.py
real 0m9.707s
user 0m9.533s
sys 0m0.128s
$ cat test1.pl
#! /usr/bin/env perl
package Scaperl;
use Scaperl;
$s = "\x00\x07\xcb\x0c\x67\xa6\x00\x50\x70\x34\x88\xb4\x08\x00\x45\x00\x02\x62\x9d\x23\x40";
$s .= "\x00\x40\x06\x84\xe8\x52\xef\xcb\x49\x40\xe9\xb7\x68\xbf\xe9\x00\x50\xfc\xec\x17";
$s .= "\x4e\x50\x75\x81\x49\x50\x18\x7f\xff\xf7\x7d\x00\x00\x47\x45\x54\x20\x2f\x20\x48";
$s .= "\x54\x54\x50\x2f\x31\x2e\x31\x0d\x0a\x0d\x0a";
for($i=0;$i<10000;$i++) {
$p=Ether($s.$i);
}
$ time ./test1.pl
real 0m5.904s
user 0m5.844s
sys 0m0.032s
$ cat test2.py
#! /usr/bin/env python
from scapy import *;
p=IP()
for i in range(500):
p/=IP(len=i)
$ time ./test2.py
real 0m26.908s
user 0m26.226s
sys 0m0.264s
$ cat test2.pl
#! /usr/bin/env perl
package Scaperl;
use Scaperl;
$p=IP();
for($i=0;$i<500;$i++) {
$p/=IP(len=>$i);
}
$ time ./test2.pl
real 0m0.490s
user 0m0.164s
sys 0m0.008s