diff --git a/1.md b/1.md index f4127ab8b7cdb06356ce815cb34fdb749f7236e0..dba370ae7db9549e4f8a8faf908bc2408ddd3e6f 100644 --- a/1.md +++ b/1.md @@ -1,6 +1,6 @@ # 介绍 -> 译者:[pdcxs007](http://blog.csdn.net/pdcxs007/article/details/46686843) +> 译者:[pdcxs007](http://blog.csdn.net/pdcxs007/) > 来源:[Scapy介绍官方文档翻译](http://blog.csdn.net/pdcxs007/article/details/46686843) diff --git a/4.md b/4.md new file mode 100644 index 0000000000000000000000000000000000000000..1f57a242701501cedfa8f6dd78a1512e373084f1 --- /dev/null +++ b/4.md @@ -0,0 +1,805 @@ +# 高级用法 + +> 译者:[草帽小子_DJ](http://blog.csdn.net/dj1174232716/) + +> 来源:[Python Scapy(2.3.1)文档学习(四):高级用法](http://blog.csdn.net/dj1174232716/article/details/49004813) + +> 原文:[Advanced usage](http://www.secdev.org/projects/scapy/doc/advanced_usage.html) + +> 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) + +## ASN.1 和 SNMP + +### 什么是ASN.1 ? + +> 注意:这只是我对ASN.1的个人观点,我会尽可能的做简单的解释。至于更多的理论或者学术观点,我相信你会在互联网上找到更好的。 + +ASN.1(抽象语法标记)是一种对数据进行表示、编码、传输和解码的数据格式。它用一种独立的方式给数据编码,用指定的编码规则给数据编码。 + +最常用的编码规则是BER(基本编码规则)和DER(识别名编码规则),两者看起来是一样的,但是后者特殊在它保证了生成的编码的唯一性,当谈到加密,哈希和签名时,这个属性非常有意思。 + +ASN.1提供了基本的对象:整数,多种类型的字符串,浮点数,布尔值,容器,等等。它们组成了通用类。一种给定的协议能提供组成其他对象的上下文类。比如,SNMP定义了`PDU_SET`和`PDU_GET`对象,还有其他的应用和私有类。 + +每一个对象将会给一个标签用来定义编码规则。从1开始用于通由类,1是布尔值,2是整型,3是一个字节字符串,6是OID,48是一个序列。标签来自`Context`类,从0xa0开始。当从0xa0遇到一个对象标签,我们将需要知道能够解码的`context`。比如说,在SNMP的`context`下,0xa0是一个`PDU_GET`对象,而在X509的`context`下,它是一个证书版本的容器。 + +其他对象通过组装基本的对象产生。新的结构是是用先前已经定义或者存在的序列和阵列组成。最终的对象(X509证书,一个SNMP数据包)是一棵非叶子结点序列的树,并设置对象(或者派生的对象),叶子节点是整数,字符串,OID等等。 + +### Scapy和ASN.1 + +Scapy提供了一种简单的方法加解密ASN.1,还提供了一个编码器/解码器。它比ASN.1的解析器更加宽松并忽略了一些约束。它不会取代ASN.1的解析器或者是ASN.1的编译器,事实上,它被编写的可以编码或者解密损坏的ASN.1。它可以处理损坏的编码字符串并创建他们。 + +#### ASN.1引擎 + +注意:这里介绍的许多类的定义都用到了元类。如果你不仔细的看源码,只看我的讲解,你可能认为他们有时的行为很神奇。Scapy的ASN.1引擎提供连接对象和他们的标签。它们都继承自`ASN1_Class`。第一个是`ASN1_Class_UNIVERSAL`,它提供的标签是最为通用的标签。每个新的`context`(SNMP,X509)都将继承它,并添加自己的标签。 + +```py +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 +# [...] +``` + +所有的ASN.1对象都被简单的Python实例表示,并隐藏的原始的值。简单的逻辑被`ASN1_Object`所继承处理。因此他们相当简单。 + +```py +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 +``` + +这些实例可以组装并创建一个ASN.1树: + +``` +>>> x=ASN1_SEQUENCE([ASN1_INTEGER(7),ASN1_STRING("egg"),ASN1_SEQUENCE([ASN1_BOOLEAN(False)])]) +>>> x +, , ]]>]]> +>>> x.show() +# ASN1_SEQUENCE: + + + # ASN1_SEQUENCE: + +``` + +#### 编码引擎 + +作为标准,ASN.1和编码是独立的。我们只看到怎样生成一个组合的ASN.1对象,解码或者编码它我们只需要选择一个解码规则。Scapy目前只提供BER编码规则(事实上,它可能是DER规则,DER看起来像是BER,除了一小部分编码通过授权才可以得到)。我称它为ASN.1编解码器。 + +编码和解码都是编解码器的类方法提供的。比如说`BERcodec_INTEGER` 提供了一个`enc()`和一个`dec()`类方法可以将编码的字符串和其类型的值之间进行转换。它们都继承自`BERcodec_Object`,它能解码任何类型。 + +``` +>>> 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对象使用它们的`enc()`方法解码。这个方法必须被我们要使用的编解码器所调用,所有的便把解码器都被`ASN1_Codecs`对象所引用。`str()`也能被用到,在这种情况下,默认的编解码器(`conf.ASN1_default_codec`)也会被用到。 + +``` +>>> 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 +'' +``` + +默认情况下,解码器使用`Universal`类进行解码,这就意味着在`Context`类中定义的对象将不会被解码,这有一个比较好的原因:这个解码取决于`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协议层 + +虽然这可能不错,但是只是ASN.1的一个编解码器,和Scapy没有什么关系。 + +##### ASN.1字段 + +Scapy提供ASN.1字段,它们封装了ASN.1对象并提供了必要的逻辑绑定对象名到值。ASN.1数据包将会被解析成一棵ASN.1字段树,然后在同一个层面上每一个字段名将会做成一个正常的数据包提供(比如说:为了访问SNMP数据包的版本字段,你不用知道它包装了多少层容器)。 + +每一个 ASN.1字段都会通过它的标签连接到ASN.1对象。 + +##### ASN.1数据包 + +ASN.1数据包继承自`Packet`类。而不是一个`fields_desc`序列的字段,它们定义了`ASN1_codec`和`ASN1_root`属性。第一个是一个编解码器(比如说:`ASN1_Codecs.BER`),第二个是一个ASN.1字段的组合树。 + +### 一个完整的例子:SNMP + +SNMP定义了新的ASN.1对象,我们需要定义它们: + +```py +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 +``` + +这个对象是PDU,实际上是一个序列容器的新名称,(这通常是在`context`对象的情况下:他们只是旧的容器有了新的名称),这意味着创建一个相应的ASN.1对象和BER编解码器是很容易的: + +```py +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 + +# [...] +``` + +元类提供的魔法基于一切都是自动注册和ASN.1对象和BER编解码器都能找到对方的事实。 + +ASN.1的字段也是不重要的: + +```py +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 + +# [...] +``` + +现在,困难的部分,ASN.1数据包: + +```py +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) +``` + +这些不会有太大的困难,如果你认为不可能有这么短就能实现一个SNMP编解码器,我可能会删减很多,请看看完整的源代码。 + +现在,如何使用它?通常: + +``` +>>> 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= +``` + +### 从MIB解析OID + +#### 关于OID对象 + +OID对象是通过`ASN1_OID`类产生的: + +``` +>>> o1=ASN1_OID("2.5.29.10") +>>> o2=ASN1_OID("1.2.840.113549.1.1.1") +>>> o1,o2 +(, ) +``` + +#### 加载一个MIB + +Scapy可以解析MIB文件并注意到OID和它的名称之间的映射。 + +``` +>>> load_mib("mib/*") +>>> o1,o2 +(, ) +``` + +#### Scapy的MIB数据库 + +所有的MIB信息都被存储在`cong.mib`对象中,这些对象用来寻找一个OID的名称。 + +``` +>>> conf.mib.sha1_with_rsa_signature +'1.2.840.113549.1.1.5' +``` + +或者解析一个OID: + +``` +>>> conf.mib._oidname("1.2.3.6.1.4.1.5") +'enterprises.5' +``` + +甚至可以图形化表示它: + +``` +>>> conf.mib._make_graph() +``` + +## 自动机 + +Scapy能够创建一个简单的网络自动机。Scapy不会拘泥于一个特定的模型,像是Moore和Mealy自动机。它为你提供了灵活的方法去选择你想要的。 + +Scapy中的自动机是确定性的。他有不同的状态,一个开始状态和一些结束,错误状态,他们从一种状态过渡到另一种状态。过渡可以在一种特殊的状态下过渡,在一个特定的数据包或者超时过渡。当一个过渡被接受了,一个或者多个动作将会运行,一个动作可以被绑定在多个过渡中。参数可以通过从状态到过渡和从过渡到状态和动作中传递。 + +从一个开发者的角度看,状态,过渡和动作都是来自自动机子类的方法。他们都被包装起来提供元信息以供自动机工作。 + +### 第一个例子 + +让我们开始一个简单的例子。我按照惯例用字幕大写编写状态,但是每一个有效的Python语法都会工作的很好。 + +```py +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" +``` + +在这个例子中,我们可以看到三个装饰器: + +`ATMT.state`被用来表示一个方法就是一个状态,并且能用`initial`, `final` 和 `error`可选参数来设置非零的特殊状态。 + +`ATMT.condition`用来表示一个方法,当自动机的状态到达指示的状态时将会运行。参数是代表这个状态的函数的名称。 + +`ATMT.action`绑定到一个过渡的方法,当一个过渡被接受了该函数就会运行。 + +运行这个例子将会得到下面这个结果: + +``` +>>> a=HelloWorld() +>>> a.run() +State=BEGIN +Wait for nothing... +Action on 'nothing' condition +State=END +``` + +这个简单的自动机可以用下面的这个图描述: + +![](http://img.blog.csdn.net/20151010210026266) + +这个图可以用下面的代码自动画出![](file:///C:/Users/DJ/AppData/Local/Temp/SGPicFaceTpBq/6036/008D65C6.gif): + +``` +>>> HelloWorld.graph() +``` + +### 改变状态 + +`ATMT.state`装饰器将方法转换成一个返回一个异常的函数。如果你抛出这个异常,自动机的状态将会被改变。如果这个改变发生在一个过渡中,绑定在这个过渡上的函数将会被调用。给定函数的参数替换的方法将被保留,并最终传递到该方法中。这个异常有一个方法`action_parameters`能在抛出异常前被调用,他将存储参数传递到所有绑定在当前过渡上的动作。 + +作为一个例子,让我们来考虑一下下面的状态: + +```py +@ATMT.state() +def MY_STATE(self, param1, param2): + print "state=MY_STATE. param1=%r param2=%r" % (param1, param2) +``` + +这个状态将会到达下面的代码: + +```py +@ATMT.receive_condition(ANOTHER_STATE) +def received_ICMP(self, pkt): + if ICMP in pkt: + raise self.MY_STATE("got icmp", pkt[ICMP].type) +``` + +让我们假设我们想绑定一个动作到这个状态,这也将需要一些参数: + +```py +@ATMT.action(received_ICMP) +def on_ICMP(self, icmp_type, icmp_code): + self.retaliate(icmp_type, icmp_code) +``` + +这个条件应该被满足: + +```py +@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) +``` + +### 真正的例子 + +这里有一个来自Scapy的真正的例子。他实现了一个TFTP客户端,能够发送和读取请求。 + +![](http://img.blog.csdn.net/20151010212241960) + +```py +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 +``` + +他运行起来像是这样,这是实例: + +``` +>>> TFTP_read("my_file", "192.168.1.128").run() +``` + +### 详细的文档 + +#### 装饰器 + +##### 状态的装饰器 + +状态是被`ATMT.state`函数结果装饰过的方法。他有三个可选的参数,`initial`,`final`和`error`,当我们设置为`True`时,就意味着这个状态是一个`initial`,`final`或者`error`状态。 + +```py +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" +# [...] +``` + +##### 过渡装饰器 + +过渡是被`ATMT.condition`, `ATMT.receive_condition`, `ATMT.timeout`之一的函数结果装饰过的方法。他们都作为他们关联的状态方法的参数。`ATMT.timeout`也有一个强制性的参数timeout用来提供超时的时间秒值。`ATMT.condition`和`ATMT.receive_condition`有一个可选的`prio`参数,因此在这种情况下的调用的顺序是可以被强制提高优先级的。默认的优先级是0。相同的优先级的过渡调用的顺序是不确定的。 + +当自动机切换到一个给定的状态,这个状态的方法将会被执行,然后过渡方法将会在特殊的时刻被调用,直到触发一个新的状态。(有时候像是抛出`self.MY_NEW_STATE()`)。首先,在状态方法返回正确之后,`ATMT.condition`装饰的方法通过提升优先级运行,然后每一个时刻通过主要的过滤器收到和接受的数据包所有的`ATMT.receive_condition`装饰过的方法都会通过提升优先级运行。当一个超时的数据包进入当前的空间,相应的`ATMT.timeout`装饰过的方法将会被调用。 + +```py +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() +``` + +##### 动作装饰器 + +动作是被`ATMT.action`函数结果装饰过的方法。这个函数接受过渡方法绑定他作为第一个参数,并且可选的优先级`prio`作为第二个参数,默认的优先级是0。一个动作方法能被装饰很多次并且被绑定在很多过渡上。 + +```py +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!..." +``` + +两种可能的输出结果是: + +``` +>> a=Example() +>>> a.run() +We are not lucky... +This wasn't luck!... +>>> a.run() +We are lucky... +This wasn't luck!... +``` + +#### 重载方法 + +两种方法通过hooks重载: + +`parse_args()`方法是通过`__init__()`和`run()`提供参数被调用的。使用这些来确定你的自动机的行为。 + +`master_filter()`方法被每一时刻嗅探到的数据包所调用,如果自动机感兴趣的话。当工作在一个特殊的协议上时,这将确保这些数据包属于你连接到的一部分,所以你不必在每一个过渡中做明确的检查。 \ No newline at end of file diff --git a/5.md b/5.md new file mode 100644 index 0000000000000000000000000000000000000000..c86ab112c8235a746bb92b5c9ae623d103ea93b6 --- /dev/null +++ b/5.md @@ -0,0 +1,106 @@ +# 构建你自己的工具 + +> 译者:[草帽小子_DJ](http://blog.csdn.net/dj1174232716/) + +> 来源:[Python Scapy(2.3.1)文档学习(五):构建自己的工具](http://blog.csdn.net/dj1174232716/article/details/49046043) + +> 原文:[Build your own tools](http://www.secdev.org/projects/scapy/doc/extending.html) + +> 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) + +你可以使用Scapy构建你自己的自动化工具。你也可以扩展Scapy而不必编辑它的源文件。如果你构建了一些有趣的工具,请捐献给我们的邮件列表。 + +## 在你的工具中使用Scapy + +你可以很容易的在你的工具中使用Scapy,只需要导入你需要的便可以使用。 + +第一个例子是传入一个IP或者一个主机名作为参数,发送一个ICMP响应请求,然后显示返回包完整的构造。 + +```py +#! /usr/bin/env python + +import sys +from scapy.all import sr1,IP,ICMP + +p=sr1(IP(dst=sys.argv[1])/ICMP()) +if p: + p.show() +``` + +找个有一个更加灵活的例子,就是生成一个ARP的ping包,并用LaTeX格式报告它所发现的东西。 + +```py +#! /usr/bin/env python +# arping2tex : arpings a network and outputs a LaTeX table as a result + +import sys +if len(sys.argv) != 2: + print "Usage: arping2tex \n eg: arping2tex 192.168.1.0/24" + sys.exit(1) + +from scapy.all import srp,Ether,ARP,conf +conf.verb=0 +ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=sys.argv[1]), + timeout=2) + +print r"\begin{tabular}{|l|l|}" +print r"\hline" +print r"MAC & IP\\" +print r"\hline" +for snd,rcv in ans: + print rcv.sprintf(r"%Ether.src% & %ARP.psrc%\\") +print r"\hline" +print r"\end{tabular}" +``` + +这有另外一个工具,它将时刻监控机器上的所有的网卡并打印所有的ARP请求。即使是混杂模式下的无线网卡上的801.11数据帧。注意,`sniffer()`函数的参数`store=0`是为了避免将所有的数据包存储在内存。 + +```py +#! /usr/bin/env python +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 + return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%") + +sniff(prn=arp_monitor_callback, filter="arp", store=0) +``` + +这里有一个生活中真实的例子,你可以参考WiFitap([http://sid.rstack.org/static/articles/w/i/f/Wifitap_EN_9613.html](http://sid.rstack.org/static/articles/w/i/f/Wifitap_EN_9613.html)). + +## 扩展Scapy + +如果你想添加一些新的协议,新的函数,或者任何东西,你可以直接编辑Scapy的源代码。但是这是非常不方便的。即使这些修改将会整合到Scapy中去。可以更加方便的编写他们在单独的文件中。 + +一旦你这么做了,你可以启动Scapy并导入自己的文件,但是这还是不是很方便,另外一个能做到这一点的方法是让你文件执行并且调用Scapy的`interact()`函数。 + +```py +#! /usr/bin/env python + +# Set log level to benefit from Scapy warnings +import logging +logging.getLogger("scapy").setLevel(1) + +from scapy.all import * + +class Test(Packet): + name = "Test packet" + fields_desc = [ ShortField("test1", 1), + ShortField("test2", 2) ] + +def make_test(x,y): + return Ether()/IP()/Test(test1=x,test2=y) + +if __name__ == "__main__": + interact(mydict=globals(), mybanner="Test add-on v3.14") +``` + +如果你运行上面的代码,便会得到下面的结果: + +``` +# ./test_interact.py +Welcome to Scapy (0.9.17.109beta) +Test add-on v3.14 +>>> make_test(42,666) +>> +``` \ No newline at end of file diff --git a/6.md b/6.md new file mode 100644 index 0000000000000000000000000000000000000000..04c0926657c83e4f0d12360555287c17d07394c8 --- /dev/null +++ b/6.md @@ -0,0 +1,951 @@ +# 添加新的协议 + +> 译者:[草帽小子_DJ](http://blog.csdn.net/dj1174232716/) + +> 来源:[Python Scapy(2.3.1)文档学习(六):添加新的协议](http://blog.csdn.net/dj1174232716/article/details/49046409) + +> 原文:[Adding new protocols](http://www.secdev.org/projects/scapy/doc/build_dissect.html) + +> 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) + +在Scapy中添加新的协议(或者是更加的高级:新的协议层)是非常容易的。所有的魔法都在字段中,如果你需要的字段已经有了,你就不必对这个协议太伤脑筋,几分钟就能搞定了。 + +## 简单的例子 + +每一个协议层都是`Packet`类的子类。协议层背后所有逻辑的操作都是被`Packet`类和继承的类所处理的。一个简单的协议层是被一系列的字段构成,他们关联在一起组成了协议层,解析时拆分成一个一个的字符串。这些字段都包含在名为`fields_desc`的属性中。每一个字段都是一个`field`类的实例: + +```py +class Disney(Packet): + name = "DisneyPacket " + fields_desc=[ ShortField("mickey",5), + XByteField("minnie",3) , + IntEnumField("donald" , 1 , + { 1: "happy", 2: "cool" , 3: "angry" } ) ] +``` + +在这个例子中,我们的协议层有三个字段,第一个字段是一个2个字节的短整型字段,名字为`mickey`,默认值是5,第二个字段是1个自己的整形字段,名字为`minnie`,默认值是3,普通的`ByteField`和`XByteField`之间的唯一不同的就是首选的字段值是十六进制。最后一个字段是一个4个字节的整数字段,名字为`donald`,他不同于普通的`IntField`类型的是他有一些更小的值供选择,类似于枚举类型,比如说,如果他的值是3的话则显示`angry`。此外,如果`cool`值被关联到这个字段上,他将会明白接受的是2. + +如果你的协议像上面这么简单,他已经可以用了: + +``` +>>> d=Disney(mickey=1) +>>> ls(d) +mickey : ShortField = 1 (5) +minnie : XByteField = 3 (3) +donald : IntEnumField = 1 (1) +>>> d.show() +###[ Disney Packet ]### +mickey= 1 +minnie= 0x3 +donald= happy +>>> d.donald="cool" +>>> str(d) +’\x00\x01\x03\x00\x00\x00\x02’ +>>> Disney( ) + +``` + +本章讲解了用Scapy如何构建一个新的协议,这里有两个目标: + +分解:这样做是为了当接收到一个数据包时(来自网络或者是文件)能够被转化成Scapy的内部结构。 + +构建:当想发送一个新的数据包时,有些填充数据需要被自动的额调整。 + +## 协议层 + +在深入剖析之前,让我们来看看数据包是怎样组织的。 + +``` +>>> p = IP()/TCP()/"AAAA" +>>> p +>> +>>> p.summary() +'IP / TCP 127.0.0.1:ftp-data > 127.0.0.1:www S / Raw' +``` + +我们很感兴趣这两个内部的字段类`Packet`: + ++ `p.underlayer` + ++ `p.payload` + +这里是主要的“伎俩”。你不必在乎数据包,只关注协议层,一个堆在另一个上面。 + +一个可以通过协议层的名字容易的访问协议层:`p[TCP]`返回的是TCP和下面的协议,这是`p.getlayer(TCP)`的一个快捷方式。 + +注意:这里有一个可选的参数`nb`,用来返回所需协议的第几层协议层。 + +让我们将所有的放在一起,玩玩这个TCP协议层: + +``` +>>> tcp=p[TCP] +>>> tcp.underlayer +>> +>>> tcp.payload + +``` + +不出所料,`tcp.underlayer`指向的是我们IP数据包的开始,而`tcp.payload`是他的有效载荷。 + +### 构建一个新的协议层 + +非常简单!一个协议层就是由一系列的字段构成。让我们来看看UDP的定义: + +```py +class UDP(Packet): + name = "UDP" + fields_desc = [ ShortEnumField("sport", 53, UDP_SERVICES), + ShortEnumField("dport", 53, UDP_SERVICES), + ShortField("len", None), + XShortField("chksum", None), ] +``` + +为了方便,内部已经定义了许多字段,看看文档“W”的源码Phil会告诉你的。(这句我也不知道原文是什么意思)。 + +所以,定义一个协议层就是简单的组合一系列的字段,现在的目标是为每个字段提供有限的默认值,所以当用户构建数据包的时候不必给他们赋值。 + +主要的机制是基于`Field`结构,要牢记协议层就只是一系列的字段,不用记得太多。 + +所以,为了理解协议层是怎样工作的,一个就是需要快速的看看字段是怎么被处理的。 + +### 操作数据包==操作他们的字段 + +一个字段应该被考虑到有多种状态 + ++ i (internal) :这是Scapy怎样操作它们的方法。 + ++ m (machine) :这是真正的数据,这就是他们在网络上的样子。 + ++ h (human) :如何将数据展示给人们看。 + +这解释了一些神秘的方法`i2h()`,`i2m()`,`m2i()`可以用于每一个字段:他们都是将一种状态转换成另一种状态,用于特殊的用途。 + +其他特殊的方法有: + ++ `any2i()`:猜测输入的状态装换为internal状态。 + ++ `i2repr()`:比`i2h()`更好。 + +然而,所有的这些都是底层的方法。用于添加或提取字段到当前的协议的方法是: + ++ `addfield(self, pkt, s, val)`:复制网络上原始的字段值`val`(属于`pkt`协议层的)到原始的字符串数据包`s`: + + ```py + class StrFixedLenField(StrField): + def addfield(self, pkt, s, val): + return s+struct.pack("%is"%self.length,self.i2m(pkt, val)) + ``` + ++ `getfield(self, pkt, s)`:从原始的数据包`s`中提取出属于协议层`pkt`的字段值。他返回一个序列,第一个元素是移除了被抽取的字段值的原始的数据包字符串,第二个元素是被抽取字段的internal的表示状态: + + ```py + class StrFixedLenField(StrField): + def getfield(self, pkt, s): + return s[self.length:], self.m2i(pkt,s[:self.length]) + ``` + +当定义你自己的协议层,你通常只需要定义一些`*2*()`方法,有时候也会有`addfield()`和`getfield()`方法。 + +### 示例:可变长度的数值 + +在协议中经常使用可变长度的数值的方法来表示整数,例如处理信号进程(MIDI)。 + +每一个数值的字节的MSB编码被设置为1,除了最后一个字节。比如,0x123456将会编码为0xC8E856: + +```py +def vlenq2str(l): + s = [] + s.append( hex(l & 0x7F) ) + l = l >> 7 + while l>0: + s.append( hex(0x80 | (l & 0x7F) ) ) + l = l >> 7 + s.reverse() + return "".join(map( lambda(x) : chr(int(x, 16)) , s)) + +def str2vlenq(s=""): + i = l = 0 + while i>> f = FOO(data="A"*129) + >>> f.show() + ###[ FOO ]### + len= 0 + data= 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +``` + +这里,`len`不必被计算,默认值会被直接显示的,这是目前我们协议层internal的表示,让我们强行来计算一下: + +``` +>>> f.show2() +###[ FOO ]### + len= 129 + data= 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +``` + +`show2()`方法显示这些字段被发送到网络中时的值,但是为了人类阅读方便,我们看到`len=129`。最后但并非最不重要,让我们来看看machine的表示: + +``` +>>> str(f) +'\x81\x01AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' +``` + +前两个字节是`\x81\x01`,是129编码后的结果。 + +## 剖析 + +协议层只是一系列的字段,但是每一个字段之间使用什么连接的,协议层之间呢?这一节我们将解释这个秘密。 + +### 基本的填充数据 + +剖析数据包的核心的方法是`Packet.dissect()`。 + +```py +def dissect(self, s): + s = self.pre_dissect(s) + s = self.do_dissect(s) + s = self.post_dissect(s) + payl,pad = self.extract_padding(s) + self.do_dissect_payload(payl) + if pad and conf.padding: + self.add_payload(Padding(pad)) +``` + +当被调用时,`s`是一个将要被剖析的字符串,`self`指向当前协议层。 + +``` +>>> p=IP("A"*20)/TCP("B"*32) +WARNING: bad dataofs (4). Assuming dataofs=5 +>>> p +>> +``` + +`Packet.dissect()`被调用了三次: + +1.解析`"A"*20`为IPv4头 + +2.解析`"B"*32`为TCP头 + +3.到此为止数据包还有12个字节,他们将被解析成原始"Raw"的数据(有一些是默认的协议层类型)。 + +当传入一个协议层的时候,一切都很简单: + ++ `pre_dissect()`在协议层之前被调用。 + ++ `do_dissect()`执行协议层真正的解析。 + ++ `post_dissection()`当解析时需要更新输入的时候被调用(比如说,解密,解压缩) + ++ `extract_padding()`是一个重要的方法,应该被每一层所调用控制他们的大小,所以他可以被用来区分真正相关联的协议层的有效载荷,并且什么将被视为额外的填充字节。 + ++ `do_dissect_payload()`方法主要负责剖析有效载荷(如果有的话)。它基于`guess_payload_class()`(见下文),一旦是已知类型的有效荷载,该有效载荷将会以新的类型绑定到当前协议层: + +```py +def do_dissect_payload(self, s): + cls = self.guess_payload_class(s) + p = cls(s, _internal=1, _underlayer=self) + self.add_payload(p) +``` + +最后,数据包中所有的协议层都被解析了,并和已知的类型关联在一起。 + +### 剖析字段 + +所有协议层和它的字段之间的魔法方法是`do_dissect()`。如果你已经理解了协议层的不同的表示,你也应该理解剖析一个协议层就是将构建它的字段从machine表示转换到internal表示。 + +猜猜是什么?这正是`do_dissect()`干的事: + +```py +def do_dissect(self, s): + flist = self.fields_desc[:] + flist.reverse() + while s and flist: + f = flist.pop() + s,fval = f.getfield(self, s) + self.fields[f] = fval + return s +``` + +所以,他接受原始的字符串数据包,并进入每一个字段,只要还有数据或者字段剩余: + +``` +>>> FOO("\xff\xff"+"B"*8) + +``` + +当编写`FOO("\xff\xff"+"B"*8)`的时候,他调用`do_dissect()`方法。第一个字段是`VarLenQField`,因此他接收字节,只要MSB设置过,因此,一直到(包括)第一个"B"。这个映射做到了多亏了`VarLenQField.getfield()`,并且可以进行交叉检查: + +``` +>>> vlenq2str(2097090) +'\xff\xffB' +``` + +然后,下一个字段以相同的方法被提取,直到2097090个字节都放进`FOO.data`中(或者更少,如果2097090是不可用的)。 + +如果当剖析完后还剩下一些字节,他们将以相同的方式映射到下一步要处理的(默认是Raw): + +``` +>>> FOO("\x05"+"B"*8) +> +``` + +因此,现在我们该理解协议层是怎样被绑定在一起的。 + +### 绑定协议层 + +Scapy在解析协议层时一个很酷的特性是他试图猜测下一层协议是什么。连接两个协议层官方的方法是`bind_layers()`: + +比如说,如果你有一个`HTTP`类,你可能会认为所有的接受或者发送的数据包都是80端口的,你将会这样解码,下面是简单的方式: + +```py +bind_layers( TCP, HTTP, sport=80 ) +bind_layers( TCP, HTTP, dport=80 ) +``` + +这就是所有的啦!现在所有和80端口相关联的数据包都将被连接到HTTP协议层上,不管他是从`pcap`文件中读取的,还是从网络中接收到的。 + +#### `guess_payload_class()`方法 + +有时候,猜测一个有效载荷类不是像定义一个单一的端口那么简单。比如说,他可能依赖于当前协议传入的一个字节值。有两个方法是必须的: + ++ `guess_payload_class()`必须返回有效载荷的`guessed`类(下一层协议层的)。默认情况下,它使用类之间已有的关联通过`bind_layer()`放到合适的位置。 + ++ `default_payload_class()`返回默认的值。这个方法在`Packet`类中定义返回Raw,但是他能被重载。 + +比如说,解码802.11的变化取决于他是否被加密: + +```py +class Dot11(Packet): + def guess_payload_class(self, payload): + if self.FCfield & 0x40: + return Dot11WEP + else: + return Packet.guess_payload_class(self, payload) +``` + +这里有需要的几点意见: + ++ 这些事是使用`bind_layer()`不可能完成的,因为测试中应该是`"field==value"`,但是这里我们测试的字段值比单一的字节要发杂。 + ++ 如果测试失败了,没有这种假设,我们会回到默认的机制调用`Packet.guess_payload_class()`。 + +大多数时间,定义一个`guess_payload_class()`方法是没有必要的,可以从`bind_layers()`得到相同的结果。 + +#### 改变默认的行为 + +如果你不喜欢Scapy得到协议层后的行为,你也可以通过调用`split_layer()`来改变或者禁用这些行为。比如说你不想要UDP/53绑定到DNS协议,只需要添加代码`split_layers(UDP, DNS, sport=53)`,现在所有源端口是53的数据包都不会当做DNS协议处理了,但是什么东西你要做特殊处理。 + +### 在后台:将所有的东西都放在一起 + +事实上,每一个协议层都有一个字段的`guess_payload`。当你使用`bind_layers()`的方式,他将定义的下一个添加到该列表中。 + +``` +>>> p=TCP() +>>> p.payload_guess +[({'dport': 2000}, ), ({'sport': 2000}, ), ... )] +``` + +然后,当他需要猜测下一个协议层类,他调用默认的方法`Packet.guess_payload_class()`,该方法通过`payload_guess`序列的每一个元素,每一个元素都是一个元组: + ++ 第一个值是一个字段,我们用`('dport':2000)`测试 + ++ 第二个值是`guessed`类,如果他匹配到Skinny + +所以,默认的`guess_payload_class()`尝试序列中所有的元素,知道偶一个匹配到,如果没发现一个元素,他将调用`default_payload_class()`。如果你重新定义了这个方法,你的方法将会被调用,否则,默认的方法会被调用,Raw将会被返回。 + +`Packet.guess_payload_class()` + ++ 测试字段中有什么`guess_payload` + ++ 调用被重载的`guess_payload_class()` + +## 构建 + +构建一个数据包跟构建每一个协议层一样简单,然后一些魔法的事情发生了当关联一切的时候,让我们来试一试这些魔法。 + +### 基本的填充数据 + +首先要明确,构建是什么意思?正如我们所看到的,一个协议层能被不同的方法所表示(human, internal, machine),构建的意思是转换到machine格式。 + +第二个要理解的事情是什么时候一个协议层将会被构建。答案不是那么明显,但是当你需要machine表示的时候,协议层就被构建了:当数据包在网络上被丢弃或者写入一个文件,当他装换成一个字符串,。。。事实上,machine表示应该被视为附加了协议层的大字符串。 + +``` +>>> p = IP()/TCP() +>>> hexdump(p) +0000 45 00 00 28 00 01 00 00 40 06 7C CD 7F 00 00 01 E..(....@.|..... +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. ..|.. +``` + +调用`str()`构建这个数据包: + ++ 没有实例化的字段设置他们的默认值 + ++ 长度自动更新 + ++ 计算校验和 + ++ 等等 + +事实上,使用`str()`而不是`show2()`不是一个随机的选择,就像所有的函数构建数据包都要调用`Packet.__str__()`,然而,`__str__()`调用了另一个函数:`build()`: + +```py +def __str__(self): + return self.__iter__().next().build() +``` + +重要的也是要理解的是,通常你不必关心machine表示,这就是为什么human和internal也在这里。 + +所以,核心的函数式`build()`(代码被缩短了只保留了相关的部分): + +```py +def build(self,internal=0): + pkt = self.do_build() + pay = self.build_payload() + p = self.post_build(pkt,pay) + if not internal: + pkt = self + while pkt.haslayer(Padding): + pkt = pkt.getlayer(Padding) + p += pkt.load + pkt = pkt.payload + return p +``` + +所以,他通过构建当前协议层开始,然后是有效载荷,并且`post_build()`被调用更新后期的一些评估字段(像是校验和),最后将填充数据添加到数据包的尾部。 + +当然,构建一个协议层和构建它的字段是一样的,这正是`do_build()`干的事。 + +### 构建字段 + +构建每一个协议层的每一个字段都会调用`Packet.do_build()`: + +```py +def do_build(self): + p="" + for f in self.fields_desc: + p = f.addfield(self, p, self.getfieldval(f)) + return p +``` + +构建字段的核心函数是`getfield()`,他接收internal字段视图,并将它放在`p`的后面。通常,这个方法会调用`i2m()`并返回一些东西,如`p.self.i2mval(val)`(在`val=self.getfieldval(f)`处)。 + +如果`val`设置了,`i2m()`只是一个必须使用的格式化的方法,不如,如果预期是一个字节,`struct.pack('B',val)`是在正确转化他的方法。 + +然而,如果`val`没有被设置,事情将会变得更加复杂,这就意味着不能简单的提供默认值,然后这些字段现在或者以后需要计算一些“填充数据”。 + +“刚刚好”意味着多亏了`i2m()`,如果所有的信息将是可用的。如果你必须处理一个长度直到遇到一个分隔符。 + +比如说:计算一个长度直到遇到一个分隔符: + +```py +class XNumberField(FieldLenField): + + def __init__(self, name, default, sep="\r\n"): + FieldLenField.__init__(self, name, default, fld) + self.sep = sep + + def i2m(self, pkt, x): + x = FieldLenField.i2m(self, pkt, x) + return "%02x" % x + + def m2i(self, pkt, x): + return int(x, 16) + + def addfield(self, pkt, s, val): + return s+self.i2m(pkt, val) + + def getfield(self, pkt, s): + sep = s.find(self.sep) + return s[sep:], self.m2i(pkt, s[:sep]) +``` + +在这个例子中,在`i2m()`中,如果`x`已经有一个值,他将装换为十六进制。如果没有提供任何值,将会返回0。 + +关联由`Packet.do_build()`提供,他为协议层的每一个字段调用`Field.addfield()`并以此调用`Field.i2m()`:如果值是有效的,协议层将会被构建。 + +### 处理默认值:`do_build()` + +字段给定的默认值有时候也不知道或者不可能知道什么时候将字段放在一起。比如说,如果我们在协议层中使用预先定义的`XNumberField`,我们希望当他被构建是被设置一个被给定的值,然后如果他没有被设置`i2m()`不会返回任何值。 + +这个问题的答案是`Packet.post_build()`。 + +当这个方法被调用,数据包已经被构建了,但是有些字段还是需要被计算,一个典型的例子就是需要计算检验和或者是长度。这是每一个字段每次都取决于一些东西,而不是当前需要的。所以,让我们假设我们有一个有`XNumberField`的数据包来看看他的构建过程: + +```py +class Foo(Packet): + fields_desc = [ + ByteField("type", 0), + XNumberField("len", None, "\r\n"), + StrFixedLenField("sep", "\r\n", 2) + ] + + def post_build(self, p, pay): + if self.len is None and pay: + l = len(pay) + p = p[:1] + hex(l)[2:]+ p[2:] + return p+pay +``` + +当`post_build()`被调用的时候,`p`是当前的协议层,`pay`是有效载荷,这就已经构建好了,我们想要我们的长度是将所有的数据都放到分隔符之后的全部长度,所以我们在`post_build()`中添加他们的计算。 + +``` +>>> p = Foo()/("X"*32) +>>> p.show2() +###[ Foo ]### + type= 0 + len= 32 + sep= '\r\n' +###[ Raw ]### + load= 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' +``` + +`len`现在正确的被计算: + +``` +>>> hexdump(str(p)) +0000 00 32 30 0D 0A 58 58 58 58 58 58 58 58 58 58 58 .20..XXXXXXXXXXX +0010 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX +0020 58 58 58 58 58 +``` + +而且machine也是期望的那样。 + +### 处理默认值:自动计算 + +像我们向前看到的那样,剖析机制是建立在程序员创造的协议层之间的连接之上的。然而,他也可以用在构建的过程中。 + +在协议层`Foo()`,我们第一个字节的类型是在下面定义的,比如说,如果`type=0`,下一层协议层是`Bar0`,如果是1,下一层是协议层是`Bar1`,以此类推。我们希望字段在下面自动设置。 + +```py +class Bar1(Packet): + fields_desc = [ + IntField("val", 0), + ] + +class Bar2(Packet): + fields_desc = [ + IPField("addr", "127.0.0.1") + ] +``` + +如果我们除此之外没有做其他的事情,我们在解析数据包的时候将会有麻烦,不会有任何的`Bar*`绑定在`Foo`协议层,甚至是当我们通过调用`show2()`函数明确的构建数据包时也没有。 + +``` +>>> p = Foo()/Bar1(val=1337) +>>> p +> +>>> p.show2() +###[ Foo ]### + type= 0 + len= 4 + sep= '\r\n' +###[ Raw ]### + load= '\x00\x00\x059' +``` + +问题: + ++ `type`还是等于0当我们将它设置为1的时候,我们当然可以通过`p=Foo(type=1)/Bar0(val=1337)`来构建`p`,但是这样不方便。 + ++ 当`Bar1`注册为Raw的时候,数据包将会被错误的解析。这是因为`Foo()`和`Bar*()`之间没有设置任何的连接。 + +为了理解我们应该怎么做才能获得适当的行为,我们必须看看协议层是怎么组装的。当两个独立的数据包实例`Foo()`和`Bar1(val=1337)`通过分隔符`/`连接在一起的时候,将产生一个新的数据包,先前的实例被克隆了(也就是说这来了明确的对象构造不同,但是持有相同的值)。 + +```py +def __div__(self, other): + if isinstance(other, Packet): + cloneA = self.copy() + cloneB = other.copy() + cloneA.add_payload(cloneB) + return cloneA + elif type(other) is str: + return self/Raw(load=other) +``` + +操作符右边的是左边的有效载荷,这种行为是通过调用`add_payload()`完成的。最后返回一个新的数据包。 + +我们可以观察到,如果`other`是一个字符串而不是一个数据包,Raw将会从`payload`实例化得来。就像下面的例子: + +``` +>>> IP()/"AAAA" +> +``` + +这样的话`add_payload()`该执行什么?只是将两个数据包关联在一起吗?不仅仅是这样,在我们的例子中,该方法会适当的设置当前的值给`type`。 + +本能的我们可以感觉到上层协议(`/`右边的协议层)能收集值设置给下层协议(`/`左边的协议层)。看看向前的解释,这有一个方便的机制来指定两个相邻协议层之间的绑定。 + +再一次,这些信息必须提供给`bind_layer()`,内部将调用`bind_top_down()`让这些字段被重载,在这种情况下,我们需要指定这些: + +```py +bind_layers( Foo, Bar1, {'type':1} ) +bind_layers( Foo, Bar2, {'type':2} ) +``` + +然后,`add_payload()`遍历上面数据包(`payload`)的`overload_fields`,得到这些字段相关联的底层数据包(通过他们的`type`)并插入到`overloaded_fields`。 + +现在,当这个字段的值被请求,`getfieldval()`将返回插到`overloaded_fields`中的值。 + +字段被处理有三个方向: + ++ `fields`:明确被设置的字段值,像是pdst在TCP中是(pdst='42') + ++ `overloaded_fields`:重载字段 + ++ `default_fields`:所有的字段都是他们的默认值。(这些字段根据`fields_desc`的初始化构造函数调用`init_fields()`) + +在下面代码中,我们可以观察到一个字段是如何选择的并且看到他的返回值: + +```py +def getfieldval(self, attr): + for f in self.fields, self.overloaded_fields, self.default_fields: + if f.has_key(attr): + return f[attr] + return self.payload.getfieldval(attr) +``` + +字段被插入到`fields`有更高的权限,然后是`overloaded_fields`,最后是`default_fields`,因此如果字段的type在`overloaded_fields`中设置,他的值将会被返回而不是在`default_fields`中获取。 + +现在我们理解了背后的所有的魔法了! + +``` +>>> p = Foo()/Bar1(val=0x1337) +>>> p +> +>>> p.show() +###[ Foo ]### + type= 1 + len= 4 + sep= '\r\n' +###[ Bar1 ]### + val= 4919 +``` + +我们的两个问题都解决了,而没有发太多的功夫。 + +### 理解底层:把所有的东西放在一起 + +最后但不是不重要,理解当构建数据包的时候每一个函数什么时候被调用是很重要的。 + +``` +>>> hexdump(str(p)) +Packet.str=Foo +Packet.iter=Foo +Packet.iter=Bar1 +Packet.build=Foo +Packet.build=Bar1 +Packet.post_build=Bar1 +Packet.post_build=Foo +``` + +正如你所看到的,他首先运行序列的每一个字段,然从头开始构建,一旦所有的协议层构建好了,他们从头开始调用`post_build()`。 + +## 字段 + +这里列出了一些Scapy支持的字段。 + +### 简单的数据类型 + +表示: + ++ X --- 十六进制表示 + ++ LE --- 小端(默认是大端) + ++ Signal --- 有符号的(默认是无符号的) + +``` +ByteField +XByteField + +ShortField +LEShortField +XShortField + +X3BytesField # three bytes (in hexad + +IntField +SignedIntField +LEIntField +LESignedIntField +XIntField + +LongField +XLongField +LELongField + +IEEEFloatField +IEEEDoubleField +BCDFloatField # binary coded decimal + +BitField +XBitField + +BitFieldLenField # BitField specifying a length (used in RTP) +FlagsField +FloatField +``` + +### 枚举 + +字段的值可能来自枚举 + +``` +ByteEnumField("code", 4, {1:"REQUEST",2:"RESPONSE",3:"SUCCESS",4:"FAILURE"}) +``` + +``` +EnumField(name, default, enum, fmt = "H") +CharEnumField +BitEnumField +ShortEnumField +LEShortEnumField +ByteEnumField +IntEnumField +SignedIntEnumField +LEIntEnumField +XShortEnumField +``` + +### 字符串 + +``` +StrField(name, default, fmt="H", remain=0, shift=0) +StrLenField(name, default, fld=None, length_from=None, shift=0): +StrFixedLenField +StrNullField +StrStopField +``` + +### 序列和定长长度 + +``` +FieldList(name, default, field, fld=None, shift=0, length_from=None, count_from=None) + # A list assembled and dissected with many times the same field type + + # field: instance of the field that will be used to assemble and disassemble a list item + # length_from: name of the FieldLenField holding the list length + +FieldLenField # holds the list length of a FieldList field +LEFieldLenField + +LenField # contains len(pkt.payload) + +PacketField # holds packets +PacketLenField # used e.g. in ISAKMP_payload_Proposal +PacketListField +``` + +#### 可变长度字段 + +这是关于Scapy怎么处理字段的可变长度的。这些字段通常可以从另外的字段那知道他们的长度,我们称他们为可变字段和定长字段。其思想是让每一个字段都引用另一个字段,这样当数据包被剖析时,可变就可以从定长字段那知道自己的长度,如果数据包时被组合的,你不必填充满定长字段,直接可以从可变长度推测他的值。 + +问题出现在你意识到可变长度字段和定长字段之间的关系并不总是明确的。有时候定长字段指示了字节长度,有时候是对象的数量。有时候长度包含首部部分,所以你必须减去固定的头部长度来推算出可变字段的长度。有时候长度不是以字节而是以16位来表示的,有时候相同的不变字段被两个不同的可变字段使用,有时候相同的可变字段引用不同的不可变字段,一个是一个字节,一个是来那个字节。 + +##### 定长字段 + +首先,一个定长字段是用FieldLenField定义的(或者是他的派生)。当组装数据包的时候如果他的值是空,他的值将会从引用他的可变长度字段推倒出来。引用用了其他的`length_of`参数或者`count_of`参数,`count_of`参数只有当可变字段拥有一个序列(`PacketListField`或者`FieldListField`)的时候才会有意义。该值将用可变长度字段命名,作为一个字符串。根据那个参数使用`i2len()`或者 `i2count()`方法将会在不可变字段值找个调用。返回的值将会被函数调整提供给合适的参数。调整将适用于两个参数:`i2len()`或者`i2count()`返回的数据包实例和值。默认情况下,调整是不会做什么事的: + +```py +adjust=lambda pkt,x: x +``` + +比如说,如果`the_varfield`是一个序列: + +```py +FieldLenField("the_lenfield", None, count_of="the_varfield") +``` + +或者如果他是16位的: + +```py +FieldLenField("the_lenfield", None, length_of="the_varfield", adjust=lambda pkt,x:(x+1)/2) +``` + +##### 可变长度字段 + +可变长度有:`StrLenField`, `PacketLenField`, `PacketListField`, `FieldListField`, ... + +这有两个第一,当一个数据包被剖析时,他们的长度会从已经已经解析的定长字段长度推到来,连接通络使用length_from参数,应用到一个函数,适用于被解析的数据包的一部分,返回一个字节的长度,例如: + +```py +StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield) +``` + +或者: + +```py +StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield-12) +``` + +对于`PacketListField`和`FieldListField`和他们的派生,当需要长度的时候,工作内容和他们的一样。如果你需要大量的元素,`length_from`参数必须被忽略并且`count_from`参数必须被替代,比如说: + +```py +FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), count_from = lambda pkt: pkt.the_lenfield) +``` + +#### 例子 + +```py +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) ] +``` + +测试`FieldListField`类: + +``` +>>> TestFLF("\x00\x02ABCDEFGHIJKL") +> +``` + +### 特殊的 + +``` +Emph # Wrapper to emphasize field when printing, e.g. Emph(IPField("dst", "127.0.0.1")), + +ActionField + +ConditionalField(fld, cond) + # Wrapper to make field 'fld' only appear if + # function 'cond' evals to True, e.g. + # ConditionalField(XShortField("chksum",None),lambda pkt:pkt.chksumpresent==1) + +PadField(fld, align, padwith=None) + # Add bytes after the proxified field so that it ends at + # the specified alignment from its beginning +``` + +### TCP/IP + +``` +IPField +SourceIPField + +IPoptionsField +TCPOptionsField + +MACField +DestMACField(MACField) +SourceMACField(MACField) +ARPSourceMACField(MACField) + +ICMPTimeStampField +``` + +### 802.11 + +``` +Dot11AddrMACField +Dot11Addr2MACField +Dot11Addr3MACField +Dot11Addr4MACField +Dot11SCField +``` + +### DNS + +``` +DNSStrField +DNSRRCountField +DNSRRField +DNSQRField +RDataField +RDLenField +``` + +### ASN.1 + +``` +ASN1F_element +ASN1F_field +ASN1F_INTEGER +ASN1F_enum_INTEGER +ASN1F_STRING +ASN1F_OID +ASN1F_SEQUENCE +ASN1F_SEQUENCE_OF +ASN1F_PACKET +ASN1F_CHOICE +``` + +### 其他协议 + +``` +NetBIOSNameField # NetBIOS (StrFixedLenField) + +ISAKMPTransformSetField # ISAKMP (StrLenField) + +TimeStampField # NTP (BitField) +``` \ No newline at end of file diff --git a/7.md b/7.md new file mode 100644 index 0000000000000000000000000000000000000000..92042db2cdebfc43f7856545cf494627d1b0ca3e --- /dev/null +++ b/7.md @@ -0,0 +1,73 @@ +# 常见问题 + +> 译者:[飞龙](https://github.com/wizardforcel) + +> 原文:[Troubleshooting](http://www.secdev.org/projects/scapy/doc/troubleshooting.html) + +> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) + + +## 我的 TCP 连接被 Scapy 或者是我的内核重置了 + +内核不知道 Scapy 在他背后做什么。 如果 Scapy 发送 SYN,目标回复 SYN-ACK,并且你的内核看到它,它将回复 RST。 为了防止这种情况,请使用本地防火墙规则(例如 Linux 上的 NetFilter)。 Scapy 不介意本地防火墙。 + +## 我 Ping 不通 127.0.0.1,Scapy 在 127.0.0.1 上或是本地回送接口上不工作 + +回送接口是一个非常特殊的接口。 通过它的数据包没有真正组装和拆卸。 内核将数据包路由到其目的地,而它仍然存储于内部结构中。 你看到的`tcpdump -i lo`只是假的,让你认为一切正常。 内核不知道 Scapy 在背后做什么,所以你在回送接口上看到的也是假的。 这个是不会在本地结构中的,因此内核永远不会收到它。 + +为了和本地的程序交流,你应该在上层协议中构建你的数据包。使用`PF_INET/SOCK_RAW`套接字而不是`PF_PACKET/SOCK_RAW` + +```py +>>> conf.L3socket + +>>> conf.L3socket=L3RawSocket +>>> sr1(IP(dst="127.0.0.1")/ICMP()) +> +``` + +## BPF 过滤器在 PPP 链路上不能工作 + +这是一个已知的 bug。BPF 过滤器必须在 PPP 链路上以不同的偏移来编译。如果你使用`libpcap`(将用来编译 BFP 过滤器),而不是使用 Linux 本地的支持(`PF_PACKET`套接字),他可能会工作。 + +## `traceroute()`在 PPP 链路上不能工作 + +这是一个已知的 bug,BPF 过滤器在 PPP 链路上不能工作。 + +为了能让他正常工作,使用`nofilter=1`: + +``` +>>> traceroute("target", nofilter=1) +``` + +## 画图太丑,字体太大,图片被截断 + +快速修复:用 png 格式 + +``` +>>> x.graph(format="png") +``` + +## 更新 GraphViz 的最新版本 + +尝试提供不同的 DPI 选项(比如说:50,70,75,96,101,125): + +``` +>>> x.graph(options="-Gdpi=70") +``` + +如果它工作了,你可以永久设置它: + +``` +>>> conf.prog.dot = "dot -Gdpi=70" +``` + +你也可以将这一行放在你的`~/.scapy_startup.py`文件中。 + +## 获取帮助 + +常见问题都在 FAQ 中。 + +在`scapy.ml(at)secdev.org`([归档](http://news.gmane.org/gmane.comp.security.scapy.general),[RSS,NNTP](http://gmane.org/info.php?group=gmane.comp.security.scapy.general))上有一个低流量邮件列表。 我们鼓励你向此列表发送问题,错误报告,建议,想法,Scapy 的有趣用法等。 通过发送邮件到`scapy.ml-subscribe(at)secdev.org`来订阅。 + +为了避免垃圾邮件,你必须订阅邮件列表才能发布。 + diff --git a/8.md b/8.md new file mode 100644 index 0000000000000000000000000000000000000000..5cab10295ac1f028441053a4d38f971f24461670 --- /dev/null +++ b/8.md @@ -0,0 +1,187 @@ +# Scapy 开发 + +> 译者:[飞龙](https://github.com/wizardforcel) + +> 原文:[Scapy development](http://www.secdev.org/projects/scapy/doc/development.html) + +> 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) + + +## 项目组织 + +Scapy 开发使用 Mercurial 版本控制系统。 Scapy 的参考资料库位于 。 + +项目管理由 Trac 完成。 Trac 在 Scapy 的参考资料库中工作。 它提供了一个可以自由编辑的 Wiki(请贡献!),可以引用项目的 ticket,变更集,文件。 它还提供了一个 ticket 管理服务,用于避免遗忘补丁或错误。 + +Mercurial 的分布式工作方式使 Philippe 可提供两个仓库,任何人都可以提交:Scapy [社区仓库](http://hg.secdev.org/scapy-com)和 Scapy [Windows 端口仓库](http://hg.secdev.org/scapy-com)。 + +## 如何贡献 + ++ 在 Scapy 中发现了一个错误?[添加 ticket](http://trac.secdev.org/scapy/newticket)。 ++ 改进此文档。 ++ 编程一个新的协议层,并在邮件列表上分享。或者在 bugtracker 上将其添加为改进。 ++ 贡献新的[回归测试](http://trac.secdev.org/scapy/wiki/RegressionTests)。 ++ 在[封包样例](http://trac.secdev.org/scapy/wiki/PacketsSamples)页面上为新协议上传封包样例。 + +## 使用 UTScapy 测试 + +### 什么是 UTScapy? + +UTScapy 是一个小型 Python 程序,它读取测试活动,使用 Scapy 运行活动,并生成一个指示测试状态的报告。报告可以是四种格式之一,即 text,ansi,HTML 或 LaTeX。 + +UTScapy 存在三个基本测试容器,单元测试,测试集和测试活动。单元测试是 Scapy 命令列表,由 Scapy 或 Scapy 的派生作品运行。在单元测试中最后一个命令的评估,将确定单个单元测试的最终结果。测试集是一组具有某种关联的单元测试。测试活动由一或多个测试集组成。测试集和单元测试可以被赋予关键字来形成逻辑分组。运行测试活动时,可以按关键字选择测试。这允许用户在期望的分组内运行测试。 + +对于每个单元测试,测试集和活动,测试的 CRC32 被计算并显​​示为该测试的签名。该测试签名足以确定实际测试按预期运行而没有修改。如果你遇到的一些恶意用户,试图修改或损坏文件,而不改变 CRC32,全局 SHA1 会在整个文件计算。 + +### 活动的语法 + +表 1 显示了 UTScapy 正在寻找的语法指标。 在定义测试的文本文件的每一行中,语法限定符必须是第一个字符。 由 UTScapy 解释的参数是遵循语法限定符的文本描述。 在没有前导语法限定符的情况下出现的行将被视为 Python 命令,前提是它们出现在单元测试的上下文中。 没有语法限定符,并出现在正确上下文之外的行将被 UTScapy 拒绝,并且将发出警告。 + + +| 语法限定符 | 定义 | +| --- | --- | +| `%` | 提供测试活动的名称 | +| `+` | 声明新的测试集 | +| `=` | 声明新的单元测试 | +| `~` | 为当前单元测试声明关键字 | +| `*` | 表示需要在报告中包含的注释 | +| `#` | 测试用例的注解,会被解释器忽略 | + +表 1 - UTScapy 语法限定符 + +在测试报告中的注释有一个上下文。 每个注释将与最后定义的测试容器相关联 - 这是单个单元测试,测试集或测试活动。 与特定容器相关联的多个注释将连接在一起,并将直接显示在测试容器声明后的报告中。 在声明测试活动之前,应该会显示测试文件的一般注释。 对于与测试活动相关联的注释,它们必须位于声明测试活动之后,但在任何测试集或单元测试之前出现。 测试集的注释应在集合的第一个单元测试的定义之前出现。 + +测试活动的通用格式如下表所示: + +``` +% Test Campaign Name +* Comment describing this campaign + + ++ Test Set 1 +* comments for test set 1 + += Unit Test 1 +~ keywords +* Comments for unit test 1 +# Python statements follow +a = 1 +print a +a == 1 +``` + +Python 语句由缺少定义的 UTScapy 语法限定符来标识。 Python 语句直接提供给 Python 解释器,就像在交互式 Scapy shell(交互)中操作一样。循环,迭代和条件是允许的,但必须以空行终止。测试集可以包括多个单元测试,并且可以为每个活动定义多个测试集。甚至可以在特定测试定义文件中包含多个测试活动。使用关键字可以测试整个活动的子集。例如,在测试活动的开发期间,用户可能希望使用关键词“debug”来标记正在开发的新测试。一旦测试成功运行出他们想要的结果,关键字“debug”可以被删除。也可以使用诸如“regression”或“limited”的关键词。 + +重要的是要注意,UTScapy 使用来自最后一个 Python 语句的真值作为测试是通过还是失败的指示符。最后一行可能出现多个逻辑测试。如果结果为 0 或`False`,则测试失败。否则,测试通过。如果需要,使用`assert()`语句可以强制中间值的求值。 + +UTScapy 的语法如表 3 所示 - UTScapy 命令行语法: + +``` +[root@localhost scapy]# ./UTscapy.py –h +Usage: UTscapy [-m module] [-f {text|ansi|HTML|LaTeX}] [-o output_file] + [-t testfile] [-k keywords [-k ...]] [-K keywords [-K ...]] + [-l] [-d|-D] [-F] [-q[q]] +-l : generate local files +-F : expand only failed tests +-d : dump campaign +-D : dump campaign and stop +-C : don't calculate CRC and SHA +-q : quiet mode +-qq : [silent mode] +-n : only tests whose numbers are given (eg. 1,3-7,12) +-m : additional module to put in the namespace +-k ,,... : include only tests with one of those keywords (can be used many times) +-K ,,... : remove tests with one of those keywords (can be used many times) +``` + +表 3 - UTScapy 命令行语法 + +所有参数都是可选的。 没有相关联的参数值的参数可以串在一起(即`-lqF`)。 如果未指定测试文件,则测试定义来自``。 类似地,如果没有指定输出文件,则它被定向到``。 默认输出格式为“ansi”。 表 4 列出了参数,相关联的参数值及其对 UTScapy 的含义。 + +| 参数 | 参数值 | 对 UTScapy 的含义 | +| --- | --- | +| -t | `testfile` | 定义测试活动的测试文件(默认为``) | +| -o | `output_file` | 测试活动结果的输出文件(默认为``) | +| -f | `test` | `ansi`,`HTML`,`LaTeX`,输出报告的格式(默认为`ansi`) | +| -l | | 在本地生成报告的相关文件。对于 HTML,生成 JavaScript 和样式表 | +| -F | | 默认情况下,失败的测试用例会在 HTML 输出中展开 | +| -d | | 在执行活动之前打印测试活动的简短列表。 | +| -D | | 打印测试活动的简短列表并停止。不执行测试活动。 | +| -C | | 不要计算测试签名 | +| -q | | 在测试执行时,不要在屏幕上显示测试流程 | +| -qq | | 静默模式 | +| -n | `testnum` | 只执行由数字列出的这些测试。 测试编号可以使用`–d`或`–D`来获取。测试可以使用以逗号分隔的列表来列出,并且可以包含范围(例如 1, 3-7, 12)。 | +| -m | `module` | 在执行测试之前加载模块。 使用 Scapy 的派生作品来测试。 注意:要作为`__main__`执行的派生作品不会被 UTScapy 作为`__main__`调用。 | +| -k | `kw1, kw2, ...` | 只包含带有关键字`kw1`的测试,可以指定多个关键字。 | +| -K | `kw1, kw2, ...` | 排除带有关键字`kw1`的测试,可以指定多个关键字。 | + +表 4 - UTScapy 参数 + +表 5 显示了具有多个测试集定义的简单测试活动。 此外,关键字指定了仅允许执行有限数量的测试用例。 注意在测试 3 和 5 中使用`assert()`语句来检查中间结果。 测试 2 和 5 为失败而设计。 + +``` +% Example Test Campaign + +# Comment describing this campaign +# +# To run this campaign, try: +# ./UTscapy.py -t example_campaign.txt -f html -o example_campaign.html -F +# + +* This comment is associated with the test campaign and will appear +* in the produced output. + ++ Test Set 1 + += Unit Test 1 +~ test_set_1 simple +a = 1 +print a + += Unit test 2 +~ test_set_1 simple +* this test will fail +b = 2 +a == b + += Unit test 3 +~ test_set_1 harder +a = 1 +b = 2 +c = "hello" +assert (a != b) +c == "hello" + ++ Test Set 2 + += Unit Test 4 +~ test_set_2 harder +b = 2 +d = b +d is b + += Unit Test 5 +~ test_set_2 harder hardest +a = 2 +b = 3 +d = 4 +e = (a * b)**d +# The following statement evaluates to False but is not last; continue +e == 6 +# assert evaluates to False; stop test and fail +assert (e == 7) +e == 1296 + += Unit Test 6 +~ test_set_2 hardest +print e +e == 1296 +``` + +为了查看以 Scapy 为目标的示例,请访问 。将页面底部的示例复制粘贴到文件`demo_campaign.txt`,并对它运行 UTScapy: + +``` +./UTscapy.py -t demo_campaign.txt -f html -o demo_campaign.html –F -l +``` + +在文件`demo_campaign.html`中检测生成的结果。