mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-05 23:53:28 +02:00
487 lines
15 KiB
Lua
487 lines
15 KiB
Lua
print("Loading hfudt")
|
|
bit32 = require("bit32")
|
|
|
|
-- create the HFUDT protocol
|
|
p_hfudt = Proto("hfudt", "HFUDT Protocol")
|
|
|
|
-- create fields shared between packets in HFUDT
|
|
local f_data = ProtoField.string("hfudt.data", "Data")
|
|
|
|
-- create the fields for data packets in HFUDT
|
|
local f_length = ProtoField.uint16("hfudt.length", "Length", base.DEC)
|
|
local f_control_bit = ProtoField.uint8("hfudt.control", "Control Bit", base.DEC)
|
|
local f_reliable_bit = ProtoField.uint8("hfudt.reliable", "Reliability Bit", base.DEC)
|
|
local f_message_bit = ProtoField.uint8("hfudt.message", "Message Bit", base.DEC)
|
|
local f_obfuscation_level = ProtoField.uint8("hfudt.obfuscation_level", "Obfuscation Level", base.DEC)
|
|
local f_sequence_number = ProtoField.uint32("hfudt.sequence_number", "Sequence Number", base.DEC)
|
|
local f_message_position = ProtoField.uint8("hfudt.message_position", "Message Position", base.DEC)
|
|
local f_message_number = ProtoField.uint32("hfudt.message_number", "Message Number", base.DEC)
|
|
local f_message_part_number = ProtoField.uint32("hfudt.message_part_number", "Message Part Number", base.DEC)
|
|
local f_type = ProtoField.uint8("hfudt.type", "Type", base.DEC)
|
|
local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC)
|
|
local f_type_text = ProtoField.string("hfudt.type_text", "TypeText")
|
|
local f_sender_id = ProtoField.uint16("hfudt.sender_id", "Sender ID", base.DEC)
|
|
local f_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash")
|
|
|
|
-- create the fields for control packets in HFUDT
|
|
local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC)
|
|
local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII)
|
|
local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC)
|
|
|
|
local SEQUENCE_NUMBER_MASK = 0x07FFFFFF
|
|
|
|
p_hfudt.fields = {
|
|
f_length,
|
|
f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version,
|
|
f_sender_id, f_hmac_hash,
|
|
f_message_position, f_message_number, f_message_part_number, f_obfuscation_level,
|
|
f_control_type, f_control_type_text, f_ack_sequence_number, f_data
|
|
}
|
|
|
|
local control_types = {
|
|
[0] = { "ACK", "Acknowledgement" },
|
|
[1] = { "Handshake", "Handshake" },
|
|
[2] = { "HandshakeACK", "Acknowledgement of Handshake" },
|
|
[3] = { "HandshakeRequest", "Request a Handshake" }
|
|
}
|
|
|
|
local message_positions = {
|
|
[0] = "ONLY",
|
|
[1] = "LAST",
|
|
[2] = "FIRST",
|
|
[3] = "MIDDLE"
|
|
}
|
|
|
|
local packet_types = {
|
|
[0] = "Unknown",
|
|
[1] = "DomainConnectRequestPending",
|
|
[2] = "DomainList",
|
|
[3] = "Ping",
|
|
[4] = "PingReply",
|
|
[5] = "KillAvatar",
|
|
[6] = "AvatarData",
|
|
[7] = "InjectAudio",
|
|
[8] = "MixedAudio",
|
|
[9] = "MicrophoneAudioNoEcho",
|
|
[10] = "MicrophoneAudioWithEcho",
|
|
[11] = "BulkAvatarData",
|
|
[12] = "SilentAudioFrame",
|
|
[13] = "DomainListRequest",
|
|
[14] = "RequestAssignment",
|
|
[15] = "CreateAssignment",
|
|
[16] = "DomainConnectionDenied",
|
|
[17] = "MuteEnvironment",
|
|
[18] = "AudioStreamStats",
|
|
[19] = "DomainServerPathQuery",
|
|
[20] = "DomainServerPathResponse",
|
|
[21] = "DomainServerAddedNode",
|
|
[22] = "ICEServerPeerInformation",
|
|
[23] = "ICEServerQuery",
|
|
[24] = "OctreeStats",
|
|
[25] = "SetAvatarTraits",
|
|
[26] = "AvatarIdentityRequest",
|
|
[27] = "AssignmentClientStatus",
|
|
[28] = "NoisyMute",
|
|
[29] = "AvatarIdentity",
|
|
[30] = "NodeIgnoreRequest",
|
|
[31] = "DomainConnectRequest",
|
|
[32] = "DomainServerRequireDTLS",
|
|
[33] = "NodeJsonStats",
|
|
[34] = "OctreeDataNack",
|
|
[35] = "StopNode",
|
|
[36] = "AudioEnvironment",
|
|
[37] = "EntityEditNack",
|
|
[38] = "ICEServerHeartbeat",
|
|
[39] = "ICEPing",
|
|
[40] = "ICEPingReply",
|
|
[41] = "EntityData",
|
|
[42] = "EntityQuery",
|
|
[43] = "EntityAdd",
|
|
[44] = "EntityErase",
|
|
[45] = "EntityEdit",
|
|
[46] = "DomainServerConnectionToken",
|
|
[47] = "DomainSettingsRequest",
|
|
[48] = "DomainSettings",
|
|
[49] = "AssetGet",
|
|
[50] = "AssetGetReply",
|
|
[51] = "AssetUpload",
|
|
[52] = "AssetUploadReply",
|
|
[53] = "AssetGetInfo",
|
|
[54] = "AssetGetInfoReply",
|
|
[55] = "DomainDisconnectRequest",
|
|
[56] = "DomainServerRemovedNode",
|
|
[57] = "MessagesData",
|
|
[58] = "MessagesSubscribe",
|
|
[59] = "MessagesUnsubscribe",
|
|
[60] = "ICEServerHeartbeatDenied",
|
|
[61] = "AssetMappingOperation",
|
|
[62] = "AssetMappingOperationReply",
|
|
[63] = "ICEServerHeartbeatACK",
|
|
[64] = "NegotiateAudioFormat",
|
|
[65] = "SelectedAudioFormat",
|
|
[66] = "MoreEntityShapes",
|
|
[67] = "NodeKickRequest",
|
|
[68] = "NodeMuteRequest",
|
|
[69] = "RadiusIgnoreRequest",
|
|
[70] = "UsernameFromIDRequest",
|
|
[71] = "UsernameFromIDReply",
|
|
[72] = "AvatarQuery",
|
|
[73] = "RequestsDomainListData",
|
|
[74] = "PerAvatarGainSet",
|
|
[75] = "EntityScriptGetStatus",
|
|
[76] = "EntityScriptGetStatusReply",
|
|
[77] = "ReloadEntityServerScript",
|
|
[78] = "EntityPhysics",
|
|
[79] = "EntityServerScriptLog",
|
|
[80] = "AdjustAvatarSorting",
|
|
[81] = "OctreeFileReplacement",
|
|
[82] = "CollisionEventChanges",
|
|
[83] = "ReplicatedMicrophoneAudioNoEcho",
|
|
[84] = "ReplicatedMicrophoneAudioWithEcho",
|
|
[85] = "ReplicatedInjectAudio",
|
|
[86] = "ReplicatedSilentAudioFrame",
|
|
[87] = "ReplicatedAvatarIdentity",
|
|
[88] = "ReplicatedKillAvatar",
|
|
[89] = "ReplicatedBulkAvatarData",
|
|
[90] = "DomainContentReplacementFromUrl",
|
|
[91] = "ChallengeOwnership",
|
|
[92] = "EntityScriptCallMethod",
|
|
[93] = "ChallengeOwnershipRequest",
|
|
[94] = "ChallengeOwnershipReply",
|
|
[95] = "OctreeDataFileRequest",
|
|
[96] = "OctreeDataFileReply",
|
|
[97] = "OctreeDataPersist",
|
|
[98] = "EntityClone",
|
|
[99] = "EntityQueryInitialResultsComplete",
|
|
[100] = "BulkAvatarTraits",
|
|
[101] = "AudioSoloRequest",
|
|
[102] = "BulkAvatarTraitsAck",
|
|
[103] = "StopInjector",
|
|
[104] = "AvatarZonePresence",
|
|
[105] = "WebRTCSignaling"
|
|
}
|
|
|
|
-- PacketHeaders.h, getNonSourcedPackets()
|
|
local unsourced_packet_types = {
|
|
["DomainConnectRequestPending"] = true,
|
|
["CreateAssignment"] = true,
|
|
["RequestAssignment"] = true,
|
|
["DomainServerRequireDTLS"] = true,
|
|
["DomainConnectRequest"] = true,
|
|
["DomainList"] = true,
|
|
["DomainConnectionDenied"] = true,
|
|
["DomainServerPathQuery"] = true,
|
|
["DomainServerPathResponse"] = true,
|
|
["DomainServerAddedNode"] = true,
|
|
["DomainServerConnectionToken"] = true,
|
|
["DomainSettingsRequest"] = true,
|
|
["OctreeDataFileRequest"] = true,
|
|
["OctreeDataFileReply"] = true,
|
|
["OctreeDataPersist"] = true,
|
|
["DomainContentReplacementFromUrl"] = true,
|
|
["DomainSettings"] = true,
|
|
["ICEServerPeerInformation"] = true,
|
|
["ICEServerQuery"] = true,
|
|
["ICEServerHeartbeat"] = true,
|
|
["ICEServerHeartbeatACK"] = true,
|
|
["ICEPing"] = true,
|
|
["ICEPingReply"] = true,
|
|
["ICEServerHeartbeatDenied"] = true,
|
|
["AssignmentClientStatus"] = true,
|
|
["StopNode"] = true,
|
|
["DomainServerRemovedNode"] = true,
|
|
["UsernameFromIDReply"] = true,
|
|
["OctreeFileReplacement"] = true,
|
|
["ReplicatedMicrophoneAudioNoEcho"] = true,
|
|
["ReplicatedMicrophoneAudioWithEcho"] = true,
|
|
["ReplicatedInjectAudio"] = true,
|
|
["ReplicatedSilentAudioFrame"] = true,
|
|
["ReplicatedAvatarIdentity"] = true,
|
|
["ReplicatedKillAvatar"] = true,
|
|
["ReplicatedBulkAvatarData"] = true,
|
|
["AvatarZonePresence"] = true,
|
|
["WebRTCSignaling"] = true
|
|
}
|
|
|
|
-- PacketHeaders.h, getNonVerifiedPackets()
|
|
local nonverified_packet_types = {
|
|
["NodeJsonStats"] = true,
|
|
["EntityQuery"] = true,
|
|
["OctreeDataNack"] = true,
|
|
["EntityEditNack"] = true,
|
|
["DomainListRequest"] = true,
|
|
["StopNode"] = true,
|
|
["DomainDisconnectRequest"] = true,
|
|
["UsernameFromIDRequest"] = true,
|
|
["NodeKickRequest"] = true,
|
|
["NodeMuteRequest"] = true,
|
|
}
|
|
|
|
local fragments = {}
|
|
|
|
local RFC_5389_MAGIC_COOKIE = 0x2112A442
|
|
|
|
function p_hfudt.dissector(buf, pinfo, tree)
|
|
|
|
-- make sure this isn't a STUN packet - those don't follow HFUDT format
|
|
if buf:len() >= 8 and buf(4, 4):uint() == RFC_5389_MAGIC_COOKIE then
|
|
return 0
|
|
end
|
|
|
|
-- validate that the packet length is at least the minimum control packet size
|
|
if buf:len() < 4 then return end
|
|
|
|
-- create a subtree for HFUDT
|
|
subtree = tree:add(p_hfudt, buf(0))
|
|
|
|
-- set the packet length
|
|
subtree:add(f_length, buf:len())
|
|
|
|
-- pull out the entire first word
|
|
local first_word = buf(0, 4):le_uint()
|
|
|
|
-- pull out the control bit and add it to the subtree
|
|
local control_bit = bit32.rshift(first_word, 31)
|
|
subtree:add(f_control_bit, control_bit)
|
|
|
|
local data_length = 0
|
|
|
|
if control_bit == 1 then
|
|
-- dissect the control packet
|
|
pinfo.cols.protocol = p_hfudt.name .. " Control"
|
|
|
|
-- remove the control bit and shift to the right to get the type value
|
|
local shifted_type = bit32.rshift(bit32.lshift(first_word, 1), 17)
|
|
local type = subtree:add(f_control_type, shifted_type)
|
|
|
|
if control_types[shifted_type] ~= nil then
|
|
-- if we know this type then add the name
|
|
type:append_text(" (".. control_types[shifted_type][1] .. ")")
|
|
|
|
subtree:add(f_control_type_text, control_types[shifted_type][1])
|
|
pinfo.cols.info:append(" [" .. control_types[shifted_type][1] .. "]")
|
|
end
|
|
|
|
if shifted_type == 0 then
|
|
local data_index = 4
|
|
|
|
-- This is an ACK let's read out the sequence number
|
|
local sequence_number = buf(data_index, 4):le_uint()
|
|
subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK))
|
|
data_index = data_index + 4
|
|
|
|
data_length = buf:len() - data_index
|
|
|
|
-- set the data from whatever is left in the packet
|
|
subtree:add(f_data, buf(data_index, data_length))
|
|
else
|
|
data_length = buf:len() - 4
|
|
|
|
-- no sub-sequence number, just read the data
|
|
subtree:add(f_data, buf(4, data_length))
|
|
end
|
|
else
|
|
-- dissect the data packet
|
|
pinfo.cols.protocol = p_hfudt.name
|
|
|
|
-- set the reliability bit
|
|
subtree:add(f_reliable_bit, bit32.rshift(first_word, 30))
|
|
|
|
local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29))
|
|
|
|
-- set the message bit
|
|
subtree:add(f_message_bit, message_bit)
|
|
|
|
-- read the obfuscation level
|
|
local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27))
|
|
subtree:add(f_obfuscation_level, obfuscation_bits)
|
|
|
|
-- read the sequence number
|
|
subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK))
|
|
|
|
local payload_offset = 4
|
|
|
|
local message_number = 0
|
|
local message_part_number = 0
|
|
local message_position = 0
|
|
|
|
-- if the message bit is set, handle the second word
|
|
if message_bit == 1 then
|
|
payload_offset = 12
|
|
|
|
local second_word = buf(4, 4):le_uint()
|
|
|
|
-- read message position from upper 2 bits
|
|
message_position = bit32.rshift(second_word, 30)
|
|
local position = subtree:add(f_message_position, message_position)
|
|
|
|
if message_positions[message_position] ~= nil then
|
|
-- if we know this position then add the name
|
|
position:append_text(" (".. message_positions[message_position] .. ")")
|
|
end
|
|
|
|
-- read message number from lower 30 bits
|
|
message_number = bit32.band(second_word, 0x3FFFFFFF)
|
|
subtree:add(f_message_number, message_number)
|
|
|
|
-- read the message part number
|
|
message_part_number = buf(8, 4):le_uint()
|
|
subtree:add(f_message_part_number, message_part_number)
|
|
end
|
|
|
|
if obfuscation_bits ~= 0 then
|
|
local newbuf = deobfuscate(message_bit, buf, obfuscation_bits)
|
|
buf = newbuf:tvb("Unobfuscated")
|
|
end
|
|
|
|
-- read the type
|
|
local packet_type = buf(payload_offset, 1):le_uint()
|
|
local ptype = subtree:add_le(f_type, buf(payload_offset, 1))
|
|
local packet_type_text = packet_types[packet_type]
|
|
|
|
if packet_type_text ~= nil then
|
|
subtree:add(f_type_text, packet_type_text)
|
|
-- if we know this packet type then add the name
|
|
ptype:append_text(" (".. packet_type_text .. ")")
|
|
pinfo.cols.info:append(" [" .. packet_type_text .. "]")
|
|
end
|
|
|
|
-- read the version
|
|
subtree:add_le(f_version, buf(payload_offset + 1, 1))
|
|
|
|
local i = payload_offset + 2
|
|
|
|
if unsourced_packet_types[packet_type_text] == nil then
|
|
-- read node local ID
|
|
local sender_id = buf(payload_offset + 2, 2)
|
|
subtree:add_le(f_sender_id, sender_id)
|
|
i = i + 2
|
|
|
|
if nonverified_packet_types[packet_type_text] == nil then
|
|
-- read HMAC MD5 hash
|
|
subtree:add(f_hmac_hash, buf(i, 16))
|
|
i = i + 16
|
|
end
|
|
end
|
|
|
|
local payload_to_dissect = nil
|
|
|
|
-- check if we have part of a message that we need to re-assemble
|
|
-- before it can be dissected
|
|
-- limit array indices to prevent lock-up with arbitrary data
|
|
if message_bit == 1 and message_position ~= 0 and message_number < 100
|
|
and message_part_number < 100 then
|
|
|
|
if fragments[message_number] == nil then
|
|
fragments[message_number] = {}
|
|
end
|
|
|
|
if fragments[message_number][message_part_number] == nil then
|
|
fragments[message_number][message_part_number] = {}
|
|
end
|
|
|
|
-- set the properties for this fragment
|
|
fragments[message_number][message_part_number] = {
|
|
payload = buf(i):bytes()
|
|
}
|
|
|
|
-- if this is the last part, set our maximum part number
|
|
if message_position == 1 then
|
|
fragments[message_number].last_part_number = message_part_number
|
|
end
|
|
|
|
-- if we have the last part
|
|
-- enumerate our parts for this message and see if everything is present
|
|
if fragments[message_number].last_part_number ~= nil then
|
|
local i = 0
|
|
local has_all = true
|
|
|
|
local finalMessage = ByteArray.new()
|
|
local message_complete = true
|
|
|
|
while i <= fragments[message_number].last_part_number do
|
|
if fragments[message_number][i] ~= nil then
|
|
finalMessage = finalMessage .. fragments[message_number][i].payload
|
|
else
|
|
-- missing this part, have to break until we have it
|
|
message_complete = false
|
|
end
|
|
|
|
i = i + 1
|
|
end
|
|
|
|
if message_complete then
|
|
debug("Message " .. message_number .. " is " .. finalMessage:len())
|
|
payload_to_dissect = ByteArray.tvb(finalMessage, message_number)
|
|
end
|
|
end
|
|
|
|
else
|
|
payload_to_dissect = buf(i):tvb()
|
|
end
|
|
|
|
if payload_to_dissect ~= nil then
|
|
-- Domain packets
|
|
if packet_type_text == "DomainList" then
|
|
Dissector.get("hf-domain"):call(payload_to_dissect, pinfo, tree)
|
|
end
|
|
|
|
-- AvatarData or BulkAvatarDataPacket
|
|
if packet_type_text == "AvatarData" or
|
|
packet_type_text == "BulkAvatarData" or
|
|
packet_type_text == "BulkAvatarTraits" then
|
|
Dissector.get("hf-avatar"):call(payload_to_dissect, pinfo, tree)
|
|
end
|
|
|
|
if packet_type_text == "EntityEdit" then
|
|
Dissector.get("hf-entity"):call(payload_to_dissect, pinfo, tree)
|
|
end
|
|
|
|
if packet_types[packet_type] == "MicrophoneAudioNoEcho" or
|
|
packet_types[packet_type] == "MicrophoneAudioWithEcho" or
|
|
packet_types[packet_type] == "SilentAudioFrame" then
|
|
Dissector.get("hf-audio"):call(payload_to_dissect, pinfo, tree)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-- return the size of the header
|
|
return buf:len()
|
|
|
|
end
|
|
|
|
function p_hfudt.init()
|
|
local udp_dissector_table = DissectorTable.get("udp.port")
|
|
|
|
for port=1000, 65000 do
|
|
udp_dissector_table:add(port, p_hfudt)
|
|
end
|
|
end
|
|
|
|
function deobfuscate(message_bit, buf, level)
|
|
local out = ByteArray.new()
|
|
out:set_size(buf:len())
|
|
if (level == 1) then
|
|
key = ByteArray.new("6362726973736574")
|
|
elseif level == 2 then
|
|
key = ByteArray.new("7362697261726461")
|
|
elseif level == 3 then
|
|
key = ByteArray.new("72687566666d616e")
|
|
else
|
|
return
|
|
end
|
|
|
|
local start = 4
|
|
if message_bit == 1 then
|
|
local start = 12
|
|
end
|
|
|
|
local p = 0
|
|
for i = start, buf:len() - 1 do
|
|
out:set_index(i, bit.bxor(buf(i, 1):le_uint(), key:get_index(7 - (p % 8))) )
|
|
p = p + 1
|
|
end
|
|
|
|
return out
|
|
end
|