gdb disas main
Follow sylv1_secu on Twitter

Flux RSS


Derniers billets blog









Version 3.0beta4-tuxfamily

Scruby

This documentation is released under the GFDL license.

Download

Scruby is released under the GPL v2. Have a look at the CHANGELOG.

Version 0.3 of Scruby (03/2008)

Scruby is now part of the Metasploit Framework (01/2008).

For Metasploit users, H D Moore has hacked on the source to make it fit better into the framework. Pick his version here: version 0.2.1-hdm of Scruby. UPDATE1: a newer version is available here: version 0.2.1-hdm-2 of Scruby (10/2007).

Version 0.2.2 of Scruby (02/2008)

Version 0.2.1 of Scruby (09/2007)

Version 0.2 of Scruby (09/2007)

Version 0.1 of Scruby (04/2007)

What is Scruby?

Scruby is a portable, customizable packet creation and sending/sniffing tool written in Ruby. It was tested on NetBSD, GNU/Linux and MacOS X, and should theoretically work on some other platforms such as FreeBSD, OpenBSD, and proprietary Unixes.

This is just of proof of concept of a minimal implementation of Scapy concepts in Ruby.

Thanks to the similarities between Python and Ruby, you can copy your favorite dissectors for Scapy and paste them in Scruby without any modification (see section "Adding a dissector or a field for a new protocol").

You can contact me at sylvain.sarmejeanne.ml AT gmail.com

Screenshots

See Scruby in action:

User guide

Installation

First download the Scruby 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. the "standard" PCAP wrapper for Ruby is a bit weird, that's why Scruby is based on PcapRub, a minimal PCAP wrapper. Uncompress the archive, ruby extconf.rb, make, make install (version 0.6 works well). A local copy can be found here.
  3. enjoy ./scruby.rb :) (some functions require you to be root).

Windows

I just didn't have enough time to test it, but it should work... If you are looking for a Scapy clone that works under Windows, try Scaperl (Scapy in Perl) :)

Known limitations

Don't expect Scruby to be as exhaustive as Scapy, it is just a proof of concept. The most important limitations in the current version are:
  • it was only tested on Ethernet and 802.11 link types.
  • only a few field types are implemented (see the list of fields).
  • only a few protocols are implemented (see the list of dissectors).
  • "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 RDoc format.
  • there is no support for IPv6.

Getting help

The first thing to do in Scruby may be to get some general help. The help command displays a message that introduces Scruby, gives a link to this page, basically explains what you can do with Scruby and lists the available dissectors and functions:
$ ./scruby.rb
Welcome to Scruby, Copyright 2007 Sylvain Sarméjeanne
If you're lost, just shout for "help".
scruby> help
This is Scruby, a portable, customizable packet creation, [...]
			
See http://sylv1.tuxfamily.org/projects/scruby.html for more information.

With Scruby, you can:
[...]
			
Available dissectors:
["Ether", "IP" [...] ]

Available functions (type "lsc 'myfunction'" to have detailed information): 
["send", "sendp", "ls", "lsc"]

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), use the "lsc" function:
scruby> lsc '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:
scruby> $conf	
iface (default interface): eth0
gateway_hwaddr (gateway Ethernet address): 00:00:00:00:00:00
promisc (promiscuous mode): true
To override a setting:
scruby> $conf.iface = "eth1"
scruby> $conf
iface (default interface): eth1
gateway_hwaddr (gateway Ethernet address): 00:00:00:00:00:00
promisc (promiscuous mode): true

Creating and modifying packets

Creating a packet is easy using the Ruby syntax. To create a simple IP packet with default values:
scruby> p=IP()
<IP |>
To display detailed information about your packet:
scruby> p.show
###[ IPv4 ]###
version = 4
ihl = 5
tos = 0x0
len = 20
id = 0x0
flags = 0 ()
frag = 0
ttl = 64
proto = 6 (TCP)
chksum = 0x0
src = 127.0.0.1
dst = 127.0.0.1
To modify your packet:
scruby> p.ttl = 128
scruby> p.dst = "www.google.com"
This is the same as:
scruby> p=IP(:ttl=>128, :dst=>"www.google.com")
If you just want to display non-default parameters:
scruby> p		
<IP 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:
scruby> p=IP(:dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n"
<IP dst=www.google.com |><TCP |><Raw load="GET / HTTP 1.0\r\n\r\n" |>
scruby> p.show
###[ IPv4 ]###
version = 4
ihl = 5
tos = 0x0
len = 20
id = 0x0
flags = 0 ()
frag = 0
ttl = 64
proto = 6 (TCP)
chksum = 0x0
src = 127.0.0.1
dst = www.google.com
###[ TCP ]###
sport = 1024
dport = 80
seq = 0
ack = 0
dataofs = 5
reserved = 0
flags = 2 (SYN)
window = 8192
chksum = 0x0
urgptr = 0
###[ Raw ]###
load = "GET / HTTP 1.0\r\n\r\n"
Note that unlike Scapy, binding layer B over A will not modify layer A, for performance reason (Scapy's behaviour may become an option in a future release). That is to say, the following is a incorrect ICMP packet:
scruby> p=IP()/ICMP()

You have to write:

scruby> p=IP(:proto=>1)/ICMP()

As IP.proto is an Enum field, you could also have written (see below for more on this):

scruby> p=IP(:proto=>'ICMP')/ICMP()

Available protocols/dissectors

The following protocols/dissectors/file formats can be used to build packets or files (use the "ls" command to list them):
  • Ether (Ethernet and Linux loopback header)
  • ARP
  • IP (IPv4)
  • ICMP
  • TCP
  • UDP
  • Raw (any data)
  • ClassicBSDLoopback (loopback header for NetBSD, FreeBSD and Mac OS X)
  • OpenBSDLoopback (loopback header for OpenBSD)
  • RIFF
  • ANI
  • LLC
  • Dot11 and al.
The "ls" command will display the different fields for a dissector, their field types and their default values. Let's take IP as an example:
scruby> ls 'IP'
Field name	Field type	Default value
version		BitField	4
ihl		BitField	5
tos		XByteField	0x0
len		ShortField	20
id		XShortField	0x0
flags		FlagsField	0
frag		BitField	0
ttl		ByteField	64
proto		ByteEnumField	6
chksum		XShortField	0x0
src		IPField	"127.0.0.1"
dst		IPField	"127.0.0.1"

When you see an Enum field (e.g. IP.proto), that means some user-friendliness has been added to it. If you display the field value with full details, its value will be displayed in clear text as well:

scruby> IP().show
version = 4
ihl = 5
tos = 0x0
len = 20
id = 0x0
flags = 0 ()
frag = 0
ttl = 64
proto = 6 (TCP) <-- here, instead of "proto = 6" only
chksum = 0x0
src = 127.0.0.1
dst = 127.0.0.1

With an Enum field, you can also use the text version to specify the value:

scruby> IP(:proto=>'UDP')
<IP proto=17 |>				

If a field is of "FlagsField" type (e.g. IP and TCP flags), setting and displaying its value is user friendly as well:

scruby> p=TCP(:flags=>"SYN ACK")
scruby> p.show
###[ TCP ]###
sport = 1024
dport = 80
seq = 0
ack = 0
dataofs = 5
reserved = 0
flags = 18 (ACK SYN) <- here
window = 8192
chksum = 0x0
urgptr = 0

RFC3514 way of life:

scruby> IP(:flags=>6).show
###[ IPv4 ]###
version = 4
ihl = 5
tos = 0x0
len = 20
id = 0x0
flags = 6 (evil DF) <- here
frag = 0
ttl = 64
proto = 6 (TCP)
chksum = 0x0
src = 127.0.0.1
dst = 127.0.0.1

If your terminal supports it, you can have TAB completion for functions and dissectors:

scruby> p=I[tab][tab]
ICMP  IP
scruby> s[tab][tab]
sendp sniff
You can also have an history:
scruby> p=IP()
scruby> q=UDP()
scruby> [up]q=UDP()[up]p=IP()

Building a packet from a string (dissecting a string)

To create a Scruby packet from a string, pass it as an argument to the corresponding dissector:
scruby> p=IP(:dst=>"www.google.com")/TCP()/"GET / HTTP 1.0\r\n\r\n"
scruby> s=p.to_net
scruby> puts "result=#{IP(s)}"
result=<IP len=58 chksum=0x635c dst=66.249.85.104 |><TCP chksum=0xa47e |>
<Raw load="GET / HTTP 1.0\r\n\r\n" |>
This mechanism is used to build Scruby packets from bytes sniffed on a interface.

If not enough bytes are passed to dissect the whole fields of the protocol, Scruby dissects as much as it can:

scruby> Ether('A'*5)
<Ether |>
scruby> Ether('A'*6)
<Ether dst=41:41:41:41:41:41 |>

Note that dissecting a string returns a Packet and not a Layer, that's why you won't be able to directly access its fields. Use the layers_list member instead (this behaviour may change in a future release):

scruby> p=IP('A')
<IP ihl=1 |>
scruby> p.ttl
undefined method `ttl' for #<Scruby::Packet:0xb7cec0f4>
scruby> p.layers_list[0].ttl
64

Dealing with layers and payloads

If a payload could not have been decoded as a Scruby layer, you can force the dissection with a given dissector:
scruby> s=(IP()/TCP()/"GET / HTTP/1.0\r\n\r\n").to_net
scruby> p=Ether()/s
<Ether |><Raw load="E\000\000:\000\000\000\000@\006|\274\177\000\000\001\177\000\000\001\004\000\000
P\000\000\000\000\000\000\000\000P\002 \000\256\336\000\000GET / HTTP/1.0\r\n\r\n" |>
scruby> p.decode_payload_as(IP)
scruby> p
<Ether |><IP len=58 chksum=0x7cbc |><TCP chksum=0xaede |><Raw load="GET / HTTP/1.0\r\n\r\n" |>

Now that you have a fully dissected packet, you can ask for more information about its layers. The get_layer function gives a pointer to the first layer (including its own payload) corresponding to the given dissector:

scruby> q=p.get_layer(TCP)
<TCP chksum=0xaede |><Raw load="GET / HTTP/1.0\r\n\r\n" |>

There are also the has_layer and the last_layer functions:

scruby> p.has_layer(TCP)
true
scruby> p.has_layer(UDP)
false
scruby> p.last_layer
<Raw load="GET / HTTP/1.0\r\n\r\n" |>

Note that these *_layer functions returns a pointer, that is to say if you modify the returned object, the original is modified as well:

scruby> q.layers_list[0].sport=123
scruby> p
<Ether |><IP len=58 chksum=0x7cbc |><TCP sport=123 chksum=0xaede |><Raw load="GET / HTTP/1.0\r\n\r\n" |>

Sending a packet at layer 3

Note that if Libdnet is not available (which is the case in the current version), sending at layer 3 is not possible. Sending at layer 2 (see below) works, just specify the correct values for Ethernet (source) and IP (source and destination).

Sending a packet at layer 2

Make sure the configuration is good. The function used here is sendp:
scruby> p=Ether(:src=>"00:11:22:33:44:55")/IP(:src=>"10.0.0.1", :dst=>"www.google.com")/TCP()/
"GET / HTTP 1.0\r\n\r\n"
scruby> sendp(p)
Sent on eth0.
The Ethernet destination field is filled with $conf.gateway_hwaddr if nothing is specified. After sending:
scruby> p
<Ether dst=55:44:33:22:11:00 src=00:11:22:33:44:55 |>
<IP len=58 chksum=0xc429 src=11.22.33.44.55 dst=www.google.com |><TCP chksum=0x54c |>
<Raw load="GET / HTTP 1.0\r\n\r\n" |>

Sniffing on an interface

By default, the sniff function listens on the interface defined in $conf.iface (see configuration section above):
scruby> sniff
listening on eth0	
1161032765.823136 <Ether dst=00:11:22:33:44:55 src=55:44:33:22:11:00 |>
<IP len=59 id=0x48f proto=17 chksum=0xcd3 src=11.22.33.44.55 dst=212.27.54.252 |>
<Raw load="5'JPawwwgooglefr" |>
			
1161032765.853398 <Ether dst=55:44:33:22:11:00 src=00:11:22:33:44:55 |>
<IP len=103 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.

Note that non-printable characters are displayed with their octal (not hexadecimal) values (this is the default behaviour of the inspect function in Ruby, this may change in a future Scruby version).

Reading a PCAP file

The sniff function can also read packets from a PCAP file:
scruby> sniff(:offline=>"slammer.pcap")
1188850680.58843 <Ether dst=55:44:33:22:11:00 src=00:11:22:33:44:55 |><IP len=404 id=0xba2f
 ttl=37 proto=17 chksum=0xa275 |><UDP sport=1086 dport=1434 len=384 chksum=0xf958 |><Raw 
 load="\004\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001
 \001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001
 \001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001
 \001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\334\311\260B\353\016
 \001\001\001\001\001\001\001p\256B\001p\256B\220\220\220\220\220\220\220\220h\334\311\260B\270\001\001
 \001\0011\311\261\030P\342\3755\001\001\001\005P\211\345Qh.dllhel32hkernQhounthickChGetTf
 \271llQh32.dhws2_f\271etQhsockf\271toQhsend\276\030\020\256B\215E\324P\377\026P\215E\340P\215E\360P\377
 \026P\276\020\020\256B\213\036\213\003=U\213\354Qt\005\276\034\020\256B\377\026\377\3201\311QQP\201\361
 \003\001\004\233\201\361\001\001\001\001Q\215E\314P\213E\300P\377\026j\021j\002j\002\377\320P\215E\304P
 \213E\300P\377\026\211\306\t\333\201\363<a\331\377\213E\264\215\f@\215\024\210\301\342\004\001\302\301
 \342\b)\302\215\004\220\001\330\211E\264j\020\215E\260P1\311Qf\201\361x\001Q\215E\003P\213E\254P\377\326
 \353\312" |>

Scripting Scruby

Writing scripts that use Scruby is very easy. This may be one of the shortest sniffers ever:
#! /usr/bin/env ruby
require 'scruby'
module Scruby
sniff
end
The first two lines import everything from Scruby (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 ruby
require 'scruby'
module Scruby

def Scruby.callback(pcap, packet)
   # Getting the link type
   linktype = pcap.datalink
  
   # Ethernet
   if linktype == Pcap::DLT_EN10MB
      dissect = Ether(packet)
      l4 = dissect.layers_list[2]
    
      return if l4.nil?
      return if not l4.instance_of?(TCP)
      return if not l4.dport == 445
    
      puts "just received a packet on TCP/445: #{dissect}"
    
   # Unknown link type
   else
      puts "Unknown link type: #{linktype}"
   end
end

sniff(:prn=>:callback)

end

Reference guide

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

sendp

This function sends a packet at layer 2 on the default interface ($conf.iface). If not specified, the Ethernet destination will be $conf.gateway_hwaddr.

If Libdnet is available, source Ethernet address and source IP address are automatically filled according to this interface.

example> p=Ether(:src=>"00:11:22:33:44:55")/IP(:src=>"1.2.3.4", :dst=>"www.google.com")/TCP()/
"GET / HTTP 1.0\r\n\r\n"
example> sendp(p)
Sent on eth0.

sniff

This function captures packets on an interface or reads a PCAP file. The default capture interface is stored in $conf.iface.

Without any argument, sniff captures on the default interface:

example> sniff
listening on eth0

1158608918.45960 <Ether dst=00:11:22:33:44:55 src=55:44:33:22:11:00 |>
<IP 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 <Ether dst=55:44:33:22:11:00 src=00:11:22:33:44:55 |>
<IP 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)
  • filter: 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 (1, seems not to work?)
  • offline: PCAP file to read packets from
  • store: 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> def Scruby.my_prn(pcap, packet) puts "GOT ONE: raw=|#{packet.inspect}|" end
example> sniff(:iface=>"eth1", :prn=>:my_prn, :filter=>"icmp", :count=>2)
listening on eth0
GOT ONE: raw=|"\000\a\313\fg\246\000Pp4\210\264\b\000E\000\000T\000\000@\000@\001\030KR\357
\313I\324\e0\n\b\000\e\360}!\000\001O2%F\363q\f\000\b\t\n\v\f\r\016\017\020\021\022\023\024
\025\026\027\030\031\032\e\034\035\036\037 !\"\#$%&'()*+,-./01234567"|
GOT ONE: raw=|"\000Pp4\210\264\000\a\313\fg\246\b\000E\000\000T\235*\000\000{\001\200 \324
\e0\nR\357\313I\000\000#\360}!\000\001O2%F\363q\f\000\b\t\n\v\f\r\016\017\020\021\022\023
\024\025\026\027\030\031\032\e\034\035\036\037 !\"\#$%&'()*+,-./01234567"|
Note that by default, packets captured are not stored in memory for performance reason. To stop sniffing, press ^C.

To read from a PCAP file:

example> sniff(:offline=>"mycapture.pcap")

Developer guide

Global organization

Scruby consists of several modules. In the archive, you will find the following files:
  • scruby.rb: the main script, imports everything from the other modules and spawns an interpreter.
  • layer.rb: definition of a Layer (instantiation of a dissector).
  • packet.rb: definition of a Packet (an array of Layers).
  • dissectors.rb: implemented dissectors (Ether, IP, etc). A dissector is like a list a Fields.
  • field.rb: implemented fields (for integers, shorts, bytes, strings, etc) to be used for building protocol dissectors.
  • func.rb: end-user functions (sniff, sendp, etc).
  • conf.rb: global configuration.
  • const.rb: global constants.
  • help.rb: everything related to help.
  • unittest.rb: you will not need to use this file, as it only contains a list of unit tests run just before releasing a new version :)

Adding a dissector or a field for a new protocol

Scruby has been explicitly devised so that the syntax for dissectors is the same as in Scapy. 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 Scruby

class ICMP<Layer

attr_accessor :type, :code, :chksum, :id, :seq

def init
   @protocol = 'ICMP'
   @fields_desc = [ ByteField('type', ICMPTYPE_ECHO),
                    ByteField('code', 0),
                    XShortField('chksum', 0),
                    XShortField('id', 0),
                    XShortField('seq', 0) ]
end
Yes, you just have to copy and paste dissectors from Scapy to get them work in Scruby :)

Note: in this version, you have to give "attr_accessors" in plain text AND the corresponding fields. I had a piece of code that did the trick automatically, but it was reeeeeeally slow (test 1 took 20 seconds instead of 9).

And some specific functions follow (checkum computation, etc).

Let's say you want to implement MySuperProtocol (MSP). In dissectors.rb, create a new package, inherit from Layer, describe your protocol (name and fields) and write specific functions if needed. Finally, don't forget to add MSP to DISSECTOR_LIST at the end of dissector.rb.

class MSP<Layer
def method_missing(method, *args) return Scruby.field(method, *args) end

# Global definition (name and fields)
attr_accessor :foo, :bar, :chksum

def init
   @protocol = 'MySuperProtocol'
   @fields_desc = [ ByteField('foo', 1),
                    StrField('bar', 2),
                    XShortField('chksum', 0) ]
end

def pre_send(underlayer, payload)
    self.chksum = 0
    self.chksum = Layer.checksum(self.to_net() + payload)
end
end

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.rb for real-life examples.

Now, use your new MSP dissector:

scruby> p=MSP(:foo=>"A", :bar=>"pouet")
<MySuperProtocol foo=A bar="pouet" |>

Here is the list of fields that can be used to build dissectors:

  • StrField(field name, default value): string
  • BitField(field name, default value, size): bit(s)
  • ByteField(field name, default value): byte
  • ShortField(field name, default value): short (exactly 2 bytes)
  • IntField(field name, default value): integer (exactly 4 bytes)
  • FloatField(field name, default value): float (exactly 4 bytes)
  • DoubleField(field name, default value): double (exactly 4 bytes)
  • IPField(field name, default value): IP address
  • MACField(field name, default value): Ethernet MAC address
  • FlagsField(field name, default, value, size, labels): flags (each bit has a label)
  • FieldLenField(field name, default value, name of the associated StrLenField, format
  • StrLenField(field name, default value, name of the FieldLenField holding its size

When writing your own dissectors, note:

  • some fields have a version with hexadecimal display, prefixed by "X" (e.g. XByteField)
  • by default, the fields are packed using the big endian byte order; some fields have a little endian version, prefixed with "LE" (e.g. LEIntField), some also have an "host order" version (e.g. HostOrderIntField)

Moreover, some fields also have an Enum version, allowing to deal with text values. For the IP dissector, the proto field is defined as:

ByteEnumField('proto', IPPROTO_TCP, IPPROTO_ALL)

IPPROTO_TCP is the default value (6) and IPPROTO_ALL is a hash defined in const.rb:

IPPROTO_ALL = { IPPROTO_ICMP=>"ICMP",
                IPPROTO_TCP=>"TCP",
                IPPROTO_UDP=>"UDP" }

For a FlagsField, the "flags" argument must be an array of strings, each of those being a name for the associated bit. For instance, let's have a look at TCP.flags:

FlagsField('flags', 0x2, 8, TCPFLAGS)

TCPFLAGS is defined in const.rb:

TCPFLAGS = %w[FIN SYN RST PSH ACK URG ECN RES]

Namely, FIN is for bit 0, SYN for bit 1, etc.

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 (NB: this is only a historical example; XLEShortField is now "officially" implemented :):

class XLEShortField<Field
def init
   @format = 'v'
end
end
That's all for the general definition of the format. Looking at field.rb, you will see that there are 3 functions in the Field package that you can implement in your own fields:
  • from_net: converts from network to internal encoding (e.g. IP addresses displayed as a 4-dotted number)
  • to_net: converts from internal encoding to network (e.g. IP address in 4-dotted form to the real network number)
  • to_human: converts from internal encoding to human display (e.g. display checksum as "0xDEADBEEF")
  • to_human_complete: same as to_human, but more detailed (e.g. "proto = 6 (TCP)" for IP().show, instead of "proto = 6"). Only used within the "show" function.
  • from_human: converts from human input to internal encoding (e.g. 'TCP' converted into 6 when building IP.proto).
  • dissect: receives all the remaining string (see BitField for an example of this)
  • is_applicable?: sets some conditions for a field to be packed when sending the packet (see Dot11Addr* fields)
  • pre_build: some actions to perform being packing a layer (e.g. computing a checksum)
In our XLEShortField example, we have to implement to_human to get hexa display:
def to_human(value)
   return sprintf('0x%x', value)
end
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).

At the end of dissector.rb, 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 ETHERTYPE_IPv4 (0x800, see const.rb), then the upper layer is IP":

'Ether' => [
             ['type', ETHERTYPE_IPv4, 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 Scruby performs quite well compared to Scapy. Performance is in fact one of the reasons why I began Scaperl, which was used as a basis for Scruby. Note that Scruby was not really profiled yet (there exist special tools in Ruby for that purpose).

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

Time to dissect M different strings

For this test to be honest, I removed all the protocols in Scapy's layer_bounds that were not implemented in Scruby.
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
Scruby
$ cat test1.rb
#! /usr/bin/env ruby

require 'scruby'
module Scruby

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 (0..9999)
   p=Ether(s+i.to_s)
end
end

$ time ./test1.rb
real	0m9.208s
user	0m8.633s
sys	0m0.528s
Note: I expect even better performance with Ruby 2.0 (e.g. the same kind of performance I got with Scaperl).

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
Scruby
$ cat test2.rb
#! /usr/bin/env ruby

require 'scruby'
module Scruby

p=IP()

for i in (0..499)
   p/=IP(:len=>i)
end
end

$ time ./test2.rb
real	0m0.544s
user	0m0.504s
sys	0m0.020s
[ Site créé par Sylvain Sarméjeanne ]
Cette page a été générée par mes scripts en 0.035 secondes :)
[Valid XHTML 1.1!] [Valid CSS!] [[Valid RSS]]