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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::Tcp
  include Msf::Auxiliary::Scanner
  include Msf::Auxiliary::Report

  def initialize
    super(
      'Name'        => 'H.323 Version Scanner',
      'Description' => 'Detect H.323 Version.',
      'Author'      => 'hdm',
      'License'     => MSF_LICENSE
    )

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

  def run_host(ip)

    remote_display    = nil
    remote_product_id = nil
    remote_version_id = nil
    remote_vendor_id  = nil
    remote_protocol   = nil

    begin

    # Wrap this in a timeout to prevent dead services from
    # hanging this thread.
    Timeout.timeout( call_timeout) do

    connect

    caller_name = "SYSTEM\x00"
    h323_id     = Rex::Text.rand_text_alpha(3)
    vendor_id   = Rex::Text.rand_text_alpha(32)
    caller_host = Rex::Socket.source_address( ip )
    caller_port = rand( 32768 ) + 30000
    callee_host = rhost
    callee_port = rport
    conf_guid   = Rex::Text.rand_text(16)
    call_guid   = Rex::Text.rand_text(16)

    pkt_setup = h323_setup_call({
      :caller_name => caller_name,
      :h323_id => h323_id,
      :vendor_id => vendor_id,
      :callee_host => callee_host,
      :callee_port => callee_port,
      :caller_host => caller_host,
      :caller_port => caller_port,
      :conf_guid => conf_guid,
      :call_guid => call_guid
    })

    res = sock.put(pkt_setup) rescue nil
    if not res
      disconnect
      return
    end

    cnt = 0
    while( true )
      info = read_packet
      break if not info

      # The remote side of the call disconnected us
      break if info[:type] == @@H323_STATUS_RELEASE_COMPLETE

      remote_display     = info[40].strip if info[40]
      remote_product_id  = info[:product_id].strip if info[:product_id]
      remote_version_id  = info[:version_id].strip if info[:version_id]
      remote_protocol    = info[:protocol_version].strip  if info[:protocol_version]

      if info[:vendor_id] and [nil, "Unknown"].include?( remote_vendor_id )
        remote_vendor_id   = info[:vendor_id].strip
      end

      # Diagnostics
      # print_status("Host: #{rhost}:#{rport} => #{info.inspect}")

      # The remote side of the call was connected (kill it)
      break if info[:type] == @@H323_STATUS_CONNECT

      # Exit if we already received 5 packets from the server
      break if (cnt +=1) > 5

    end

    # Make sure the call was shut down cleanly
    pkt_release = h323_release_call({
      :caller_name => caller_name,
      :call_guid => call_guid
    })
    sock.put(pkt_release) rescue nil

    # End timeout block
    end

    rescue ::Timeout::Error
    rescue ::Interrupt
      raise $!
    rescue ::Rex::ConnectionError, ::IOError, ::Errno::ECONNRESET, ::Errno::ENOPROTOOPT
    rescue ::Exception
      print_error("#{rhost}:#{rport} #{$!.class} #{$!} #{$!.backtrace}")
    ensure
      disconnect
    end

    if remote_vendor_id
      remote_product_id   = remote_product_id.to_s.gsub(/[^\x20-\x7e]/, '')
      remote_version_id   = remote_version_id.to_s.gsub(/[^\x20-\x7e]/, '')

      banner = "Protocol: #{ remote_protocol }  VendorID: #{ remote_vendor_id }  "

      if remote_version_id and remote_version_id.length > 0
        banner << "VersionID: #{ remote_version_id }  "
      end

      if remote_product_id and remote_product_id.length > 0
        banner << "ProductID: #{ remote_product_id }  "
      end

      if remote_display and remote_display.length > 0
        remote_display = remote_display.to_s.gsub(/[^\x20-\x7e]/, '')
        banner << "DisplayName: #{ remote_display }"
      end

      print_good("#{rhost}:#{rport} #{banner}")
      report_service(:host => rhost, :port => rport, :name => "h323", :info => banner)
    end

  end

  def read_packet
    begin
      ::Timeout.timeout( read_timeout ) do
        ver = sock.read(2)
        return if not (ver and ver == "\x03\x00")

        bin = sock.read(2)
        return if not bin

        len = [ bin.unpack("n")[0] - 4, 0 ].max
        return if len == 0

        bin = sock.read(len)
        return if not bin

        f_desc, cref_len = bin.unpack("CC")
        cref_val = bin[2, cref_len]
        f_type = bin[2 + cref_len, 1].unpack("C")[0]

        return { :type => f_type, :call_ref => cref_val }.merge( read_ies(f_type, bin[ 2 + cref_len + 1, bin.length] ) )
      end
    rescue ::Timeout::Error
    end
    nil
  end

  def read_ies(mtype, data)
    r = { }
    i = 0

    while( i < (data.length - 1) )
      ie_type = data[i, 1].unpack("C")[0]
      break if not ie_type

      ie_len  = 0
      ie_data = ""

      case ie_type
        when @@H225_IE_USER_USER
          ie_len  = data[i+1, 2].unpack("n")[0]
          break if not ie_len

          ie_data = data[i+3, ie_len]
          break if not ie_data

          i = i + 3 + ie_len
        else
          ie_len  = data[i+1, 1].unpack("C")[0]
          break if not ie_len

          ie_data = data[i+2, ie_len]
          break if not ie_data

          i = i + 2 + ie_len
      end

      r[ ie_type ] = ie_data

      if ie_type == @@H225_IE_USER_USER
        r.merge!( ( read_user_user(mtype, ie_data) rescue {} ) )
      end
    end
    r
  end


  # This provides a weak method of decoding USER-USER PDUs. These are
  # actually PER-encoded ASN.1, but we take a few shortcuts since PER
  # encoding is such a pain.
  def read_user_user(mtype, data)
    r = {}

    # Identify the embedded version (2/3/4/5/6 commonly found)
    i = data.index("\x00\x08\x91\x4a\x00")
    return r if not i

    # Store the protocol version
    pver = data[i + 5, 1].unpack("C")[0]

    r[:protocol_version] = pver.to_s

    # Bump the index over the version
    i+= 6

    # print_line( Rex::Text.to_hex_dump( data[i, 32] ) )

    # Set a placeholder VendorID so this system will be reported
    r[:vendor_id] = "Unknown"

    # We use the version offset to identify the destination block location
    # This changes slightly based on the type of packet we receive
    case mtype
    when @@H323_STATUS_ALERTING, @@H323_STATUS_PROCEEDING

      if pver == 2 and data[i, 2] == "\x20\x00"
        r[ :vendor_id ] = "0x%.8x" %  ( data[i + 2, 4].unpack("N")[0] rescue 0 )
        return r
      end

      # Find the offset to the VendorID
      if data[i + 1, 1] != "\xc0"
        i+= 7
      end

      # Stop processing if we can't identify a VendorID
      return r if data[i + 1, 1] != "\xc0"

      # Otherwise just add 2 to the offset of the version
      i += 2

    when @@H323_STATUS_CONNECT

      # Bail early in some corner cases
      return r if data[i, 1] == "\x00"

      # Find the offset to the VendorID
      if data[i + 1, 1] != "\xc0"
        i+= 7
      end

      # Stop processing if we can't identify a VendorID
      return r if data[i + 1, 1] != "\xc0"

      i += 2

      return r
    else
      return r
    end

    # Extract the manufacturer ID
    r[ :vendor_id ] = "0x%.8x" %  ( data[i, 4].unpack("N")[0] rescue 0 )
    i+= 4

    # No Product ID / Version ID in versions less than 3 (unless special cased above)
    return r if pver < 3

    # Get the product_id length (-1)
    product_id_length = data[i, 1].unpack("C")[0] + 1
    i+= 1

    # Extract the product ID
    r[ :product_id ] = data[i, product_id_length]
    i+= product_id_length

    # Get the version ID length (-1)
    version_id_length = data[i, 1].unpack("C")[0] + 1
    i+= 1

    # Extract the version ID
    r[ :version_id ] = data[i, version_id_length]

    # Thats it for now

    r
  end

  def read_timeout
    10
  end

  def call_timeout
    30
  end


  @@H225_IE_BEARER_CAP   = 0x04
  @@H225_IE_DISPLAY      = 0x28
  @@H225_IE_USER_USER    = 0x7e  # Yes, really User-user


  @@H323_STATUS_ALERTING          = 0x01
  @@H323_STATUS_PROCEEDING        = 0x02
  @@H323_STATUS_SETUP             = 0x05
  @@H323_STATUS_SETUP_ACK         = 0x0D
  @@H323_STATUS_CONNECT           = 0x07
  @@H323_STATUS_RELEASE_COMPLETE  = 0x5a
  @@H323_STATUS_FACILITY          = 0x62


  def encap_tpkt(ver,data)
    [ ver, 0, data.length + 4 ].pack("CCn") + data
  end

  def encap_q225(desc, cref_value, msg_type, data)
    [ desc, cref_value.length, cref_value, msg_type].pack("CCA*C") + data
  end

  def encap_q225_standard(msg_type, data)
    encap_q225(0x08, [0x733f].pack("n"), msg_type, data)
  end

  def encap_q225_setup(data)
    encap_q225_standard(0x05, data)
  end

  def encap_q225_release(data)
    encap_q225_standard(0x5a, data)
  end

  def create_ie_byte(ie_type, data)
    [ie_type, data.length].pack("CC") + data
  end

  def create_ie_short(ie_type, data)
    [ie_type, data.length].pack("Cn") + data
  end

  def create_ie_bearer_capability(cap = 0x00038893)
    create_ie_byte( @@H225_IE_BEARER_CAP, [cap].pack("N")[0,3] )
  end

  def create_ie_display(name = "DEBUG\x00")
    create_ie_byte( @@H225_IE_DISPLAY, name )
  end

  def create_ie_user_user(data)
    create_ie_short( @@H225_IE_USER_USER, data )
  end

  #
  # This is ugly. Doing it properly requires a PER capable ASN.1 encoder, which is overkill for this task
  #
  def create_user_info(opts = {})
    h323_id = opts[:h323_id]
    vendor_id = opts[:vendor_id]
    callee_host = opts[:callee_host]
    callee_port = opts[:callee_port]
    caller_host = opts[:caller_host]
    caller_port = opts[:caller_port]
    conf_guid = opts[:conf_guid]
    call_guid = opts[:call_guid]

    buff = "\x05" # Protocol descriminator: X.208/X.209 coded user information

    buff << "\x20\xa8\x06\x00\x08\x91\x4a\x00\x06\x01\x40\x02"

    # H323-ID
    buff << h323_id.unpack("C*").pack("n*")

    buff << "\x22\xc0\x09\x00\x00\x3d\x02\x00\x00\x00\x21"

    # VENDOR: 32 + 2 null bytes
    buff << [vendor_id].pack("Z32") + "\x00\x00"

    buff << "\x00"

    # Remote IP + Remote Port
    buff << ( ::Rex::Socket.addr_aton( callee_host ) + [ callee_port.to_i ].pack("n") )

    buff << "\x00"

    # Conference GUID
    buff << conf_guid

    buff << "\x00\xc5\x1d\x80\x04\x07\x00"

    # Local IP + Port
    buff << ( ::Rex::Socket.addr_aton( caller_host ) + [ caller_port.to_i ].pack("n") )

    buff << "\x11\x00"

    # Call GUID
    buff << call_guid

    buff <<
      "\x82\x49\x10\x47\x40\x00\x00\x06\x04\x01\x00\x4c\x10\xb5" +
      "\x00\x00\x26\x25\x73\x70\x65\x65\x78\x20\x73\x72\x3d\x31" +
      "\x36\x30\x30\x30\x3b\x6d\x6f\x64\x65\x3d\x36\x3b\x76\x62" +
      "\x72\x3d\x6f\x66\x66\x3b\x63\x6e\x67\x3d\x6f\x66\x66\x80" +
      "\x12\x1c\x40\x01\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc6\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\x90\x3c\x00\x00\x64\x0c\x10\xb5\x00\x00\x26\x25" +
      "\x73\x70\x65\x65\x78\x20\x73\x72\x3d\x31\x36\x30\x30\x30" +
      "\x3b\x6d\x6f\x64\x65\x3d\x36\x3b\x76\x62\x72\x3d\x6f\x66" +
      "\x66\x3b\x63\x6e\x67\x3d\x6f\x66\x66\x80\x0b\x0d\x40\x01" +
      "\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\x48\x31\x40\x00\x00\x06\x04\x01\x00\x4c\x10\x09" +
      "\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20" +
      "\x57\x69\x64\x65\x36\x80\x12\x1c\x40\x01\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc6\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\xa0\x26\x00\x00\x65\x0c\x10\x09\x00\x00\x3d\x0f" +
      "\x53\x70\x65\x65\x78\x20\x62\x73\x34\x20\x57\x69\x64\x65" +
      "\x36\x80\x0b\x0d\x40\x01\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\x50\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x60\x13" +
      "\x80\x11\x1c\x00\x01\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc6\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\x13\x00\x00\x66\x0c\x60\x13\x80\x0b\x0d\x00\x01" +
      "\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\x00\x1d\x40\x00\x00\x06\x04\x01\x00\x4c\x20\x13" +
      "\x80\x11\x1c\x00\x01\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc6\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\x13\x00\x00\x67\x0c\x20\x13\x80\x0b\x0d\x00\x01" +
      "\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc7\x00\x23\x40\x00\x00\x06\x04\x01\x00\x48\x78\x00" +
      "\x4a\xff\x00\x80\x01\x00\x80\x11\x1c\x00\x02\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc8\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x19\x00\x00\x68\x08\x78\x00\x4a\xff\x00\x80\x01" +
      "\x00\x80\x0b\x0d\x00\x02\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x00\x22\x40\x00\x00\x06\x04\x01\x00\x48\x68\x4a" +
      "\xff\x00\x80\x01\x00\x80\x11\x1c\x00\x02\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc8\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x18\x00\x00\x69\x08\x68\x4a\xff\x00\x80\x01\x00" +
      "\x80\x0b\x0d\x00\x02\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x00\x22\x40\x00\x00\x06\x04\x01\x00\x48\x70\x4a" +
      "\xff\x00\x80\x01\x00\x80\x11\x1c\x00\x02\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc8\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x18\x00\x00\x6a\x08\x70\x4a\xff\x00\x80\x01\x00" +
      "\x80\x0b\x0d\x00\x02\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x00\x2c\x40\x00\x00\x06\x04\x01\x00\x48\xee\x00" +
      "\x00\x20\x9f\xff\x20\x50\x40\x01\x00\x80\x17\x1c\x20\x02" +
      "\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc8\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x80\x04\x48\x08\x8d\x44\x22\x00\x00\x6b\x08\xee" +
      "\x00\x00\x20\x9f\xff\x20\x50\x40\x01\x00\x80\x11\x0d\x20" +
      "\x02\x00" +

      Rex::Socket.addr_aton( caller_host ) +

      "\x13\xc9\x40\x00\x04\x48\x08\x8d\x44\x01\x00\x01\x00\x01" +
      "\x00\x01\x00\x80\xfa\x02\x80\xef\x02\x70\x01\x06\x00\x08" +
      "\x81\x75\x00\x0d\x80\x1a\x80\x01\xf4\x00\x01\x00\x00\x01" +
      "\x00\x00\x01\x00\x04\x02\x05\x00\x48\x08\x8d\x44\x06\x60" +
      "\x01\x00\x01\x80\x0b\x80\x00\x00\x20\x20\xb5\x00\x00\x26" +
      "\x25\x73\x70\x65\x65\x78\x20\x73\x72\x3d\x31\x36\x30\x30" +
      "\x30\x3b\x6d\x6f\x64\x65\x3d\x36\x3b\x76\x62\x72\x3d\x6f" +
      "\x66\x66\x3b\x63\x6e\x67\x3d\x6f\x66\x66\x80\x00\x01\x20" +
      "\x20\x09\x00\x00\x3d\x0f\x53\x70\x65\x65\x78\x20\x62\x73" +
      "\x34\x20\x57\x69\x64\x65\x36\x80\x00\x02\x20\xc0\xef\x80" +
      "\x00\x03\x20\x40\xef\x80\x00\x04\x08\xf0\x00\x4a\xff\x00" +
      "\x80\x01\x00\x80\x00\x05\x08\xd0\x4a\xff\x00\x80\x01\x00" +
      "\x80\x00\x06\x08\xe0\x4a\xff\x00\x80\x01\x00\x80\x00\x07" +
      "\x09\xdc\x00\x00\x40\x9f\xff\x20\x50\x40\x01\x00\x80\x00" +
      "\x08\x83\x01\x50\x80\x00\x09\x83\x01\x10\x80\x00\x0a\x83" +
      "\x01\x40\x80\x00\x0b\x8a\x0c\x14\x0a\x30\x2d\x31\x36\x2c" +
      "\x33\x32\x2c\x33\x36\x00\x80\x01\x03\x03\x00\x00\x00\x01" +
      "\x00\x02\x00\x03\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00" +
      "\x00\x08\x02\x00\x09\x00\x0a\x00\x0b\x07\x01\x00\x32\x80" +
      "\x96\x61\x41\x02\x80\x01\x80"

    buff
  end

  def create_user_release_info(call_guid)
    "\x05" +
    "\x25\x80\x06\x00\x08\x91\x4a\x00\x05\x01\x11\x00" +
    call_guid +
    "\x02\x80\x01\x00"
  end

  def h323_release_call(opts = {})
    caller_name = opts[:caller_name]
    call_guid = opts[:call_guid]

    encap_tpkt(3,
      encap_q225_release(
        create_ie_display(caller_name) +
        create_ie_user_user(
          create_user_release_info(call_guid )
        )
      )
    )
  end

  def h323_setup_call(opts = {})
    caller_name = opts[:caller_name]
    h323_id = opts[:h323_id]
    vendor_id = opts[:vendor_id]
    callee_host = opts[:callee_host]
    callee_port = opts[:callee_port]
    caller_host = opts[:caller_host]
    caller_port = opts[:caller_port]
    conf_guid = opts[:conf_guid]
    call_guid = opts[:call_guid]

    encap_tpkt(3,
      encap_q225_setup(
        create_ie_bearer_capability() +
        create_ie_display(caller_name) +
        create_ie_user_user(
          create_user_info({
            :h323_id => h323_id,
            :vendor_id => vendor_id,
            :callee_host => callee_host,
            :callee_port => callee_port,
            :caller_host => caller_host,
            :caller_port => caller_port,
            :conf_guid => conf_guid,
            :call_guid => call_guid
          })
        )
      )
    )
  end
end
