序言
压缩 pcap 数据包索引程序 (cppip) 是一种工具,可以从压缩的 pcap 文件中极快地提取数据包。此工具适用于处理大型 pcap 文件的安全和网络人员。本文提供了该工具的完整讨论,分为两部分。第一部分面向最终用户,将详细解释如何构建和使用该工具。第二部分面向C程序员,涵盖了cppip的内部工作原理。
简介
Cppip 是一个命令行实用程序,旨在非常快速地从大型 pcap 文件中提取数据包,而无需解压缩整个文件。它依赖于使用免费提供的 bgzip 压缩的 pcap 文件,bgzip 是一种向后兼容的 gzip 实用程序,拥有特殊的添加剂——能够快速、廉价地动态解压缩文件的特定区域。如果您使用大型 pcap 文件并需要提取一个或多个数据包以进行后续检查,您会发现 cpip 非常有用。如您所见,准备 pcap 文件以与 cpip 一起使用是一个两步过程,即使用 bgzip 压缩 pcap 文件,然后使用 cpppp 对其进行索引。但在使用 cpppp 之前,您首先必须安装它。
安装
Cppip 与 GNU autoconf 脚本一起分发,旨在让您的生活更轻松。那些熟悉自动配置基架工具的人熟悉用于配置和构建此类软件包的命令序列“./configure && make”。然而,在 cppip 的情况下,核心依赖项之一 tabix 库不会在没有一些干预的情况下将自己整齐地安装在主机操作系统上。因为 tabix 维护者没有使用 GNU 强大的 autoconf 框架,所以在构建和安装 cpip 之前,你必须做一些准备工作。为此,请按照以下简单步骤操作:
[sb:tabix-0.2.6]$ sudo cp libtabix.a /usr/local/lib
工具:CPPIP
您将使用 cpppp 的第一个正式版本 1.3。在深入了解一些示例之前,让我们首先探索一下使用该工具时可以指定的所有选项,为此,请使用 -h 开关调用 cppip:
[sb:cppip] $ ./src/cppip -h
Compressed Pcap Packet Indexing Program
(c) Cisco Systems 2013, Inc
Mike Schiffman
Fast compressed pcap indexing and extraction, made easy
see http://blogs.cisco.com/tag/cppip for complete documentation
Usage: cppip [options] [file(s)...]
Indexing:
-i index_mode:index_level index.cppip pcap.gz
index a bgzip compressed pcap.gz file using `index_mode`
index.cppip will be created or overwritten and packets
will be indexed at every `index_level` mark.
invoke with -I for more information/help on indexing
-I print supported index/extract modes/format guidelines
-v index.cppip verify index file
-d index.cppip dump index file
Extracting:
-e index_mode:n|n-m index.cppip pcap.gz new.pcap
extract using `index_mode` the nth packet or n-m packets
from pcap.gz into new.pcap
invoke with -I for more information/help on extracting
-f enable fuzzy matching (timestamp extraction only)
General Options:
-D enable debug messages
-V program version
-h this message
让我们谈谈一些主要选项:
压缩 pcap
首先要做的事。我们需要使用 bgzip 压缩您的 pcap 文件。 在以下示例中,为了获取有关 pcap 文件(压缩或非压缩)内内容的信息,我们将使用 Wireshark 的 capinfos 工具。对于以下所有示例,我们将使用包含超过 7.5M 数据包的 5GB pcap 文件:
[sb:cppip]$ ls -l *.pcap
-rw-r--r-- 1 mike staff 2000000101 Apr 19 20:43 pktdump.pcap
[sb:cppip]$ capinfos -cuae pktdump.pcap.gz
File name: pktdump.pcap.gz
Number of packets: 7552072
Capture duration: 411 seconds
Start time: Fri Apr 19 16:56:44 2013
End time: Fri Apr 19 17:03:35 2013
作为参考,您会注意到 pcap 使用普通的旧 gzip 压缩到大约 892M:
[sb:cppip]$ ls -l *.pcap*
-rw-r--r-- 1 mike staff 838087510 Apr 19 20:43 pktdump.pcap.gz
使用 bgzip 压缩文件会带来一些开销,在我们的例子中,在 7M 时只有大约 838%:
[sb:cppip]$ bgzip pktdump.pcap
[sb:cppip]$ ls -l *.pcap*
-rw-r--r-- 1 mike staff 892089319 Apr 19 20:43 pktdump.pcap.gz
数据包索引 压缩文件
后,需要使用 cpppp 对其进行索引。编制索引时,cppip 将创建一个配套文件,其中包含 pcap.gz 中数据包的 bgzip 偏移量。换句话说,索引文件将保存位于压缩 pcap 中的数据包的地址。这些地址随后将用于稍后快速提取数据包。
目前,cppip 支持两种索引模式:数据包编号和时间戳。数据包编号模式通过数据包在 pcap 文件中的序号位置对数据包进行索引,而时间戳模式则通过数据包的 pcap 标头时间戳为数据包编制索引。决定使用哪种模式来构建索引将与您期望如何提取数据包有关。我们将在接下来的几节中了解有关两者的更多信息。
确定索引模式后,需要选择索引级别。这是一个值,指示 cppip 将存储地址的数据包数。在理想情况下,您可以选择尽可能小的索引级别,并在某些情况下存储每个数据包的地址,并具有近乎即时的查找。这也会导致索引文件非常大。实际上,索引级别将是一个较大的值,可在索引文件大小和查找速度之间提供良好的平衡。要查看 cpip 期望如何指定索引级别,请使用 -I 选项调用 cppip:
[sb:cppip] $ ./src/cppip -I
pkt-num: index_level should be a single integer from:
1 - (total number of packets - 1)
To index every 1000 packets: -i pkt-num:1000
timestamp: index_level should be a number indicating the index
followed by a time range specifier which can be one of
following:
d - days
h - hours
m - minutes
s - seconds
To index every 100 seconds: -i timestamp:100s
通过数据包编号进行数据包索引 选择数据包编号
索引级别时,需要考虑 pcap 中的数据包数量.gz并确定哪个更重要:磁盘空间或执行速度。较小的索引级别转换为更多的数据包被索引,并导致更大的索引文件。对于具有大量数据包的 pcap 文件,这将导致一个非常大的索引文件。这样做的好处是寻道时间越快,因为索引越细,平均而言,cppip 就越接近您的目标提取(我们很快就会看到这一点)。如果您选择尽可能小的索引级别 1,则告诉 cppip “请将每个数据包的地址存储在我的索引文件中”,它将尽职尽责地为 pcap 中的每个数据包写入索引记录。这将导致尽可能大的索引文件和尽可能快的提取,因为 cpip 将知道每个数据包的地址,并可以直接查找包含所需数据包的 BGZF 偏移量的索引记录。在实践中,您可能希望选择在索引文件大小方面提供平衡的东西。
在以下示例中,我们将使用合理的索引级别 1,000 为 pcap.gz 文件编制索引,这将生成 120K 的索引文件:
[sb:cppip]$ ./src/cppip -i pkt-num:1000 index-pn-1000.cppip pktdump.pcap.gz
indexing pktdump.pcap.gz...
wrote 7552072 records to index-pn-01.cppip
[sb:cppip]$ ls -l index-pn-1000.cppip
-rw-r--r-- 1 mike staff 120876 Apr 15 12:03 index-pn-1000.cppip
通过数据包编号
提取数据包 现在您已经构建了索引文件,您实际上可以完成一些工作了!假设您迫切需要来自该 pcap.gz 内部的数据包 3,480,123 到 4,080,012。下面的信息图描述了这个典型的工作流方案:
让我们看看这在命令行上是什么样子的,这样你就知道这需要多长时间,让我们计时:
[sb:cppip] $ time ./src/cppip -e pkt-num:3480123-4080012 index-pn-1000.cppip pktdump.pcap.gz new.pcap
wrote 599890 packets to new.pcap
1.42 real 0.56 user 0.84 sys
甜。在我公认的速度很快的MacBook Pro上使用每7,552个数据包记录的数据包编号索引文件,cppip花了不到一秒半的时间来定位,读取和写入近600,000个数据包。为了更好地衡量,让我们检查一下cppip的工作:
[sb:cppip] $ capinfos -c new.pcap
File name: new.pcap
Number of packets: 599890
看起来不错!使用数据包编号索引,cppip 可以从压缩的 pcap 文件中提取单个数据包或一系列数据包。接下来,我们将继续介绍 cpipp 的时间戳索引和提取功能。
通过时间戳进行数据包索引 时间戳
索引根据数据包在 pcap 文件中的捕获时间戳对数据包进行索引。Cppip 将根据数据包到达 pcap.gz 文件的时间而不是它们在文件中的相对位置来对数据包进行密钥。虽然数据包时间戳几乎总是会增加,但我们不能依赖它们单调地这样做。选择时间戳索引级别时,需要了解捕获文件的持续时间。
目前,从版本 1.3 开始,可以为时间戳索引级别选择的最小值为 1 秒。让我们看一下基于时间戳的索引和提取的标准工作流程:
由于我们知道 pcap.gz 文件跨越相对较短的 411 秒时间范围,让我们使用 1 秒的最小索引级别创建一个索引文件,这将产生一个包含 411 条记录的微小索引文件:
[sb:cppip]$ ./src/cppip -i timestamp:1s index-ts-1s.cppip pktdump.pcap.gz
indexing ../pktdump.pcap.gz...
wrote 411 records to index-ts-1s.cppip
[sb:cppip]$ ls -l index-ts-1s.cppip
-rw-r--r-- 1 mike staff 9920 Apr 20 16:21 index-ts-1s.cppip
通过时间戳提取数据包
虽然无法为数据包索引指定微秒分辨率,但可以选择提取。您可以指定带或不带微秒的时间戳:
让我们来看看另一个典型的 cppip 用例。在这种情况下,您的坚定的思科 IPS 已通知您当地时间下午 5:00 发生了一次闯入尝试。内部取证团队需要从下午 4:59 到下午 5:02 的所有网络流量。由于您之前已设置了一个 bgzip 压缩和索引所有外围 pcap 文件的自动化过程,因此您已准备好处理此请求。让我们抓取事件发生前一分钟和事件发生后两分钟的所有数据包:
此操作的命令行如下所示:
[sb:cppip] $ ./src/cppip -e timestamp:2012-10-07:16:59:00-2012-10-07:17:02:00 index-ts:1s pktdump_20121008000335.pcap.gz new2.pcap
extracting from pktdump.pcap.gz using index-ts:1s...
extract(): 2013-04-19 16:59:00.000000 not found, closest is 2013-04-19 16:59:00.000102 (try -f)
wrote 0 packets to new2.pcap.
这里发生了什么?显然,我们没有为 cpppp 指定一个特定的足够时间戳来匹配相应的起始数据包。要解决此问题,您有两种选择:
[sb:cppip] $ ./src/cppip -f -e timestamp:2012-10-07:16:59:00-2012-10-07:17:02:00 index-ts:1s pktdump_20121008000335.pcap.gz new2.pcap
extracting from pktdump_20121008000335.pcap.gz using index-ts:1s...
start ts: 2012-10-07 16:59:00.000000 not found, instead fuzzy matched on 2012-10-07 16:59:00.000102
stop ts: 2012-10-07 17:02:00.000000 not found, instead fuzzy matched on 2012-10-07 17:02:00.000046
wrote 3461342 packets to new2.pcap.
棒!您已经拿到了数据包,是时候进行一些取证分析了。
最后,让我们探讨一下 cppip 的一些诊断功能。
数据包验证和索引转储
Cppip 提供了一些诊断功能,让您有机会查看索引文件以确保其有效性并浏览其内容。第一个是验证索引文件并显示其一些元数据的简单命令:
[sb:cppip] mike% ./src/cppip -v index-pn-1000.cppip
valid cppip index file
version: 1.3
created: 2013-04-19 20:03:17.463926
packets in pcap:7552072
indexing mode: packet-number
index level: 1000
record count: 7552
我们看到我们的索引文件几乎符合预期。这里的一个要点是确保您使用的索引文件的版本与 cpppp 的版本一致。我可以保证我会尝试使未来的版本向后兼容,但与所有事情一样,您的里程可能会有所不同。
cppip 公开的另一个漂亮的诊断功能是转储索引文件内容的选项。如果您想查看数据包在 pcap 中的物理布局方式,这将非常有用.gz:
[sjc-vpn5-288:~/Code/cppip/cppip] mike% ./src/cppip -d index-pn-1000.cppip |& more
valid cppip index file
version: 1.3
created: 2013-04-19 20:03:17.463926
packets in pcap:7552072
indexing mode: packet-number
index level: 1000
record count: 7552
pkt num:1000
offset: 153b9d4c5
pkt num:2000
offset: 30e3bae9f
pkt num:3000
offset: 4dadb7482
pkt num:4000
...
用户手册到此结束,所以让我们继续看看cppip是如何做到的。
正如我过去所做的那样,在我发布代码的所有技术博客中,我喜欢选择一些关键的代码块并讨论它。那么cppip是如何做到的呢?让我们来了解一下!我们将深入了解索引文件头、索引记录和一些用于提取索引的函数。
数据包编号索引记录 由于我们将探索数据包编号模式的索引和提取过程,因此我们最好先查看数据包编号索引记录
。如您所料,这是一个非常简单的结构。它有一个无符号的 32 位计数器用于数据包编号,并在 pcap 中有一个 64 位地址.gz:
/*
* Packet Number Index Record:
*
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Packet Number |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Virtual BGZF Virtual Record Locator |
* | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct cppip_record_pn
{
uint32_t pkt_num; /** the packet number */
uint64_t bgzf_offset; /** its offset into bgzf file */
};
typedef struct cppip_record_pn cppip_record_pn_t;
#define CPPIP_REC_PN_SIZ sizeof(struct cppip_record_pn)
数据包编号索引 下一个块是用于构建索引
文件的主事件循环。你会注意到 cpipp 做的第一件事是获取文件指针的当前地址到 pcap.gz。未显示的是已经读取并写出 pcap 文件头的代码块,因此对于循环的第一次迭代,对 bgzf_tell() 的调用将反映 cppip 已经读取这些字节的事实。在读取数据包并确保它没有遇到错误或文件末尾后,cppip 会检查它是否在 pcap 中获得了第一个数据包,或者(通过模运算符)一个是索引级别的倍数。如果任一状态为 true,cppip 将写入数据包编号、偏移量到索引文件,并增加几个计数器。如果启用了调试,您将收到一条很好的消息,通知您刚刚发生的事情。下一个块只是跳过数据包的内容——cppip 在索引时不关心它们。此过程将重复,直到遇到错误或 cpip 到达 pcap.gz 的末尾。
for (rec_cnt = 0, pkt_cnt = 1, done = 0; !done; pkt_cnt++)
{
/** ...[pcap packet header][packet]...
* ^
* bgzf fp is pointing here, the BGZF offset to this
* packet.. This is
* the offset we will record in our index
*/
offset = bgzf_tell(c->pcap);
switch (bgzf_read(c->pcap, buf, PCAP_PKTH_SIZ))
{
case -1:
snprintf(c->errbuf, BUFSIZ, "bgzf_read() error
");
return (-1);
case 0:
/* all done */
done = 1;
break;
default:
pcap_h = (pcap_offline_pkthdr_t *)buf;
/** write first packet then write as per index_level */
if (pkt_cnt == 1 || pkt_cnt % c->index_level.num == 0)
{
cppip_rec.pkt_num = pkt_cnt;
cppip_rec.bgzf_offset = offset;
if (write(c->index, &cppip_rec, CPPIP_REC_PN_SIZ) == -1)
{
snprintf(c->errbuf, BUFSIZ, "write(): %s",
strerror(errno));
return (-1);
}
rec_cnt++;
if (c->flags & CPPIP_CTRL_DEBUG)
{
fprintf(stderr, "DBG: add> [%d]: %d @ %llx
",
rec_cnt, pkt_cnt, offset);
}
}
/**
* we don't care about the contents -- we skip past the
* packet
*/
if (bgzf_skip(c->pcap, pcap_h->caplen) == -1)
{
snprintf(c->errbuf, BUFSIZ, "bgzf_skip() error
");
return (-1);
}
}
}
数据包编号提取 提取
逻辑负责获取数据包。
让我们看看它是如何工作的。
首先,cppip 希望确保您没有在命令行上搞砸,并指定一个超过 pcap.gz 中数据包总数的停止数据包。如果您的输入通过了该测试,cppip 将自行设置为查找起始数据包。
如果起始数据包小于索引级别,cppip 将从第一个数据包发出线性搜索,直到到达指定的起始数据包。如果起始数据包大于索引级别,cppip 会将第一个数据包除以索引级别以获得最接近的索引记录,然后 lseek() 到索引文件中的该位置。
接下来,cppip 读取该记录,在 pcap.gz 中寻找适当的位置,然后再次执行线性搜索,直到找到起始数据包。然后,Cppip 下降到提取循环中,读取 pcap 标头以获取数据包捕获长度,然后读取数据包本身。然后,它将 pcap 标头和数据包复制到内存缓冲区,并将该缓冲区写入新的 pcap 文件。Cppip 继续此过程,直到它命中停止数据包或遇到错误。
/** sanity check only checks stop, we verified earlier stop > start */
if (c->e_pkts.pkt_stop > c->cppip_h.pkt_cnt)
{
snprintf(c->errbuf, BUFSIZ,
"extraction would exceed packet count, %d and/or %d > %d
",
c->e_pkts.pkt_start, c->e_pkts.pkt_stop, c->cppip_h.pkt_cnt);
return (-1);
}
/**
* We need to locate the offset of pkt_start and then we can
* extract in a linear fashion until we hit pkt_last.
*
* If the indexing is too coarse the pkt_start will lie before the
* first index. If this is the case we have to do a linear search from
* the very first packet until we find pkt_first...
*/
if (c->e_pkts.pkt_start < c->cppip_index_pn_hdr.index_level)
{
if (linear_search(c, 1, c->e_pkts.pkt_start) == -1)
{
return (-1);
}
}
/** seek to index, obtain closest offset, linear search from there */
else
{
/**
* pkt_start / index_level will give us the closest index record to our
* starting packet. We lseek to 1 before this location so we don't
* step past the record we need.
*/
if (lseek(c->index,
(((c->e_pkts.pkt_start / c->cppip_index_pn_hdr.index_level) - 1)
* CPPIP_REC_PN_SIZ) + CPPIP_FH_SIZ + CPPIP_INDEX_PN_H_SIZ,
SEEK_SET) == -1)
{
snprintf(c->errbuf, BUFSIZ, "lseek() error: %s
", strerror(errno));
return (-1);
}
if (read(c->index, (cppip_record_pn_t *)&rec, CPPIP_REC_PN_SIZ)
!= CPPIP_REC_PN_SIZ)
{
snprintf(c->errbuf, BUFSIZ, "read() error: %s
", strerror(errno));
return (-1);
}
if (bgzf_seek(c->pcap, rec.bgzf_offset, SEEK_SET) == -1)
{
snprintf(c->errbuf, BUFSIZ, "bgzf_seek() error.
");
return (-1);
}
if (linear_search(c, rec.pkt_num, c->e_pkts.pkt_start) == -1)
{
return (-1);
}
}
/** we've got pkt_first, do extraction until we hit pkt_last */
for (c->e_pkts.pkts_w = 0, i = c->e_pkts.pkt_start;
i < (c->e_pkts.pkt_stop + 1); i++, c->e_pkts.pkts_w++)
{
if (bgzf_read(c->pcap, (pcap_offline_pkthdr_t *)&pcap_h, PCAP_PKTH_SIZ)
!= PCAP_PKTH_SIZ)
{
snprintf(c->errbuf, BUFSIZ,
"bgzf_read() error: cant read pcap hdr
");
return (-1);
}
pkt_caplen = pcap_h.caplen;
if (bgzf_read(c->pcap, buf, pkt_caplen) != pkt_caplen)
{
snprintf(c->errbuf, BUFSIZ,
"bgzf_read() error: can't read packet
");
return (-1);
}
memcpy(&buf2, &pcap_h, PCAP_PKTH_SIZ);
memcpy(&buf2[PCAP_PKTH_SIZ], &buf, pkt_caplen);
if (write(c->pcap_new, buf2, PCAP_PKTH_SIZ + pkt_caplen) !=
PCAP_PKTH_SIZ + pkt_caplen)
{
snprintf(c->errbuf, BUFSIZ, "write() error: %s
", strerror(errno));
return (-1);
}
}
return (1);
结论与未来
在本文中,您了解了压缩 pcap 数据包索引和提取 (cpppip) 的最新热潮。我们探讨了如何安装和使用该工具,并偷看了它的一些内部结构。将来,我计划进行相当数量的代码清理和优化。我还计划在文件头中添加微秒级时间戳索引、字节序信息、创建 C 库,可能还有一些基于协议的新索引方法。一如既往,欢迎拉取请求。感谢您抽出宝贵时间,请随时发表评论!
地址:https://blogs.cisco.com/security/tools-of-the-trade-the-compressed-pcap-packet-indexing-program
页面更新:2024-05-19
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号