[sb:cppip] $ ./src/cppip -hCompressed Pcap Packet Indexing Program(c) Cisco Systems 2013, IncMike Schiffman Fast compressed pcap indexing and extraction, made easysee http://blogs.cisco.com/tag/cppip for complete documentationUsage: cppip [options] [file(s)...]Indexing: -i index_mode:index_level index.cppip pcap.gzindex a bgzip compressed pcap.gz file using `index_mode`index.cppip will be created or overwritten and packetswill be indexed at every `index_level` mark.invoke with -I for more information/help on indexing -Iprint supported index/extract modes/format guidelines -v index.cppipverify index file -d index.cppipdump index fileExtracting: -e index_mode:n|n-m index.cppip pcap.gz new.pcapextract using `index_mode` the nth packet or n-m packetsfrom pcap.gz into new.pcapinvoke with -I for more information/help on extracting -fenable fuzzy matching (timestamp extraction only)General Options: -Denable debug messages -Vprogram version -hthis message
让我们谈谈一些主要选项:-i (索引)通常,这是第一步 — 您将为每个 pcap 创建一个索引文件.gz-e (摘录)当您要从 pcap 中提取一个或多个数据包时,使用此模式.gz-I (打印模式)此选项显示支持的索引模式-v (验证)此选项用于验证索引文件是否已正确生成、检查其版本以及查看它有多少条记录-d (转储)此选项转储整个索引文件-f(模糊匹配)此选项允许在时间戳模式下提取时进行模糊匹配(稍后会详细介绍)-D (调试)启用调试消息压缩 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.gzFile name: pktdump.pcap.gzNumber of packets: 7552072Capture duration: 411 secondsStart time: Fri Apr 19 16:56:44 2013End 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 -Ipkt-num:index_level should be a single integer from:1 - (total number of packets - 1)To index every 1000 packets:-i pkt-num:1000timestamp:index_level should be a number indicating the indexfollowed by a time range specifier which can be one offollowing:d - daysh - hoursm - minutess - secondsTo 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.gzindexing 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下面的信息图描述了这个典型的工作流方案:Cppip 在索引文件中查询:使用指定的数据包范围,cppip 在索引文件内部查找起始数据包的最近 BGZF 偏移量如果 cpppip 足够幸运地直接登陆数据包索引,它将知道您想要的数据包范围在 pcap.gz 中开始的确切地址如果没有,就像上面的情况一样,它将尽可能接近这是有效的,因为数据包以顺序、单调递增的方式存储因此,cppip 知道数据包编号 3,473,920 是它知道所需起始数据包 3,480,123 的地址的最接近的前置邻居Cppip 索引直接到 pcap.gz:使用从索引文件查询中获得的 BGZF 偏移量,cpip 将直接在 pcap 中查找该地址.gz并进行线性搜索以查找所需的起始数据包Cppip 将数据包写入 new.pcap:找到起始数据包后,cppip 将复制原始的 24 字节 pcap 文件头,然后将指定范围内的每个数据包写入 new.pcap让我们看看这在命令行上是什么样子的,这样你就知道这需要多长时间,让我们计时:
[sb:cppip] $ time ./src/cppip -e pkt-num:3480123-4080012 index-pn-1000.cppip pktdump.pcap.gz new.pcapwrote 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.pcapFile name: new.pcapNumber 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.gzindexing ../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
通过时间戳提取数据包虽然无法为数据包索引指定微秒分辨率,但可以选择提取您可以指定带或不带微秒的时间戳:以微秒为单位:YYYY-MM-DD:HH:MM:SS.uuuuuu无微秒: YYYY-MM-DD:HH:MM:SS让我们来看看另一个典型的 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.pcapextracting 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 指定一个特定的足够时间戳来匹配相应的起始数据包要解决此问题,您有两种选择:指定特定时间戳:Cppip 很好心地告诉您与您的请求最接近的匹配时间戳,因此您可以使用该时间戳但是,您可能会遇到关闭时间戳的相同问题(但是,这是捕获从开始时间戳到pcap.gz结束的所有数据包的有用方法)使用模糊匹配:有了这个方便的选项,cppip 将查找指定的时间戳,但如果找不到它,cpip 将在最接近命令行指定的时间戳上开始和停止匹配让我们试试看[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.pcapextracting 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.000102stop ts: 2012-10-07 17:02:00.000000 not found, instead fuzzy matched on 2012-10-07 17:02:00.000046wrote 3461342 packets to new2.pcap.
棒您已经拿到了数据包,是时候进行一些取证分析了最后,让我们探讨一下 cppip 的一些诊断功能数据包验证和索引转储Cppip 提供了一些诊断功能,让您有机会查看索引文件以确保其有效性并浏览其内容第一个是验证索引文件并显示其一些元数据的简单命令:
[sb:cppip] mike% ./src/cppip -v index-pn-1000.cppip valid cppip index fileversion:1.3created:2013-04-19 20:03:17.463926packets in pcap:7552072indexing mode:packet-numberindex level:1000record count:7552
我们看到我们的索引文件几乎符合预期这里的一个要点是确保您使用的索引文件的版本与 cpppp 的版本一致我可以保证我会尝试使未来的版本向后兼容,但与所有事情一样,您的里程可能会有所不同cppip 公开的另一个漂亮的诊断功能是转储索引文件内容的选项如果您想查看数据包在 pcap 中的物理布局方式,这将非常有用.gz:[sjc-vpn5-288:~/Code/cppip/cppip] mike% ./src/cppip -d index-pn-1000.cppip |& morevalid cppip index fileversion: 1.3created: 2013-04-19 20:03:17.463926packets in pcap:7552072indexing mode: packet-numberindex level: 1000record count: 7552pkt num:1000offset: 153b9d4c5pkt num:2000offset: 30e3bae9fpkt num:3000offset: 4dadb7482pkt 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\n"); 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\n", 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\n"); 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\n", 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\n", 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\n", strerror(errno)); return (-1); } if (bgzf_seek(c->pcap, rec.bgzf_offset, SEEK_SET) == -1) { snprintf(c->errbuf, BUFSIZ, "bgzf_seek() error.\n"); 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\n"); 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\n"); 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\n", strerror(errno)); return (-1); } } return (1);
结论与未来在本文中,您了解了压缩 pcap 数据包索引和提取 (cpppip) 的最新热潮我们探讨了如何安装和使用该工具,并偷看了它的一些内部结构将来,我计划进行相当数量的代码清理和优化我还计划在文件头中添加微秒级时间戳索引、字节序信息、创建 C 库,可能还有一些基于协议的新索引方法一如既往,欢迎拉取请求感谢您抽出宝贵时间,请随时发表评论地址:https://blogs.cisco.com/security/tools-of-the-trade-the-compressed-pcap-packet-indexing-program
(图片来源网络,侵删)
0 评论