Perl Net::Pcap で pcap ファイルを見てみる
たばこを吸っていたら、ふと pcap ファイルを解析してみたくなったので調べてみました。
ちゃんとした人は、C で libpcap を直で叩くんでしょうけど、私はものぐさなので Perl で試してみます。
# いまどき Perl かよという声がする…
pcap ファイルを用意してみる
とりあえず、今回はパケットキャプチャが目的では無いので、適当に tcpdump でキャプチャします。
# tcpdump -i eth0 -n -s 0 -w moetora.pcap tcp port 80
別に 80/tcp に限定する必要はないのだけれど、なんとなく。
適当にキャプチャして終わらせます。
Net::Pcap をインストールしてみる
Net::Pcapをインストールしないと始まりません。
Gentoo だったら portage にあるので、そこからインストールしてみることにします。
# emerge -s pcap
* dev-perl/Net-Pcap
Latest version available: 0.160.0
Latest version installed: [ Not Installed ]
Size of files: 80 kB
Homepage: http://search.cpan.org/dist/Net-Pcap/
Description: Perl Net::Pcap - Perl binding to the LBL pcap
License: || ( Artistic GPL-1 GPL-2 GPL-3 )
# emerge dev-perl/Net-Pcap
メジャーなディストリビューションなら大概用意されていると思いますが、そうじゃなければ cpan からインストールしてください。
Net::Pcap を使ってみる
Net::Pcapを見ればわかりますが、とりあえず今回使うのは、
- Net::Pcap::open_offline($filename, \$err)
- Net::Pcap::loop($pcap_t, $cnt, \&callback_fn, $user_data)
- Net::Pcap::close($pcap_t)
このへんです。
上から、「pcap ファイルを開いて」「パケットごとに読み込んで」「閉じる」ですかね。
とりあえず 1 パケット読み込んでみます。
1 #!/usr/bin/perl
2
3 use warnings;
4 use strict;
5 use Net::Pcap;
6
7 if (scalar @ARGV != 1) {
8 print STDERR "Usage: ./pcap.pl pcapfile\n";
9 }
10
11 my $file = $ARGV[0];
12 my $err;
13 my $user_data = "TEST STRING\n";
14
15 # open pcap descriptor
16 my $pd = Net::Pcap::open_offline($file, \$err);
17 if (defined($err)) {
18 print STDERR "$err\n";
19 exit;
20 }
21
22 # analyze pcap (read 1 packet)
23 Net::Pcap::loop($pd, 1, \&print_header, $user_data);
24
25 # close pcap descriptor
26 Net::Pcap::close($pd);
27
28 sub print_header {
29 my ($user_data, $hdr, $pkt) = @_;
30
31 print $user_data;
32 print "Len : $hdr->{'len'}\n";
33 print "Caplen: $hdr->{'caplen'}\n";
34 print "Time : $hdr->{'tv_sec'} sec, $hdr->{'tv_usec'} usec\n";
35 return 0;
36 }
16行目で pcap ファイルを読み込んで、
23行目で 1 パケット読み込んで、それに関して、print_header サブルーチンを実行して、
26行目で pcap ファイルを閉じる。
print_header の中では、パケット全体の長さと、パケット部分の長さと、送受信時刻(タイムスタンプ)を表示させてます。
これを実行してみると、
./pcap.pl moetora.pcap TEST STRING Len : 74 Caplen: 74 Time : 1333185109 sec, 839719 usec
ほー、という感じです。
ちなみに、$user_data は今回はしょうもない目的に使ってますが、
ハッシュリファレンスとか渡して、パケット解析結果を返したりするのが普通の使い方なんでしょう。
もうちょっとNet::Pcap を使ってみる
さすがに pcap ヘッダを読んだだけだと刺されそうなので、もう少し遊んでみました。
1 #!/usr/bin/perl
2
3 use warnings;
4 use strict;
5 use Net::Pcap;
6
7 my $ETHER_HDR_LEN = 14; # src: 6, dst: 6, type: 2
8 my $IP_HDR_LEN = 20;
9 my $TCP_HDR_LEN = 20 + 12; # option len = 12
10
11 if (scalar @ARGV != 1) {
12 print STDERR "Usage: ./pcap.pl pcapfile\n";
13 }
14
15 my $file = $ARGV[0];
16 my $err;
17 my $user_data = "TEST STRING\n";
18
19 # open pcap descriptor
20 my $pd = Net::Pcap::open_offline($file, \$err);
21 if (defined($err)) {
22 print STDERR "$err\n";
23 exit;
24 }
25
26 # analyze pcap
27 Net::Pcap::loop($pd, 1, \&print_header, $user_data);
28
29 # close pcap descriptor
30 Net::Pcap::close($pd);
31
32 sub print_header {
33 my ($user_data, $hdr, $pkt) = @_;
34
35 print $user_data;
36 print "Len : $hdr->{'len'}\n";
37 print "Caplen: $hdr->{'caplen'}\n";
38 print "Time : $hdr->{'tv_sec'} sec, $hdr->{'tv_usec'} usec\n";
39
40 my $eth_hdr = substr($pkt, 0, $ETHER_HDR_LEN);
41 my $ip_hdr = substr($pkt, $ETHER_HDR_LEN, $IP_HDR_LEN);
42 my $tcp_hdr = substr($pkt, $ETHER_HDR_LEN + $IP_HDR_LEN, $TCP_HDR_LEN);
43 my $payload = substr($pkt, $ETHER_HDR_LEN + $IP_HDR_LEN + $TCP_HDR_LEN);
44
45 print $payload;
46 }
HTTPなパケットをひとつ捕まえて、それを読んでみました。
$TCP_HDR_LEN のオプション長などは別途 wireshark などで確認したものなので、すごく反則です。
ほんとはちゃんとひとつずつ読んでいって解析しないといけませんが、まあそんなやる気もそんなスペースもここにはありません。
で、これを実行してみると
TEST STRING Len : 737 Caplen: 737 Time : 1333185109 sec, 858741 usec GET / HTTP/1.1 Host: www.moetora.com User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:11.0) Gecko/20100101 Firefox/11.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: ja,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive
もうちょっと、ふーんという感じになれました。
ちなみに、pcap ファイルのフォーマットについては、こちらなどで解説されておりました。
今回は、$pkt の中しかみてませんが、$hdr の中ももう少し楽しく読めそうです。
あとがき
こういうのは普通(の情報系の人)だったら、おそくとも大学4年の5月ぐらいには触れている話なんだろうな…















