[CodeSnippet]Perl抓包分析之DNS debug

Posted by c4pr1c3 on August 3, 2010

依赖的Perl第三方库在Ubuntu上安装:

  • libnet-pcap-perl
  • libnet-dns-perl

如果还需要其他Perl第三方库,可以通过apt-file(Ubuntu Lucid默认没有安装)来检索所需要的pm文件在哪个deb包。

例如:

$ apt-file search Net::DNS::Packet
 libnet-dns-perl: /usr/share/man/man3/Net::DNS::Packet.3pm.gz
$ apt-file search Net::Pcap
 libnet-pcap-perl: /usr/share/man/man3/Net::Pcap.3pm.gz

Wireshark虽然也可以抓包分析,但是用可编程的脚本语言来辅助分析可以省去很多肉眼比对的工作。

下面直接上代码:

#!/usr/bin/perl -w
#
# Copyright 2003, Brian Hatch, released under the GPL
#
# watch_dns:
#   A program to watch for inbound DNS queries, and print the
#   source, destination, and requested domain name of the queries.
#
# You'll need to fill this in with your actual IP address
# (If we didn't restrict the destination IP address, we'd
# catch all our outbound queries too.)
#
# Original Downloaded from:
# http://www.hackinglinuxexposed.com/articles/20030730.html
#
# Modified by huangwei.me 2010-08-02

# 本机IP地址定义
my $MY_IP_ADDRESS='10.2.1.83';
# 非特权权限的uid/gid,可以用id命令查看当前用户的uid和gid
my $UNPRIV="1001";
# 自行指定监听网卡,留空将使用系统默认探测到的网卡
my $MY_DEV="vmnet8";
#
# No changes required hereafter
#
use Net::Pcap;
use Net::DNS::RR;
use FileHandle;
use English;  # $UID等价于$<, $EUID等价于$>
use strict;

STDOUT->autoflush(1);

while ( 1 ) {

	my $pid = fork();
	if ( ! defined $pid ) { die "Unable to fork.  Yikes." };

	if ( $pid ) {
		# Parent process (running as root) will wait for
		# child.  If child exits, we'll create another one.
		wait();
		sleep(1);  # To keep us from respawning too fast if necessary.
	} else {
		print "Child starting\n";

		# Child process will do actual sniffing.
		# First, create our packet capturing device
		my($pcap_t) = create_pcap();

		unless ( $pcap_t ) {
			die "Unable to create pcap";
		}

		# Let's stop running as root.  Since we already
		# have our pcap descriptor, we can still use it.
		$EGID="$UNPRIV $UNPRIV";	# setgid and setgroups()
		$GID=$UNPRIV;
		$UID=$UNPRIV; $EUID=$UNPRIV;

		# Capture packets forever.
		Net::Pcap::loop($pcap_t, -1, \&process_pkt, 0);

		# Technically, we shouldn't get here since the loop
		# is infinite (-1), but just in case, close and exit.
		Net::Pcap::close($pcap_t);
		exit 1;
	}
}

sub create_pcap {
	my $promisc = 1;   # enter promiscuous mode or not.
	my $snaplen = 1500; # the maximum number of bytes to capture from each packet

	my $to_ms = 0;			# timeout
	my $opt=1;                          # Sure, optimisation is good...
	my($err,$net,$mask,$dev,$filter_t);

	my $filter = "udp dst port 53 or udp src port 53"; # pcap capture filter

	# Look up an appropriate device (eth0 usually)
	$dev = $MY_DEV || Net::Pcap::lookupdev(\$err);
	print "sniffing on ", $dev, "\n";
	$dev or die "Net::Pcap::lookupdev failed.  Error was $err";

	if ( (Net::Pcap::lookupnet($dev, \$net, \$mask, \$err) ) == -1 ) {
		die "Net::Pcap::lookupnet failed.  Error was $err";
	}

	# Actually open up our descriptor
	my $pcap_t = Net::Pcap::open_live($dev, $snaplen, $promisc, $to_ms, \$err);
	$pcap_t || die "Can't create packet descriptor.  Error was $err";

	if ( Net::Pcap::compile($pcap_t, \$filter_t, $filter, $opt, $net) == -1 ) {
		die "Unable to compile filter string '$filter'\n";
	}

	# Make sure our sniffer only captures those bytes we want in
	# our filter.
	Net::Pcap::setfilter($pcap_t, $filter_t);

	# Return our pcap descriptor
	$pcap_t;
}

# Routine to process the packet -- called by Net::Pcap::loop()
# every time an appropriate packet is snagged.
sub process_pkt {
	my($user_data, $hdr, $pkt) = @_;

	my($src_ip) = 26;           # start of the source IP in the packet
	my($dst_ip) = 30;           # start of the dest IP in the packet
	my($udp) = 42;              # start of UDP pkt payload
	my($domain_start) = 55;     # start of the domain in the packet
	my($data);

	# extract the source IP addr into dotted quad form.
	my($source) = sprintf("%d.%d.%d.%d",
		ord( substr($pkt, $src_ip, 1) ),
		ord( substr($pkt, $src_ip+1, 1) ),
		ord( substr($pkt, $src_ip+2, 1) ),
		ord( substr($pkt, $src_ip+3, 1) ));

	# extract the destination IP addr into dotted quad form.
	my($destination) = sprintf("%d.%d.%d.%d",
		ord( substr($pkt, $dst_ip, 1) ),
		ord( substr($pkt, $dst_ip+1, 1) ),
		ord( substr($pkt, $dst_ip+2, 1) ),
		ord( substr($pkt, $dst_ip+3, 1) ));

	$data = substr($pkt, $domain_start);

	$data =~ s/0.*//g;             # strip off everything after the domain
	$data =~ s/[^-a-zA-Z0-9]/./g;    # change the domain component separators
	# back int to dots.

	print "$source -> $destination: $data\n" if ( $source and $destination and $data);
	dump_dns(parse_dns($pkt, $udp));
}

# dump dns query & response in details
sub dump_dns {
	my $dns = $_;
	$dns->print;

}

sub parse_dns {
	my($pkt, $udp) = @_;
	my $udp_payload = substr($pkt, $udp);
	my $dns = Net::DNS::Packet->new(\$udp_payload);
	return $dns;
}