文件在这里可以找到:
PcapngReaderPython/pcapng_demo.py · duocore/TurtleRock - Gitee.com
#coding=utf-8
#
# 说明文字大部分是这个网址翻译的:
# https://pcapng.github.io/pcapng/draft-ietf-opsawg-pcapng.txt
# 注意:
# 1、测试的pcapng文件里有些类型的包没有出现,下面的脚本执行过程中有一些未覆盖到。
# 2、一些option因为时间关系没有解析。后续有时间再补
# 3、运行版本python3.4+
from enum import Enum
from optparse import OptionParser
def round_to_4byte(input_integer_value):
"""
32位向上对齐。例如输入值=3,则返回值=4。又比如0=>0, 5=>8
:param input_integer_value: 需要向上对齐的数字
:return:
"""
return (input_integer_value + 3) & (~3)
class BlockTypeCode(Enum):
BTS_SectionHeaderBlock = 0x0A0D0D0A
BTS_InterfaceDescriptionBlock = 0x00000001
BTS_PacketBlock_Obsolete = 0x00000002
BTS_SimplePacketBlock = 0x00000003
BTS_NameResolutionBlock = 0x00000004
BTS_InterfaceStatisticsBlock = 0x00000005
BTS_EnhancedPacketBlock = 0x00000006
BTS_DecryptionSecretsBlock = 0x0000000A
BTS_CustomBlockWhichCanBeCopied = 0x00000BAD
BTS_CustomBlockWhichShouldNotBeCopied = 0x40000BAD
class PcapngBlockHeader:
"""
3.1. General Block Structure
捕获文件以块的形式组织,这些块相互堆叠以形成文件。 所有块共享一个通用格式,如图1所示。.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Block Type |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Block Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ Block Body /
/ /* 可变长度, 但是必须32位(4字节)对齐*/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Block Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 1: Basic block structure.
这些字段具有以下含义:
Block Type (32 bits): 标识块的唯一值。最高有效位(MSB)等于1的值保留供本地使用。 They allow to save private data
to the file and to extend the file format. The list of currently defined types can be found in Appendix B
Block Total Length: 此块的总大小,以字节为单位。例如一个没有正文的块的长度是12个字节。
Block Body: 块的内容。
Block Total Length: 此块的总大小,以字节为单位。该字段和复制前值放在尾部以方便文件的反向访问。
这种在所有块之间共享的结构使得处理文件和跳过不需要或未知的块变得容易。 一些块可以在内部包含
其他块(嵌套块)。 一些块是必需的,即如果它们不存在则转储文件无效,其他块是可选的。
The General Block Structure allows defining other blocks if needed. A parser that does non understand them
can simply ignore their content.
"""
block_type = -1
block_total_length = -1
_is_big_endian = True
def __init__(self, is_big_endian):
self._is_big_endian = is_big_endian
def read_from_file_object(self, file_object):
#open("file.pcapng", "rb") with file_object
return_value = False
try:
string_from_bytes_endian = "big"
if not self._is_big_endian:
string_from_bytes_endian = "little"
data = file_object.read(4)
# 一般而言 big: 网络字节序, little:主机字节序
self.block_type = int.from_bytes(data, string_from_bytes_endian) # 'big' or 'little'
data = file_object.read(4)
self.block_total_length = int.from_bytes(data, string_from_bytes_endian)
print("block type:%x, block total length:%x"%(self.block_type, self.block_total_length))
return_value = True
except Exception as e:
print(e)
return return_value
class PcapngOptions:
"""
3.5. Options
所有block的body部分都可以嵌入可选字段。可选字段可用于插入一些在读取数据时可能有用的信息,但对于数据包处理来
说并不是真正需要的。因此每个工具都可以读取可选字段(如果有)的内容,或者跳过其中的一些甚至全部。
一次跳过所有可选字段很简单,因为大多数块由具有固定格式的第一部分和可选的第二个部分组成。 因此,块长度字段(
Block Length,出现在通用块结构中,参见第 2.1 节)可用于跳过所有内容,直到下一个块。
选项是一系列类型 - 长度 - 值字段,每个字段包含一个值:
Option Type (2 bytes): 它包含指定当前 TLV 记录类型的代码。最高有效位等于 1 的选项类型保留供本地使用;因此,
不能保证所用的code在所有捕获文件(由其他应用程序生成)中是唯一的。In case of vendor-specific extensions
that have to be identified uniquely, vendors must request an Option Code whose MSB is equal to zero.
Option Length (2 bytes): 它包含以下“选项值”字段的实际长度,不含填充字节。
Option Value (variable length): 它包含给定选项的值,与32位边界对齐(4字节对齐)。该字段的实际长度(即排除
填充字节后)由选项长度字段指定。
选项可能会重复多次(例如一个接口有多个关联的IP地址)TODO: mention for each option, if it can/shouldn't
appear more than one time. The option list is terminated by a Option which uses the special
'End of Option' code (opt_endofopt).
The format of the optional fields is shown in Figure 7.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Code | Option Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ Option Value /
/ /* variable length, 4字节对齐(32 bits)*/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
/ . . . other options . . . /
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Option Code == opt_endofopt | Option Length == 0 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 7: Options format.
以下code可以出现在任何可选字段中:
Name Code Length Description Example(s)
opt_endofopt 0 0 It delimits the end of the optional fields. This block cannot be repeated within a given list of options.
opt_comment 1 variable 包含与当前块关联的注释的 UTF-8 字符串。 "This packet is the beginning of all of our problems" / "Packets 17-23 showing a bogus TCP retransmission, as reported in bugzilla entry 1486!" / "Captured at the southern plant" / "I've checked again, now it's working ok" / ...
"""
_option_buffer = None
_option_length = 0
def __init__(self, option_buffer, option_length):
self._option_buffer = option_buffer
self._option_length = option_length
def parse_option_for_section_header_block(self, section_endian):
"""
解析并打印所有用于section header block的options。
除了第2.5节中定义的选项外,还有以下选项在此块中有效:
名字 代码 长度 说明 例子
shb_hardware 2 可变 UTF-8字符串,描述创建此section的硬件。 "x86 Personal Computer" / "Sun Sparc Workstation" / ...
shb_os 3 可变 UTF-8字符串,创建此section的操作系统名称。 "Windows XP SP2" / "openSUSE 10.2" / ...
shb_userappl 4 可变 UTF-8字符串,创建此section的应用程序名称。 "dumpcap V0.99.7" / ...
:section_endian: "big"或者"little"表示大端还是小端机器
:return:
"""
remain_option_length = self._option_length
cursor = 0
opt_endofopt = 0
pnp = {2:"opt_shb_hardware", 3:"opt_shb_os", 4:"opt_shb_userappl"}
while remain_option_length != 0:
option_code = int.from_bytes(self._option_buffer[cursor : cursor+2], section_endian)
if option_code == opt_endofopt:
print("match end of option list op code.")
break
option_length = int.from_bytes(self._option_buffer[cursor+2 : cursor+4], section_endian)
option_value = self._option_buffer[cursor+4 : cursor+4+option_length]
if option_code in pnp:
print("option:%s, option_length:%d(%d)" % (pnp[option_code], option_length, round_to_4byte(option_length)),
"option_value:", option_value)
else:
print("option:%d, option_length:%d(%d)"%(option_code, option_length, round_to_4byte(option_length)),
"option_value:", option_value)
cursor += (4 + round_to_4byte(option_length))
def parse_option_for_interface_description_block(self, section_endian):
pnp = {2: "if_name", 3: "if_description", 4: "if_IPv4addr",
5: "if_IPv6addr", 6: "if_MACaddr", 7: "if_EUIaddr",
8: "if_speed", 9: "if_tsresol", 10: "if_tzone",
11: "if_filter", 12: "if_os", 13: "if_fcslen",
14: "if_tsoffset"}
remain_option_length = self._option_length
cursor = 0
opt_endofopt = 0
while remain_option_length != 0:
option_code = int.from_bytes(self._option_buffer[cursor: cursor + 2], section_endian)
if option_code == opt_endofopt:
print("match end of option list op code.")
break
option_length = int.from_bytes(self._option_buffer[cursor + 2: cursor + 4], section_endian)
option_value = self._option_buffer[cursor + 4: cursor + 4 + option_length]
if option_code in pnp:
print("option:%s, option_length:%d(%d)" % (
pnp[option_code], option_length, round_to_4byte(option_length)),
"option_value:", option_value)
else:
print("option:%d, option_length:%d(%d)" % (option_code, option_length, round_to_4byte(option_length)),
"option_value:", option_value)
cursor += (4 + round_to_4byte(option_length))
class PcapngSecionHeaderBlock:
"""
Section Header Block是必要数据。它用于标识捕获转储文件的一个section的开头。 Section Header Block不包含数据,
而是标识逻辑相关的块(接口、数据包)列表。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
0 | Block Type = 0x0A0D0D0A |
+---------------------------------------------------------------+
4 | Block Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8 | Byte-Order Magic |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
12| Major Version | Minor Version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16| |
| Section Length |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24/ /
/ Options (variable) /
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Block Total Length |
+---------------------------------------------------------------+
Section Header Block 的块类型是与4个字符的字符串“\r\n\n\r”对应的整数(0x0A0D0D0A)。使用此值有两个原因:
1,此数字用于检测文件是否已通过FTP或 HTTP 从一台机器传输到另一台机器并进行了不适当的ASCII转换。在这种情况
下,此字段的值将不同于标准值 ("\r\n\n\r"),并且读取方可以检测到可能已损坏的文件。
2,该值是回文的,因此无论节的字节序如何,读取方都能够识别节标题块。字节顺序是通过读取Byte-Order Magic来识
别的,它位于块类型之后的8个字节。
Block Total Length: total size of this block,以字节为单位。
Byte-Order Magic: 幻数,其值为十六进制数 0x1A2B3C4D。这个数字可以用来区分这个section是小端机还是大端机上生成的。
Major Version:当前格式主版本号。当前值为1。如果格式的更改导致工具无法读取旧格式存档,则该值应该更改。
Minor Version:当前格式次版本号。当前值为0。如果格式的更改导致工具无法读取旧格式存档,则该值应该更改。
Section Length:此section的长度,以字节为单位,不包含本Section Header Block的长度,此字段可用于跳过该section
,以便在大文件中更快地导航。Section Length为-1(0xFFFFFFFFFFFFFFFF)表示未指定节的大小,跳过该节的唯一方法是
解析其包含的块(block)。请注意如果此字段有效(即不是 -1),则其值始终与32位对齐,因为所有块(block)都与32位
边界对齐。此外在访问此字段时应特别小心:由于文件中所有块的对齐方式都是32位,因此不能保证此字段与64位边界对齐
。这可能在64位工作站上发生问题。
Options: 可选的选项列表(根据第2.5节中定义的规则格式化)
添加新的块类型或选项不一定需要更改Major或Minor版本号,因为不知道块类型或选项的代码可以跳过它;仅当跳过块或选项
会导致无法工作时,才需要更改Minor版本号。
除了第2.5节中定义的选项外,还有以下选项在此块中有效:
名字 代码 长度 说明 例子
shb_hardware 2 可变 UTF-8字符串,描述创建此section的硬件。 "x86 Personal Computer" / "Sun Sparc Workstation" / ...
shb_os 3 可变 UTF-8字符串,创建此section的操作系统名称。 "Windows XP SP2" / "openSUSE 10.2" / ...
shb_userappl 4 可变 UTF-8字符串,创建此section的应用程序名称。 "dumpcap V0.99.7" / ...
"""
magic = -1
version_major = -1
version_minor = -1
section_length = -1 # 如值为-1表示未知
_endian = ""
incorrect_format = False
block_type = 0x0A0D0D0A
block_total_length = -1
block_total_length_with_padding = -1
def read_from_file_object(self, file_object):
return_value = False
try:
# 这里先不计算,因为还不知道字节序,下面要根据magic的值确定是否要转换。
# file_offset + 8 (函数调用前还有个0x0A0D0D0A头)
block_total_length_raw = file_object.read(4)
# file_offset + 12
data = file_object.read(4)
self.magic = int.from_bytes(data, "big")
if 0x1A2B3C4D != self.magic:
self._endian = "little"
self.magic = int.from_bytes(data, "little")
if 0x1A2B3C4D != self.magic:
self.incorrect_format = True
return False
else:
self._endian = "big"
self.block_total_length = int.from_bytes(block_total_length_raw, self._endian)
self.block_total_length_with_padding = round_to_4byte(self.block_total_length)
print("block type:%#x, block total length:%#x(with padding: %#x)"%(self.block_type,
self.block_total_length,
self.block_total_length_with_padding))
# file_offset + 14
data = file_object.read(2)
self.version_major = int.from_bytes(data, self._endian)
# file_offset + 16
data = file_object.read(2)
self.version_minor = int.from_bytes(data, self._endian)
print("File version: %d.%d"%(self.version_major, self.version_minor))
# file_offset + 24
data = file_object.read(8)
self.section_length = int.from_bytes(data, self._endian)
print("section length : %#x" % self.section_length)
data_raw_options = file_object.read(self.block_total_length_with_padding - 24 - 4)
po = PcapngOptions(data_raw_options, self.block_total_length_with_padding - 24 - 4)
po.parse_option_for_section_header_block(self._endian)
data = file_object.read(4)
block_total_length_backward = int.from_bytes(data, self._endian)
print("Block total length backward:%#x"%block_total_length_backward)
if block_total_length_backward != self.block_total_length:
print("错误,前后块长度不一致。")
return False
return_value = True
except Exception as e:
print(e)
return return_value
def get_endian(self):
return self._endian
class PcapngInterfaceDescriptionBlock:
"""4.2. Interface Description Block (必需)
网络接口描述块是必要的。 需要此块来细化进行捕获的网络接口的特征。为了将捕获的数据正确关联到相应的接口,必须在使用
它的任何其他块之前定义接口描述块;因此,该块通常紧跟在Section Header Block之后。
接口描述块仅在它所属的section内有效。The structure of a Interface Description Block is shown in Figure 9.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
0 | Block Type = 0x00000001 |
+---------------------------------------------------------------+
4 | Block Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-&#