diff --git a/doc/scapy/README b/doc/scapy/README new file mode 100644 index 000000000..b34335927 --- /dev/null +++ b/doc/scapy/README @@ -0,0 +1,19 @@ +This folder includes source files (text and graphics) for Scapy's documentation, +which is automatically built using Sphinx + +The *.rst files are written as reStructuredText and should be quite readable +in your favourite text editor without any further formatting. + +To generate much nicer, searchable HTML docs, install Sphinx, open a command +line, change to the directory where this README is placed, and type the +following command: + + $ make html + +To generate a single PDF file (useful for printing) you need a working +LaTeX installation (e.g. ). +The following commands produce the file Scapy.pdf (>100 pages): + + $ make latex + $ cd _build/latex + $ make all-pdf \ No newline at end of file diff --git a/doc/scapy/advanced_usage.rst b/doc/scapy/advanced_usage.rst new file mode 100644 index 000000000..ae9129827 --- /dev/null +++ b/doc/scapy/advanced_usage.rst @@ -0,0 +1,792 @@ +************** +Advanced usage +************** + +ASN.1 and SNMP +============== + +What is ASN.1? +-------------- + +.. note:: + + This is only my view on ASN.1, explained as simply as possible. For more theoretical or academic views, I'm sure you'll find better on the Internet. + +ASN.1 is a notation whose goal is to specify formats for data exchange. It is independant of the way data is encoded. Data encoding is specified in Encoding Rules. + +The most used encoding rules are BER (Basic Encoding Rules) and DER (Distinguished Encoding Rules). Both look the same, but the latter is specified to guarantee uniqueness of encoding. This property is quite interesting when speaking about cryptography, hashes and signatures. + +ASN.1 provides basic objects: integers, many kinds of strings, floats, booleans, containers, etc. They are grouped in the so called Universal class. A given protocol can provide other objects which will be grouped in the Context class. For example, SNMP defines PDU_GET or PDU_SET objects. There are also the Application and Private classes. + +Each of theses objects is given a tag that will be used by the encoding rules. Tags from 1 are used for Universal class. 1 is boolean, 2 is integer, 3 is a bit string, 6 is an OID, 48 is for a sequence. Tags from the ``Context`` class begin at 0xa0. When encountering an object tagged by 0xa0, we'll need to know the context to be able to decode it. For example, in SNMP context, 0xa0 is a PDU_GET object, while in X509 context, it is a container for the certificate version. + +Other objects are created by assembling all those basic brick objects. The composition is done using sequences and arrays (sets) of previously defined or existing objects. The final object (an X509 certificate, a SNMP packet) is a tree whose non-leaf nodes are sequences and sets objects (or derived context objects), and whose leaf nodes are integers, strings, OID, etc. + +Scapy and ASN.1 +--------------- + +Scapy provides a way to easily encode or decode ASN.1 and also program those encoders/decoders. It is quite more lax than what an ASN.1 parser should be, and it kind of ignores constraints. It won't replace neither an ASN.1 parser nor an ASN.1 compiler. Actually, it has been written to be able to encode and decode broken ASN.1. It can handle corrupted encoded strings and can also create those. + +ASN.1 engine +^^^^^^^^^^^^ + +Note: many of the classes definitions presented here use metaclasses. If you don't look precisely at the source code and you only rely on my captures, you may think they sometimes exhibit a kind of magic behaviour. +`` +Scapy ASN.1 engine provides classes to link objects and their tags. They inherit from the ``ASN1_Class``. The first one is ``ASN1_Class_UNIVERSAL``, which provide tags for most Universal objects. Each new context (``SNMP``, ``X509``) will inherit from it and add its own objects. + +:: + + class ASN1_Class_UNIVERSAL(ASN1_Class): + name = "UNIVERSAL" + # [...] + BOOLEAN = 1 + INTEGER = 2 + BIT_STRING = 3 + # [...] + + class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): + name="SNMP" + PDU_GET = 0xa0 + PDU_NEXT = 0xa1 + PDU_RESPONSE = 0xa2 + + class ASN1_Class_X509(ASN1_Class_UNIVERSAL): + name="X509" + CONT0 = 0xa0 + CONT1 = 0xa1 + # [...] + +All ASN.1 objects are represented by simple Python instances that act as nutshells for the raw values. The simple logic is handled by ``ASN1_Object`` whose they inherit from. Hence they are quite simple:: + + class ASN1_INTEGER(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.INTEGER + + class ASN1_STRING(ASN1_Object): + tag = ASN1_Class_UNIVERSAL.STRING + + class ASN1_BIT_STRING(ASN1_STRING): + tag = ASN1_Class_UNIVERSAL.BIT_STRING + +These instances can be assembled to create an ASN.1 tree:: + + >>> x=ASN1_SEQUENCE([ASN1_INTEGER(7),ASN1_STRING("egg"),ASN1_SEQUENCE([ASN1_BOOLEAN(False)])]) + >>> x + , , ]]>]]> + >>> x.show() + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + +Encoding engines +^^^^^^^^^^^^^^^^^ + +As with the standard, ASN.1 and encoding are independent. We have just seen how to create a compounded ASN.1 object. To encode or decode it, we need to choose an encoding rule. Scapy provides only BER for the moment (actually, it may be DER. DER looks like BER except only minimal encoding is authorised which may well be what I did). I call this an ASN.1 codec. + +Encoding and decoding are done using class methods provided by the codec. For example the ``BERcodec_INTEGER`` class provides a ``.enc()`` and a ``.dec()`` class methods that can convert between an encoded string and a value of their type. They all inherit from BERcodec_Object which is able to decode objects from any type:: + + >>> BERcodec_INTEGER.enc(7) + '\x02\x01\x07' + >>> BERcodec_BIT_STRING.enc("egg") + '\x03\x03egg' + >>> BERcodec_STRING.enc("egg") + '\x04\x03egg' + >>> BERcodec_STRING.dec('\x04\x03egg') + (, '') + >>> BERcodec_STRING.dec('\x03\x03egg') + Traceback (most recent call last): + File "", line 1, in ? + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2178, in do_dec + l,s,t = cls.check_type_check_len(s) + File "/usr/bin/scapy", line 2076, in check_type_check_len + l,s3 = cls.check_type_get_len(s) + File "/usr/bin/scapy", line 2069, in check_type_get_len + s2 = cls.check_type(s) + File "/usr/bin/scapy", line 2065, in check_type + (cls.__name__, ord(s[0]), ord(s[0]),cls.tag), remaining=s) + BER_BadTag_Decoding_Error: BERcodec_STRING: Got tag [3/0x3] while expecting + ### Already decoded ### + None + ### Remaining ### + '\x03\x03egg' + >>> BERcodec_Object.dec('\x03\x03egg') + (, '') + +ASN.1 objects are encoded using their ``.enc()`` method. This method must be called with the codec we want to use. All codecs are referenced in the ASN1_Codecs object. ``str()`` can also be used. In this case, the default codec (``conf.ASN1_default_codec``) will be used. + +:: + + >>> x.enc(ASN1_Codecs.BER) + '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' + >>> str(x) + '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' + >>> xx,remain = BERcodec_Object.dec(_) + >>> xx.show() + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + >>> remain + '' + +By default, decoding is done using the ``Universal`` class, which means objects defined in the ``Context`` class will not be decoded. There is a good reason for that: the decoding depends on the context! + +:: + + >>> cert=""" + ... MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC + ... VVMxHTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNB + ... bWVyaWNhIE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIg + ... Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAw + ... MFoXDTM3MDkyODIzNDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRB + ... T0wgVGltZSBXYXJuZXIgSW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUg + ... SW5jLjE3MDUGA1UEAxMuQU9MIFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNh + ... dGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC + ... ggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ7ouZzU9AhqS2TcnZsdw8TQ2FTBVs + ... RotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilbm2BPJoPRYxJWSXakFsKlnUWs + ... i4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOYxFSMFkpBd4aVdQxHAWZg + ... /BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZYYCLqJV+FNwSbKTQ + ... 2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbqJS5Gr42whTg0 + ... ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fxI2rSAG2X + ... +Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETzkxml + ... J85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh + ... EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNo + ... Kk/SBtc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJ + ... Kg71ZDIMgtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1Ex + ... MVCgyhwn2RAurda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMB + ... Af8wHQYDVR0OBBYEFE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaA + ... FE9pbQN+nZ8HGEO8txBO1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG + ... 9w0BAQUFAAOCAgEAO/Ouyuguh4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0 + ... cnAxa8cZmIDJgt43d15Ui47y6mdPyXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRF + ... ASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q7C+qPBR7V8F+GBRn7iTGvboVsNIY + ... vbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKTRuidDV29rs4prWPVVRaAMCf/ + ... drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/ClTluUI8JPu3B5wwn3la + ... 5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyBM5kYJRF3p+v9WAks + ... mWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQmy8YJPamTQr5 + ... O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xOAU++CrYD + ... 062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT9Y41 + ... xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H + ... hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOL + ... Z8/5fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg= + ... """.decode("base64") + >>> (dcert,remain) = BERcodec_Object.dec(cert) + Traceback (most recent call last): + File "", line 1, in ? + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2094, in do_dec + return codec.dec(s,context,safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2218, in do_dec + o,s = BERcodec_Object.dec(s, context, safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2094, in do_dec + return codec.dec(s,context,safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2218, in do_dec + o,s = BERcodec_Object.dec(s, context, safe) + File "/usr/bin/scapy", line 2099, in dec + return cls.do_dec(s, context, safe) + File "/usr/bin/scapy", line 2092, in do_dec + raise BER_Decoding_Error("Unknown prefix [%02x] for [%r]" % (p,t), remaining=s) + BER_Decoding_Error: Unknown prefix [a0] for ['\xa0\x03\x02\x01\x02\x02\x01\x010\r\x06\t*\x86H...'] + ### Already decoded ### + [[]] + ### Remaining ### + '\xa0\x03\x02\x01\x02\x02\x01\x010\r\x06\t*\x86H\x86\xf7\r\x01\x01\x05\x05\x000\x81\x831\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x1d0\x1b\x06\x03U\x04\n\x13\x14AOL Time Warner Inc.1\x1c0\x1a\x06\x03U\x04\x0b\x13\x13America Online Inc.1705\x06\x03U\x04\x03\x13.AOL Time Warner Root Certification Authority 20\x1e\x17\r020529060000Z\x17\r370928234300Z0\x81\x831\x0b0\t\x06\x03U\x04\x06\x13\x02US1\x1d0\x1b\x06\x03U\x04\n\x13\x14AOL Time Warner Inc.1\x1c0\x1a\x06\x03U\x04\x0b\x13\x13America Online Inc.1705\x06\x03U\x04\x03\x13.AOL Time Warner Root Certification Authority 20\x82\x02"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x02\x0f\x000\x82\x02\n\x02\x82\x02\x01\x00\xb47Z\x08\x16\x99\x14\xe8U\xb1\x1b$k\xfc\xc7\x8b\xe6\x87\xa9\x89\xee\x8b\x99\xcdO@\x86\xa4\xb6M\xc9\xd9\xb1\xdc\xd6Q\xc8\x95\x17\x01\x15\xa9\xf2\xaa\xaa\xf2\xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\xe9\xa8\xffA\x9cp*O*9\x18\x95\x1e~\xfba\x01>> (dcert,remain) = BERcodec_Object.dec(cert, context=ASN1_Class_X509) + >>> dcert.show() + # ASN1_SEQUENCE: + # ASN1_SEQUENCE: + # ASN1_X509_CONT0: + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SET: + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + # ASN1_SEQUENCE: + + + + # ASN1_X509_CONT3: + # ASN1_SEQUENCE: + # ASN1_SEQUENCE: + + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + + + + # ASN1_SEQUENCE: + + + \xd6Q\xc8\x95\x17\x01\x15\xa9\xf2\xaa\xaa\xf2\xbf/e\x1bo\xd0\xb9\x1a\x93\xf5\x8e5\xc4\x80\x87>\x94/f\xe4\xe9\xa8\xffA\x9cp*O*9\x18\x95\x1e~\xfba\x01 + +ASN.1 layers +^^^^^^^^^^^^ + +While this may be nice, it's only an ASN.1 encoder/decoder. Nothing related to Scapy yet. + +ASN.1 fields +~~~~~~~~~~~~ + +Scapy provides ASN.1 fields. They will wrap ASN.1 objects and provide the necessary logic to bind a field name to the value. ASN.1 packets will be described as a tree of ASN.1 fields. Then each field name will be made available as a normal ``Packet`` object, in a flat flavor (ex: to access the version field of a SNMP packet, you don't need to know how many containers wrap it). + +Each ASN.1 field is linked to an ASN.1 object through its tag. + + +ASN.1 packets +~~~~~~~~~~~~~ + +ASN.1 packets inherit from the Packet class. Instead of a ``fields_desc`` list of fields, they define ``ASN1_codec`` and ``ASN1_root`` attributes. The first one is a codec (for example: ``ASN1_Codecs.BER``), the second one is a tree compounded with ASN.1 fields. + +A complete example: SNMP +------------------------ + +SNMP defines new ASN.1 objects. We need to define them:: + + class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): + name="SNMP" + PDU_GET = 0xa0 + PDU_NEXT = 0xa1 + PDU_RESPONSE = 0xa2 + PDU_SET = 0xa3 + PDU_TRAPv1 = 0xa4 + PDU_BULK = 0xa5 + PDU_INFORM = 0xa6 + PDU_TRAPv2 = 0xa7 + +These objects are PDU, and are in fact new names for a sequence container (this is generally the case for context objects: they are old containers with new names). This means creating the corresponding ASN.1 objects and BER codecs is simplistic:: + + class ASN1_SNMP_PDU_GET(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_GET + + class ASN1_SNMP_PDU_NEXT(ASN1_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_NEXT + + # [...] + + class BERcodec_SNMP_PDU_GET(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_GET + + class BERcodec_SNMP_PDU_NEXT(BERcodec_SEQUENCE): + tag = ASN1_Class_SNMP.PDU_NEXT + + # [...] + +Metaclasses provide the magic behind the fact that everything is automatically registered and that ASN.1 objects and BER codecs can find each other. + +The ASN.1 fields are also trivial:: + + class ASN1F_SNMP_PDU_GET(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_GET + + class ASN1F_SNMP_PDU_NEXT(ASN1F_SEQUENCE): + ASN1_tag = ASN1_Class_SNMP.PDU_NEXT + + # [...] + +Now, the hard part, the ASN.1 packet:: + + SNMP_error = { 0: "no_error", + 1: "too_big", + # [...] + } + + SNMP_trap_types = { 0: "cold_start", + 1: "warm_start", + # [...] + } + + class SNMPvarbind(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("oid","1.3"), + ASN1F_field("value",ASN1_NULL(0)) + ) + + + class SNMPget(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_GET( ASN1F_INTEGER("id",0), + ASN1F_enum_INTEGER("error",0, SNMP_error), + ASN1F_INTEGER("error_index",0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) + ) + + class SNMPnext(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SNMP_PDU_NEXT( ASN1F_INTEGER("id",0), + ASN1F_enum_INTEGER("error",0, SNMP_error), + ASN1F_INTEGER("error_index",0), + ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) + ) + # [...] + + class SNMP(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_enum_INTEGER("version", 1, {0:"v1", 1:"v2c", 2:"v2", 3:"v3"}), + ASN1F_STRING("community","public"), + ASN1F_CHOICE("PDU", SNMPget(), + SNMPget, SNMPnext, SNMPresponse, SNMPset, + SNMPtrapv1, SNMPbulk, SNMPinform, SNMPtrapv2) + ) + def answers(self, other): + return ( isinstance(self.PDU, SNMPresponse) and + ( isinstance(other.PDU, SNMPget) or + isinstance(other.PDU, SNMPnext) or + isinstance(other.PDU, SNMPset) ) and + self.PDU.id == other.PDU.id ) + # [...] + bind_layers( UDP, SNMP, sport=161) + bind_layers( UDP, SNMP, dport=161) + +That wasn't that much difficult. If you think that can't be that short to implement SNMP encoding/decoding and that I may may have cut too much, just look at the complete source code. + +Now, how to use it? As usual:: + + >>> a=SNMP(version=3, PDU=SNMPget(varbindlist=[SNMPvarbind(oid="1.2.3",value=5), + ... SNMPvarbind(oid="3.2.1",value="hello")])) + >>> a.show() + ###[ SNMP ]### + version= v3 + community= 'public' + \PDU\ + |###[ SNMPget ]### + | id= 0 + | error= no_error + | error_index= 0 + | \varbindlist\ + | |###[ SNMPvarbind ]### + | | oid= '1.2.3' + | | value= 5 + | |###[ SNMPvarbind ]### + | | oid= '3.2.1' + | | value= 'hello' + >>> hexdump(a) + 0000 30 2E 02 01 03 04 06 70 75 62 6C 69 63 A0 21 02 0......public.!. + 0010 01 00 02 01 00 02 01 00 30 16 30 07 06 02 2A 03 ........0.0...*. + 0020 02 01 05 30 0B 06 02 7A 01 04 05 68 65 6C 6C 6F ...0...z...hello + >>> send(IP(dst="1.2.3.4")/UDP()/SNMP()) + . + Sent 1 packets. + >>> SNMP(str(a)).show() + ###[ SNMP ]### + version= + community= + \PDU\ + |###[ SNMPget ]### + | id= + | error= + | error_index= + | \varbindlist\ + | |###[ SNMPvarbind ]### + | | oid= + | | value= + | |###[ SNMPvarbind ]### + | | oid= + | | value= + + + +Resolving OID from a MIB +------------------------ + +About OID objects +^^^^^^^^^^^^^^^^^ + +OID objects are created with an ``ASN1_OID`` class:: + + >>> o1=ASN1_OID("2.5.29.10") + >>> o2=ASN1_OID("1.2.840.113549.1.1.1") + >>> o1,o2 + (, ) + +Loading a MIB +^^^^^^^^^^^^^ + +Scapy can parse MIB files and become aware of a mapping between an OID and its name:: + + >>> load_mib("mib/*") + >>> o1,o2 + (, ) + +The MIB files I've used are attached to this page. + +Scapy's MIB database +^^^^^^^^^^^^^^^^^^^^ + +All MIB information is stored into the conf.mib object. This object can be used to find the OID of a name + +:: + + >>> conf.mib.sha1_with_rsa_signature + '1.2.840.113549.1.1.5' + +or to resolve an OID:: + + >>> conf.mib._oidname("1.2.3.6.1.4.1.5") + 'enterprises.5' + +It is even possible to graph it:: + + >>> conf.mib._make_graph() + + + +Automata +======== + +Scapy enables to create easily network automata. Scapy does not stick to a specific model like Moore or Mealy automata. It provides a flexible way for you to choose you way to go. + +An automaton in Scapy is deterministic. It has different states. A start state and some end and error states. There are transitions from one state to another. Transitions can be transitions on a specific condition, transitions on the reception of a specific packet or transitions on a timeout. When a transition is taken, one or more actions can be run. An action can be bound to many transitions. Parameters can be passed from states to transitions and from transitions to states and actions. + +From a programmer's point of view, states, transitions and actions are methods from an Automaton subclass. They are decorated to provide meta-information needed in order for the automaton to work. + +First example +------------- + +Let's begin with a simple example. I take the convention to write states with capitals, but anything valid with Python syntax would work as well. + +:: + + class HelloWorld(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + print "State=BEGIN" + + @ATMT.condition(BEGIN) + def wait_for_nothing(self): + print "Wait for nothing..." + raise self.END() + + @ATMT.action(wait_for_nothing) + def on_nothing(self): + print "Action on 'nothing' condition" + + @ATMT.state(final=1) + def END(self): + print "State=END" + +In this example, we can see 3 decorators: + +* ``ATMT.state`` that is used to indicate that a method is a state, and that can + have initial, final and error optional arguments set to non-zero for special states. +* ``ATMT.condition`` that indicate a method to be run when the automaton state + reaches the indicated state. The argument is the name of the method representing that state +* ``ATMT.action`` binds a method to a transition and is run when the transition is taken. + +Running this example gives the following result:: + + >>> a=HelloWorld() + >>> a.run() + State=BEGIN + Wait for nothing... + Action on 'nothing' condition + State=END + +This simple automaton can be described with the following graph: + +.. image:: graphics/ATMT_HelloWorld.* + +The graph can be automatically drawn from the code with:: + + >>> HelloWorld.graph() + +Changing states +--------------- + +The ``ATMT.state`` decorator transforms a method into a function that returns an exception. If you raise that exception, the automaton state will be changed. If the change occurs in a transition, actions bound to this transition will be called. The parameters given to the function replacing the method will be kept and finally delivered to the method. The exception has a method action_parameters that can be called before it is raised so that it will store parameters to be delivered to all actions bound to the current transition. + +As an example, let's consider the following state:: + + @ATMT.state() + def MY_STATE(self, param1, param2): + print "state=MY_STATE. param1=%r param2=%r" % (param1, param2) + +This state will be reached with the following code:: + + @ATMT.receive_condition(ANOTHER_STATE) + def received_ICMP(self, pkt): + if ICMP in pkt: + raise self.MY_STATE("got icmp", pkt[ICMP].type) + +Let's suppose we want to bind an action to this transition, that will also need some parameters:: + + @ATMT.action(received_ICMP) + def on_ICMP(self, icmp_type, icmp_code): + self.retaliate(icmp_type, icmp_code) + +The condition should become:: + + @ATMT.receive_condition(ANOTHER_STATE) + def received_ICMP(self, pkt): + if ICMP in pkt: + raise self.MY_STATE("got icmp", pkt[ICMP].type).action_parameters(pkt[ICMP].type, pkt[ICMP].code) + +Real example +------------ + +Here is a real example take from Scapy. It implements a TFTP client that can issue read requests. + +.. image:: graphics/ATMT_TFTP_read.* + +:: + + class TFTP_read(Automaton): + def parse_args(self, filename, server, sport = None, port=69, **kargs): + Automaton.parse_args(self, **kargs) + self.filename = filename + self.server = server + self.port = port + self.sport = sport + + def master_filter(self, pkt): + return ( IP in pkt and pkt[IP].src == self.server and UDP in pkt + and pkt[UDP].dport == self.my_tid + and (self.server_tid is None or pkt[UDP].sport == self.server_tid) ) + + # BEGIN + @ATMT.state(initial=1) + def BEGIN(self): + self.blocksize=512 + self.my_tid = self.sport or RandShort()._fix() + bind_bottom_up(UDP, TFTP, dport=self.my_tid) + self.server_tid = None + self.res = "" + + self.l3 = IP(dst=self.server)/UDP(sport=self.my_tid, dport=self.port)/TFTP() + self.last_packet = self.l3/TFTP_RRQ(filename=self.filename, mode="octet") + self.send(self.last_packet) + self.awaiting=1 + + raise self.WAITING() + + # WAITING + @ATMT.state() + def WAITING(self): + pass + + @ATMT.receive_condition(WAITING) + def receive_data(self, pkt): + if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting: + if self.server_tid is None: + self.server_tid = pkt[UDP].sport + self.l3[UDP].dport = self.server_tid + raise self.RECEIVING(pkt) + @ATMT.action(receive_data) + def send_ack(self): + self.last_packet = self.l3 / TFTP_ACK(block = self.awaiting) + self.send(self.last_packet) + + @ATMT.receive_condition(WAITING, prio=1) + def receive_error(self, pkt): + if TFTP_ERROR in pkt: + raise self.ERROR(pkt) + + @ATMT.timeout(WAITING, 3) + def timeout_waiting(self): + raise self.WAITING() + @ATMT.action(timeout_waiting) + def retransmit_last_packet(self): + self.send(self.last_packet) + + # RECEIVED + @ATMT.state() + def RECEIVING(self, pkt): + recvd = pkt[Raw].load + self.res += recvd + self.awaiting += 1 + if len(recvd) == self.blocksize: + raise self.WAITING() + raise self.END() + + # ERROR + @ATMT.state(error=1) + def ERROR(self,pkt): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return pkt[TFTP_ERROR].summary() + + #END + @ATMT.state(final=1) + def END(self): + split_bottom_up(UDP, TFTP, dport=self.my_tid) + return self.res + +It can be run like this, for instance:: + + >>> TFTP_read("my_file", "192.168.1.128").run() + +Detailed documentation +---------------------- + +Decorators +^^^^^^^^^^ +Decorator for states +~~~~~~~~~~~~~~~~~~~~ + +States are methods decorated by the result of the ``ATMT.state`` function. It can take 3 optional parameters, ``initial``, ``final`` and ``error``, that, when set to ``True``, indicate that the state is an initial, final or error state. + +:: + + class Example(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + pass + + @ATMT.state() + def SOME_STATE(self): + pass + + @ATMT.state(final=1) + def END(self): + return "Result of the automaton: 42" + + @ATMT.state(error=1) + def ERROR(self): + return "Partial result, or explanation" + # [...] + +Decorators for transitions +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Transitions are methods decorated by the result of one of ``ATMT.condition``, ``ATMT.receive_condition``, ``ATMT.timeout``. They all take as argument the state method they are related to. ``ATMT.timeout`` also have a mandatory ``timeout`` parameter to provide the timeout value in seconds. ``ATMT.condition`` and ``ATMT.receive_condition`` have an optional ``prio`` parameter so that the order in which conditions are evaluated can be forced. Default priority is 0. Transitions with the same priority level are called in an undetermined order. + +When the automaton switches to a given state, the state's method is executed. Then transitions methods are called at specific moments until one triggers a new state (something like ``raise self.MY_NEW_STATE()``). First, right after the state's method returns, the ``ATMT.condition`` decorated methods are run by growing prio. Then each time a packet is received and accepted by the master filter all ``ATMT.receive_condition`` decorated hods are called by growing prio. When a timeout is reached since the time we entered into the current space, the corresponding ``ATMT.timeout`` decorated method is called. + +:: + + class Example(Automaton): + @ATMT.state() + def WAITING(self): + pass + + @ATMT.condition(WAITING) + def it_is_raining(self): + if not self.have_umbrella: + raise self.ERROR_WET() + + @ATMT.receive_condition(WAITING, prio=1) + def it_is_ICMP(self, pkt): + if ICMP in pkt: + raise self.RECEIVED_ICMP(pkt) + + @ATMT.receive_condition(WAITING, prio=2) + def it_is_IP(self, pkt): + if IP in pkt: + raise self.RECEIVED_IP(pkt) + + @ATMT.timeout(WAITING, 10.0) + def waiting_timeout(self): + raise self.ERROR_TIMEOUT() + +Decorator for actions +~~~~~~~~~~~~~~~~~~~~~ + +Actions are methods that are decorated by the return of ``ATMT.action`` function. This function takes the transition method it is bound to as first parameter and an optionnal priority ``prio`` as a second parameter. Default priority is 0. An action method can be decorated many times to be bound to many transitions. + +:: + + class Example(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + pass + + @ATMT.state(final=1) + def END(self): + pass + + @ATMT.condition(BEGIN, prio=1) + def maybe_go_to_end(self): + if random() > 0.5: + raise self.END() + @ATMT.condition(BEGIN, prio=2) + def certainly_go_to_end(self): + raise self.END() + + @ATMT.action(maybe_go_to_end) + def maybe_action(self): + print "We are lucky..." + @ATMT.action(certainly_go_to_end) + def certainly_action(self): + print "We are not lucky..." + @ATMT.action(maybe_go_to_end, prio=1) + @ATMT.action(certainly_go_to_end, prio=1) + def always_action(self): + print "This wasn't luck!..." + +The two possible outputs are:: + + >>> a=Example() + >>> a.run() + We are not lucky... + This wasn't luck!... + >>> a.run() + We are lucky... + This wasn't luck!... + +Methods to overload +^^^^^^^^^^^^^^^^^^^ + +Two methods are hooks to be overloaded: + +* The ``parse_args()`` method is called with arguments given at ``__init__()`` and ``run()``. Use that to parametrize the behaviour of your automaton. + +* The ``master_filter()`` method is called each time a packet is sniffed and decides if it is interesting for the automaton. When working on a specific protocol, this is where you will ensure the packet belongs to the connection you are being part of, so that you do not need to make all the sanity checks in each transition. + diff --git a/doc/scapy/backmatter.rst b/doc/scapy/backmatter.rst index c54e06873..9fa0791e8 100644 --- a/doc/scapy/backmatter.rst +++ b/doc/scapy/backmatter.rst @@ -6,6 +6,7 @@ Credits - Philippe Biondi is Scapy's author. He has also written most of the documentation. - Fred Raynal wrote the chapter on building and dissecting packets. - Sebastien Martini added some details in "Handling default values: automatic computation". +- Peter Kacherginsky contributed several tutorial sections, one-liners and recipes. - Dirk Loss integrated and restructured the existing docs to make this book. He has also written the installation instructions for Windows. diff --git a/doc/scapy/build_dissect.rst b/doc/scapy/build_dissect.rst index e014a915c..70bdac75f 100644 --- a/doc/scapy/build_dissect.rst +++ b/doc/scapy/build_dissect.rst @@ -26,7 +26,7 @@ of a field class:: In this example, our layer has three fields. The first one is an 2 byte integer field named ``mickey`` and whose default value is 5. The second one is a 1 byte integer field named ``minnie`` and whose default value is 3. The difference between -a vanilla ``ByteField`` and a ``XByteField` is only the fact that the prefered human +a vanilla ``ByteField`` and a ``XByteField`` is only the fact that the prefered human representation of the field’s value is in hexadecimal. The last field is a 4 byte integer field named ``donald``. It is different from a vanilla ``IntField`` by the fact that some of the possible values of the field have litterate representations. For @@ -72,7 +72,7 @@ organized. >>> p.summary() 'IP / TCP 127.0.0.1:ftp-data > 127.0.0.1:www S / Raw' -We are interested in 2 "inside" fields of the class Packet: +We are interested in 2 "inside" fields of the class ``Packet``: * ``p.underlayer`` * ``p.payload`` @@ -312,7 +312,7 @@ dissected. ``self`` points to the current layer. dissected as "``Raw``" data (which is some kind of default layer type) -For a given layer, everything is quite straightforward. +For a given layer, everything is quite straightforward: - ``pre_dissect()`` is called to prepare the layer. - ``do_dissect()`` perform the real dissection of the layer. @@ -407,13 +407,13 @@ Sometimes, guessing the payload class is not as straightforward as defining a single port. For instance, it can depends on a value of a given byte in the current layer. The 2 needed methods are: - - ``guess_payload_class()`` which must return the guessed class for the - payload (next layer). By default, it uses links between classes - that have been put in place by ``bind_layers()``. +- ``guess_payload_class()`` which must return the guessed class for the + payload (next layer). By default, it uses links between classes + that have been put in place by ``bind_layers()``. - - ``default_payload_class()`` which returns the default value. This - method defined in the class ``Packet`` returns ``Raw``, but it can be - overloaded. +- ``default_payload_class()`` which returns the default value. This + method defined in the class ``Packet`` returns ``Raw``, but it can be + overloaded. For instance, decoding 802.11 changes depending on whether it is ciphered or not:: @@ -427,12 +427,12 @@ ciphered or not:: Several comments are needed here: - - this cannot be done using ``bind_layers()`` because the tests are - supposed to be "``field==value``", but it is more complicated here as we - test a single bit in the value of a field. +- this cannot be done using ``bind_layers()`` because the tests are + supposed to be "``field==value``", but it is more complicated here as we + test a single bit in the value of a field. - - if the test fails, no assumption is made, and we plug back to the - default guessing mechanisms calling ``Packet.guess_payload_class()`` +- if the test fails, no assumption is made, and we plug back to the + default guessing mechanisms calling ``Packet.guess_payload_class()`` Most of the time, defining a method ``guess_payload_class()`` is not a necessity as the same result can be obtained from ``bind_layers()``. @@ -468,8 +468,8 @@ default method ``Packet.guess_payload_class()``. This method runs through each element of the list payload_guess, each element being a tuple: - - the 1st value is a field to test (``'dport': 2000``) - - the 2nd value is the guessed class if it matches (``Skinny``) +- the 1st value is a field to test (``'dport': 2000``) +- the 2nd value is the guessed class if it matches (``Skinny``) So, the default ``guess_payload_class()`` tries all element in the list, until one matches. If no element are found, it then calls @@ -511,7 +511,7 @@ appended altogether. 0010 7F 00 00 01 00 14 00 50 00 00 00 00 00 00 00 00 .......P........ 0020 50 02 20 00 91 7C 00 00 P. ..|.. -Calling str() builds the packet: +Calling ``str()`` builds the packet: - non instanced fields are set to their default value - lengths are updated automatically - checksums are computed @@ -906,6 +906,7 @@ e.g.:: Strings ------- +:: StrField(name, default, fmt="H", remain=0, shift=0) StrLenField(name, default, fld=None, length_from=None, shift=0): @@ -933,7 +934,86 @@ Lists and lengths PacketLenField # used e.g. in ISAKMP_payload_Proposal PacketListField -The FieldListField and LengthFields articles on the Wiki have more info on this topic. + +Variable length fields +^^^^^^^^^^^^^^^^^^^^^^ + +This is about how fields that have a variable length can be handled with Scapy. These fields usually know their length from another field. Let's call them varfield and lenfield. The idea is to make each field reference the other so that when a packet is dissected, varfield can know its length from lenfield when a packet is assembled, you don't have to fill lenfield, that will deduce its value directly from varfield value. + +Problems arise whe you realize that the relation between lenfield and varfield is not always straightforward. Sometimes, lenfield indicates a length in bytes, sometimes a number of objects. Sometimes the length includes the header part, so that you must substract the fixed header length to deduce the varfield length. Sometimes the length is not counted in bytes but in 16bits words. Sometimes the same lenfield is used by two different varfields. Sometimes the same varfield is referenced by two lenfields, one in bytes one in 16bits words. + + +The length field +~~~~~~~~~~~~~~~~ + +First, a lenfield is declared using ``FieldLenField`` (or a derivate). If its value is None when assembling a packet, its value will be deduced from the varfield that was referenced. The reference is done using either the ``length_of`` parameter or the ``count_of`` parameter. The ``count_of`` parameter has a meaning only when varfield is a field that holds a list (``PacketListField`` or ``FieldListField``). The value will be the name of the varfield, as a string. According to which parameter is used the ``i2len()`` or ``i2count()`` method will be called on the varfield value. The returned value will the be adjusted by the function provided in the adjust parameter. adjust will be applied on 2 arguments: the packet instance and the value returned by ``i2len()`` or ``i2count()``. By default, adjust does nothing:: + + adjust=lambda pkt,x: x + +For instance, if ``the_varfield`` is a list + +:: + + FieldLenField("the_lenfield", None, count_of="the_varfield") + +or if the length is in 16bits words:: + + FieldLenField("the_lenfield", None, length_of="the_varfield", adjust=lambda pkt,x:(x+1)/2) + +The variable length field +~~~~~~~~~~~~~~~~~~~~~~~~~ + +A varfield can be: ``StrLenField``, ``PacketLenField``, ``PacketListField``, ``FieldListField``, ... + +For the two firsts, whe a packet is being dissected, their lengths are deduced from a lenfield already dissected. The link is done using the ``length_from`` parameter, which takes a function that, applied to the partly dissected packet, returns the length in bytes to take for the field. For instance:: + + StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield) + +or + +:: + + StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield-12) + +For the ``PacketListField`` and ``FieldListField`` and their derivatives, they work as above when they need a length. If they need a number of elements, the length_from parameter must be ignored and the count_from parameter must be used instead. For instance:: + + FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), count_from = lambda pkt: pkt.the_lenfield) + +Examples +^^^^^^^^ + +:: + + class TestSLF(Packet): + fields_desc=[ FieldLenField("len", None, length_of="data"), + StrLenField("data", "", length_from=lambda pkt:pkt.len) ] + + class TestPLF(Packet): + fields_desc=[ FieldLenField("len", None, count_of="plist"), + PacketListField("plist", None, IP, count_from=lambda pkt:pkt.len) ] + + class TestFLF(Packet): + fields_desc=[ + FieldLenField("the_lenfield", None, count_of="the_varfield"), + FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), + count_from = lambda pkt: pkt.the_lenfield) ] + + class TestPkt(Packet): + fields_desc = [ ByteField("f1",65), + ShortField("f2",0x4244) ] + def extract_padding(self, p): + return "", p + + class TestPLF2(Packet): + fields_desc = [ FieldLenField("len1", None, count_of="plist",fmt="H", adjust=lambda pkt,x:x+2), + FieldLenField("len2", None, length_of="plist",fmt="I", adjust=lambda pkt,x:(x+1)/2), + PacketListField("plist", None, TestPkt, length_from=lambda x:(x.len2*2)/3*3) ] + +Test the ``FieldListField`` class:: + + >>> TestFLF("\x00\x02ABCDEFGHIJKL") + > + Special ------- diff --git a/doc/scapy/conf.py b/doc/scapy/conf.py index 0165d5188..9097facc6 100644 --- a/doc/scapy/conf.py +++ b/doc/scapy/conf.py @@ -36,15 +36,15 @@ master_doc = 'index' # General substitutions. project = 'Scapy' -copyright = '2008, Philippe Biondi and the Scapy community' +copyright = '2008, 2009 Philippe Biondi and the Scapy community' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. -version = '2.0.0' +version = '2.0.1' # The full version, including alpha/beta/rc tags. -release = '2.0.0' +release = '2.0.1' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/doc/scapy/graphics/ATMT_HelloWorld.png b/doc/scapy/graphics/ATMT_HelloWorld.png new file mode 100644 index 000000000..e5b5ddb18 Binary files /dev/null and b/doc/scapy/graphics/ATMT_HelloWorld.png differ diff --git a/doc/scapy/graphics/ATMT_TFTP_read.png b/doc/scapy/graphics/ATMT_TFTP_read.png new file mode 100644 index 000000000..50621d976 Binary files /dev/null and b/doc/scapy/graphics/ATMT_TFTP_read.png differ diff --git a/doc/scapy/index.rst b/doc/scapy/index.rst index ef319da40..b15473927 100644 --- a/doc/scapy/index.rst +++ b/doc/scapy/index.rst @@ -18,10 +18,11 @@ This document is under a `Creative Commons Attribution - Non-Commercial installation usage + advanced_usage extending - troubleshooting - build_dissect + + troubleshooting development backmatter diff --git a/doc/scapy/usage.rst b/doc/scapy/usage.rst index 50c749e0b..80704f7fd 100644 --- a/doc/scapy/usage.rst +++ b/doc/scapy/usage.rst @@ -336,6 +336,103 @@ If there is a limited rate of answers, you can specify a time interval to wait b Received 100 packets, got 3 answers, remaining 9 packets (, ) + +SYN Scans +--------- + +.. index:: + single: SYN Scan + +Classic SYN Scan can be initialized by executing the following command from Scapy's prompt:: + + >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S")) + +The above will send a single SYN packet to Google's port 80 and will quit after receving a single response:: + + Begin emission: + .Finished to send 1 packets. + * + Received 2 packets, got 1 answers, remaining 0 packets + >> + +From the above output, we can see Google returned “SA” or SYN-ACK flags indicating an open port. + +Use either notations to scan ports 400 through 443 on the system: + + >>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S")) + +or + + >>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],flags="S")) + +In order to quickly review responses simply request a summary of collected packets:: + + >>> ans,unans = _ + >>> ans.summary() + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:440 S ======> IP / TCP 192.168.1.1:440 > 192.168.1.100:ftp-data RA / Padding + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:441 S ======> IP / TCP 192.168.1.1:441 > 192.168.1.100:ftp-data RA / Padding + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:442 S ======> IP / TCP 192.168.1.1:442 > 192.168.1.100:ftp-data RA / Padding + IP / TCP 192.168.1.100:ftp-data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp-data SA / Padding + +The above will display stimulus/response pairs for answered probes. We can display only the information we are interested in by using a simple loop: + + >>> ans.summary( lambda(s,r): r.sprintf("%TCP.sport% \t %TCP.flags%") ) + 440 RA + 441 RA + 442 RA + https SA + +Even better, a table can be built using the ``make_table()`` function to display information about multiple targets:: + + >>> ans,unans = sr(IP(dst=["192.168.1.1","yahoo.com","slashdot.org"])/TCP(dport=[22,80,443],flags="S")) + Begin emission: + .......*.**.......Finished to send 9 packets. + **.*.*..*.................. + Received 362 packets, got 8 answers, remaining 1 packets + >>> ans.make_table( + ... lambda(s,r): (s.dst, s.dport, + ... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}"))) + 66.35.250.150 192.168.1.1 216.109.112.135 + 22 66.35.250.150 - dest-unreach RA - + 80 SA RA SA + 443 SA SA SA + +The above example will even print the ICMP error type if the ICMP packet was received as a response instead of expected TCP. + +For larger scans, we could be interested in displaying only certain responses. The example below will only display packets with the “SA” flag set:: + + >>> ans.nsummary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") ====== "SA") + 0003 IP / TCP 192.168.1.100:ftp_data > 192.168.1.1:https S ======> IP / TCP 192.168.1.1:https > 192.168.1.100:ftp_data SA + +In case we want to do some expert analysis of responses, we can use the following command to indicate which ports are open:: + + >>> ans.summary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") ====== "SA",prn=lambda(s,r):r.sprintf("%TCP.sport% is open")) + https is open + +Again, for larger scans we can build a table of open ports:: + + >>> ans.filter(lambda (s,r):TCP in r and r[TCP].flags&2).make_table(lambda (s,r): + ... (s.dst, s.dport, "X")) + 66.35.250.150 192.168.1.1 216.109.112.135 + 80 X - X + 443 X X X + +If all of the above methods were not enough, Scapy includes a report_ports() function which not only automates the SYN scan, but also produces a LaTeX output with collected results:: + + >>> report_ports("192.168.1.1",(440,443)) + Begin emission: + ...*.**Finished to send 4 packets. + * + Received 8 packets, got 4 answers, remaining 0 packets + '\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440 + & closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed & + TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n' + + TCP traceroute -------------- @@ -503,6 +600,24 @@ We can easily capture some packets or even clone tcpdump or tethereal. If no int ---[ Padding ]--- load = '\n_\x00\x0b' +For even more control over displayed information we can use the ``sprintf()`` function:: + + >>> pkts = sniff(prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}")) + 192.168.1.100 -> 64.233.167.99 + + 64.233.167.99 -> 192.168.1.100 + + 192.168.1.100 -> 64.233.167.99 + + 192.168.1.100 -> 64.233.167.99 + 'GET / HTTP/1.1\r\nHost: 64.233.167.99\r\nUser-Agent: Mozilla/5.0 + (X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071022 Ubuntu/7.10 (gutsy) + Firefox/2.0.0.8\r\nAccept: text/xml,application/xml,application/xhtml+xml, + text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: + en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: + ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: + keep-alive\r\nCache-Control: max-age=0\r\n\r\n' + We can sniff and do passive OS fingerprinting:: >>> p @@ -579,6 +694,143 @@ Here is an example of a (h)ping-like functionnality : you always send the same s IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S + +Importing and Exporting Data +---------------------------- +PCAP +^^^^ + +It is often useful to save capture packets to pcap file for use at later time or with different applications:: + + >>> wrpcap("temp.cap",pkts) + +To restore previously saved pcap file: + + >>> pkts = rdpcap("temp.cap") + +or + + >>> pkts = sniff(offline="temp.cap") + +Hexdump +^^^^^^^ + +Scapy allows you to export recorded packets in various hex formats. + +Use ``hexdump()`` to display one or more packets using classic hexdump format:: + + >>> hexdump(pkt) + 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. + 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... + 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. + 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ + 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% + 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 + 0060 36 37 67 + +Hexdump above can be reimported back into Scapy using ``import_hexcap()``:: + + >>> pkt_hex = Ether(import_hexcap()) + 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. + 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... + 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. + 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ + 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% + 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 + 0060 36 37 67 + >>> pkt_hex + >>> + +Hex string +^^^^^^^^^^ + +You can also convert entire packet into a hex string using the ``str()`` function:: + + >>> pkts = sniff(count = 1) + >>> pkt = pkts[0] + >>> pkt + >>> + >>> pkt_str = str(pkt) + >>> pkt_str + '\x00PV\xfc\xceP\x00\x0c)+S\x19\x08\x00E\x00\x00T\x00\x00@\x00@\x01Z|\xc0\xa8 + \x19\x82\x04\x02\x02\x01\x08\x00\x9c\x90Za\x00\x01\xe6\xdapI\xb6\xe5\x08\x00 + \x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b + \x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' + +We can reimport the produced hex string by selecting the appropriate starting layer (e.g. ``Ether()``). + + >>> new_pkt = Ether(pkt_str) + >>> new_pkt + >>> + +Base64 +^^^^^^ + +Using the ``export_object()`` function, Scapy can export a base64 encoded Python data structure representing a packet:: + + >>> pkt + >>> + >>> export_object(pkt) + eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST + OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao + bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT + WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 + ... + +The output above can be reimported back into Scapy using ``import_object()``:: + + >>> new_pkt = import_object() + eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST + OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao + bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT + WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 + ... + >>> new_pkt + >>> + +Sessions +^^^^^^^^ + +At last Scapy is capable of saving all session variables using the ``save_session()`` function: + +>>> dir() +['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts'] +>>> save_session("session.scapy") + +Next time you start Scapy you can load the previous saved session using the ``load_session()`` command:: + + >>> dir() + ['__builtins__', 'conf'] + >>> load_session("session.scapy") + >>> dir() + ['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts'] + + Making tables ------------- @@ -805,26 +1057,117 @@ you can have a kind of FakeAP:: Simple one-liners ================= + +ACK Scan +-------- + +Using Scapy's powerful packet crafting facilities we can quick replicate classic TCP Scans. +For example, the following string will be sent to simulate an ACK Scan:: + + >>> ans,unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A")) + +We can find unfiltered ports in answered packets:: + + >>> for s,r in ans: + ... if s[TCP].dport == r[TCP].sport: + ... print str(s[TCP].dport) + " is unfiltered" + +Similarly, filtered ports can be found with unanswered packets:: + + >>> for s in unans: + ... print str(s[TCP].dport) + " is filtered" + + +Xmas Scan +--------- + +Xmas Scan can be launced using the following command:: + + >>> ans,unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) + +Checking RST responses will reveal closed ports on the target. + +IP Scan +------- + +A lower level IP Scan can be used to enumerate supported protocols:: + + >>> ans,unans=sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2) + + +ARP Ping +-------- + +The fastest way to discover hosts on a local ethernet network is to use the ARP Ping method:: + + >>> ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"),timeout=2) + +Answers can be reviewed with the following command:: + + >>> ans.summary(lambda (s,r): r.sprintf("%Ether.src% %ARP.psrc%") ) + +Scapy also includes a built-in arping() function which performs similar to the above two commands: + + >>> arping("192.168.1.*") + + +ICMP Ping +--------- + +Classical ICMP Ping can be emulated using the following command:: + + >>> ans,unans=sr(IP(dst="192.168.1.1-254")/ICMP()) + +Information on live hosts can be collected with the following request:: + + >>> ans.summary(lambda (s,r): r.sprintf("%IP.src% is alive") ) + + +TCP Ping +-------- + +In cases where ICMP echo requests are blocked, we can still use various TCP Pings such as TCP SYN Ping below:: + + >>> ans,unans=sr( IP(dst="192.168.1.*")/TCP(dport=80,flags="S") ) + +Any response to our probes will indicate a live host. We can collect results with the following command:: + + >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) + + +UDP Ping +-------- + +If all else fails there is always UDP Ping which will produce ICMP Port unreachable errors from live hosts. Here you can pick any port which is most likely to be closed, such as port 0:: + + >>> ans,unans=sr( IP(dst="192.168.*.1-10")/UDP(dport=0) ) + +Once again, results can be collected with this command: + + >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) + + + Classical attacks ----------------- Malformed packets:: - send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) + >>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) Ping of death (Muuahahah):: - send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) + >>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) Nestea attack:: - send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) - send(IP(dst=target, id=42, frag=48)/("X"*116)) - send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) + >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) + >>> send(IP(dst=target, id=42, frag=48)/("X"*116)) + >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) Land attack (designed for Microsoft Windows):: - send(IP(src=target,dst=target)/TCP(sport=135,dport=135)) + >>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135)) ARP cache poisoning ------------------- @@ -833,12 +1176,12 @@ its ARP cache through a VLAN hopping attack. Classic ARP cache poisoning:: - send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), + >>> send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), inter=RandNum(10,40), loop=1 ) ARP cache poisoning with double 802.1q encapsulation:: - send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2) + >>> send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2) /ARP(op="who-has", psrc=gateway, pdst=client), inter=RandNum(10,40), loop=1 ) @@ -847,14 +1190,14 @@ TCP Port Scanning Send a TCP SYN on each port. Wait for a SYN-ACK or a RST or an ICMP error:: - res,unans = sr( IP(dst="target") + >>> res,unans = sr( IP(dst="target") /TCP(flags="S", dport=(1,1024)) ) Possible result visualization: open ports :: - res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) ) + >>> res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) ) IKE Scanning @@ -863,31 +1206,74 @@ IKE Scanning We try to identify VPN concentrators by sending ISAKMP Security Association proposals and receiving the answers:: - res,unans = sr( IP(dst="192.168.1.*")/UDP() + >>> res,unans = sr( IP(dst="192.168.1.*")/UDP() /ISAKMP(init_cookie=RandString(8), exch_type="identity prot.") /ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) ) Visualizing the results in a list:: - res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) ) - + >>> res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) ) + + +Advanced traceroute +------------------- + +TCP SYN traceroute +^^^^^^^^^^^^^^^^^^ + +:: + + >>> ans,unans=sr(IP(dst="4.2.2.1",ttl=(1,10))/TCP(dport=53,flags="S")) + +Results would be:: + + >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src%\t{ICMP:%ICMP.type%}\t{TCP:%TCP.flags%}")) + 192.168.1.1 time-exceeded + 68.86.90.162 time-exceeded + 4.79.43.134 time-exceeded + 4.79.43.133 time-exceeded + 4.68.18.126 time-exceeded + 4.68.123.38 time-exceeded + 4.2.2.1 SA + + UDP traceroute --------------- +^^^^^^^^^^^^^^ Tracerouting an UDP application like we do with TCP is not reliable, because there's no handshake. We need to give an applicative payload (DNS, ISAKMP, NTP, etc.) to deserve an answer:: - res,unans = sr(IP(dst="target", ttl=(1,20)) + >>> res,unans = sr(IP(dst="target", ttl=(1,20)) /UDP()/DNS(qd=DNSQR(qname="test.com")) We can visualize the results as a list of routers:: - res.make_table(lambda (s,r): (s.dst, s.ttl, r.src)) + >>> res.make_table(lambda (s,r): (s.dst, s.ttl, r.src)) +DNS traceroute +^^^^^^^^^^^^^^ + +We can perform a DNS traceroute by specifying a complete packet in ``l4`` parameter of ``traceroute()`` function:: + + >>> ans,unans=traceroute("4.2.2.1",l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org"))) + Begin emission: + ..*....******...******.***...****Finished to send 30 packets. + *****...***............................... + Received 75 packets, got 28 answers, remaining 2 packets + 4.2.2.1:udp53 + 1 192.168.1.1 11 + 4 68.86.90.162 11 + 5 4.79.43.134 11 + 6 4.79.43.133 11 + 7 4.68.18.62 11 + 8 4.68.123.6 11 + 9 4.2.2.1 + ... + Etherleaking ------------ @@ -919,6 +1305,20 @@ make a packet jump to another VLAN:: >>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP()) +Wireless sniffing +----------------- + +The following command will display information similar to most wireless sniffers:: + +>>> sniff(iface="ath0",prn=lambda x:x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%\t%PrismHeader.channel%\tDot11Beacon.cap%}")) + +The above command will produce output similar to the one below:: + + 00:00:00:01:02:03 netgear 6L ESS+privacy+PBCC + 11:22:33:44:55:66 wireless_100 6L short-slot+ESS+privacy + 44:55:66:00:11:22 linksys 6L short-slot+ESS+privacy + 12:34:56:78:90:12 NETGEAR 6L short-slot+ESS+privacy+short-preamble + Recipes ======= @@ -931,7 +1331,7 @@ This program uses the ``sniff()`` callback (paramter prn). The store parameter i :: #! /usr/bin/env python - from scapy import * + from scapy.all import * def arp_monitor_callback(pkt): if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at @@ -992,6 +1392,7 @@ See also http://en.wikipedia.org/wiki/Rogue_DHCP + Firewalking ----------- @@ -1010,6 +1411,23 @@ only his own NIC’s IP are reachable with this TTL:: >>> for i in unans: print i.dst +TCP Timestamp Filtering +------------------------ + +Problem +^^^^^^^ + +Many firewalls include a rule to drop TCP packets that do not have TCP Timestamp option set which is a common occurrence in popular port scanners. + +Solution +^^^^^^^^ + +To allow Scapy to reach target destination additional options must be used:: + + >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',(0,0))])) + + + Viewing packets with Wireshark ------------------------------ @@ -1042,4 +1460,61 @@ You can tell Scapy where to find the Wireshark executable by changing the ``conf +OS Fingerprinting +----------------- + +ISN +^^^ + +Scapy can be used to analyze ISN (Initial Sequence Number) increments to possibly discover vulnerable systems. First we will collect target responses by sending a number of SYN probes in a loop:: + + >>> ans,unans=srloop(IP(dst="192.168.1.1")/TCP(dport=80,flags="S")) + +Once we obtain a reasonable number of responses we can start analyzing collected data with something like this: + + >>> temp = 0 + >>> for s,r in ans: + ... temp = r[TCP].seq - temp + ... print str(r[TCP].seq) + "\t+" + str(temp) + ... + 4278709328 +4275758673 + 4279655607 +3896934 + 4280642461 +4276745527 + 4281648240 +4902713 + 4282645099 +4277742386 + 4283643696 +5901310 + +nmap_fp +^^^^^^^ + +If you have nmap installed you can use it's active os fingerprinting database with Scapy. First make sure that version 1 of signature database is located in the path specified by:: + + >>> conf.nmap_base + +Scapy includes a built-in ``nmap_fp()`` function which implements same probes as in Nmap's OS Detection engine:: + + >>> nmap_fp("192.168.1.1",oport=443,cport=1) + Begin emission: + .****..**Finished to send 8 packets. + *................................................ + Received 58 packets, got 7 answers, remaining 1 packets + (1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch', + 'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86) + w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11']) + +p0f +^^^ + +If you have p0f installed on your system, you can use it to guess OS name and version right from Scapy (only SYN database is used). First make sure that p0f database exists in the path specified by:: + + >>> conf.p0f_base + +For example to guess OS from a single captured packet: + + >>> sniff(prn=prnp0f) + 192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs) + -> 74.125.19.104:www (distance 0) + + + diff --git a/run_scapy b/run_scapy index 5f51187b3..c4723fe52 100755 --- a/run_scapy +++ b/run_scapy @@ -1,3 +1,3 @@ #! /bin/sh DIR=$(dirname $0) -PYTHONPATH=$DIR exec python -m scapy/ +PYTHONPATH=$DIR exec python -m scapy.__init__ diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py index 68089bd00..76a46fb02 100644 --- a/scapy/arch/linux.py +++ b/scapy/arch/linux.py @@ -333,7 +333,7 @@ class L3PacketSocket(SuperSocket): for i in self.iff: set_promisc(self.ins, i, 0) SuperSocket.close(self) - def recv(self, x): + def recv(self, x=MTU): pkt, sa_ll = self.ins.recvfrom(x) if sa_ll[2] == socket.PACKET_OUTGOING: return None @@ -382,7 +382,7 @@ class L3PacketSocket(SuperSocket): except socket.error,msg: x.sent_time = time.time() # bad approximation if conf.auto_fragment and msg[0] == 90: - for p in fragment(x): + for p in x.fragment(): self.outs.sendto(str(ll(p)), sdto) else: raise @@ -419,7 +419,7 @@ class L2Socket(SuperSocket): self.LL = conf.default_l2 warning("Unable to guess type (interface=%s protocol=%#x family=%i). Using %s" % (sa_ll[0],sa_ll[1],sa_ll[3],self.LL.name)) - def recv(self, x): + def recv(self, x=MTU): pkt, sa_ll = self.ins.recvfrom(x) if sa_ll[2] == socket.PACKET_OUTGOING: return None diff --git a/scapy/arch/pcapdnet.py b/scapy/arch/pcapdnet.py index e91c49317..6da9af377 100644 --- a/scapy/arch/pcapdnet.py +++ b/scapy/arch/pcapdnet.py @@ -102,7 +102,7 @@ if conf.use_pcap: def close(self): del(self.ins) - def recv(self, x): + def recv(self, x=MTU): ll = self.ins.datalink() if ll in conf.l2types: cls = conf.l2types[ll] @@ -292,7 +292,7 @@ if conf.use_pcap and conf.use_dnet: if filter: self.ins.setfilter(filter) self.outs = dnet.eth(iface) - def recv(self,x): + def recv(self,x=MTU): ll = self.ins.datalink() if ll in conf.l2types: cls = conf.l2types[ll] diff --git a/scapy/automaton.py b/scapy/automaton.py index b5be2b143..66cf0cf9c 100644 --- a/scapy/automaton.py +++ b/scapy/automaton.py @@ -3,13 +3,59 @@ ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license -import types,itertools,time +from __future__ import with_statement +import types,itertools,time,os,sys from select import select +from collections import deque +import thread from config import conf from utils import do_graph from error import log_interactive from plist import PacketList from data import MTU +from supersocket import SuperSocket + +class ObjectPipe: + def __init__(self): + self.rd,self.wr = os.pipe() + self.queue = deque() + def fileno(self): + return self.rd + def send(self, obj): + self.queue.append(obj) + os.write(self.wr,"X") + def recv(self, n=0): + os.read(self.rd,1) + return self.queue.popleft() + + +class Message: + def __init__(self, **args): + self.__dict__.update(args) + def __repr__(self): + return "" % " ".join("%s=%r"%(k,v) + for (k,v) in self.__dict__.iteritems() + if not k.startswith("_")) + +class _instance_state: + def __init__(self, instance): + self.im_self = instance.im_self + self.im_func = instance.im_func + self.im_class = instance.im_class + def __getattr__(self, attr): + return getattr(self.im_func, attr) + + def __call__(self, *args, **kargs): + return self.im_func(self.im_self, *args, **kargs) + def breaks(self): + return self.im_self.add_breakpoints(self.im_func) + def intercepts(self): + return self.im_self.add_interception_points(self.im_func) + def unbreaks(self): + return self.im_self.remove_breakpoints(self.im_func) + def unintercepts(self): + return self.im_self.remove_interception_points(self.im_func) + ############## ## Automata ## @@ -21,6 +67,7 @@ class ATMT: CONDITION = "Condition" RECV = "Receive condition" TIMEOUT = "Timeout condition" + IOEVENT = "I/O event" class NewStateRequested(Exception): def __init__(self, state_func, automaton, *args, **kargs): @@ -40,6 +87,8 @@ class ATMT: return self def run(self): return self.func(self.automaton, *self.args, **self.kargs) + def __repr__(self): + return "NewStateRequested(%s)" % self.state @staticmethod def state(initial=0,final=0,error=0): @@ -89,6 +138,17 @@ class ATMT: return f return deco @staticmethod + def ioevent(state, name, prio=0, as_supersocket=None): + def deco(f, state=state): + f.atmt_type = ATMT.IOEVENT + f.atmt_state = state.atmt_state + f.atmt_condname = f.func_name + f.atmt_ioname = name + f.atmt_prio = prio + f.atmt_as_supersocket = as_supersocket + return f + return deco + @staticmethod def timeout(state, timeout): def deco(f, state=state, timeout=timeout): f.atmt_type = ATMT.TIMEOUT @@ -98,6 +158,58 @@ class ATMT: return f return deco +class _ATMT_Command: + RUN = "RUN" + NEXT = "NEXT" + FREEZE = "FREEZE" + STOP = "STOP" + END = "END" + EXCEPTION = "EXCEPTION" + SINGLESTEP = "SINGLESTEP" + BREAKPOINT = "BREAKPOINT" + INTERCEPT = "INTERCEPT" + ACCEPT = "ACCEPT" + REPLACE = "REPLACE" + REJECT = "REJECT" + +class _ATMT_supersocket(SuperSocket): + def __init__(self, name, ioevent, automaton, proto, args, kargs): + self.name = name + self.ioevent = ioevent + self.proto = proto + self.spa,self.spb = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM) + kargs["external_fd"] = {ioevent:self.spb} + self.atmt = automaton(*args, **kargs) + self.atmt.runbg() + def fileno(self): + return self.spa.fileno() + def send(self, s): + if type(s) is not str: + s = str(s) + return self.spa.send(s) + def recv(self, n=MTU): + r = self.spa.recv(n) + if self.proto is not None: + r = self.proto(r) + return r + def close(self): + pass + def sr(self, *args, **kargs): + return sndrcv(self, *args, **kargs) + def sr1(self, *args, **kargs): + a,b = sndrcv(self, *args, **kargs) + if len(a) > 0: + return a[0][1] + else: + return None + +class _ATMT_to_supersocket: + def __init__(self, name, ioevent, automaton): + self.name = name + self.ioevent = ioevent + self.automaton = automaton + def __call__(self, proto, *args, **kargs): + return _ATMT_supersocket(self.name, self.ioevent, self.automaton, proto, args, kargs) class Automaton_metaclass(type): def __new__(cls, name, bases, dct): @@ -106,9 +218,12 @@ class Automaton_metaclass(type): cls.state = None cls.recv_conditions={} cls.conditions={} + cls.ioevents={} cls.timeout={} cls.actions={} cls.initial_states=[] + cls.ionames = [] + cls.iosupersockets = [] members = {} classes = [cls] @@ -127,11 +242,12 @@ class Automaton_metaclass(type): s = m.atmt_state cls.states[s] = m cls.recv_conditions[s]=[] + cls.ioevents[s]=[] cls.conditions[s]=[] cls.timeout[s]=[] if m.atmt_initial: cls.initial_states.append(m) - elif m.atmt_type in [ATMT.CONDITION, ATMT.RECV, ATMT.TIMEOUT]: + elif m.atmt_type in [ATMT.CONDITION, ATMT.RECV, ATMT.TIMEOUT, ATMT.IOEVENT]: cls.actions[m.atmt_condname] = [] for m in decorated: @@ -139,6 +255,11 @@ class Automaton_metaclass(type): cls.conditions[m.atmt_state].append(m) elif m.atmt_type == ATMT.RECV: cls.recv_conditions[m.atmt_state].append(m) + elif m.atmt_type == ATMT.IOEVENT: + cls.ioevents[m.atmt_state].append(m) + cls.ionames.append(m.atmt_ioname) + if m.atmt_as_supersocket is not None: + cls.iosupersockets.append(m) elif m.atmt_type == ATMT.TIMEOUT: cls.timeout[m.atmt_state].append((m.atmt_timeout, m)) elif m.atmt_type == ATMT.ACTION: @@ -150,14 +271,17 @@ class Automaton_metaclass(type): v.sort(lambda (t1,f1),(t2,f2): cmp(t1,t2)) v.append((None, None)) for v in itertools.chain(cls.conditions.itervalues(), - cls.recv_conditions.itervalues()): + cls.recv_conditions.itervalues(), + cls.ioevents.itervalues()): v.sort(lambda c1,c2: cmp(c1.atmt_prio,c2.atmt_prio)) for condname,actlst in cls.actions.iteritems(): actlst.sort(lambda c1,c2: cmp(c1.atmt_cond[condname], c2.atmt_cond[condname])) + for ioev in cls.iosupersockets: + setattr(cls, ioev.atmt_as_supersocket, _ATMT_to_supersocket(ioev.atmt_as_supersocket, ioev.atmt_ioname, cls)) + return cls - def graph(self, **kargs): s = 'digraph "%s" {\n' % self.__class__.__name__ @@ -177,7 +301,9 @@ class Automaton_metaclass(type): s += '\t"%s" -> "%s" [ color=green ];\n' % (st.atmt_state,n) - for c,k,v in [("purple",k,v) for k,v in self.conditions.items()]+[("red",k,v) for k,v in self.recv_conditions.items()]: + for c,k,v in ([("purple",k,v) for k,v in self.conditions.items()]+ + [("red",k,v) for k,v in self.recv_conditions.items()]+ + [("orange",k,v) for k,v in self.ioevents.items()]): for f in v: for n in f.func_code.co_names+f.func_code.co_consts: if n in self.states: @@ -203,37 +329,170 @@ class Automaton_metaclass(type): class Automaton: __metaclass__ = Automaton_metaclass - def __init__(self, *args, **kargs): - self.debug_level=0 - self.init_args=args - self.init_kargs=kargs - self.parse_args(*args, **kargs) - - def debug(self, lvl, msg): - if self.debug_level >= lvl: - log_interactive.debug(msg) - - - - - class ErrorState(Exception): - def __init__(self, msg, result=None): - Exception.__init__(self, msg) - self.result = result - class Stuck(ErrorState): - pass - + ## Methods to overload def parse_args(self, debug=0, store=1, **kargs): self.debug_level=debug self.socket_kargs = kargs - self.store_packets = store - + self.store_packets = store def master_filter(self, pkt): return True - def run_condition(self, cond, *args, **kargs): + def my_send(self, pkt): + self.send_sock.send(pkt) + + + ## Utility classes and exceptions + class _IO_fdwrapper: + def __init__(self,rd,wr): + if rd is not None and type(rd) is not int: + rd = rd.fileno() + if wr is not None and type(wr) is not int: + wr = wr.fileno() + self.rd = rd + self.wr = wr + def fileno(self): + return self.rd + def read(self, n=65535): + return os.read(self.rd, n) + def write(self, msg): + return os.write(self.wr,msg) + def recv(self, n=65535): + return self.read(n) + def send(self, msg): + return self.write(msg) + + class _IO_mixer: + def __init__(self,rd,wr): + self.rd = rd + self.wr = wr + def fileno(self): + if type(self.rd) is int: + return self.rd + return self.rd.fileno() + def recv(self, n=None): + return self.rd.recv(n) + def read(self, n=None): + return self.rd.recv(n) + def send(self, msg): + return self.wr.send(msg) + def write(self, msg): + return self.wr.send(msg) + + + class AutomatonException(Exception): + def __init__(self, msg, state=None, result=None): + Exception.__init__(self, msg) + self.state = state + self.result = result + + class AutomatonError(AutomatonException): + pass + class ErrorState(AutomatonException): + pass + class Stuck(AutomatonException): + pass + class AutomatonStopped(AutomatonException): + pass + + class Breakpoint(AutomatonStopped): + pass + class Singlestep(AutomatonStopped): + pass + class InterceptionPoint(AutomatonStopped): + def __init__(self, msg, state=None, result=None, packet=None): + Automaton.AutomatonStopped.__init__(self, msg, state=state, result=result) + self.packet = packet + + class CommandMessage(AutomatonException): + pass + + + ## Services + def debug(self, lvl, msg): + if self.debug_level >= lvl: + log_interactive.debug(msg) + + def send(self, pkt): + if self.state.state in self.interception_points: + self.debug(3,"INTERCEPT: packet intercepted: %s" % pkt.summary()) + self.intercepted_packet = pkt + cmd = Message(type = _ATMT_Command.INTERCEPT, state=self.state, pkt=pkt) + self.cmdout.send(cmd) + cmd = self.cmdin.recv() + self.intercepted_packet = None + if cmd.type == _ATMT_Command.REJECT: + self.debug(3,"INTERCEPT: packet rejected") + return + elif cmd.type == _ATMT_Command.REPLACE: + pkt = cmd.pkt + self.debug(3,"INTERCEPT: packet replaced by: %s" % pkt.summary()) + elif cmd.type == _ATMT_Command.ACCEPT: + self.debug(3,"INTERCEPT: packet accepted") + else: + raise self.AutomatonError("INTERCEPT: unkown verdict: %r" % cmd.type) + self.my_send(pkt) + self.debug(3,"SENT : %s" % pkt.summary()) + self.packets.append(pkt.copy()) + + + ## Internals + def __init__(self, *args, **kargs): + external_fd = kargs.pop("external_fd",{}) + self.send_sock_class = kargs.pop("ll", conf.L3socket) + self.started = thread.allocate_lock() + self.threadid = None + self.breakpointed = None + self.breakpoints = set() + self.interception_points = set() + self.intercepted_packet = None + self.debug_level=0 + self.init_args=args + self.init_kargs=kargs + self.io = type.__new__(type, "IOnamespace",(),{}) + self.oi = type.__new__(type, "IOnamespace",(),{}) + self.cmdin = ObjectPipe() + self.cmdout = ObjectPipe() + self.ioin = {} + self.ioout = {} + for n in self.ionames: + extfd = external_fd.get(n) + if type(extfd) is not tuple: + extfd = (extfd,extfd) + ioin,ioout = extfd + if ioin is None: + ioin = ObjectPipe() + elif type(ioin) is not types.InstanceType: + ioin = self._IO_fdwrapper(ioin,None) + if ioout is None: + ioout = ObjectPipe() + elif type(ioout) is not types.InstanceType: + ioout = self._IO_fdwrapper(None,ioout) + + self.ioin[n] = ioin + self.ioout[n] = ioout + ioin.ioname = n + ioout.ioname = n + setattr(self.io, n, self._IO_mixer(ioout,ioin)) + setattr(self.oi, n, self._IO_mixer(ioin,ioout)) + + for stname in self.states: + setattr(self, stname, + _instance_state(getattr(self, stname))) + + self.parse_args(*args, **kargs) + + self.start() + + def __iter__(self): + return self + + def __del__(self): + self.stop() + + def _run_condition(self, cond, *args, **kargs): try: + self.debug(5, "Trying %s [%s]" % (cond.atmt_type, cond.atmt_condname)) cond(self,*args, **kargs) except ATMT.NewStateRequested, state_req: self.debug(2, "%s [%s] taken to state [%s]" % (cond.atmt_type, cond.atmt_condname, state_req.state)) @@ -243,33 +502,88 @@ class Automaton: self.debug(2, " + Running action [%s]" % action.func_name) action(self, *state_req.action_args, **state_req.action_kargs) raise + except Exception,e: + self.debug(2, "%s [%s] raised exception [%s]" % (cond.atmt_type, cond.atmt_condname, e)) + raise else: self.debug(2, "%s [%s] not taken" % (cond.atmt_type, cond.atmt_condname)) - - def run(self, *args, **kargs): - # Update default parameters - a = args+self.init_args[len(args):] - k = self.init_kargs - k.update(kargs) - self.parse_args(*a,**k) + def _do_start(self, *args, **kargs): + + thread.start_new_thread(self._do_control, args, kargs) - # Start the automaton - self.state=self.initial_states[0](self) - self.send_sock = conf.L3socket() - l = conf.L2listen(**self.socket_kargs) - self.packets = PacketList(name="session[%s]"%self.__class__.__name__) - while 1: + + def _do_control(self, *args, **kargs): + with self.started: + self.threadid = thread.get_ident() + + # Update default parameters + a = args+self.init_args[len(args):] + k = self.init_kargs.copy() + k.update(kargs) + self.parse_args(*a,**k) + + # Start the automaton + self.state=self.initial_states[0](self) + self.send_sock = self.send_sock_class() + self.listen_sock = conf.L2listen(**self.socket_kargs) + self.packets = PacketList(name="session[%s]"%self.__class__.__name__) + + singlestep = True + iterator = self._do_iter() + self.debug(3, "Starting control thread [tid=%i]" % self.threadid) + try: + while True: + c = self.cmdin.recv() + self.debug(5, "Received command %s" % c.type) + if c.type == _ATMT_Command.RUN: + singlestep = False + elif c.type == _ATMT_Command.NEXT: + singlestep = True + elif c.type == _ATMT_Command.FREEZE: + continue + elif c.type == _ATMT_Command.STOP: + break + while True: + state = iterator.next() + if isinstance(state, self.CommandMessage): + break + elif isinstance(state, self.Breakpoint): + c = Message(type=_ATMT_Command.BREAKPOINT,state=state) + self.cmdout.send(c) + break + if singlestep: + c = Message(type=_ATMT_Command.SINGLESTEP,state=state) + self.cmdout.send(c) + break + except StopIteration,e: + c = Message(type=_ATMT_Command.END, result=e.args[0]) + self.cmdout.send(c) + except Exception,e: + self.debug(3, "Transfering exception [%s] from tid=%i"% (e,self.threadid)) + m = Message(type = _ATMT_Command.EXCEPTION, exception=e, exc_info=sys.exc_info()) + self.cmdout.send(m) + self.debug(3, "Stopping control thread (tid=%i)"%self.threadid) + self.threadid = None + + def _do_iter(self): + while True: try: self.debug(1, "## state=[%s]" % self.state.state) - + # Entering a new state. First, call new state function + if self.state.state in self.breakpoints and self.state.state != self.breakpointed: + self.breakpointed = self.state.state + yield self.Breakpoint("breakpoint triggered on state %s" % self.state.state, + state = self.state.state) + self.breakpointed = None state_output = self.state.run() if self.state.error: - raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), result=state_output) + raise self.ErrorState("Reached %s: [%r]" % (self.state.state, state_output), + result=state_output, state=self.state.state) if self.state.final: - return state_output - + raise StopIteration(state_output) + if state_output is None: state_output = () elif type(state_output) is not list: @@ -277,54 +591,146 @@ class Automaton: # Then check immediate conditions for cond in self.conditions[self.state.state]: - self.run_condition(cond, *state_output) - + self._run_condition(cond, *state_output) + # If still there and no conditions left, we are stuck! - if ( len(self.recv_conditions[self.state.state]) == 0 - and len(self.timeout[self.state.state]) == 1 ): - raise self.Stuck("stuck in [%s]" % self.state.state,result=state_output) - + if ( len(self.recv_conditions[self.state.state]) == 0 and + len(self.ioevents[self.state.state]) == 0 and + len(self.timeout[self.state.state]) == 1 ): + raise self.Stuck("stuck in [%s]" % self.state.state, + state=self.state.state, result=state_output) + # Finally listen and pay attention to timeouts expirations = iter(self.timeout[self.state.state]) next_timeout,timeout_func = expirations.next() t0 = time.time() + fds = [self.cmdin] + if len(self.recv_conditions[self.state.state]) > 0: + fds.append(self.listen_sock) + for ioev in self.ioevents[self.state.state]: + fds.append(self.ioin[ioev.atmt_ioname]) while 1: t = time.time()-t0 if next_timeout is not None: if next_timeout <= t: - self.run_condition(timeout_func, *state_output) + self._run_condition(timeout_func, *state_output) next_timeout,timeout_func = expirations.next() if next_timeout is None: remain = None else: remain = next_timeout-t - r,_,_ = select([l],[],[],remain) - if l in r: - pkt = l.recv(MTU) - if pkt is not None: - if self.master_filter(pkt): - self.debug(3, "RECVD: %s" % pkt.summary()) - for rcvcond in self.recv_conditions[self.state.state]: - self.run_condition(rcvcond, pkt, *state_output) - else: - self.debug(4, "FILTR: %s" % pkt.summary()) - + self.debug(5, "Select on %r" % fds) + r,_,_ = select(fds,[],[],remain) + self.debug(5, "Selected %r" % r) + for fd in r: + self.debug(5, "Looking at %r" % fd) + if fd == self.cmdin: + yield self.CommandMessage("Received command message") + elif fd == self.listen_sock: + pkt = self.listen_sock.recv(MTU) + if pkt is not None: + if self.master_filter(pkt): + self.debug(3, "RECVD: %s" % pkt.summary()) + for rcvcond in self.recv_conditions[self.state.state]: + self._run_condition(rcvcond, pkt, *state_output) + else: + self.debug(4, "FILTR: %s" % pkt.summary()) + else: + self.debug(3, "IOEVENT on %s" % fd.ioname) + for ioevt in self.ioevents[self.state.state]: + if ioevt.atmt_ioname == fd.ioname: + self._run_condition(ioevt, fd, *state_output) + except ATMT.NewStateRequested,state_req: self.debug(2, "switching from [%s] to [%s]" % (self.state.state,state_req.state)) self.state = state_req - except KeyboardInterrupt: - self.debug(1,"Interrupted by user") - break - - def my_send(self, pkt): - self.send_sock.send(pkt) - - def send(self, pkt): - self.my_send(pkt) - self.debug(3,"SENT : %s" % pkt.summary()) - self.packets.append(pkt.copy()) - + yield state_req + ## Public API + def add_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt,"atmt_state"): + ipt = ipt.atmt_state + self.interception_points.add(ipt) + def remove_interception_points(self, *ipts): + for ipt in ipts: + if hasattr(ipt,"atmt_state"): + ipt = ipt.atmt_state + self.interception_points.discard(ipt) + + def add_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp,"atmt_state"): + bp = bp.atmt_state + self.breakpoints.add(bp) + + def remove_breakpoints(self, *bps): + for bp in bps: + if hasattr(bp,"atmt_state"): + bp = bp.atmt_state + self.breakpoints.discard(bp) + + def start(self, *args, **kargs): + if not self.started.locked(): + self._do_start(*args, **kargs) + + def run(self, resume=None, wait=True): + if resume is None: + resume = Message(type = _ATMT_Command.RUN) + self.cmdin.send(resume) + if wait: + try: + c = self.cmdout.recv() + except KeyboardInterrupt: + self.cmdin.send(Message(type = _ATMT_Command.FREEZE)) + return + if c.type == _ATMT_Command.END: + return c.result + elif c.type == _ATMT_Command.INTERCEPT: + raise self.InterceptionPoint("packet intercepted", state=c.state.state, packet=c.pkt) + elif c.type == _ATMT_Command.SINGLESTEP: + raise self.Singlestep("singlestep state=[%s]"%c.state.state, state=c.state.state) + elif c.type == _ATMT_Command.BREAKPOINT: + raise self.Breakpoint("breakpoint triggered on state [%s]"%c.state.state, state=c.state.state) + elif c.type == _ATMT_Command.EXCEPTION: + raise c.exc_info[0],c.exc_info[1],c.exc_info[2] + + def runbg(self, resume=None, wait=False): + self.run(resume, wait) + + def next(self): + return self.run(resume = Message(type=_ATMT_Command.NEXT)) + + def stop(self): + self.cmdin.send(Message(type=_ATMT_Command.STOP)) + with self.started: + # Flush command pipes + while True: + r,_,_ = select([self.cmdin, self.cmdout],[],[],0) + if not r: + break + for fd in r: + fd.recv() + + def restart(self, *args, **kargs): + self.stop() + self.start(*args, **kargs) + + def accept_packet(self, pkt=None, wait=False): + rsm = Message() + if pkt is None: + rsm.type = _ATMT_Command.ACCEPT + else: + rsm.type = _ATMT_Command.REPLACE + rsm.pkt = pkt + return self.run(resume=rsm, wait=wait) + + def reject_packet(self, wait=False): + rsm = Message(type = _ATMT_Command.REJECT) + return self.run(resume=rsm, wait=wait) + + + diff --git a/scapy/base_classes.py b/scapy/base_classes.py index 1a77642e9..6d36053c0 100644 --- a/scapy/base_classes.py +++ b/scapy/base_classes.py @@ -122,43 +122,75 @@ class OID(Gen): class Packet_metaclass(type): def __new__(cls, name, bases, dct): + if "fields_desc" in dct: # perform resolution of references to other packets + current_fld = dct["fields_desc"] + resolved_fld = [] + for f in current_fld: + if isinstance(f, Packet_metaclass): # reference to another fields_desc + for f2 in f.fields_desc: + resolved_fld.append(f2) + else: + resolved_fld.append(f) + else: # look for a field_desc in parent classes + resolved_fld = None + for b in bases: + if hasattr(b,"fields_desc"): + resolved_fld = b.fields_desc + break + + if resolved_fld: # perform default value replacements + final_fld = [] + for f in resolved_fld: + if f.name in dct: + f = f.copy() + f.default = dct[f.name] + del(dct[f.name]) + final_fld.append(f) + + dct["fields_desc"] = final_fld + newcls = super(Packet_metaclass, cls).__new__(cls, name, bases, dct) - for f in newcls.fields_desc: + if hasattr(newcls,"register_variant"): + newcls.register_variant() + for f in newcls.fields_desc: f.register_owner(newcls) config.conf.layers.register(newcls) return newcls + def __getattr__(self, attr): for k in self.fields_desc: if k.name == attr: return k raise AttributeError(attr) + def __call__(cls, *args, **kargs): + if "dispatch_hook" in cls.__dict__: + cls = cls.dispatch_hook(*args, **kargs) + i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) + i.__init__(*args, **kargs) + return i + + class NewDefaultValues(Packet_metaclass): - """NewDefaultValues metaclass. Example usage: - class MyPacket(Packet): - fields_desc = [ StrField("my_field", "my default value"), ] - - class MyPacket_variant(MyPacket): + """NewDefaultValues is deprecated (not needed anymore) + + remove this: __metaclass__ = NewDefaultValues - my_field = "my new default value" + and it should still work. """ def __new__(cls, name, bases, dct): - fields = None - for b in bases: - if hasattr(b,"fields_desc"): - fields = b.fields_desc - break - if fields is None: - raise error.Scapy_Exception("No fields_desc in superclasses") - - new_fields = [] - for f in fields: - if f.name in dct: - f = f.copy() - f.default = dct[f.name] - del(dct[f.name]) - new_fields.append(f) - dct["fields_desc"] = new_fields + from error import log_loading + import traceback + try: + for tb in traceback.extract_stack()+[("??",-1,None,"")]: + f,l,_,line = tb + if line.startswith("class"): + break + except: + f,l="??",-1 + raise + log_loading.warning("Deprecated (no more needed) use of NewDefaultValues (%s l. %i)." % (f,l)) + return super(NewDefaultValues, cls).__new__(cls, name, bases, dct) class BasePacket(Gen): diff --git a/scapy/data.py b/scapy/data.py index c1d52f924..299d5a9cb 100644 --- a/scapy/data.py +++ b/scapy/data.py @@ -44,7 +44,7 @@ IPV6_ADDR_UNSPECIFIED = 0x10000 -MTU = 1600 +MTU = 0x7fff # a.k.a give me all you have WINDOWS=sys.platform.startswith("win") diff --git a/scapy/fields.py b/scapy/fields.py index 34cea5286..a174137af 100644 --- a/scapy/fields.py +++ b/scapy/fields.py @@ -1,4 +1,4 @@ -## This file is part of Scapy +# This file is part of Scapy ## See http://www.secdev.org/projects/scapy for more informations ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license @@ -249,8 +249,6 @@ class ByteField(Field): class XByteField(ByteField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) class X3BytesField(XByteField): @@ -272,8 +270,6 @@ class LEShortField(Field): class XShortField(ShortField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) @@ -299,8 +295,6 @@ class LESignedIntField(Field): class XIntField(IntField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) @@ -310,8 +304,6 @@ class LongField(Field): class XLongField(LongField): def i2repr(self, pkt, x): - if x is None: - x = 0 return lhex(self.i2h(pkt, x)) class IEEEFloatField(Field): @@ -370,7 +362,12 @@ class PacketLenField(PacketField): self.length_from = length_from def getfield(self, pkt, s): l = self.length_from(pkt) - i = self.m2i(pkt, s[:l]) + try: + i = self.m2i(pkt, s[:l]) + except Exception: + if conf.debug_dissector: + raise + i = conf.raw_layer(load=s[:l]) return s[l:],i @@ -415,13 +412,20 @@ class PacketListField(PacketField): if c <= 0: break c -= 1 - p = self.m2i(pkt,remain) - if 'Padding' in p: - pad = p['Padding'] - remain = pad.load - del(pad.underlayer.payload) - else: + try: + p = self.m2i(pkt,remain) + except Exception: + if conf.debug_dissector: + raise + p = conf.raw_layer(load=remain) remain = "" + else: + if 'Padding' in p: + pad = p['Padding'] + remain = pad.load + del(pad.underlayer.payload) + else: + remain = "" lst.append(p) return remain+ret,lst def addfield(self, pkt, s, val): @@ -757,6 +761,36 @@ class XShortEnumField(ShortEnumField): return self.i2s[x] return lhex(x) +class MultiEnumField(EnumField): + def __init__(self, name, default, enum, depends_on, fmt = "H"): + + self.depends_on = depends_on + self.i2s_multi = enum + self.s2i_multi = {} + self.s2i_all = {} + for m in enum: + self.s2i_multi[m] = s2i = {} + for k,v in enum[m].iteritems(): + s2i[v] = k + self.s2i_all[v] = k + Field.__init__(self, name, default, fmt) + def any2i_one(self, pkt, x): + if type (x) is str: + v = self.depends_on(pkt) + if v in self.s2i_multi: + s2i = self.s2i_multi[v] + if x in s2i: + return s2i[x] + return self.s2i_all[x] + return x + def i2repr_one(self, pkt, x): + v = self.depends_on(pkt) + if v in self.i2s_multi: + return self.i2s_multi[v].get(x,x) + return x + + + # Little endian long field class LELongField(Field): def __init__(self, name, default): diff --git a/scapy/layers/bluetooth.py b/scapy/layers/bluetooth.py index cfa874f53..89757a98d 100644 --- a/scapy/layers/bluetooth.py +++ b/scapy/layers/bluetooth.py @@ -9,6 +9,7 @@ from scapy.config import conf from scapy.packet import * from scapy.fields import * from scapy.supersocket import SuperSocket +from scapy.data import MTU class HCI_Hdr(Packet): @@ -163,7 +164,7 @@ class BluetoothL2CAPSocket(SuperSocket): self.ins = self.outs = s - def recv(self, x): + def recv(self, x=MTU): return L2CAP_CmdHdr(self.ins.recv(x)) @@ -189,7 +190,7 @@ class BluetoothHCISocket(SuperSocket): def srbt(peer, pkts, inter=0.1, *args, **kargs): """send and receive using a bluetooth socket""" s = conf.BTsocket(peer=peer) - a,b,c=sndrcv(s,pkts,inter=inter,*args,**kargs) + a,b = sndrcv(s,pkts,inter=inter,*args,**kargs) s.close() return a,b diff --git a/scapy/layers/dhcp6.py b/scapy/layers/dhcp6.py index db61c38f9..07eee8afb 100644 --- a/scapy/layers/dhcp6.py +++ b/scapy/layers/dhcp6.py @@ -282,7 +282,6 @@ class DHCP6OptClientId(_DHCP6OptGuessPayload): # RFC sect 22.2 class DHCP6OptServerId(DHCP6OptClientId): # RFC sect 22.3 name = "DHCP6 Server Identifier Option" - __metaclass__ = NewDefaultValues optcode = 2 # Should be encapsulated in the option field of IA_NA or IA_TA options @@ -941,7 +940,6 @@ class DHCP6(_DHCP6OptGuessPayload): class DHCP6_Solicit(DHCP6): name = "DHCPv6 Solicit Message" - __metaclass__ = NewDefaultValues msgtype = 1 overload_fields = { UDP: {"sport": 546, "dport": 547} } @@ -955,7 +953,6 @@ class DHCP6_Solicit(DHCP6): class DHCP6_Advertise(DHCP6): name = "DHCPv6 Advertise Message" - __metaclass__ = NewDefaultValues msgtype = 2 overload_fields = { UDP: {"sport": 547, "dport": 546} } @@ -983,7 +980,6 @@ class DHCP6_Advertise(DHCP6): class DHCP6_Request(DHCP6): name = "DHCPv6 Request Message" - __metaclass__ = NewDefaultValues msgtype = 3 ##################################################################### @@ -996,7 +992,6 @@ class DHCP6_Request(DHCP6): class DHCP6_Confirm(DHCP6): name = "DHCPv6 Confirm Message" - __metaclass__ = NewDefaultValues msgtype = 4 ##################################################################### @@ -1018,7 +1013,6 @@ class DHCP6_Confirm(DHCP6): class DHCP6_Renew(DHCP6): name = "DHCPv6 Renew Message" - __metaclass__ = NewDefaultValues msgtype = 5 ##################################################################### @@ -1029,7 +1023,6 @@ class DHCP6_Renew(DHCP6): class DHCP6_Rebind(DHCP6): name = "DHCPv6 Rebind Message" - __metaclass__ = NewDefaultValues msgtype = 6 ##################################################################### @@ -1057,7 +1050,6 @@ class DHCP6_Rebind(DHCP6): class DHCP6_Reply(DHCP6): name = "DHCPv6 Reply Message" - __metaclass__ = NewDefaultValues msgtype = 7 def answers(self, other): @@ -1072,7 +1064,6 @@ class DHCP6_Reply(DHCP6): class DHCP6_Release(DHCP6): name = "DHCPv6 Release Message" - __metaclass__ = NewDefaultValues msgtype = 8 ##################################################################### @@ -1087,7 +1078,6 @@ class DHCP6_Release(DHCP6): class DHCP6_Decline(DHCP6): name = "DHCPv6 Decline Message" - __metaclass__ = NewDefaultValues msgtype = 9 ##################################################################### @@ -1105,7 +1095,6 @@ class DHCP6_Decline(DHCP6): class DHCP6_Reconf(DHCP6): name = "DHCPv6 Reconfigure Message" - __metaclass__ = NewDefaultValues msgtype = 10 overload_fields = { UDP: { "sport": 547, "dport": 546 } } @@ -1124,7 +1113,6 @@ class DHCP6_Reconf(DHCP6): class DHCP6_InfoRequest(DHCP6): name = "DHCPv6 Information Request Message" - __metaclass__ = NewDefaultValues msgtype = 11 def hashret(self): @@ -1172,7 +1160,6 @@ class DHCP6_RelayForward(_DHCP6GuessPayload,Packet): class DHCP6_RelayReply(DHCP6_RelayForward): name = "DHCPv6 Relay Reply Message (Relay Agent/Server Message)" - __metaclass__= NewDefaultValues msgtype = 13 def hashret(self): # We filter on peer address field. return inet_pton(socket.AF_INET6, self.peeraddr) diff --git a/scapy/layers/dot11.py b/scapy/layers/dot11.py index cf73c1669..5c3aec63e 100644 --- a/scapy/layers/dot11.py +++ b/scapy/layers/dot11.py @@ -362,11 +362,12 @@ bind_layers( Dot11Auth, Dot11Elt, ) bind_layers( Dot11Elt, Dot11Elt, ) -conf.l2types.register(801, Dot11) -conf.l2types.register_num2layer(105, Dot11) -conf.l2types.register(802, PrismHeader) -conf.l2types.register(803, RadioTap) -conf.l2types.register_num2layer(127, RadioTap) +conf.l2types.register(105, Dot11) +conf.l2types.register_num2layer(801, Dot11) +conf.l2types.register(119, PrismHeader) +conf.l2types.register_num2layer(802, PrismHeader) +conf.l2types.register(127, RadioTap) +conf.l2types.register_num2layer(803, RadioTap) class WiFi_am(AnsweringMachine): diff --git a/scapy/layers/inet.py b/scapy/layers/inet.py index b3951a9fe..6a79bc5a6 100644 --- a/scapy/layers/inet.py +++ b/scapy/layers/inet.py @@ -14,6 +14,7 @@ from scapy.packet import * from scapy.volatile import * from scapy.sendrecv import sr,sr1,srp1 from scapy.plist import PacketList,SndRcvList +from scapy.automaton import Automaton,ATMT import scapy.as_resolvers @@ -34,18 +35,160 @@ class IPTools: return self.ottl()-self.ttl-1 +_ip_options_names = { 0: "end_of_list", + 1: "nop", + 2: "security", + 3: "loose_source_route", + 4: "timestamp", + 5: "extended_security", + 6: "commercial_security", + 7: "record_route", + 8: "stream_id", + 9: "strict_source_route", + 10: "experimental_measurement", + 11: "mtu_probe", + 12: "mtu_reply", + 13: "flow_control", + 14: "access_control", + 15: "encode", + 16: "imi_traffic_descriptor", + 17: "extended_IP", + 18: "traceroute", + 19: "address_extension", + 20: "router_alert", + 21: "selective_directed_broadcast_mode", + 23: "dynamic_packet_state", + 24: "upstream_multicast_packet", + 25: "quick_start", + 30: "rfc4727_experiment", + } + -class IPoptionsField(StrField): - def i2m(self, pkt, x): - return x+"\x00"*(3-((len(x)+3)%4)) - def getfield(self, pkt, s): - opsz = (pkt.ihl-5)*4 - if opsz < 0: - warning("bad ihl (%i). Assuming ihl=5"%pkt.ihl) - opsz = 0 - return s[opsz:],s[:opsz] - def randval(self): - return RandBin(RandNum(0,39)) +class _IPOption_HDR(Packet): + fields_desc = [ BitField("copy_flag",0, 1), + BitEnumField("optclass",0,2,{0:"control",2:"debug"}), + BitEnumField("option",0,5, _ip_options_names) ] + +class IPOption(Packet): + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", # Only option 0 and 1 have no length and value + length_of="value", adjust=lambda pkt,l:l+2), + StrLenField("value", "",length_from=lambda pkt:pkt.length-2) ] + + def extract_padding(self, p): + return "",p + + registered_ip_options = {} + @classmethod + def register_variant(cls): + cls.registered_ip_options[cls.option.default] = cls + @classmethod + def dispatch_hook(cls, pkt=None, *args, **kargs): + if pkt: + opt = ord(pkt[0])&0x1f + if opt in cls.registered_ip_options: + return cls.registered_ip_options[opt] + return cls + +class IPOption_EOL(IPOption): + option = 0 + fields_desc = [ _IPOption_HDR ] + + +class IPOption_NOP(IPOption): + option=1 + fields_desc = [ _IPOption_HDR ] + +class IPOption_Security(IPOption): + copy_flag = 1 + option = 2 + fields_desc = [ _IPOption_HDR, + ByteField("length", 11), + ShortField("security",0), + ShortField("compartment",0), + ShortField("handling_restrictions",0), + StrFixedLenField("transmission_control_code","xxx",3), + ] + +class IPOption_LSRR(IPOption): + name = "IP Option Loose Source and Record Route" + copy_flag = 1 + option = 3 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="routers", adjust=lambda pkt,l:l+3), + ByteField("pointer",4), # 4 is first IP + FieldListField("routers",[],IPField("","0.0.0.0"), + length_from=lambda pkt:pkt.length-3) + ] + def get_current_router(self): + return self.routers[self.pointer/4-1] + +class IPOption_RR(IPOption_LSRR): + name = "IP Option Record Route" + option = 7 + +class IPOption_SSRR(IPOption_LSRR): + name = "IP Option Strict Source and Record Route" + option = 9 + +class IPOption_Stream_Id(IPOption): + name = "IP Option Stream ID" + option = 8 + fields_desc = [ _IPOption_HDR, + ByteField("length", 4), + ShortField("security",0), ] + +class IPOption_MTU_Probe(IPOption): + name = "IP Option MTU Probe" + option = 11 + fields_desc = [ _IPOption_HDR, + ByteField("length", 4), + ShortField("mtu",0), ] + +class IPOption_MTU_Reply(IPOption_MTU_Probe): + name = "IP Option MTU Reply" + option = 12 + +class IPOption_Traceroute(IPOption): + copy_flag = 1 + option = 18 + fields_desc = [ _IPOption_HDR, + ByteField("length", 12), + ShortField("id",0), + ShortField("outbound_hops",0), + ShortField("return_hops",0), + IPField("originator_ip","0.0.0.0") ] + +class IPOption_Address_Extension(IPOption): + name = "IP Option Address Extension" + copy_flag = 1 + option = 19 + fields_desc = [ _IPOption_HDR, + ByteField("length", 10), + IPField("src_ext","0.0.0.0"), + IPField("dst_ext","0.0.0.0") ] + +class IPOption_Router_Alert(IPOption): + name = "IP Option Router Alert" + copy_flag = 1 + option = 20 + fields_desc = [ _IPOption_HDR, + ByteField("length", 4), + ShortEnumField("alert",0, {0:"router_shall_examine_packet"}), ] + + +class IPOption_SDBM(IPOption): + name = "IP Option Selective Directed Broadcast Mode" + copy_flag = 1 + option = 21 + fields_desc = [ _IPOption_HDR, + FieldLenField("length", None, fmt="B", + length_of="addresses", adjust=lambda pkt,l:l+2), + FieldListField("addresses",[],IPField("","0.0.0.0"), + length_from=lambda pkt:pkt.length-2) + ] + TCPOptions = ( @@ -181,9 +324,10 @@ class IP(Packet, IPTools): #IPField("src", "127.0.0.1"), Emph(SourceIPField("src","dst")), Emph(IPField("dst", "127.0.0.1")), - IPoptionsField("options", "") ] + PacketListField("options", [], IPOption, length_from=lambda p:p.ihl*4-20) ] def post_build(self, p, pay): ihl = self.ihl + p += "\0"*((-len(p))%4) # pad IP options if needed if ihl is None: ihl = len(p)/4 p = chr(((self.version&0xf)<<4) | ihl&0x0f)+p[1:] @@ -244,7 +388,35 @@ class IP(Packet, IPTools): s += " frag:%i" % self.frag return s - + def fragment(self, fragsize=1480): + """Fragment IP datagrams""" + fragsize = (fragsize+7)/8*8 + lst = [] + fnb = 0 + fl = self + while fl.underlayer is not None: + fnb += 1 + fl = fl.underlayer + + for p in fl: + s = str(p[fnb].payload) + nb = (len(s)+fragsize-1)/fragsize + for i in range(nb): + q = p.copy() + del(q[fnb].payload) + del(q[fnb].chksum) + del(q[fnb].len) + if i == nb-1: + q[IP].flags &= ~1 + else: + q[IP].flags |= 1 + q[IP].frag = i*fragsize/8 + r = Raw(load=s[i*fragsize:(i+1)*fragsize]) + r.overload_fields = p[IP].payload.overload_fields.copy() + q.add_payload(r) + lst.append(q) + return lst + class TCP(Packet): name = "TCP" @@ -374,10 +546,37 @@ icmptypes = { 0 : "echo-reply", 17 : "address-mask-request", 18 : "address-mask-reply" } +icmpcodes = { 3 : { 0 : "network-unreachable", + 1 : "host-unreachable", + 2 : "protocol-unreachable", + 3 : "port-unreachable", + 4 : "fragmentation-needed", + 5 : "source-route-failed", + 6 : "network-unknown", + 7 : "host-unknown", + 9 : "network-prohibited", + 10 : "host-prohibited", + 11 : "TOS-network-unreachable", + 12 : "TOS-host-unreachable", + 13 : "communication-prohibited", + 14 : "host-precedence-violation", + 15 : "precedence-cutoff", }, + 5 : { 0 : "network-redirect", + 1 : "host-redirect", + 2 : "TOS-network-redirect", + 3 : "TOS-host-redirect", }, + 11 : { 0 : "ttl-zero-during-transit", + 1 : "ttl-zero-during-reassembly", }, + 12 : { 0 : "ip-header-bad", + 1 : "required-option-missing", }, } + + + + class ICMP(Packet): name = "ICMP" fields_desc = [ ByteEnumField("type",8, icmptypes), - ByteField("code",0), + MultiEnumField("code",0, icmpcodes, depends_on=lambda pkt:pkt.type,fmt="B"), XShortField("chksum", None), ConditionalField(XShortField("id",0), lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]), ConditionalField(XShortField("seq",0), lambda pkt:pkt.type in [0,8,13,14,15,16,17,18]), @@ -1104,6 +1303,133 @@ traceroute(target, [maxttl=30,] [dport=80,] [sport=80,] [verbose=conf.verb]) -> +############################# +## Simple TCP client stack ## +############################# + +class TCP_client(Automaton): + + def parse_args(self, ip, port, *args, **kargs): + self.dst = ip + self.dport = port + self.sport = random.randrange(0,2**16) + self.l4 = IP(dst=ip)/TCP(sport=self.sport, dport=self.dport, flags=0, + seq=random.randrange(0,2**32)) + self.src = self.l4.src + self.swin=self.l4[TCP].window + self.dwin=1 + self.rcvbuf="" + bpf = "host %s and host %s and port %i and port %i" % (self.src, + self.dst, + self.sport, + self.dport) + +# bpf=None + Automaton.parse_args(self, filter=bpf, **kargs) + + + def master_filter(self, pkt): + return (IP in pkt and + pkt[IP].src == self.dst and + pkt[IP].dst == self.src and + TCP in pkt and + pkt[TCP].sport == self.dport and + pkt[TCP].dport == self.sport and + self.l4[TCP].seq >= pkt[TCP].ack and # XXX: seq/ack 2^32 wrap up + ((self.l4[TCP].ack == 0) or (self.l4[TCP].ack <= pkt[TCP].seq <= self.l4[TCP].ack+self.swin)) ) + + + @ATMT.state(initial=1) + def START(self): + pass + + @ATMT.state() + def SYN_SENT(self): + pass + + @ATMT.state() + def ESTABLISHED(self): + pass + + @ATMT.state() + def LAST_ACK(self): + pass + + @ATMT.state(final=1) + def CLOSED(self): + pass + + + @ATMT.condition(START) + def connect(self): + raise self.SYN_SENT() + @ATMT.action(connect) + def send_syn(self): + self.l4[TCP].flags = "S" + self.send(self.l4) + self.l4[TCP].seq += 1 + + + @ATMT.receive_condition(SYN_SENT) + def synack_received(self, pkt): + if pkt[TCP].flags & 0x3f == 0x12: + raise self.ESTABLISHED().action_parameters(pkt) + @ATMT.action(synack_received) + def send_ack_of_synack(self, pkt): + self.l4[TCP].ack = pkt[TCP].seq+1 + self.l4[TCP].flags = "A" + self.send(self.l4) + + @ATMT.receive_condition(ESTABLISHED) + def incoming_data_received(self, pkt): + if not isinstance(pkt[TCP].payload, NoPayload) and not isinstance(pkt[TCP].payload, Padding): + raise self.ESTABLISHED().action_parameters(pkt) + @ATMT.action(incoming_data_received) + def receive_data(self,pkt): + data = str(pkt[TCP].payload) + if data and self.l4[TCP].ack == pkt[TCP].seq: + self.l4[TCP].ack += len(data) + self.l4[TCP].flags = "A" + self.send(self.l4) + self.rcvbuf += data + if pkt[TCP].flags & 8 != 0: #PUSH + self.oi.tcp.send(self.rcvbuf) + self.rcvbuf = "" + + @ATMT.ioevent(ESTABLISHED,name="tcp", as_supersocket="tcplink") + def outgoing_data_received(self, fd): + raise self.ESTABLISHED().action_parameters(fd.recv()) + @ATMT.action(outgoing_data_received) + def send_data(self, d): + self.l4[TCP].flags = "PA" + self.send(self.l4/d) + self.l4[TCP].seq += len(d) + + + @ATMT.receive_condition(ESTABLISHED) + def reset_received(self, pkt): + if pkt[TCP].flags & 4 != 0: + raise self.CLOSED() + + @ATMT.receive_condition(ESTABLISHED) + def fin_received(self, pkt): + if pkt[TCP].flags & 0x1 == 1: + raise self.LAST_ACK().action_parameters(pkt) + @ATMT.action(fin_received) + def send_finack(self, pkt): + self.l4[TCP].flags = "FA" + self.l4[TCP].ack = pkt[TCP].seq+1 + self.send(self.l4) + self.l4[TCP].seq += 1 + + @ATMT.receive_condition(LAST_ACK) + def ack_of_fin_received(self, pkt): + if pkt[TCP].flags & 0x3f == 0x10: + raise self.CLOSED() + + + + ##################### ## Reporting stuff ## ##################### diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py index 87e4f36a7..5466e9857 100644 --- a/scapy/layers/inet6.py +++ b/scapy/layers/inet6.py @@ -1227,7 +1227,6 @@ class ICMPv6EchoRequest(_ICMPv6): class ICMPv6EchoReply(ICMPv6EchoRequest): name = "ICMPv6 Echo Reply" - __metaclass__ = NewDefaultValues type = 129 def answers(self, other): # We could match data content between request and reply. @@ -1262,7 +1261,6 @@ class _ICMPv6ML(_ICMPv6): # Option in a Destination Option Header. class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Query" - __metaclass__ = NewDefaultValues type = 130 mrd = 10000 mladdr = "::" # 10s for mrd @@ -1278,7 +1276,6 @@ class ICMPv6MLQuery(_ICMPv6ML): # RFC 2710 # Option in a Destination Option Header. class ICMPv6MLReport(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Report" - __metaclass__ = NewDefaultValues type = 131 overload_fields = {IPv6: {"hlim": 1}} # implementer le hashret et le answers @@ -1291,7 +1288,6 @@ class ICMPv6MLReport(_ICMPv6ML): # RFC 2710 # Option in a Destination Option Header. class ICMPv6MLDone(_ICMPv6ML): # RFC 2710 name = "MLD - Multicast Listener Done" - __metaclass__ = NewDefaultValues type = 132 overload_fields = {IPv6: { "dst": "ff02::2", "hlim": 1}} @@ -1430,7 +1426,6 @@ class ICMPv6NDOptSrcLLAddr(_ICMPv6NDGuessPayload, Packet): class ICMPv6NDOptDstLLAddr(ICMPv6NDOptSrcLLAddr): name = "ICMPv6 Neighbor Discovery Option - Destination Link-Layer Address" - __metaclass__ = NewDefaultValues type = 2 class ICMPv6NDOptPrefixInfo(_ICMPv6NDGuessPayload, Packet): @@ -1695,7 +1690,6 @@ class ICMPv6ND_NS(_ICMPv6NDGuessPayload, _ICMPv6, Packet): class ICMPv6ND_NA(ICMPv6ND_NS): name = "ICMPv6 Neighbor Discovery - Neighbor Advertisement" - __metaclass__ = NewDefaultValues type = 136 R = 1 O = 1 @@ -1729,7 +1723,6 @@ class ICMPv6NDOptSrcAddrList(_ICMPv6NDGuessPayload, Packet): class ICMPv6NDOptTgtAddrList(ICMPv6NDOptSrcAddrList): name = "ICMPv6 Inverse Neighbor Discovery Option - Target Address List" - __metaclass__ = NewDefaultValues type = 10 @@ -2009,19 +2002,16 @@ class ICMPv6NIQueryNOOP(_ICMPv6NIHashret, _ICMPv6): class ICMPv6NIQueryName(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv6 Name Query" - __metaclass__ = NewDefaultValues qtype = 2 # We ask for the IPv6 address of the peer class ICMPv6NIQueryIPv6(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv6 Address Query" - __metaclass__ = NewDefaultValues qtype = 3 flags = 0x3E class ICMPv6NIQueryIPv4(ICMPv6NIQueryNOOP): name = "ICMPv6 Node Information Query - IPv4 Address Query" - __metaclass__ = NewDefaultValues qtype = 4 _nireply_code = { 0: "Successful Reply", @@ -2176,27 +2166,22 @@ class ICMPv6NIReplyNOOP(_ICMPv6NIAnswers, _ICMPv6NIHashret, _ICMPv6): class ICMPv6NIReplyName(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Node Names" - __metaclass__ = NewDefaultValues qtype = 2 class ICMPv6NIReplyIPv6(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - IPv6 addresses" - __metaclass__ = NewDefaultValues qtype = 3 class ICMPv6NIReplyIPv4(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - IPv4 addresses" - __metaclass__ = NewDefaultValues qtype = 4 class ICMPv6NIReplyRefuse(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Responder refuses to supply answer" - __metaclass__ = NewDefaultValues code = 1 class ICMPv6NIReplyUnknown(ICMPv6NIReplyNOOP): name = "ICMPv6 Node Information Reply - Qtype unknown to the responder" - __metaclass__ = NewDefaultValues code = 2 @@ -2674,7 +2659,6 @@ class MIP6MH_HoTI(_MobilityHeader): class MIP6MH_CoTI(MIP6MH_HoTI): name = "IPv6 Mobility Header - Care-of Test Init" - __metaclass__ = NewDefaultValues mhtype = 2 def hashret(self): return self.cookie @@ -2703,7 +2687,6 @@ class MIP6MH_HoT(_MobilityHeader): class MIP6MH_CoT(MIP6MH_HoT): name = "IPv6 Mobility Header - Care-of Test" - __metaclass__ = NewDefaultValues mhtype = 4 def hashret(self): return self.cookie diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py index f69784408..97ed5a1a3 100644 --- a/scapy/layers/l2.py +++ b/scapy/layers/l2.py @@ -4,6 +4,7 @@ ## This program is published under a GPLv2 license import os,struct,time +from scapy.base_classes import Net from scapy.config import conf from scapy.packet import * from scapy.ansmachine import * @@ -43,6 +44,8 @@ conf.netcache.new_cache("arp_cache", 120) # cache entries expire after 120s @conf.commands.register def getmacbyip(ip, chainCC=0): """Return MAC address corresponding to a given IP address""" + if isinstance(ip,Net): + ip = iter(ip).next() tmp = map(ord, inet_aton(ip)) if (tmp[0] & 0xf0) == 0xe0: # mcast @ return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1]&0x7f,tmp[2],tmp[3]) @@ -124,20 +127,8 @@ class ARPSourceMACField(MACField): ### Layers -class Ether_or_Dot3_metaclass(Packet_metaclass): - def __call__(self, _pkt=None, *args, **kargs): - cls = self - if _pkt and len(_pkt) >= 14: - if struct.unpack("!H", _pkt[12:14])[0] <= 1500: - cls = Dot3 - else: - cls = Ether - i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) - i.__init__(_pkt=_pkt, *args, **kargs) - return i class Ether(Packet): - __metaclass__ = Ether_or_Dot3_metaclass name = "Ethernet" fields_desc = [ DestMACField("dst"), SourceMACField("src"), @@ -151,9 +142,15 @@ class Ether(Packet): return 0 def mysummary(self): return self.sprintf("%src% > %dst% (%type%)") + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 14: + if struct.unpack("!H", _pkt[12:14])[0] <= 1500: + return Dot3 + return cls + class Dot3(Packet): - __metaclass__ = Ether_or_Dot3_metaclass name = "802.3" fields_desc = [ DestMACField("dst"), MACField("src", ETHER_ANY), @@ -167,6 +164,12 @@ class Dot3(Packet): return 0 def mysummary(self): return "802.3 %s > %s" % (self.src, self.dst) + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and len(_pkt) >= 14: + if struct.unpack("!H", _pkt[12:14])[0] > 1500: + return Ether + return cls class LLC(Packet): @@ -337,11 +340,11 @@ class ARP(Packet): return "",s def mysummary(self): if self.op == self.is_at: - return "ARP is at %s says %s" % (self.hwsrc, self.psrc) + return self.sprintf("ARP is at %hwsrc% says %psrc%") elif self.op == self.who_has: - return "ARP who has %s says %s" % (self.pdst, self.psrc) + return self.sprintf("ARP who has %pdst% says %psrc%") else: - return "ARP %ARP.op% %ARP.psrc% > %ARP.pdst%" + return self.sprintf("ARP %op% %psrc% > %pdst%") conf.neighbor.register_l3(Ether, ARP, lambda l2,l3: getmacbyip(l3.pdst)) @@ -429,7 +432,7 @@ class ARPingResult(SndRcvList): def show(self): for s,r in self.res: - print r.sprintf("%Ether.src% %ARP.psrc%") + print r.sprintf("%19s,Ether.src% %ARP.psrc%") diff --git a/scapy/layers/llmnr.py b/scapy/layers/llmnr.py index 62be5226e..561729c87 100644 --- a/scapy/layers/llmnr.py +++ b/scapy/layers/llmnr.py @@ -36,7 +36,6 @@ class LLMNRQuery(Packet): class LLMNRResponse(LLMNRQuery): name = "Link Local Multicast Node Resolution - Response" - __metaclass__ = NewDefaultValues qr = 1 fields_desc = [] diff --git a/scapy/layers/ppp.py b/scapy/layers/ppp.py index 06062a9d6..67fc3f2fb 100644 --- a/scapy/layers/ppp.py +++ b/scapy/layers/ppp.py @@ -188,20 +188,14 @@ class HDLC(Packet): fields_desc = [ XByteField("address",0xff), XByteField("control",0x03) ] -class PPP_metaclass(Packet_metaclass): - def __call__(self, _pkt=None, *args, **kargs): - cls = self - if _pkt and _pkt[0] == '\xff': - cls = HDLC - i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) - i.__init__(_pkt=_pkt, *args, **kargs) - return i - - class PPP(Packet): - __metaclass__ = PPP_metaclass name = "PPP Link Layer" fields_desc = [ ShortEnumField("proto", 0x0021, _PPP_proto) ] + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt and _pkt[0] == '\xff': + cls = HDLC + return cls _PPP_conftypes = { 1:"Configure-Request", 2:"Configure-Ack", @@ -218,19 +212,6 @@ _PPP_conftypes = { 1:"Configure-Request", 15:"Reset-Ack", } -class PPP_Option_metaclass(Packet_metaclass): - _known_options={} - def __call__(self, _pkt=None, *args, **kargs): - cls = self - if _pkt: - t = ord(_pkt[0]) - cls = self._known_options.get(t,self) - i = cls.__new__(cls, cls.__name__, cls.__bases__, cls.__dict__) - i.__init__(_pkt=_pkt, *args, **kargs) - return i - def _register(self, cls): - self._known_options[cls.fields_desc[0].default] = cls - ### PPP IPCP stuff (RFC 1332) @@ -245,13 +226,7 @@ _PPP_ipcpopttypes = { 1:"IP-Addresses (Deprecated)", 132:"Secondary-NBNS-Address"} - -class PPP_IPCP_Option_metaclass(PPP_Option_metaclass): - _known_options={} - - class PPP_IPCP_Option(Packet): - __metaclass__=PPP_IPCP_Option_metaclass name = "PPP IPCP Option" fields_desc = [ ByteEnumField("type" , None , _PPP_ipcpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -259,55 +234,54 @@ class PPP_IPCP_Option(Packet): def extract_padding(self, pay): return "",pay -class PPP_IPCP_Specific_Option_metaclass(PPP_IPCP_Option_metaclass): - def __new__(cls, name, bases, dct): - newcls = super(PPP_IPCP_Specific_Option_metaclass, cls).__new__(cls, name, bases, dct) - PPP_IPCP_Option._register(newcls) - + registered_options = {} + @classmethod + def register_variant(cls): + cls.registered_options[cls.type.default] = cls + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = ord(_pkt[0]) + return cls.registered_options.get(o, cls) + return cls + class PPP_IPCP_Option_IPAddress(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: IP Address" fields_desc = [ ByteEnumField("type" , 3 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_DNS1(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: DNS1 Address" fields_desc = [ ByteEnumField("type" , 129 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_DNS2(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: DNS2 Address" fields_desc = [ ByteEnumField("type" , 131 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_NBNS1(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: NBNS1 Address" fields_desc = [ ByteEnumField("type" , 130 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] class PPP_IPCP_Option_NBNS2(PPP_IPCP_Option): - __metaclass__=PPP_IPCP_Specific_Option_metaclass name = "PPP IPCP Option: NBNS2 Address" fields_desc = [ ByteEnumField("type" , 132 , _PPP_ipcpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), IPField("data","0.0.0.0"), ConditionalField(StrLenField("garbage","", length_from=lambda pkt:pkt.len-6), lambda p:p.len!=6) ] - - class PPP_IPCP(Packet): fields_desc = [ ByteEnumField("code" , 1, _PPP_conftypes), XByteField("id", 0 ), @@ -320,13 +294,7 @@ class PPP_IPCP(Packet): _PPP_ecpopttypes = { 0:"OUI", 1:"DESE", } -class PPP_ECP_Option_metaclass(PPP_Option_metaclass): - _known_options={} - - - class PPP_ECP_Option(Packet): - __metaclass__=PPP_ECP_Option_metaclass name = "PPP ECP Option" fields_desc = [ ByteEnumField("type" , None , _PPP_ecpopttypes), FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), @@ -334,16 +302,20 @@ class PPP_ECP_Option(Packet): def extract_padding(self, pay): return "",pay -class PPP_ECP_Specific_Option_metaclass(PPP_ECP_Option_metaclass): - def __new__(cls, name, bases, dct): - newcls = super(PPP_ECP_Specific_Option_metaclass, cls).__new__(cls, name, bases, dct) - PPP_ECP_Option._register(newcls) - + registered_options = {} + @classmethod + def register_variant(cls): + cls.registered_options[cls.type.default] = cls + @classmethod + def dispatch_hook(cls, _pkt=None, *args, **kargs): + if _pkt: + o = ord(_pkt[0]) + return cls.registered_options.get(o, cls) + return cls class PPP_ECP_Option_OUI(PPP_ECP_Option): - __metaclass__=PPP_ECP_Specific_Option_metaclass fields_desc = [ ByteEnumField("type" , 0 , _PPP_ecpopttypes), - FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+2), + FieldLenField("len", None, length_of="data", fmt="B", adjust=lambda p,x:x+6), StrFixedLenField("oui","",3), ByteField("subtype",0), StrLenField("data", "", length_from=lambda p:p.len-6) ] @@ -356,12 +328,14 @@ class PPP_ECP(Packet): FieldLenField("len" , None, fmt="H", length_of="options", adjust=lambda p,x:x+4 ), PacketListField("options", [], PPP_ECP_Option, length_from=lambda p:p.len-4,) ] -bind_layers( Ether, PPPoED, type=34915) -bind_layers( Ether, PPPoE, type=34916) -bind_layers( CookedLinux, PPPoED, proto=34915) -bind_layers( CookedLinux, PPPoE, proto=34916) +bind_layers( Ether, PPPoED, type=0x8863) +bind_layers( Ether, PPPoE, type=0x8864) +bind_layers( CookedLinux, PPPoED, proto=0x8863) +bind_layers( CookedLinux, PPPoE, proto=0x8864) bind_layers( PPPoE, PPP, code=0) bind_layers( HDLC, PPP, ) bind_layers( PPP, IP, proto=33) bind_layers( PPP, PPP_IPCP, proto=0x8021) bind_layers( PPP, PPP_ECP, proto=0x8053) +bind_layers( Ether, PPP_IPCP, type=0x8021) +bind_layers( Ether, PPP_ECP, type=0x8053) diff --git a/scapy/packet.py b/scapy/packet.py index 8c31aa12b..5ee093f35 100644 --- a/scapy/packet.py +++ b/scapy/packet.py @@ -689,11 +689,14 @@ Creates an EPS file describing a packet. If filename is not provided a temporary return self.payload.haslayer(cls) def getlayer(self, cls, nb=1, _track=None): """Return the nb^th layer that is an instance of cls.""" + if type(cls) is int: + nb = cls+1 + cls = None if type(cls) is str and "." in cls: ccls,fld = cls.split(".",1) else: ccls,fld = cls,None - if self.__class__ == cls or self.__class__.name == ccls: + if cls is None or self.__class__ == cls or self.__class__.name == ccls: if nb == 1: if fld is None: return self @@ -716,6 +719,12 @@ Creates an EPS file describing a packet. If filename is not provided a temporary nb = track[0] return self.payload.getlayer(cls,nb,_track=_track) + def firstlayer(self): + q = self + while q.underlayer is not None: + q = q.underlayer + return q + def __getitem__(self, cls): if type(cls) is slice: lname = cls.start @@ -748,6 +757,9 @@ Creates an EPS file describing a packet. If filename is not provided a temporary def route(self): return (None,None,None) + + def fragment(self, *args, **kargs): + return self.payload.fragment(*args, **kargs) def display(self,*args,**kargs): # Deprecated. Use show() @@ -959,12 +971,12 @@ A side effect is that, to obtain "{" and "}" characters, you must use c += "/"+pc return c -class NoPayload(Packet,object): +class NoPayload(Packet): def __new__(cls, *args, **kargs): singl = cls.__dict__.get("__singl__") if singl is None: - cls.__singl__ = singl = object.__new__(cls) - Packet.__init__(singl, *args, **kargs) + cls.__singl__ = singl = Packet.__new__(cls) + Packet.__init__(singl) return singl def __init__(self, *args, **kargs): pass @@ -1025,6 +1037,8 @@ class NoPayload(Packet,object): if _track is not None: _track.append(nb) return None + def fragment(self, *args, **kargs): + raise Scapy_Exception("cannot fragment this packet") def show(self, indent=3, lvl="", label_lvl=""): pass def sprintf(self, fmt, relax): diff --git a/scapy/plist.py b/scapy/plist.py index bc5c24f56..afed77fc3 100644 --- a/scapy/plist.py +++ b/scapy/plist.py @@ -7,6 +7,7 @@ import os from config import conf from base_classes import BasePacket,BasePacketList from packet import Padding +from collections import defaultdict from utils import do_graph,hexdump,make_table,make_lined_table,make_tex_table,get_temp_file @@ -406,6 +407,67 @@ lfilter: truth function to apply to each packet to decide whether it will be dis if multi: remain = filter(lambda x:not hasattr(x,"_answered"), remain) return SndRcvList(sr),PacketList(remain) + + def sessions(self, session_extractor=None): + if session_extractor is None: + def session_extractor(p): + if 'Ether' in p: + if 'IP' in p: + if 'TCP' in p: + sess = p.sprintf("TCP %IP.src%:%r,TCP.sport% > %IP.dst%:%r,TCP.dport%") + elif 'UDP' in p: + sess = p.sprintf("UDP %IP.src%:%r,UDP.sport% > %IP.dst%:%r,UDP.dport%") + elif 'ICMP' in p: + sess = p.sprintf("ICMP %IP.src% > %IP.dst% type=%r,ICMP.type% code=%r,ICMP.code% id=%ICMP.id%") + else: + sess = p.sprintf("IP %IP.src% > %IP.dst% proto=%IP.proto%") + elif 'ARP' in p: + sess = p.sprintf("ARP %ARP.psrc% > %ARP.pdst%") + else: + sess = p.sprintf("Ethernet type=%04xr,Ether.type%") + return sess + sessions = defaultdict(self.__class__) + for p in self.res: + sess = session_extractor(self._elt2pkt(p)) + sessions[sess].append(p) + return dict(sessions) + + def replace(self, *args, **kargs): + """ + lst.replace(,[,]) + lst.replace( (fld,[ov],nv),(fld,[ov,]nv),...) + if ov is None, all values are replaced + ex: + lst.replace( IP.src, "192.168.1.1", "10.0.0.1" ) + lst.replace( IP.ttl, 64 ) + lst.replace( (IP.ttl, 64), (TCP.sport, 666, 777), ) + """ + delete_checksums = kargs.get("delete_checksums",False) + x=PacketList(name="Replaced %s" % self.listname) + if type(args[0]) is not tuple: + args = (args,) + for p in self.res: + p = self._elt2pkt(p) + copied = False + for scheme in args: + fld = scheme[0] + old = scheme[1] # not used if len(scheme) == 2 + new = scheme[-1] + for o in fld.owners: + if o in p: + if len(scheme) == 2 or p[o].getfieldval(fld.name) == old: + if not copied: + p = p.copy() + if delete_checksums: + p.delete_checksums() + copied = True + setattr(p[o], fld.name, new) + x.append(p) + return x + + + + diff --git a/scapy/sendrecv.py b/scapy/sendrecv.py index 9cf0a88c5..b0432686d 100644 --- a/scapy/sendrecv.py +++ b/scapy/sendrecv.py @@ -12,6 +12,7 @@ from packet import Gen from utils import warning,get_temp_file import plist from error import log_runtime,log_interactive +from base_classes import SetGen ################# ## Debug class ## @@ -30,7 +31,7 @@ class debug: -def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, multi=0): +def sndrcv(pks, pkt, timeout = None, inter = 0, verbose=None, chainCC=0, retry=0, multi=0): if not isinstance(pkt, Gen): pkt = SetGen(pkt) @@ -199,10 +200,12 @@ def sndrcv(pks, pkt, timeout = 2, inter = 0, verbose=None, chainCC=0, retry=0, m if verbose: print "\nReceived %i packets, got %i answers, remaining %i packets" % (nbrecv+len(ans), len(ans), notans) - return plist.SndRcvList(ans),plist.PacketList(remain,"Unanswered"),debug.recv + return plist.SndRcvList(ans),plist.PacketList(remain,"Unanswered") def __gen_send(s, x, inter=0, loop=0, count=None, verbose=None, *args, **kargs): + if type(x) is str: + x = Raw(load=x) if not isinstance(x, Gen): x = SetGen(x) if verbose is None: @@ -295,7 +298,7 @@ iface: listen answers only on the given interface""" if not kargs.has_key("timeout"): kargs["timeout"] = -1 s = conf.L3socket(filter=filter, iface=iface, nofilter=nofilter) - a,b,c=sndrcv(s,x,*args,**kargs) + a,b=sndrcv(s,x,*args,**kargs) s.close() return a,b @@ -313,7 +316,7 @@ iface: listen answers only on the given interface""" if not kargs.has_key("timeout"): kargs["timeout"] = -1 s=conf.L3socket(filter=filter, nofilter=nofilter, iface=iface) - a,b,c=sndrcv(s,x,*args,**kargs) + a,b=sndrcv(s,x,*args,**kargs) s.close() if len(a) > 0: return a[0][1] @@ -336,7 +339,7 @@ iface: work only on the given interface""" if iface is None and iface_hint is not None: iface = conf.route.route(iface_hint)[0] s = conf.L2socket(iface=iface, filter=filter, nofilter=nofilter, type=type) - a,b,c=sndrcv(s ,x,*args,**kargs) + a,b=sndrcv(s ,x,*args,**kargs) s.close() return a,b diff --git a/scapy/supersocket.py b/scapy/supersocket.py index e3e49c061..8afca6cf8 100644 --- a/scapy/supersocket.py +++ b/scapy/supersocket.py @@ -27,7 +27,7 @@ class SuperSocket: sx = str(x) x.sent_time = time.time() return self.outs.send(sx) - def recv(self, x): + def recv(self, x=MTU): return conf.raw_layer(self.ins.recv(x)) def fileno(self): return self.ins.fileno() @@ -40,10 +40,14 @@ class SuperSocket: self.outs.close() if self.ins and self.ins.fileno() != -1: self.ins.close() - def bind_in(self, addr): - self.ins.bind(addr) - def bind_out(self, addr): - self.outs.bind(addr) + def sr(self, *args, **kargs): + return sndrcv(self, *args, **kargs) + def sr1(self, *args, **kargs): + a,b = sndrcv(self, *args, **kargs) + if len(a) > 0: + return a[0][1] + else: + return None class L3RawSocket(SuperSocket): desc = "Layer 3 using Raw sockets (PF_INET/SOCK_RAW)" @@ -51,7 +55,7 @@ class L3RawSocket(SuperSocket): self.outs = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) self.outs.setsockopt(socket.SOL_IP, socket.IP_HDRINCL, 1) self.ins = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(type)) - def recv(self, x): + def recv(self, x=MTU): return Ether(self.ins.recv(x)).payload def send(self, x): try: diff --git a/scapy/utils.py b/scapy/utils.py index b14457c88..56eb2aa56 100644 --- a/scapy/utils.py +++ b/scapy/utils.py @@ -485,7 +485,7 @@ class RawPcapReader: return pkt - def read_packet(self): + def read_packet(self, size=MTU): """return a single packet read from the file returns None when no more packets are available @@ -494,7 +494,7 @@ class RawPcapReader: if len(hdr) < 16: return None sec,usec,caplen,wirelen = struct.unpack(self.endian+"IIII", hdr) - s = self.f.read(caplen) + s = self.f.read(caplen)[:MTU] return s,(sec,usec,wirelen) # caplen = len(s) @@ -520,10 +520,10 @@ class RawPcapReader: res.append(p) return res - def recv(self, size): + def recv(self, size=MTU): """ Emulate a socket """ - return self.read_packet()[0] + return self.read_packet(size)[0] def fileno(self): return self.f.fileno() @@ -541,8 +541,8 @@ class PcapReader(RawPcapReader): except KeyError: warning("PcapReader: unknown LL type [%i]/[%#x]. Using Raw packets" % (self.linktype,self.linktype)) self.LLcls = conf.raw_layer - def read_packet(self): - rp = RawPcapReader.read_packet(self) + def read_packet(self, size=MTU): + rp = RawPcapReader.read_packet(self,size) if rp is None: return None s,(sec,usec,wirelen) = rp @@ -561,8 +561,8 @@ class PcapReader(RawPcapReader): res = RawPcapReader.read_all(self, count) import plist return plist.PacketList(res,name = os.path.basename(self.filename)) - def recv(self, size): - return self.read_packet() + def recv(self, size=MTU): + return self.read_packet(size) @@ -667,7 +667,7 @@ class PcapWriter(RawPcapWriter): RawPcapWriter._write_packet(self, s, sec, usec, caplen, caplen) -re_extract_hexcap = re.compile("^(0x[0-9a-fA-F]{2,}[ :\t]|(0x)?[0-9a-fA-F]{2,}:|(0x)?[0-9a-fA-F]{3,}[: \t]|) *(([0-9a-fA-F]{2} {,2}){,16})") +re_extract_hexcap = re.compile("^((0x)?[0-9a-fA-F]{2,}[ :\t]{,3}|) *(([0-9a-fA-F]{2} {,2}){,16})") def import_hexcap(): p = "" @@ -675,7 +675,7 @@ def import_hexcap(): while 1: l = raw_input().strip() try: - p += re_extract_hexcap.match(l).groups()[3] + p += re_extract_hexcap.match(l).groups()[2] except: warning("Parsing error during hexcap") continue @@ -683,10 +683,7 @@ def import_hexcap(): pass p = p.replace(" ","") - p2="" - for i in range(len(p)/2): - p2 += chr(int(p[2*i:2*i+2],16)) - return p2 + return p.decode("hex") diff --git a/scapy/volatile.py b/scapy/volatile.py index 17a66e35f..d1f72c7bb 100644 --- a/scapy/volatile.py +++ b/scapy/volatile.py @@ -3,7 +3,7 @@ ## Copyright (C) Philippe Biondi ## This program is published under a GPLv2 license -import random,time +import random,time,math from base_classes import Net from utils import corrupt_bits,corrupt_bytes @@ -12,7 +12,7 @@ from utils import corrupt_bits,corrupt_bytes #################### -class RandomSequence: +class RandomEnumeration: """iterate through a sequence in random order. When all the values have been drawn, if forever=1, the drawing is done again. If renewkeys=0, the draw will be in the same order, guaranteeing that the same @@ -75,13 +75,13 @@ class RandField(VolatileValue): pass class RandNum(RandField): + """Instances evaluate to random integers in selected range""" min = 0 max = 0 def __init__(self, min, max): self.min = min self.max = max def _fix(self): - # XXX: replace with sth that guarantee unicity return random.randrange(self.min, self.max+1) class RandNumGamma(RandField): @@ -105,35 +105,76 @@ class RandNumExpo(RandField): def _fix(self): return self.base+int(round(random.expovariate(self.lambd))) -class RandSeq(RandNum): +class RandEnum(RandNum): + """Instances evaluate to integer sampling without replacement from the given interval""" def __init__(self, min, max): - self.seq = RandomSequence(min,max) + self.seq = RandomEnumeration(min,max) def _fix(self): return self.seq.next() -class RandByte(RandSeq): +class RandByte(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**8-1) + RandNum.__init__(self, 0, 2L**8-1) -class RandShort(RandSeq): +class RandSByte(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**16-1) + RandNum.__init__(self, -2L**7, 2L**7-1) -class RandInt(RandSeq): +class RandShort(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**32-1) + RandNum.__init__(self, 0, 2L**16-1) -class RandSInt(RandSeq): +class RandSShort(RandNum): def __init__(self): - RandSeq.__init__(self, -2L**31, 2L**31-1) + RandNum.__init__(self, -2L**15, 2L**15-1) -class RandLong(RandSeq): +class RandInt(RandNum): def __init__(self): - RandSeq.__init__(self, 0, 2L**64-1) + RandNum.__init__(self, 0, 2L**32-1) -class RandSLong(RandSeq): +class RandSInt(RandNum): def __init__(self): - RandSeq.__init__(self, -2L**63, 2L**63-1) + RandNum.__init__(self, -2L**31, 2L**31-1) + +class RandLong(RandNum): + def __init__(self): + RandNum.__init__(self, 0, 2L**64-1) + +class RandSLong(RandNum): + def __init__(self): + RandNum.__init__(self, -2L**63, 2L**63-1) + +class RandEnumByte(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2L**8-1) + +class RandEnumSByte(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2L**7, 2L**7-1) + +class RandEnumShort(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2L**16-1) + +class RandEnumSShort(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2L**15, 2L**15-1) + +class RandEnumInt(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2L**32-1) + +class RandEnumSInt(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2L**31, 2L**31-1) + +class RandEnumLong(RandEnum): + def __init__(self): + RandEnum.__init__(self, 0, 2L**64-1) + +class RandEnumSLong(RandEnum): + def __init__(self): + RandEnum.__init__(self, -2L**63, 2L**63-1) class RandChoice(RandField): def __init__(self, *args): @@ -144,9 +185,11 @@ class RandChoice(RandField): return random.choice(self._choice) class RandString(RandField): - def __init__(self, size, chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"): - self.chars = chars + def __init__(self, size=None, chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"): + if size is None: + size = RandNumExpo(0.01) self.size = size + self.chars = chars def _fix(self): s = "" for i in range(self.size): @@ -154,7 +197,7 @@ class RandString(RandField): return s class RandBin(RandString): - def __init__(self, size): + def __init__(self, size=None): RandString.__init__(self, size, "".join(map(chr,range(256)))) @@ -378,11 +421,139 @@ class RandRegExp(RandField): return RandRegExp.stack_fix(stack[1:], index) def __repr__(self): return "<%s [%r]>" % (self.__class__.__name__, self._regexp) + +class RandSingularity(RandChoice): + pass - +class RandSingNum(RandSingularity): + @staticmethod + def make_power_of_two(end): + sign = 1 + if end == 0: + end = 1 + if end < 0: + end = -end + sign = -1 + end_n = int(math.log(end)/math.log(2))+1 + return set([sign*2**i for i in range(end_n)]) + def __init__(self, mn, mx): + sing = set([0, mn, mx, int((mn+mx)/2)]) + sing |= self.make_power_of_two(mn) + sing |= self.make_power_of_two(mx) + for i in sing.copy(): + sing.add(i+1) + sing.add(i-1) + for i in sing.copy(): + if not mn <= i <= mx: + sing.remove(i) + self._choice = list(sing) +class RandSingByte(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**8-1) + +class RandSingSByte(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**7, 2L**7-1) + +class RandSingShort(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**16-1) + +class RandSingSShort(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**15, 2L**15-1) + +class RandSingInt(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**32-1) + +class RandSingSInt(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**31, 2L**31-1) + +class RandSingLong(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, 0, 2L**64-1) + +class RandSingSLong(RandSingNum): + def __init__(self): + RandSingNum.__init__(self, -2L**63, 2L**63-1) + +class RandSingString(RandSingularity): + def __init__(self): + self._choice = [ "", + "%x", + "%%", + "%s", + "%i", + "%n", + "%x%x%x%x%x%x%x%x%x", + "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + "%", + "%%%", + "A"*4096, + "\x00"*4096, + "\xff"*4096, + "\x7f"*4096, + "\x80"*4096, + " "*4096, + "\\"*4096, + "("*4096, + "../"*1024, + "/"*1024, + "${HOME}"*512, + " or 1=1 --", + "' or 1=1 --", + '" or 1=1 --', + " or 1=1; #", + "' or 1=1; #", + '" or 1=1; #', + ";reboot;", + "$(reboot)", + "`reboot`", + "index.php%00", + "\x00", + "%00", + "\\", + "../../../../../../../../../../../../../../../../../etc/passwd", + "%2e%2e%2f" * 20 + "etc/passwd", + "%252e%252e%252f" * 20 + "boot.ini", + "..%c0%af" * 20 + "etc/passwd", + "..%c0%af" * 20 + "boot.ini", + "//etc/passwd", + r"..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\..\boot.ini", + "AUX:", + "CLOCK$", + "COM:", + "CON:", + "LPT:", + "LST:", + "NUL:", + "CON:", + r"C:\CON\CON", + r"C:\boot.ini", + r"\\myserver\share", + "foo.exe:", + "foo.exe\\", ] + + +class RandPool(RandField): + def __init__(self, *args): + """Each parameter is a volatile object or a couple (volatile object, weight)""" + pool = [] + for p in args: + w = 1 + if type(p) is tuple: + p,w = p + pool += [p]*w + self._pool = pool + def _fix(self): + r = random.choice(self._pool) + return r._fix() + # Automatic timestamp class AutoTime(VolatileValue): diff --git a/setup.py b/setup.py index 45282c3e6..4f8a9b8cb 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ import os EZIP_HEADER="""#! /bin/sh -PYTHONPATH=$0/%s exec python -m scapy +PYTHONPATH=$0/%s exec python -m scapy.__init__ """ def make_ezipfile(base_name, base_dir, verbose=0, dry_run=0): diff --git a/test/regression.uts b/test/regression.uts index 49149c991..f5b5f732c 100644 --- a/test/regression.uts +++ b/test/regression.uts @@ -188,11 +188,10 @@ assert( _ == "FOO\x01\x02\x03\x04" ) ############ ############ -+ Tests on NewDefaultValues metaclass ++ Tests on default value changes mechanism = Creation of an IPv3 class from IP class with different default values class IPv3(IP): - __metaclass__ = NewDefaultValues version = 3 ttl = 32 @@ -896,8 +895,238 @@ class ATMT6(Automaton): a=ATMT6() a.run() assert( _ == 'Mercury' ) - - + +a.restart() +a.run() +assert( _ == 'Mercury' ) + += Automaton test io event +~ automaton + +class ATMT7(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + self.res = "S" + @ATMT.ioevent(BEGIN, name="tst") + def tr1(self, fd): + self.res += fd.recv() + raise self.NEXT_STATE() + @ATMT.state() + def NEXT_STATE(self): + self.oi.tst.send("ur") + @ATMT.ioevent(NEXT_STATE, name="tst") + def tr2(self, fd): + self.res += fd.recv() + raise self.END() + @ATMT.state(final=1) + def END(self): + self.res += "n" + return self.res + +a=ATMT7() +a.run(wait=False) +a.io.tst.send("at") +a.io.tst.recv() +a.io.tst.send(_) +a.run() +assert( _ == "Saturn" ) + +a.restart() +a.run(wait=False) +a.io.tst.send("at") +a.io.tst.recv() +a.io.tst.send(_) +a.run() +assert( _ == "Saturn" ) + += Automaton test io event from external fd +~ automaton +class ATMT8(Automaton): + @ATMT.state(initial=1) + def BEGIN(self): + self.res = "U" + @ATMT.ioevent(BEGIN, name="extfd") + def tr1(self, fd): + self.res += fd.read(2) + raise self.NEXT_STATE() + @ATMT.state() + def NEXT_STATE(self): + pass + @ATMT.ioevent(NEXT_STATE, name="extfd") + def tr2(self, fd): + self.res += fd.read(2) + raise self.END() + @ATMT.state(final=1) + def END(self): + self.res += "s" + return self.res + +r,w = os.pipe() + +a=ATMT8(external_fd={"extfd":r}) +a.run(wait=False) +os.write(w,"ra") +os.write(w,"nu") +a.run() +assert( _ == "Uranus" ) + +a.restart() +a.run(wait=False) +os.write(w,"ra") +os.write(w,"nu") +a.run() +assert( _ == "Uranus" ) + += Automaton test interception_points, and restart +~ automaton +class ATMT9(Automaton): + def my_send(self, x): + self.io.loop.send(x) + @ATMT.state(initial=1) + def BEGIN(self): + self.res = "V" + self.send(Raw("ENU")) + @ATMT.ioevent(BEGIN, name="loop") + def received_sth(self, fd): + self.res += fd.recv().load + raise self.END() + @ATMT.state(final=1) + def END(self): + self.res += "s" + return self.res + +a=ATMT9(debug=5) +a.run() +assert( _ == "VENUs" ) + +a.restart() +a.run() +assert( _ == "VENUs" ) + +a.restart() +a.BEGIN.intercepts() +while True: + try: + x = a.run() + except Automaton.InterceptionPoint,p: + a.accept_packet(Raw(p.packet.load.lower()), wait=False) + else: + break + +x +assert( _ == "Venus" ) + + + ++ Test IP options + += IP options individual assembly +~ IP options +str(IPOption()) +assert(_ == '\x00\x02') +str(IPOption_NOP()) +assert(_ == '\x01') +str(IPOption_EOL()) +assert(_ == '\x00') +str(IPOption_LSRR(routers=["1.2.3.4","5.6.7.8"])) +assert(_ == '\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08') + += IP options individual dissection +~ IP options +IPOption("\x00") +assert(_.option == 0 and isinstance(_, IPOption_EOL)) +IPOption("\x01") +assert(_.option == 1 and isinstance(_, IPOption_NOP)) +lsrr='\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08' +p=IPOption_LSRR(lsrr) +p +q=IPOption(lsrr) +q +assert(p == q) + += IP assembly and dissection with options +~ IP options +IP(src="9.10.11.12",dst="13.14.15.16",options=IPOption_SDBM(addresses=["1.2.3.4","5.6.7.8"]))/TCP() +str(_) +assert(_ == 'H\x00\x004\x00\x01\x00\x00@\x06\xa2q\t\n\x0b\x0c\r\x0e\x0f\x10\x95\n\x01\x02\x03\x04\x05\x06\x07\x08\x00\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00_K\x00\x00') +q=IP(_) +q +assert( isinstance(q.options[0],IPOption_SDBM) ) +assert( q[IPOption_SDBM].addresses[1] == "5.6.7.8" ) +IP(src="9.10.11.12", dst="13.14.15.16", options=[IPOption_NOP(),IPOption_LSRR(routers=["1.2.3.4","5.6.7.8"]),IPOption_Security(transmission_control_code="XYZ")])/TCP() +str(_) +assert(_ == 'K\x00\x00@\x00\x01\x00\x00@\x06\xf3\x83\t\n\x0b\x0c\r\x0e\x0f\x10\x01\x83\x0b\x04\x01\x02\x03\x04\x05\x06\x07\x08\x82\x0b\x00\x00\x00\x00\x00\x00XYZ\x00\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00_K\x00\x00') +IP(_) +q=_ +assert(q[IPOption_LSRR].get_current_router() == "1.2.3.4") +assert(q[IPOption_Security].transmission_control_code == "XYZ") +assert(q[TCP].flags == 2) + + ++ Test PPP + += PPP/HDLC +~ ppp hdlc +HDLC()/PPP()/PPP_IPCP() +str(_) +s=_ +assert(s == '\xff\x03\x80!\x01\x00\x00\x04') +PPP(s) +p=_ +assert(HDLC in p) +assert(p[HDLC].control==3) +assert(p[PPP].proto==0x8021) +PPP(s[2:]) +q=_ +assert(HDLC not in q) +assert(q[PPP].proto==0x8021) + + += PPP IPCP +~ ppp ipcp +PPP('\x80!\x01\x01\x00\x10\x03\x06\xc0\xa8\x01\x01\x02\x06\x00-\x0f\x01') +p=_ +assert(p[PPP_IPCP].code == 1) +assert(p[PPP_IPCP_Option_IPAddress].data=="192.168.1.1") +assert(p[PPP_IPCP_Option].data == '\x00-\x0f\x01') +p=PPP()/PPP_IPCP(options=[PPP_IPCP_Option_DNS1(data="1.2.3.4"),PPP_IPCP_Option_DNS2(data="5.6.7.8"),PPP_IPCP_Option_NBNS2(data="9.10.11.12")]) +str(p) +assert(_ == '\x80!\x01\x00\x00\x16\x81\x06\x01\x02\x03\x04\x83\x06\x05\x06\x07\x08\x84\x06\t\n\x0b\x0c') +PPP(_) +q=_ +assert(str(p) == str(q)) +assert(PPP(str(q))==q) +PPP()/PPP_IPCP(options=[PPP_IPCP_Option_DNS1(data="1.2.3.4"),PPP_IPCP_Option_DNS2(data="5.6.7.8"),PPP_IPCP_Option(type=123,data="ABCDEFG"),PPP_IPCP_Option_NBNS2(data="9.10.11.12")]) +p=_ +str(p) +assert(_ == '\x80!\x01\x00\x00\x1f\x81\x06\x01\x02\x03\x04\x83\x06\x05\x06\x07\x08{\tABCDEFG\x84\x06\t\n\x0b\x0c') +PPP(_) +q=_ +assert( q[PPP_IPCP_Option].type == 123 ) +assert( q[PPP_IPCP_Option].data == 'ABCDEFG' ) +assert( q[PPP_IPCP_Option_NBNS2].data == '9.10.11.12' ) + + += PPP ECP +~ ppp ecp + +PPP()/PPP_ECP(options=[PPP_ECP_Option_OUI(oui="XYZ")]) +p=_ +str(p) +assert(_ == '\x80S\x01\x00\x00\n\x00\x06XYZ\x00') +PPP(_) +q=_ +assert( str(p)==str(q) ) +PPP()/PPP_ECP(options=[PPP_ECP_Option_OUI(oui="XYZ"),PPP_ECP_Option(type=1,data="ABCDEFG")]) +p=_ +str(p) +assert(_ == '\x80S\x01\x00\x00\x13\x00\x06XYZ\x00\x01\tABCDEFG') +PPP(_) +q=_ +assert( str(p) == str(q) ) +assert( q[PPP_ECP_Option].data == "ABCDEFG" ) + + # Scapy6 Regression Test Campaign