CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/metasploit/framework/afp/client.rb
Views: 1904
1
# -*- coding: binary -*-
2
module Metasploit
3
module Framework
4
module AFP
5
module Client
6
7
def next_id
8
@request_id ||= -1
9
@request_id += 1
10
11
@request_id
12
end
13
14
def get_info
15
packet = "\00" # Flag: Request
16
packet << "\x03" # Command: FPGetSrvrInfo
17
packet << [next_id].pack('n') # requestID
18
packet << "\x00\x00\x00\x00" # Data offset
19
packet << "\x00\x00\x00\x00" # Length
20
packet << "\x00\x00\x00\x00" # Reserved
21
22
sock.put(packet)
23
24
response = sock.timed_read(1024)
25
return parse_info_response(response)
26
end
27
28
def open_session
29
packet = "\00" # Flag: Request
30
packet << "\x04" # Command: DSIOpenSession
31
packet << [next_id].pack('n') # requestID
32
packet << "\x00\x00\x00\x00" # Data offset
33
packet << "\x00\x00\x00\x06" # Length
34
packet << "\x00\x00\x00\x00" # Reserved
35
packet << "\x01" # Attention Quantum
36
packet << "\x04" # Length
37
packet << "\x00\x00\x04\x00" # 1024
38
39
sock.put(packet)
40
41
response = sock.timed_read(1024)
42
return parse_open_session_response(response)
43
end
44
45
def login(user, pass)
46
if user == ''
47
return no_user_authent_login
48
end
49
50
p = OpenSSL::BN.new("BA2873DFB06057D43F2024744CEEE75B", 16)
51
g = OpenSSL::BN.new("7", 10)
52
ra = OpenSSL::BN.new('86F6D3C0B0D63E4B11F113A2F9F19E3BBBF803F28D30087A1450536BE979FD42', 16)
53
ma = g.mod_exp(ra, p)
54
55
padded_user = (user.length + 1) % 2 != 0 ? user + "\x00" : user
56
bin_user = [padded_user.length, padded_user].pack("Ca*")
57
58
length = 18 + bin_user.length + ma.to_s(2).length
59
60
packet = "\00" # Flag: Request
61
packet << "\x02" # Command: DSICommand
62
packet << [next_id].pack('n') # requestID
63
packet << "\x00\x00\x00\x00" # Data offset
64
packet << [length].pack('N') # Length (42)
65
packet << "\x00\x00\x00\x00" # Reserved
66
packet << "\x12" # AFPCommand: FPLogin (18)
67
packet << "\x06\x41\x46\x50\x33\x2e\x31" # AFPVersion: AFP3.1
68
packet << "\x09\x44\x48\x43\x41\x53\x54\x31\x32\x38" #UAM: DHCAST128
69
packet << bin_user # username
70
packet << ma.to_s(2) # random number
71
72
sock.put(packet)
73
74
begin
75
response = sock.timed_read(1024, self.login_timeout)
76
rescue Timeout::Error
77
raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)"
78
end
79
80
flags, command, request_id, error_code, length, reserved = parse_header(response)
81
82
case error_code
83
when -5001 #kFPAuthContinue
84
return parse_login_response_add_send_login_count(response, {:p => p, :g => g, :ra => ra, :ma => ma,
85
:password => pass, :user => user})
86
when -5023 #kFPUserNotAuth (User doesn't exists)
87
return :skip_user
88
else
89
return :connection_error
90
end
91
92
end
93
94
def close_session
95
packet = "\00" # Flag: Request
96
packet << "\x01" # Command: DSICloseSession
97
packet << [next_id].pack('n') # requestID
98
packet << "\x00\x00\x00\x00" #Data offset
99
packet << "\x00\x00\x00\x00" #Length
100
packet << "\x00\x00\x00\x00" #Reserved
101
102
sock.put(packet)
103
end
104
105
def no_user_authent_login
106
packet = "\00" # Flag: Request
107
packet << "\x02" # Command: DSICommand
108
packet << [next_id].pack('n') # requestID
109
packet << "\x00\x00\x00\x00" # Data offset
110
packet << "\x00\x00\x00\x18" # Length (24)
111
packet << "\x00\x00\x00\x00" # Reserved
112
packet << "\x12" # AFPCommand: FPLogin (18)
113
packet << "\x06\x41\x46\x50\x33\x2e\x31" #AFP3.1
114
packet << "\x0f\x4e\x6f\x20\x55\x73\x65\x72\x20\x41\x75\x74\x68\x65\x6e\x74" #UAM: No User Authent
115
116
sock.put(packet)
117
118
begin
119
response = sock.timed_read(1024, self.login_timeout)
120
rescue Timeout::Error
121
raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)"
122
end
123
124
flags, command, request_id, error_code, length, reserved = parse_header(response)
125
126
if error_code == 0
127
return :true
128
else
129
return false
130
end
131
end
132
133
def parse_login_response_add_send_login_count(response, data)
134
dhx_s2civ = 'CJalbert'
135
dhx_c2civ = 'LWallace'
136
137
flags, command, request_id, error_code, length, reserved = parse_header(response)
138
body = get_body(response, length)
139
id, mb, enc_data = body.unpack("nH32a*")
140
141
mb = OpenSSL::BN.new(mb, 16)
142
k = mb.mod_exp(data[:ra], data[:p] )
143
144
cipher = OpenSSL::Cipher.new('cast5-cbc').decrypt
145
cipher.key = k.to_s(2)
146
cipher.iv = dhx_s2civ
147
cipher.padding = 0
148
149
nonce = cipher.update(enc_data)
150
nonce << cipher.final
151
nonce = nonce[0..15]
152
nonce = OpenSSL::BN.new(nonce, 2) + 1
153
154
plain_text = nonce.to_s(2) + data[:password].ljust(64, "\x00")
155
cipher = OpenSSL::Cipher.new('cast5-cbc').encrypt
156
cipher.key = k.to_s(2)
157
cipher.iv = dhx_c2civ
158
auth_response = cipher.update(plain_text)
159
auth_response << cipher.final
160
161
packet = "\00" # Flag: Request
162
packet << "\x02" # Command: DSICommand
163
packet << [next_id].pack('n') # requestID
164
packet << "\x00\x00\x00\x00" # Data offset
165
packet << [auth_response.length + 2].pack("N") # Length
166
packet << "\x00\x00\x00\x00" # Reserved
167
packet << "\x13" # AFPCommand: FPLoginCont (19)
168
packet << "\x00"
169
packet << [id].pack('n')
170
packet << auth_response
171
172
sock.put(packet)
173
174
begin
175
response = sock.timed_read(1024, self.login_timeout)
176
rescue Timeout::Error
177
raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)"
178
end
179
180
flags, command, request_id, error_code, length, reserved = parse_header(response)
181
if error_code == 0
182
return true
183
else
184
return false
185
end
186
end
187
188
def parse_open_session_response(response)
189
_, _, _, error_code, _, _ = parse_header(response)
190
return error_code == 0 ? true : false
191
end
192
193
def parse_info_response(response)
194
parsed_data = {}
195
196
flags, command, request_id, error_code, length, reserved = parse_header(response)
197
raise RuntimeError, "AFP Server response with error" if error_code != 0
198
body = get_body(response, length)
199
machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags =
200
body.unpack('nnnnn')
201
202
server_name_length = body.unpack('@10C').first
203
parsed_data[:server_name] = body.unpack("@11A#{server_name_length}").first
204
205
pos = 11 + server_name_length
206
pos += 1 if pos % 2 != 0 #padding
207
208
server_signature_offset, network_addresses_offset, directory_names_offset,
209
utf8_servername_offset = body.unpack("@#{pos}nnnn")
210
211
parsed_data[:machine_type] = read_pascal_string(body, machine_type_offset)
212
parsed_data[:versions] = read_array(body, afp_versions_offset)
213
parsed_data[:uams] = read_array(body, uam_count_offset)
214
# skipped icon
215
parsed_data[:server_flags] = parse_flags(server_flags)
216
parsed_data[:signature] = body.unpack("@#{server_signature_offset}H32").first
217
218
network_addresses = read_array(body, network_addresses_offset, true)
219
parsed_data[:network_addresses] = parse_network_addresses(network_addresses)
220
# skipped directory names
221
#Error catching for offset issues on this field. Need better error handling all through here
222
begin
223
parsed_data[:utf8_server_name] = read_utf8_pascal_string(body, utf8_servername_offset)
224
rescue
225
parsed_data[:utf8_server_name] = "N/A"
226
end
227
228
return parsed_data
229
end
230
231
def parse_header(packet)
232
header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order
233
header[3] = packet[4..7].reverse.unpack("l").first
234
return header
235
end
236
237
def get_body(packet, body_length)
238
body = packet[16..body_length + 15]
239
raise RuntimeError, "AFP Invalid body length" if body.length != body_length
240
return body
241
end
242
243
def read_pascal_string(str, offset)
244
length = str.unpack("@#{offset}C").first
245
return str.unpack("@#{offset + 1}A#{length}").first
246
end
247
248
def read_utf8_pascal_string(str, offset)
249
length = str.unpack("@#{offset}n").first
250
return str[offset + 2..offset + length + 1]
251
end
252
253
def read_array(str, offset, afp_network_address=false)
254
size = str.unpack("@#{offset}C").first
255
pos = offset + 1
256
257
result = []
258
size.times do
259
result << read_pascal_string(str, pos)
260
pos += str.unpack("@#{pos}C").first
261
pos += 1 unless afp_network_address
262
end
263
return result
264
end
265
266
def parse_network_addresses(network_addresses)
267
parsed_addreses = []
268
network_addresses.each do |address|
269
case address.unpack('C').first
270
when 0 #Reserved
271
next
272
when 1 # Four-byte IP address
273
parsed_addreses << IPAddr.ntop(address[1..4]).to_s
274
when 2 # Four-byte IP address followed by a two-byte port number
275
parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}"
276
when 3 # DDP address (deprecated)
277
next
278
when 4 # DNS name (maximum of 254 bytes)
279
parsed_addreses << address[1..address.length - 1]
280
when 5 # This functionality is deprecated.
281
next
282
when 6 # IPv6 address (16 bytes)
283
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]"
284
when 7 # IPv6 address (16 bytes) followed by a two-byte port number
285
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}"
286
else # Something wrong?
287
raise RuntimeError, "Error parsing network addresses"
288
end
289
end
290
return parsed_addreses
291
end
292
293
def parse_flags(flags)
294
flags = flags.to_s(2)
295
result = {}
296
result['Super Client'] = flags[0,1] == '1' ? true : false
297
result['UUIDs'] = flags[5,1] == '1' ? true : false
298
result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false
299
result['Open Directory'] = flags[7,1] == '1' ? true : false
300
result['Reconnect'] = flags[8,1] == '1' ? true : false
301
result['Server Notifications'] = flags[9,1] == '1' ? true : false
302
result['TCP/IP'] = flags[10,1] == '1' ? true : false
303
result['Server Signature'] = flags[11,1] == '1' ? true : false
304
result['Server Messages'] = flags[12,1] == '1' ? true : false
305
result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false
306
result['Password Changing'] = flags[14,1] == '1' ? true : false
307
result['Copy File'] = flags[5,1] == '1' ? true : false
308
return result
309
end
310
311
end
312
end
313
314
end
315
end
316
317
318