Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/metasploit/framework/afp/client.rb
Views: 11784
# -*- coding: binary -*-1module Metasploit2module Framework3module AFP4module Client56def next_id7@request_id ||= -18@request_id += 1910@request_id11end1213def get_info14packet = "\00" # Flag: Request15packet << "\x03" # Command: FPGetSrvrInfo16packet << [next_id].pack('n') # requestID17packet << "\x00\x00\x00\x00" # Data offset18packet << "\x00\x00\x00\x00" # Length19packet << "\x00\x00\x00\x00" # Reserved2021sock.put(packet)2223response = sock.timed_read(1024)24return parse_info_response(response)25end2627def open_session28packet = "\00" # Flag: Request29packet << "\x04" # Command: DSIOpenSession30packet << [next_id].pack('n') # requestID31packet << "\x00\x00\x00\x00" # Data offset32packet << "\x00\x00\x00\x06" # Length33packet << "\x00\x00\x00\x00" # Reserved34packet << "\x01" # Attention Quantum35packet << "\x04" # Length36packet << "\x00\x00\x04\x00" # 10243738sock.put(packet)3940response = sock.timed_read(1024)41return parse_open_session_response(response)42end4344def login(user, pass)45if user == ''46return no_user_authent_login47end4849p = OpenSSL::BN.new("BA2873DFB06057D43F2024744CEEE75B", 16)50g = OpenSSL::BN.new("7", 10)51ra = OpenSSL::BN.new('86F6D3C0B0D63E4B11F113A2F9F19E3BBBF803F28D30087A1450536BE979FD42', 16)52ma = g.mod_exp(ra, p)5354padded_user = (user.length + 1) % 2 != 0 ? user + "\x00" : user55bin_user = [padded_user.length, padded_user].pack("Ca*")5657length = 18 + bin_user.length + ma.to_s(2).length5859packet = "\00" # Flag: Request60packet << "\x02" # Command: DSICommand61packet << [next_id].pack('n') # requestID62packet << "\x00\x00\x00\x00" # Data offset63packet << [length].pack('N') # Length (42)64packet << "\x00\x00\x00\x00" # Reserved65packet << "\x12" # AFPCommand: FPLogin (18)66packet << "\x06\x41\x46\x50\x33\x2e\x31" # AFPVersion: AFP3.167packet << "\x09\x44\x48\x43\x41\x53\x54\x31\x32\x38" #UAM: DHCAST12868packet << bin_user # username69packet << ma.to_s(2) # random number7071sock.put(packet)7273begin74response = sock.timed_read(1024, self.login_timeout)75rescue Timeout::Error76raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)"77end7879flags, command, request_id, error_code, length, reserved = parse_header(response)8081case error_code82when -5001 #kFPAuthContinue83return parse_login_response_add_send_login_count(response, {:p => p, :g => g, :ra => ra, :ma => ma,84:password => pass, :user => user})85when -5023 #kFPUserNotAuth (User doesn't exists)86return :skip_user87else88return :connection_error89end9091end9293def close_session94packet = "\00" # Flag: Request95packet << "\x01" # Command: DSICloseSession96packet << [next_id].pack('n') # requestID97packet << "\x00\x00\x00\x00" #Data offset98packet << "\x00\x00\x00\x00" #Length99packet << "\x00\x00\x00\x00" #Reserved100101sock.put(packet)102end103104def no_user_authent_login105packet = "\00" # Flag: Request106packet << "\x02" # Command: DSICommand107packet << [next_id].pack('n') # requestID108packet << "\x00\x00\x00\x00" # Data offset109packet << "\x00\x00\x00\x18" # Length (24)110packet << "\x00\x00\x00\x00" # Reserved111packet << "\x12" # AFPCommand: FPLogin (18)112packet << "\x06\x41\x46\x50\x33\x2e\x31" #AFP3.1113packet << "\x0f\x4e\x6f\x20\x55\x73\x65\x72\x20\x41\x75\x74\x68\x65\x6e\x74" #UAM: No User Authent114115sock.put(packet)116117begin118response = sock.timed_read(1024, self.login_timeout)119rescue Timeout::Error120raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)"121end122123flags, command, request_id, error_code, length, reserved = parse_header(response)124125if error_code == 0126return :true127else128return false129end130end131132def parse_login_response_add_send_login_count(response, data)133dhx_s2civ = 'CJalbert'134dhx_c2civ = 'LWallace'135136flags, command, request_id, error_code, length, reserved = parse_header(response)137body = get_body(response, length)138id, mb, enc_data = body.unpack("nH32a*")139140mb = OpenSSL::BN.new(mb, 16)141k = mb.mod_exp(data[:ra], data[:p] )142143cipher = OpenSSL::Cipher.new('cast5-cbc').decrypt144cipher.key = k.to_s(2)145cipher.iv = dhx_s2civ146cipher.padding = 0147148nonce = cipher.update(enc_data)149nonce << cipher.final150nonce = nonce[0..15]151nonce = OpenSSL::BN.new(nonce, 2) + 1152153plain_text = nonce.to_s(2) + data[:password].ljust(64, "\x00")154cipher = OpenSSL::Cipher.new('cast5-cbc').encrypt155cipher.key = k.to_s(2)156cipher.iv = dhx_c2civ157auth_response = cipher.update(plain_text)158auth_response << cipher.final159160packet = "\00" # Flag: Request161packet << "\x02" # Command: DSICommand162packet << [next_id].pack('n') # requestID163packet << "\x00\x00\x00\x00" # Data offset164packet << [auth_response.length + 2].pack("N") # Length165packet << "\x00\x00\x00\x00" # Reserved166packet << "\x13" # AFPCommand: FPLoginCont (19)167packet << "\x00"168packet << [id].pack('n')169packet << auth_response170171sock.put(packet)172173begin174response = sock.timed_read(1024, self.login_timeout)175rescue Timeout::Error176raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)"177end178179flags, command, request_id, error_code, length, reserved = parse_header(response)180if error_code == 0181return true182else183return false184end185end186187def parse_open_session_response(response)188_, _, _, error_code, _, _ = parse_header(response)189return error_code == 0 ? true : false190end191192def parse_info_response(response)193parsed_data = {}194195flags, command, request_id, error_code, length, reserved = parse_header(response)196raise RuntimeError, "AFP Server response with error" if error_code != 0197body = get_body(response, length)198machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags =199body.unpack('nnnnn')200201server_name_length = body.unpack('@10C').first202parsed_data[:server_name] = body.unpack("@11A#{server_name_length}").first203204pos = 11 + server_name_length205pos += 1 if pos % 2 != 0 #padding206207server_signature_offset, network_addresses_offset, directory_names_offset,208utf8_servername_offset = body.unpack("@#{pos}nnnn")209210parsed_data[:machine_type] = read_pascal_string(body, machine_type_offset)211parsed_data[:versions] = read_array(body, afp_versions_offset)212parsed_data[:uams] = read_array(body, uam_count_offset)213# skipped icon214parsed_data[:server_flags] = parse_flags(server_flags)215parsed_data[:signature] = body.unpack("@#{server_signature_offset}H32").first216217network_addresses = read_array(body, network_addresses_offset, true)218parsed_data[:network_addresses] = parse_network_addresses(network_addresses)219# skipped directory names220#Error catching for offset issues on this field. Need better error handling all through here221begin222parsed_data[:utf8_server_name] = read_utf8_pascal_string(body, utf8_servername_offset)223rescue224parsed_data[:utf8_server_name] = "N/A"225end226227return parsed_data228end229230def parse_header(packet)231header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order232header[3] = packet[4..7].reverse.unpack("l").first233return header234end235236def get_body(packet, body_length)237body = packet[16..body_length + 15]238raise RuntimeError, "AFP Invalid body length" if body.length != body_length239return body240end241242def read_pascal_string(str, offset)243length = str.unpack("@#{offset}C").first244return str.unpack("@#{offset + 1}A#{length}").first245end246247def read_utf8_pascal_string(str, offset)248length = str.unpack("@#{offset}n").first249return str[offset + 2..offset + length + 1]250end251252def read_array(str, offset, afp_network_address=false)253size = str.unpack("@#{offset}C").first254pos = offset + 1255256result = []257size.times do258result << read_pascal_string(str, pos)259pos += str.unpack("@#{pos}C").first260pos += 1 unless afp_network_address261end262return result263end264265def parse_network_addresses(network_addresses)266parsed_addreses = []267network_addresses.each do |address|268case address.unpack('C').first269when 0 #Reserved270next271when 1 # Four-byte IP address272parsed_addreses << IPAddr.ntop(address[1..4]).to_s273when 2 # Four-byte IP address followed by a two-byte port number274parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}"275when 3 # DDP address (deprecated)276next277when 4 # DNS name (maximum of 254 bytes)278parsed_addreses << address[1..address.length - 1]279when 5 # This functionality is deprecated.280next281when 6 # IPv6 address (16 bytes)282parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]"283when 7 # IPv6 address (16 bytes) followed by a two-byte port number284parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}"285else # Something wrong?286raise RuntimeError, "Error parsing network addresses"287end288end289return parsed_addreses290end291292def parse_flags(flags)293flags = flags.to_s(2)294result = {}295result['Super Client'] = flags[0,1] == '1' ? true : false296result['UUIDs'] = flags[5,1] == '1' ? true : false297result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false298result['Open Directory'] = flags[7,1] == '1' ? true : false299result['Reconnect'] = flags[8,1] == '1' ? true : false300result['Server Notifications'] = flags[9,1] == '1' ? true : false301result['TCP/IP'] = flags[10,1] == '1' ? true : false302result['Server Signature'] = flags[11,1] == '1' ? true : false303result['Server Messages'] = flags[12,1] == '1' ? true : false304result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false305result['Password Changing'] = flags[14,1] == '1' ? true : false306result['Copy File'] = flags[5,1] == '1' ? true : false307return result308end309310end311end312313end314end315316317318