gdb disas main
Follow sylv1_secu on Twitter

Flux RSS


Derniers billets blog









Version 3.0beta4-tuxfamily

Scaperl: Scapy in Perl

This documentation is released under the GFDL license.

Download

Version 0.1 of Scaperl (11/2006). It is released under the GPL v2.

What is Scaperl?

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.

Screenshots

See Scaperl in action:

User guide

Installation

First download the Scaperl archive from the section above.

GNU/Linux, *BSD, etc

  1. install libpcap from your favorite package manager or from the tcpdump homepage. Version 0.9.3 (or above) is mandatory for sending packets. Version 0.8 (or above) is mandatory for being able to stop a running sniff.
  2. install the Perl wrapper for libpcap from the CPAN (Net::Pcap): uncompress the archive, perl Makefile.PL, make, make install.
  3. install libdnet from your favorite package manager or from the libdnet homepage. It is not mandatory but using Scaperl with it will be more comfortable (IP and Ethernet source fields will be automagically filled for you according to the default interface).
  4. install the Perl wrapper for libdnet from the CPAN (Net::Libdnet): same as above.
  5. enjoy ./scaperl.pl :) (some functions require you to be root).

Windows

First have a look at the Win32 Perl Wiki for more information about using Perl on Windows.
  1. I am using Strawberry Perl and everything compiles and works well (except...well, we will see that later).
  2. install WinPcap from the WinPcap download page.
  3. install the WinPcap developer pack from the developers page and unzip the archive to C:\WpdPack (could be any path, but this one will be used later).
  4. download Net::Pcap from the CPAN, extract it.
  5. launch cmd.exe and go to the extract directory. For version 0.14, there seems to be a bug and you need to manually create a file named "cpp-out.h".
  6. the automatic detection of functions didn't work on my machine. To make the functions available later in the module, I needed to edit Makefile.PL and replace
    options{DEFINE} = '-DWINSOCK2_H_REQUESTED -DWPCAP -DHAVE_REMOTE';
    by
    $options{DEFINE} = '-DWINSOCK2_H_REQUESTED -DWPCAP -DHAVE_REMOTE -DHAVE_PCAP_LIB_VERSION -DHAVE_PCAP_FINDALLDEVS -DHAVE_PCAP_BREAKLOOP -DHAVE_PCAP_SETNONBLOCK -DHAVE_PCAP_GETNONBLOCK -DHAVE_PCAP_DUMP_FILE -DHAVE_PCAP_DUMP_FLUSH -DHAVE_PCAP_LIST_DATALINKS -DHAVE_PCAP_SET_DATALINK -DHAVE_PCAP_DATALINK_NAME_TO_VAL -DHAVE_PCAP_DATALINK_VAL_TO_NAME -DHAVE_PCAP_DATALINK_VAL_TO_DESCRIPTION -DHAVE_PCAP_COMPILE_NOPCAP -DHAVE_PCAP_NEXT_EX -DHAVE_PCAP_SENDPACKET -DHAVE_PCAP_SETBUFF -DHAVE_PCAP_SETUSERBUFFER -DHAVE_PCAP_SETMODE -DHAVE_PCAP_SETMINTOCOPY -DHAVE_PCAP_SENDQUEUE_ALLOC -DHAVE_PCAP_SENDQUEUE_DESTROY -DHAVE_PCAP_SENDQUEUE_QUEUE -DHAVE_PCAP_SENDQUEUE_TRANSMIT -DHAVE_PCAP_GETEVENT -DHAVE_PCAP_SETSAMPLING ';
  7. in stubs.inc, comment all definition of pcap_if_t, pcap_rmtauth, pcap_send_queue and pcap_samp.
  8. perl Makefile.PL INC=-IC:/WpdPack/Include "LIBS=-LC:/Wpdpack/Lib -lwpcap"
  9. dmake, dmake install
  10. I didn't manage to get Net::Libdnet work on Windows. It compiled but makes Perl crash when using its functions. In the source code of this page, you will find a draft with some indications. If someone...
  11. I had to uninstall ZoneAlarm Firewall in order to send packets.

Known limitations

First note that Scaperl is in its early stages of development. Don't expect it to be as exhaustive as Scapy. The most important limitations in the current version are:
  • it was only tested on Ethernet link type.
  • only a few field types are implemented (see the list of fields).
  • only a few protocols are implemented (see the list of dissectors).
  • there is no BitField (for IP, bit fields are grouped by bytes...).
  • "undefined" is not a correct value for a field. As a consequence, checksum fields cannot have an arbitrary value, they are computed each time.
  • warnings or error messages are written to STDOUT making it irrelevant for honeyd scripts.
  • in the global configuration, only one gateway MAC address can be recorded at a time.
  • the documentation is not in the POD format.
  • there is no support for IPv6.
  • it seems that sending on a loopback device doesn't work.

Getting help

The first thing to do in Scaperl may be to get some general help. The help command displays a message that introduces Scaperl, gives a link to this page, basically explains what you can do with Scaperl and lists the available dissectors:
$ ./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 [...]

Help on a specific function

The last portion of the general help message gives the list of available functions. To get specific help on one of these (e.g. the sniff function):
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.

Configuration

Global settings are stored in $conf:
scaperl> print $conf	
default interface: eth0
gateway hwaddr: 00:07:cb:0c:67:a6
promiscuous mode: 1
To override a setting:
scaperl> $conf->{iface} = "eth1"
scaperl> print $conf
default interface: eth1
gateway hwaddr: 00:07:cb:0c:67:a6
promiscuous mode: 1

Creating and modifying packets

Creating a packet is easy using the Perl syntax. To create a simple IP packet with default values:
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.1
To 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()

Available protocols/dissectors

The following protocols/dissectors can be used to build packets:
  • Ether (Ethernet and Linux loopback header)
  • IP (IPv4)
  • ICMP
  • TCP
  • UDP
  • Raw (any data)
  • ClassicBSDLoopback (loopback header for NetBSD, FreeBSD and Mac OS X)
  • OpenBSDLoopback (loopback header for OpenBSD)
A simple command will display the different fields for a dissector and their default values. Let's take IP as an example:
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.1
If 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 str
You 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.

Building a packet from a string (dissecting a string)

To create a Scaperl packet from a string, pass it as an argument to the corresponding dissector:
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>

Sending a packet at layer 3

Note that if Libdnet is not available, sending at layer 3 is not possible. Sending at layer 2 (see below) works, just specify the correct values for Ethernet and IP sources.

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

" |>

Sending a packet at layer 2

See the above section for configuration. The function used here is sendp:
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

" |>

Sniffing on an interface

By default, the sniff function listens on the interface defined in $conf->{iface} (see configuration section above):
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.

Scripting Scaperl

Writing scripts that use Scaperl is very easy. In fact, scaperl.pl itself is a script that imports everything from Scaperl(.pm). Have a look at it for a real-life example.

This may be one of the shortest sniffers ever:

#! /usr/bin/env perl
package Scaperl;
use Scaperl;
sniff
The 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')

Reference guide

The help here is the same as in Scaperl (help '<function>').

sd

This function sends a packet at layer 3 on the default interface (stored in $conf->{iface}). If Libdnet is available, source IP address is automatically filled according to this interface.
example> $p=IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n"
example> sd($p)
Sent.

sendp

This function sends a packet at layer 2 on the default interface (stored in $conf->{iface}). If Libdnet is available, source Ethernet address and source IP address are automatically filled according to this interface.
example> $p=Ether()/IP(dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n"
example> sendp($p)
Sent.

str

This function transforms a packet into a string ready to be sent on the wire (that is to say, it "packs" it). Note that some characters may not be displayable.
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

sniff

This function captures packets on an interface. The default capture interface is determined by the PCAP library and is stored in $conf->{iface}. Without any argument, sniff captures on the default interface:
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):
  • iface: the interface to listen on ($conf->{iface})
  • prn: a function that will be called for each packet received (sniff_simple)
  • lfilter: a PCAP filter (undef)
  • count: the number of packets to capture. An argument less than or equal to 0 will read "loop forever" (-1)
  • promisc: capture in promiscuous mode or not ($conf->{promisc})
  • timeout: capture timeout in milliseconds (0, seems not to work?)
  • store: not implemented yet
  • offline: not implemented yet
The prn argument is the most interesting one, it allows you to customize the behaviour of the sniff function (see section on scripting for examples):
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.

Developer guide

Global organization

Scaperl consists of one script and several modules. In the archive, you will find the following files:
  • scaperl.pl: the main script, imports everything from the Scaperl module and spawns an interpreter.
  • Scaperl.pm: the main module, imports everything from the other modules.
  • Packet.pm: contains the definition of a Packet (a linked list of Layers).
  • Layer.pm: definition of a Layer (instantiation of a dissector).
  • Dissectors.pm: implemented dissectors (Ether, IP, etc). A dissector is like a list a Fields.
  • Field.pm: implemented fields (for integers, shorts, bytes, strings, etc) to be used for building protocol dissectors.
  • Func.pm: end-user functions (sniff, sendp, etc).
  • Conf.pm: global configuration.
  • Help.pm: everything related to help.

Adding a dissector or a field for a new protocol

Scaperl has been explicitly devised so that the syntax for dissectors is the same as in Scapy (modulo the differences between Python and Perl of course). Let's have a look at how ICMP is implemented in both cases:

ICMP in Scapy

class ICMP(Packet):
    name = "ICMP"
    fields_desc = [ ByteEnumField("type",8, icmptypes),
                    ByteField("code",0),
                    XShortField("chksum", None),
                    XShortField("id",0),
                    XShortField("seq",0) ]

ICMP in Scaperl

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:

  • StrField(field name, default value): string
  • ByteField(field name, default value): byte
  • XByteField(field name, default value): byte with hexadecimal display
  • ShortField(field name, default value): short
  • XShortField(field name, default value): short with hexadecimal display
  • LongField(field name, default value): long
  • LELongField(field name, default value): long (little endian order)
  • HostOrderLongField(field name, default value): long (host order)
  • IntField(field name, default value): integer
  • IPField(field name, default value): IP address
  • MACField(field name, default value): Ethernet MAC address
If you want to have completion for this new protocol, append its name to @DISSECTOR_LIST in scaperl.pl.

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:
  • fromnet: converts from network to internal encoding (e.g. IP addresses displayed as a 4-dotted number)
  • tonet: converts from internal encoding to network (e.g. IP address in 4-dotted form to the real network number)
  • tohuman: converts from internal encoding to human display (e.g. display checksum as "0xDEADBEEF")
In our XLEShortField example, we have to implement tohuman to get hexa display:
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.

Performance

It seems that Scaperl performs quite well compared to Scapy. Performance is in fact one of the reasons why I began this project. Note that Scaperl was not profiled yet (there exist special tools in Perl for that purpose).

I wrote 2 small test (yes, there is not much to test in Scaperl yet :):

Time to dissect M different strings

For this test, I removed all the protocols in Scapy layer_bounds that was not implemented in Scaperl.
Scapy
$ 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
Scaperl
$ 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

Time to create a packet will N different layers

Scapy
$ 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
Scaperl
$ 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
[ Site créé par Sylvain Sarméjeanne ]
Cette page a été générée par mes scripts en 0.034 secondes :)
[Valid XHTML 1.1!] [Valid CSS!] [[Valid RSS]]