第 9 章 网络监控和安全性

本章攻略:

  • 嗅探网络数据包
  • 使用pcap转储器把数据包保存为pcap格式
  • 在HTTP数据包中添加额外的首部
  • 扫描远程主机的端口
  • 自定义数据包的IP地址
  • 读取保存的pcap文件以重放流量
  • 扫描数据包的广播

9.1 简介

本章介绍一些有趣的Python攻略,用于监控网络安全和漏洞扫描。我们先使用pcap库嗅探数据包。然后使用Scapy,这是一个瑞士军刀类型的库,可以完成很多相似的任务。本章介绍了一些使用Scapy完成的常见任务,例如把数据包保存为pcap格式、添加额外的首部,以及修改数据包的IP地址。

本章还介绍了一些网络入侵检测相关的高级任务,例如使用保存的pcap文件重放流量和广播扫描。

9.2 嗅探网络数据包

如果你对嗅探本地网络中的数据包感兴趣,可以参考这个攻略。记住,你可能无法嗅探发往你的设备之外的数据包,因为好的网络交换机只会转发指定给你的设备的流量。

9.2.1 准备工作

你需要安装pylibpcap库(0.6.4或以上版本)才能使用这个攻略。这个库托管在SourceForge上,地址为http://sourceforge.net/projects/pylibpcap/

你还需要安装construct库,可以使用pipeasy_install从PyPI上安装,如下面的命令所示:

  1. $ easy_install construct

9.2.2 实战演练

我们可以使用命令行参数指定要嗅探的网络接口名和TCP端口号等。

代码清单9-1是嗅探网络数据包所需的代码,如下所示:

  1. #!usrbin/env python # Python Network Programming Cookbook -- Chapter - 9

  2. # This program is optimized for Python 2.6.

  3. # It may run on any other version with/without modifications.

  4. import argparse

  5. import pcap

  6. from construct.protocols.ipstack import ip_stack

  7. def print_packet(pktlen, data, timestamp):

  8. """ Callback for priniting the packet payload"""

  9. if not data:

  10. return

  11. stack = ip_stack.parse(data)

  12. payload = stack.next.next.next

  13. print payload

  14. def main():

  15. # setup commandline arguments

  16. parser = argparse.ArgumentParser(description='Packet Sniffer') parser.add_argument('--iface', action="store", dest="iface", default='eth0') parser.add_argument('--port', action="store", dest="port", default=80, type=int) # parse arguments

  17. given_args = parser.parse_args()

  18. iface, port = given_args.iface, given_args.port # start sniffing

  19. pc = pcap.pcapObject()

  20. pc.open_live(iface, 1600, 0, 100)

  21. pc.setfilter('dst port %d' %port, 0, 0)

  22. print 'Press CTRL+C to end capture'

  23. try:

  24. while True:

  25. pc.dispatch(1, print_packet)

  26. except KeyboardInterrupt:

  27. print 'Packet statistics: %d packets received, %d packets dropped, %d packets dropped by the interface' % pc.stats()

  28. if __name__ == '__main__':

  29. main()

运行这个脚本时,如果传入的命令行参数是--iface=eth0--port=80,这个脚本会嗅探网页浏览器发出的所有HTTP数据包。运行这个脚本后,如果在浏览器中访问http://www.google.com,会看到如下所示的原始数据包:

  1. python 9_1_packet_sniffer.py --iface=eth0 --port=80

  2. Press CTRL+C to end capture

  3. ''

  4. 0000 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a GET HTTP1.1..

  5. 0010 48 6f 73 74 3a 20 77 77 77 2e 67 6f 6f 67 6c 65 Host: www.google 0020 2e 63 6f 6d 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e .com..Connection 0030 3a 20 6b 65 65 70 2d 61 6c 69 76 65 0d 0a 41 63 : keep-alive..Ac 0040 63 65 70 74 3a 20 74 65 78 74 2f 68 74 6d 6c 2c cept: text/html, 0050 61 70 70 6c 69 63 61 74 69 6f 6e 2f 78 68 74 6d application/xhtm 0060 6c 2b 78 6d 6c 2c 61 70 70 6c 69 63 61 74 69 6f l+xml,applicatio 0070 6e 2f 78 6d 6c 3b 71 3d 30 2e 39 2c 2a 2f 2a 3b n/xml;q=0.9,/; 0080 71 3d 30 2e 38 0d 0a 55 73 65 72 2d 41 67 65 q=0.8..User-Agen 0090 74 3a 20 4d 6f 7a 69 6c 6c 61 2f 35 2e 30 20 28 t: Mozilla/5.0 (

  6. 00A0 58 31 31 3b 20 4c 69 6e 75 78 20 69 36 38 36 29 X11; Linux i686) 00B0 20 41 70 70 6c 65 57 65 62 4b 69 74 2f 35 33 37 AppleWebKit/537

  7. 00C0 2e 33 31 20 28 4b 48 54 4d 4c 2c 20 6c 69 6b 65 .31 (KHTML, like 00D0 20 47 65 63 6b 6f 29 20 43 68 72 6f 6d 65 2f 32 Gecko) Chrome/2

  8. 00E0 36 2e 30 2e 31 34 31 30 2e 34 33 20 53 61 66 61 6.0.1410.43 Safa 00F0 72 69 2f 35 33 37 2e 33 31 0d 0a 58 2d 43 68 72 ri/537.31..X-Chr 0100 6f 6d 65 2d 56 61 72 69 61 74 69 6f 6e 73 3a 20 ome-Variations: 0110 43 50 71 31 79 51 45 49 6b 62 62 4a 41 51 69 59 CPq1yQEIkbbJAQiY

  9. 0120 74 73 6b 42 43 4b 4f 32 79 51 45 49 70 37 62 4a tskBCKO2yQEIp7bJ

  10. 0130 41 51 69 70 74 73 6b 42 43 4c 65 32 79 51 45 49 AQiptskBCLe2yQEI 0140 2b 6f 50 4b 41 51 3d 3d 0d 0a 44 4e 54 3a 20 31 +oPKAQ==..DNT: 1

  11. 0150 0d 0a 41 63 63 65 70 74 2d 45 6e 63 6f 64 69 6e ..Accept-Encodin 0160 67 3a 20 67 7a 69 70 2c 64 65 66 6c 61 74 65 2c g: gzip,deflate, 0170 73 64 63 68 0d 0a 41 63 63 65 70 74 2d 4c 61 6e sdch..Accept-Lan 0180 67 75 61 67 65 3a 20 65 6e 2d 47 42 2c 65 6e 2d guage: en-GB,en-0190 55 53 3b 71 3d 30 2e 38 2c 65 6e 3b 71 3d 30 2e US;q=0.8,en;q=0.

  12. 01A0 36 0d 0a 41 63 63 65 70 74 2d 43 68 61 72 73 65 6..Accept-Charse 01B0 74 3a 20 49 53 4f 2d 38 38 35 39 2d 31 2c 75 74 t: ISO-8859-1,ut 01C0 66 2d 38 3b 71 3d 30 2e 37 2c 2a 3b 71 3d 30 2e f-8;q=0.7,*;q=0.

  13. 01D0 33 0d 0a 43 6f 6f 6b 69 65 3a 20 50 52 45 46 3d 3..Cookie: PREF=

  14. ....

  15. ^CPacket statistics: 17 packets received, 0 packets dropped, 0

  16. packets dropped by the interface

9.2.3 原理分析

这个攻略依赖于pcap库中的pcapObject类创建嗅探器实例。在main()方法中创建了一个pcapObject类的实例,然后使用setfilter()方法设置了一个过滤器,只捕获HTTP数据包。最后,调用dispatch()方法开始嗅探。嗅探到的数据包发给print_packet()函数做进一步处理。

print_packet()函数中,如果数据包中有数据,就使用construct库中的ip_stack.parse()方法把数据提取出来。对数据做低层处理时,construct库能提供很大的帮助。

9.3 使用pcap转储器把数据包保存为pcap格式

pcap(packet capture,数据包捕获)是保存网络数据常用的一种文件格式。关于pcap格式的详细介绍,请访问http://wiki.wireshark.org/Development/LibpcapFileFormat

如果你想把捕获的网络数据包保存到一个文件中,以便做进一步处理,这个攻略为你提供了一个可用的示例。

9.3.1 实战演练

在这个攻略中,我们使用Scapy库嗅探数据包,然后将其写入一个文件。Scapy中的所有实用函数和定义可以使用通配符导入,如下面的代码所示:

  1. from scapy.all import *

这么做只是为了演示,不推荐在生产环境的代码中使用。

Scapy库中的sniff()函数接收的参数是回调函数的名称。我们来定义一个回调函数,把数据包写入文件。

代码清单9-2是使用pcap转储器把数据包保存为pcap格式所需的代码,如下所示:

  1. #!usrbin/env python # Python Network Programming Cookbook -- Chapter - 9

  2. # This program is optimized for Python 2.7.

  3. # It may run on any other version with/without modifications.

  4. import os

  5. from scapy.all import *

  6. pkts = []

  7. count = 0

  8. pcapnum = 0

  9. def write_cap(x):

  10. global pkts

  11. global count

  12. global pcapnum

  13. pkts.append(x)

  14. count += 1

  15. if count == 3:

  16. pcapnum += 1

  17. pname = "pcap%d.pcap" % pcapnum

  18. wrpcap(pname, pkts)

  19. pkts = []

  20. count = 0

  21. def test_dump_file():

  22. print "Testing the dump file..."

  23. dump_file = "./pcap1.pcap"

  24. if os.path.exists(dump_file):

  25. print "dump fie %s found." %dump_file

  26. pkts = sniff(offline=dump_file)

  27. count = 0

  28. while (count <=2):

  29. print "----Dumping pkt:%s----" %count

  30. print hexdump(pkts[count])

  31. count += 1

  32. else:

  33. print "dump fie %s not found." %dump_file

  34. if __name__ == '__main__':

  35. print "Started packet capturing and dumping... Press CTRL+C to exit"

  36. sniff(prn=write_cap)

  37. test\_dump\_file()

运行这个脚本后,会看到类似下面的输出:

  1. # python 9_2_save_packets_in_pcap_format.py ^CStarted packet capturing and dumping... Press CTRL+C to exit Testing the dump file...

  2. dump fie ./pcap1.pcap found.

  3. ----Dumping pkt:0----

  4. 0000 08 00 27 95 0D 1A 52 54 00 12 35 02 08 00 45 00 ..'...RT..5...E.

  5. 0010 00 DB E2 6D 00 00 40 06 7C 9E 6C A0 A2 62 0A 00 ...m..@.|.l..b..

  6. 0020 02 0F 00 50 99 55 97 98 2C 84 CE 45 9B 6C 50 18 ...P.U..,..E.lP.

  7. 0030 FF FF 53 E0 00 00 48 54 54 50 2F 31 2E 31 20 32 ..S...HTTP/1.1 2

  8. 0040 30 30 20 4F 4B 0D 0A 58 2D 44 42 2D 54 69 6D 65 00 OK..X-DB-Time 0050 6F 75 74 3A 20 31 32 30 0D 0A 50 72 61 67 6D 61 out: 120..Pragma 0060 3A 20 6E 6F 2D 63 61 63 68 65 0D 0A 43 61 63 68 : no-cache..Cach 0070 65 2D 43 6F 6E 74 72 6F 6C 3A 20 6E 6F 2D 63 61 e-Control: no-ca 0080 63 68 65 0D 0A 43 6F 6E 74 65 6E 74 2D 54 79 70 che..Content-Typ 0090 65 3A 20 74 65 78 74 2F 70 6C 61 69 6E 0D 0A 44 e: text/plain..D

  9. 00a0 61 74 65 3A 20 53 75 6E 2C 20 31 35 20 53 65 70 ate: Sun, 15 Sep 00b0 20 32 30 31 33 20 31 35 3A 32 32 3A 33 36 20 47 2013 15:22:36G

  10. 00c0 4D 54 0D 0A 43 6F 6E 74 65 6E 74 2D 4C 65 6E 67 MT..Content-Leng 00d0 74 68 3A 20 31 35 0D 0A 0D 0A 7B 22 72 65 74 22 th: 15....{"ret"

  11. 00e0 3A 20 22 70 75 6E 74 22 7D : "punt"} None ----Dumping pkt:1----

  12. 0000 52 54 00 12 35 02 08 00 27 95 0D 1A 08 00 45 00 RT..5...'.....E.

  13. 0010 01 D2 1F 25 40 00 40 06 FE EF 0A 00 02 0F 6C A0 ...%@.@.......l.

  14. 0020 A2 62 99 55 00 50 CE 45 9B 6C 97 98 2D 37 50 18 .b.U.P.E.l..-7P.

  15. 0030 F9 28 1C D6 00 00 47 45 54 20 2F 73 75 62 73 63 .(....GET subsc 0040 72 69 62 65 3F 68 6F 73 74 5F 69 6E 74 3D 35 31 ribe?host_int=51

  16. 0050 30 35 36 34 37 34 36 26 6E 73 5F 6D 61 70 3D 31 0564746&ns_map=1

  17. 0060 36 30 36 39 36 39 39 34 5F 33 30 30 38 30 38 34 60696994_3008084

  18. 0070 30 37 37 31 34 2C 31 30 31 39 34 36 31 31 5F 31 07714,10194611_1

  19. 0080 31 30 35 33 30 39 38 34 33 38 32 30 32 31 31 2C 105309843820211, 0090 31 34 36 34 32 38 30 35 32 5F 33 32 39 34 33 38 146428052_329438

  20. 00a0 36 33 34 34 30 38 34 2C 31 31 36 30 31 35 33 31 6344084,11601531

  21. 00b0 5F 32 37 39 31 38 34 34 37 35 37 37 31 2C 31 30 279184475771,10

  22. 00c0 31 39 34 38 32 38 5F 33 30 30 37 34 39 36 35 39 194828300749659

  23. 00d0 30 30 2C 33 33 30 39 39 31 39 38 32 5F 38 31 39 00,330991982_819

  24. 00e0 33 35 33 37 30 36 30 36 2C 31 36 33 32 37 38 35 35370606,1632785

  25. 00f0 35 5F 31 32 39 30 31 32 32 39 37 34 33 26 75 73 5_12901229743&us 0100 65 72 5F 69 64 3D 36 35 32 30 33 37 32 26 6E 69 er_id=6520372&ni 0110 64 3D 32 26 74 73 3D 31 33 37 39 32 35 38 35 36 d=2&ts=137925856

  26. 0120 31 20 48 54 54 50 2F 31 2E 31 0D 0A 48 6F 73 74 1 HTTP1.1..Host 0130 3A 20 6E 6F 74 69 66 79 33 2E 64 72 6F 70 62 6F : notify3.dropbo 0140 78 2E 63 6F 6D 0D 0A 41 63 63 65 70 74 2D 45 6E x.com..Accept-En 0150 63 6F 64 69 6E 67 3A 20 69 64 65 6E 74 69 74 79 coding:identity 0160 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A 20 6B 65 ..Connection:ke 0170 65 70 2D 61 6C 69 76 65 0D 0A 58 2D 44 72 6F 70 ep-alive..X-Drop 0180 62 6F 78 2D 4C 6F 63 61 6C 65 3A 20 65 6E 5F 55 box-Locale:en_U

  27. 0190 53 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20 44 S..User-Agent:D

  28. 01a0 72 6F 70 62 6F 78 44 65 73 6B 74 6F 70 43 6C 69 ropboxDesktopCli 01b0 65 6E 74 2F 32 2E 30 2E 32 32 20 28 4C 69 6E 75 ent/2.0.22(Linu 01c0 78 3B 20 32 2E 36 2E 33 32 2D 35 2D 36 38 36 3B x; 2.6.32-5-686; 01d0 20 69 33 32 3B 20 65 6E 5F 55 53 29 0D 0A 0D 0A i32; en_US)....None ----Dumping pkt:2----

  29. 0000 08 00 27 95 0D 1A 52 54 00 12 35 02 08 00 45 00 ..'...RT..5...E.

  30. 0010 00 28 E2 6E 00 00 40 06 7D 50 6C A0 A2 62 0A 00 .(.n..@.}Pl..b..

  31. 0020 02 0F 00 50 99 55 97 98 2D 37 CE 45 9D 16 50 10 ...P.U..-7.E..P.

  32. 0030 FF FF CA F1 00 00 00 00 00 00 00 00 ............None

9.3.2 原理分析

这个攻略使用Scapy库中的实用函数sniff()wrpacp()捕获所有网络数据包,然后将其转储到一个文件中。使用sniff()函数捕获数据包后,再调用write_cap()函数处理数据包。处理各数据包时用到了几个全局变量。例如,数据包保存在pkts列表中,还用到了数据包数量和变量数量。数量等于3时,把pkts列表转储到pcap1.pcap文件中,然后把count变量设为零,以便继续捕获后面三个数据包,再将其转储到pcap2.pcap文件中,以此类推。

test_dump_file()函数中,我们假设在工作目录中已经存在首个转储文件pcap1.pcap。这次调用sniff()函数时指定了offline参数,从文件而不是网络中捕获数据包。然后使用hexdump()函数解码各数据包,再把数据包中的内容打印在屏幕上。

9.4 在HTTP数据包中添加额外的首部

有时,处理应用时要添加一个包含自定义信息的HTTP首部。例如,添加授权首部可以在数据包捕获代码中实现HTTP基本认证功能。

9.4.1 实战演练

我们要使用Scapy库中的sniff()函数嗅探数据包,还要定义一个回调函数modify_packet_header(),在指定的数据包中添加额外的首部。

代码清单9-3是在HTTP数据包中添加额外的首部所需的代码,如下所示:

  1. #!usrbin/env python # Python Network Programming Cookbook -- Chapter - 9

  2. # This program is optimized for Python 2.7.

  3. # It may run on any other version with/without modifications.

  4. from scapy.all import *

  5. def modify_packet_header(pkt):

  6. """ Parse the header and add an extra header"""

  7. if pkt.haslayer(TCP) and pkt.getlayer(TCP).dport == 80 and pkt.haslayer(Raw): hdr = pkt[TCP].payload.__dict__

  8. extra_item = {'Extra Header' : ' extra value'}

  9. hdr.update(extra_item)

  10. send_hdr = '\r\n'.join(hdr)

  11. pkt[TCP].payload = send_hdr

  12. pkt.show()

  13. del pkt[IP].chksum

  14. send(pkt)

  15. if __name__ == '__main__':

  16. # start sniffing

  17. sniff(filter="tcp and ( port 80 )", prn=modify\_packet\_header)

运行这个脚本后,会显示一个捕获到的数据包,打印出修改后的版本,再把它发回网络,如下面的输出所示。这个操作可以使用其他捕获工具验证,例如tcpdumpwireshark

  1. $ python 9_3_add_extra_http_header_in_sniffed_packet.py

  2. ###[ Ethernet ]###

  3. dst = 52:54:00:12:35:02

  4. src = 08:00:27:95:0d:1a

  5. type = 0x800

  6. ###[ IP ]###

  7. version = 4L

  8. ihl = 5L

  9. tos = 0x0

  10. len = 525

  11. id = 13419

  12. flags = DF

  13. frag = 0L

  14. ttl = 64

  15. proto = tcp

  16. chksum = 0x171

  17. src = 10.0.2.15

  18. dst = 82.94.164.162

  19. \options \

  20. ###[ TCP ]###

  21. sport = 49273

  22. dport = www

  23. seq = 107715690

  24. ack = 216121024

  25. dataofs = 5L

  26. reserved = 0L

  27. flags = PA

  28. window = 6432

  29. chksum = 0x50f

  30. urgptr = 0

  31. options = []

  32. ###[ Raw ]###

  33. load = 'Extra Header\r\nsent_time\r\nfields\r\ naliastypes\r\npost_transforms\r\nunderlayer\r\nfieldtype\r\ntime\r\ ninitialized\r\noverloaded_fields\r\npacketfields\r\npayload\r\ndefault_

  34. fields'

  35. .

  36. Sent 1 packets.

9.4.2 原理分析

首先,我们使用Scapy库中的sniff()函数嗅探数据包,把modify_packet_header()函数指定为每个数据包的回调函数。所有TCP数据包都有TCP和原始层,发往80端口的数据包是我们要修改的目标。所以,我们从数据包中把当前的首部提取了出来。

然后把额外的首部添加到现有的首部字典尾部。数据包使用show()方法打印在屏幕上,为了避免正确性检查失败,我们把数据包的校验和删掉了。最后,再把数据包发回网络。

9.5 扫描远程主机的端口

如果你尝试使用某个端口连接远程主机,有时会收到一个消息,说“Connection is refused”(拒绝连接)。大多数时候,收到这个消息的原因是远程主机中的服务器停止运行了。遇到这种情况,你可以查看端口是开启还是处在监听状态中。你可以扫描多个端口,找出设备中的可用服务。

9.5.1 实战演练

使用Python标准库socket,我们可以完成这个端口扫描任务。我们要从命令行中接收三个参数:目标主机、起始端口号和终止端口号。

代码清单9-4是扫描远程主机的端口所需的代码,如下所示:

  1. #!usrbin/env python # Python Network Programming Cookbook -- Chapter - 9

  2. # This program is optimized for Python 2.7.

  3. # It may run on any other version with/without modifications.

  4. import argparse

  5. import socket

  6. import sys

  7. def scan_ports(host, start_port, end_port):

  8. """ Scan remote hosts """

  9. #Create socket

  10. try:

  11. sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) except socket.error,err_msg:

  12. print 'Socket creation failed. Error code: '+ str(err_msg[0]) + ' Error mesage: ' + err_msg[1]

  13. sys.exit()

  14. #Get IP of remote host

  15. try:

  16. remote_ip = socket.gethostbyname(host)

  17. except socket.error,error_msg:

  18. print error_msg

  19. sys.exit()

  20. #Scan ports

  21. end_port += 1

  22. for port in range(start_port,end_port):

  23. try:

  24. sock.connect((remote_ip,port))

  25. print 'Port ' + str(port) + ' is open'

  26. sock.close()

  27. sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) except socket.error:

  28. pass # skip various socket errors

  29. if __name__ == '__main__':

  30. # setup commandline arguments

  31. parser = argparse.ArgumentParser(description='Remote Port Scanner') parser.add_argument('--host', action="store", dest="host", default='localhost') parser.add_argument('--start-port', action="store", dest="start_port", default=1, type=int) parser.add_argument('--end-port', action="store", dest="end_port", default=100, type=int) # parse arguments

  32. given_args = parser.parse_args()

  33. host, start\_port, end\_port = given\_args.host, given\_args.start\_port, given\_args.end\_port scan\_ports(host, start\_port, end\_port)

如果运行这个脚本扫描本地设备的1~100号端口,查找打开的端口,会看到类似下面的输出:

  1. # python 9_4_scan_port_of_a_remote_host.py --host=localhost --start-port=1 --end-port=100

  2. Port 21 is open

  3. Port 22 is open

  4. Port 23 is open

  5. Port 25 is open

  6. Port 80 is open

9.5.2 原理分析

这个脚本演示了如何使用Python标准库socket扫描设备,找出打开的端口。scan_ports()函数接收三个参数:主机名、起始端口和终止端口。然后分三步扫描指定范围内的端口。

首先,使用socket()函数创建一个TCP套接字。

成功创建套接字后,使用gethostbyname()函数找出远程主机的IP地址。

找到主机的IP地址后,使用connect()函数尝试连接到这个IP。如果连接成功,就说明端口是打开的。然后,使用close()函数关闭端口,重复第一步开始继续扫描下一个端口。

9.6 自定义数据包的IP地址

如果创建了一个网络数据包,想自定义源IP和目标IP或者端口,可以参考这个攻略。

9.6.1 实战演练

我们可以从命令行中获取所需的全部参数,包括网络接口名、协议名、源IP、源端口、目标IP、目标端口以及可选的TCP旗标。

我们可以使用Scapy库创建一个自定义TCP或UDP数据包,再将其发送到网络中。

代码清单9-5是自定义数据包IP地址所需的代码,如下所示:

  1. #!usrbin/env python # Python Network Programming Cookbook -- Chapter - 9

  2. # This program is optimized for Python 2.7.

  3. # It may run on any other version with/without modifications.

  4. import argparse

  5. import sys

  6. import re

  7. from random import randint

  8. from scapy.all import IP,TCP,UDP,conf,send

  9. def send_packet(protocol=None, src_ip=None, src_port=None, flags=None, dst_ip=None, dst_port=None, iface=None): """Modify and send an IP packet."""

  10. if protocol == 'tcp':

  11. packet = IP(src=src_ip, dst=dst_ip)/TCP(flags=flags, sport=src_port, dport=dst_port) elif protocol == 'udp':

  12. if flags: raise Exception(" Flags are not supported for udp") packet = IP(src=src_ip, dst=dst_ip)/UDP(sport=src_port, dport=dst_port) else:

  13. raise Exception("Unknown protocol %s" % protocol)

  14. send(packet, iface=iface)

  15. if __name__ == '__main__':

  16. # setup commandline arguments

  17. parser = argparse.ArgumentParser(description='Packet Modifier') parser.add_argument('--iface', action="store", dest="iface", default='eth0') parser.add_argument('--protocol', action="store", dest="protocol", default='tcp') parser.add_argument('--src-ip', action="store", dest="src_ip", default='1.1.1.1') parser.add_argument('--src-port', action="store", dest="src_port", default=randint(0, 65535)) parser.add_argument('--dst-ip', action="store", dest="dst_ip", default='192.168.1.51') parser.add_argument('--dst-port', action="store", dest="dst_port", default=randint(0, 65535)) parser.add_argument('--flags', action="store", dest="flags", default=None) # parse arguments

  18. given_args = parser.parse_args()

  19. iface, protocol, src\_ip, src\_port, dst\_ip, dst\_port, flags = given\_args.iface, given\_args.protocol, given\_args.src\_ip,\ given\_args.src\_port, given\_args.dst\_ip, given\_args.dst\_port, given\_args.flags send\_packet(protocol, src\_ip, src\_port, flags, dst\_ip, dst\_port, iface)

若想运行这个脚本,请输入下面的命令:

  1. tcpdump src 192.168.1.66

  2. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes ^C18:37:34.309992 IP 192.168.1.66.60698 > 192.168.1.51.666: Flags [S], seq 0, win 8192, length 0

  3. 1 packets captured

  4. 1 packets received by filter

  5. 0 packets dropped by kernel

  6. $ sudo python 9_5_modify_ip_in_a_packet.py

  7. WARNING: No route found for IPv6 destination :: (no default route?) .

  8. Sent 1 packets.

9.6.2 原理分析

这个脚本定义了send_packet()函数,使用Scapy库构建一个IP数据包。send_packet()函数的参数中包含源地址、源端口、目标地址、目标端口。根据指定的不同协议,例如TCP或UDP,这个函数会构建正确的数据包类型。如果是TCP数据包,可以指定flags参数;否则,抛出异常。

为了构建TCP数据包,Sacpy库提供了IP()/TCP()函数。类似地,若想创建UDP数据包,可以使用IP()/UDP()函数。

最后,使用send()函数发送修改后的数据包。

9.7 读取保存的pcap文件以重放流量

处理网络数据包时,你可能需要读取之前保存的pcap文件来重放流量。此时,你要读取pcap文件,在发送前修改源IP或目标IP地址。

9.7.1 实战演练

我们要使用Scapy读取之前保存的pcap文件。如果没有pcap文件,可以使用9.3节中的攻略创建。

然后,解析命令行参数,连同解析后的原始数据包一起传给send_packet()函数。

代码清单9-6是读取保存的pcap文件来重放流量所需的代码,如下所示:

  1. #!usrbin/env python # Python Network Programming Cookbook -- Chapter - 9

  2. # This program is optimized for Python 2.7.

  3. # It may run on any other version with/without modifications.

  4. import argparse

  5. from scapy.all import *

  6. def send_packet(recvd_pkt, src_ip, dst_ip, count): """ Send modified packets"""

  7. pkt_cnt = 0

  8. p_out = []

  9. for p in recvd_pkt:

  10. pkt_cnt += 1

  11. new_pkt = p.payload

  12. new_pkt[IP].dst = dst_ip

  13. new_pkt[IP].src = src_ip

  14. del new_pkt[IP].chksum

  15. p_out.append(new_pkt)

  16. if pkt_cnt % count == 0:

  17. send(PacketList(p_out))

  18. p_out = []

  19. # Send rest of packet

  20. send(PacketList(p_out))

  21. print "Total packets sent: %d" %pkt_cnt

  22. if __name__ == '__main__':

  23. # setup commandline arguments

  24. parser = argparse.ArgumentParser(description='Packet Sniffer') parser.add_argument('--infile', action="store", dest="infile", default='pcap1.pcap') parser.add_argument('--src-ip', action="store", dest="src_ip", default='1.1.1.1') parser.add_argument('--dst-ip', action="store", dest="dst_ip", default='2.2.2.2') parser.add_argument('--count', action="store", dest="count", default=100, type=int) # parse arguments

  25. given_args = ga = parser.parse_args()

  26. global src_ip, dst_ip

  27. infile, src_ip, dst_ip, count = ga.infile, ga.src_ip, ga.dst_ip, ga.count try:

  28. pkt_reader = PcapReader(infile)

  29. send_packet(pkt_reader, src_ip, dst_ip, count) except IOError:

  30. print "Failed reading file %s contents" % infile sys.exit(1)

运行这个脚本后,默认会读取保存的pcap1.pcap文件,在发送数据包之前分别把源IP地址和目标IP地址修改为1.1.1.12.2.2.2,如下面的输出所示。使用tcpdump可以看到这些数据包的传输过程。

  1. # python 9_6_replay_traffic.py ...

  2. Sent 3 packets.

  3. Total packets sent 3

  4. ----

  5. # tcpdump src 1.1.1.1

  6. tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes ^C18:44:13.186302 IP 1.1.1.1.www > ARennes-651-1-107-2.w2-2.abo.wanadoo.fr.39253: Flags [P.], seq 2543332484:2543332663, ack 3460668268, win 65535, length 179

  7. 1 packets captured

  8. 3 packets received by filter

  9. 0 packets dropped by kernel

9.7.2 原理分析

这个攻略使用Scapy库中的PcapReader()函数从硬盘上读取保存的pcap1.pcap文件,返回一个数据包迭代器。如果提供了命令行参数,就解析这些参数。否则,使用默认值,如前面的输出所示。

命令行参数和数据包列表传给send_packet()函数。这个函数把新数据包保存在p_out列表中,并记录处理后的数据包。然后修改各数据包,更改源IP和目标IP。除此之外,还把checksum包删掉了,因为这个包是基于之前的IP地址生成的。

处理完一个数据包之后,立即将其发送到网络中。然后,一个接着一个地发送剩下的数据包。

9.8 扫描数据包的广播

如果你要找出网络的广播,可以参考这个攻略。我们以广播数据包为例,学习如何查找信息。

9.8.1 实战演练

我们可以使用Scapy库嗅探网络接口收到的数据包。捕获数据包之后,可以使用回调函数处理,从中获取有用的信息。

代码清单9-7是扫描数据包的广播所需的代码,如下所示:

  1. #!usrbin/env python # Python Network Programming Cookbook -- Chapter - 9

  2. # This program is optimized for Python 2.7.

  3. # It may run on any other version with/without modifications.

  4. from scapy.all import *

  5. import os

  6. captured_data = dict()

  7. END_PORT = 1000

  8. def monitor_packet(pkt):

  9. if IP in pkt:

  10. if not captured_data.has_key(pkt[IP].src): captured_data[pkt[IP].src] = []

  11. if TCP in pkt:

  12. if pkt[TCP].sport <= END_PORT:

  13. if not str(pkt[TCP].sport) in captured_data[pkt[IP].src]: captured_data[pkt[IP].src].append(str(pkt[TCP].sport))

  14. os.system('clear')

  15. ip_list = sorted(captured_data.keys())

  16. for key in ip_list:

  17. ports=', '.join(captured_data[key])

  18. if len (captured_data[key]) == 0:

  19. print '%s' % key

  20. else:

  21. print '%s (%s)' % (key, ports)

  22. if __name__ == '__main__':

  23. sniff(prn=monitor\_packet, store=0)

运行这个脚本后,会列出广播流量的源IP地址和端口。下面是一个输出示例,IP地址的第一位被替换掉了:

  1. # python 9_7_broadcast_scanning.py 10.0.2.15

  2. XXX.194.41.129 (80)

  3. XXX.194.41.134 (80)

  4. XXX.194.41.136 (443)

  5. XXX.194.41.140 (80)

  6. XXX.194.67.147 (80)

  7. XXX.194.67.94 (443)

  8. XXX.194.67.95 (80, 443)

9.8.2 原理分析

这个攻略使用Scapy库中的sniff()函数嗅探网络中的数据包。其中定义了一个回调函数monitor_packet(),对数据包做后处理。根据所用协议的不同,例如IP和TCP,分别使用不同的方式排序数据包,然后将其存入名为captured_data的字典中。

如果字典中没有某个IP,就新建一个元素。否则,更新这个IP的端口号。最后,打印这些IP地址,每行显示一个。