Skip to content

Commit

Permalink
Add detection of Roblox games
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanNardi committed Jul 20, 2023
1 parent 3edfad0 commit 6a58c46
Show file tree
Hide file tree
Showing 12 changed files with 175 additions and 27 deletions.
13 changes: 13 additions & 0 deletions doc/protocols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,16 @@ References: `Main site https://protonvpn.com/`
Apache Thrift is a generic data interchange framework that supports a bunch of different languages and platforms.

References: `Official site <https://thrift.apache.org>`_ `Github <https://github.com/apache/thrift>`_.


.. _Proto 346:

`NDPI_PROTOCOL_ROBLOX`
=====================
Roblox is an online game platform and game creation system.

References: `Main site <https://www.roblox.com/>`_.

Notes:

- Since Roblox games use a custom version of the RakNet protocol, some Roblox flows might be classified as RakNet.
1 change: 1 addition & 0 deletions src/include/ndpi_protocol_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ typedef enum {
NDPI_PROTOCOL_BITCOIN = 343,
NDPI_PROTOCOL_PROTONVPN = 344,
NDPI_PROTOCOL_APACHE_THRIFT = 345,
NDPI_PROTOCOL_ROBLOX = 346,

#ifdef CUSTOM_NDPI_PROTOCOLS
#include "../../../nDPI-custom/custom_ndpi_protocol_ids.h"
Expand Down
3 changes: 3 additions & 0 deletions src/include/ndpi_typedefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,9 @@ struct ndpi_flow_udp_struct {
u_int32_t epicgames_stage:1;
u_int32_t epicgames_word;

/* NDPI_PROTOCOL_RAKNET */
u_int32_t raknet_custom:1;

/* NDPI_PROTOCOL_SKYPE */
u_int8_t skype_crc[4];

Expand Down
31 changes: 31 additions & 0 deletions src/lib/inc_generated/ndpi_asn_roblox.c.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* This file is generated automatically and part of nDPI
*
* nDPI is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nDPI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with nDPI. If not, see <http://www.gnu.org/licenses/>.
*
*/

/* ****************************************************** */


static ndpi_network ndpi_protocol_roblox_protocol_list[] = {
{ 0x678C1C00 /* 103.140.28.0/23 */, 23, NDPI_PROTOCOL_ROBLOX },
{ 0x80740000 /* 128.116.0.0/17 */, 17, NDPI_PROTOCOL_ROBLOX },
{ 0x8DC10300 /* 141.193.3.0/24 */, 24, NDPI_PROTOCOL_ROBLOX },
{ 0xCDC93E00 /* 205.201.62.0/24 */, 24, NDPI_PROTOCOL_ROBLOX },
{ 0xD1CE2800 /* 209.206.40.0/21 */, 21, NDPI_PROTOCOL_ROBLOX },
/* End */
{ 0x0, 0, 0 }
};
3 changes: 3 additions & 0 deletions src/lib/ndpi_content_match.c.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,9 @@ static ndpi_protocol_match host_match[] =
{ "proton.me", "ProtonVPN", NDPI_PROTOCOL_PROTONVPN, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DEFAULT_LEVEL },
{ "protonvpn.com", "ProtonVPN", NDPI_PROTOCOL_PROTONVPN, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DEFAULT_LEVEL },

{ "roblox.com", "Roblox", NDPI_PROTOCOL_ROBLOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DEFAULT_LEVEL },
{ "rbxcdn.com", "Roblox", NDPI_PROTOCOL_ROBLOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DEFAULT_LEVEL },

/*
ADS/tracking/analytic
*/
Expand Down
6 changes: 6 additions & 0 deletions src/lib/ndpi_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
#include "inc_generated/ndpi_asn_hulu.c.inc"
#include "inc_generated/ndpi_asn_epicgames.c.inc"
#include "inc_generated/ndpi_asn_nvidia.c.inc"
#include "inc_generated/ndpi_asn_roblox.c.inc"

/* Third party libraries */
#include "third_party/include/ndpi_patricia.h"
Expand Down Expand Up @@ -2108,6 +2109,10 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp
"Thrift", NDPI_PROTOCOL_CATEGORY_RPC,
ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);
ndpi_set_proto_defaults(ndpi_str, 0 /* encrypted */, 1 /* app proto */, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_ROBLOX,
"Roblox", NDPI_PROTOCOL_CATEGORY_GAME,
ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */,
ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */);


#ifdef CUSTOM_NDPI_PROTOCOLS
Expand Down Expand Up @@ -2874,6 +2879,7 @@ struct ndpi_detection_module_struct *ndpi_init_detection_module(ndpi_init_prefs
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_hulu_protocol_list);
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_epicgames_protocol_list);
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_nvidia_protocol_list);
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, ndpi_protocol_roblox_protocol_list);
}

if(prefs & ndpi_track_flow_payload)
Expand Down
97 changes: 74 additions & 23 deletions src/lib/protocols/raknet.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,43 @@ static size_t raknet_dissect_ip(struct ndpi_packet_struct * const packet, size_t
return (packet->payload[offset] == 0x04 ? 4 : 16);
}

static int is_custom_version(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
{
struct ndpi_packet_struct *packet = &ndpi_struct->packet;
unsigned char magic[] = { 0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78 };

if (packet->payload_packet_len >= 1200) /* Full MTU packet */
{
/* Offset 32 has been found only in the traces; the other ones are present
also in the Raknet heuristic in Wireshark */
if (memcmp(magic, &packet->payload[1], sizeof(magic)) == 0 ||
memcmp(magic, &packet->payload[9], sizeof(magic)) == 0 ||
memcmp(magic, &packet->payload[17], sizeof(magic)) == 0 ||
memcmp(magic, &packet->payload[32], sizeof(magic)) == 0)
{
return 1;
}
}
return 0;
}

static void exclude_proto(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
{
if (flow->l4.udp.raknet_custom == 1)
{
NDPI_LOG_INFO(ndpi_struct, "found RakNet (custom version)\n");
/* Classify as Raknet or as Roblox?
This pattern ha been observed with Roblox games but it might be used by
other protocols too. Keep the generic classification, for the time being */
ndpi_int_raknet_add_connection(ndpi_struct, flow);
} else {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}
}

/* Reference: https://wiki.vg/Raknet_Protocol */
static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
Expand All @@ -55,9 +92,23 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,

NDPI_LOG_DBG(ndpi_struct, "search RakNet\n");

if (packet->udp == NULL || packet->payload_packet_len < 7)
/* There are two "versions" of Raknet:
* plaintext one: we need multiple packets for classification and for extracting metadata
* custom/encrypted one: an extension used by Roblox games (and others?).
Only the first pkt is required.
The main issue is that these two versions "overlap", i.e. some plaintext flows might be wrongly
identified as encrypted one (losing their metadata).
Solution: check for the custoom/encrypted version, cache the result and use it only if/when the
standard detection ends.
*/
if (flow->packet_counter == 1)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
flow->l4.udp.raknet_custom = is_custom_version(ndpi_struct, flow);
}

if (packet->payload_packet_len < 7)
{
exclude_proto(ndpi_struct, flow);
return;
}

Expand All @@ -68,7 +119,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
case 0x00: /* Connected Ping */
if (packet->payload_packet_len != 8)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
required_packets = 6;
Expand All @@ -78,7 +129,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
case 0x02: /* Unconnected Ping */
if (packet->payload_packet_len != 32)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
required_packets = 6;
Expand All @@ -87,7 +138,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
case 0x03: /* Connected Pong */
if (packet->payload_packet_len != 16)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
required_packets = 6;
Expand All @@ -97,7 +148,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
if (packet->payload_packet_len < 18 ||
packet->payload[17] > 10 /* maximum supported protocol version */)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
required_packets = 6;
Expand All @@ -107,15 +158,15 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
if (packet->payload_packet_len != 28 ||
packet->payload[25] > 0x01 /* connection uses encryption: bool -> 0x00 or 0x01 */)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}

{
u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 26));
if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
}
Expand All @@ -128,15 +179,15 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
!((ip_addr_offset == 16 && packet->payload_packet_len == 46) ||
(ip_addr_offset == 4 && packet->payload_packet_len == 34)))
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}

{
u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 20 + ip_addr_offset));
if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
}
Expand All @@ -148,15 +199,15 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
!((ip_addr_offset == 16 && packet->payload_packet_len == 47) ||
(ip_addr_offset == 4 && packet->payload_packet_len == 35)))
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}

{
u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 28 + ip_addr_offset));
if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
}
Expand All @@ -179,7 +230,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
ip_addr_offset += 16;
if (ip_addr_offset != packet->payload_packet_len)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
}
Expand Down Expand Up @@ -207,15 +258,15 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
u_int8_t msg_flags = get_u_int8_t(packet->payload, frame_offset);
if ((msg_flags & 0x0F) != 0)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}

u_int16_t msg_size = ntohs(get_u_int16_t(packet->payload, frame_offset + 1));
msg_size /= 8;
if (msg_size == 0)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
break;
}

Expand Down Expand Up @@ -245,7 +296,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
{
ndpi_int_raknet_add_connection(ndpi_struct, flow);
} else {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
}
return;
}
Expand All @@ -254,7 +305,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
case 0x09: /* Connection Request */
if (packet->payload_packet_len != 16)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
required_packets = 6;
Expand All @@ -268,15 +319,15 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
if (packet->payload_packet_len != 25 ||
packet->payload[17] > 10)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
break;

case 0x1c: /* Unconnected Pong */
if (packet->payload_packet_len < 35)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}

Expand All @@ -285,7 +336,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,

if (motd_len == 0 || motd_len + 35 != packet->payload_packet_len)
{
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
}
Expand All @@ -305,7 +356,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
{
record_offset += 4;
} else {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}
} while (++record_index < record_count &&
Expand All @@ -315,7 +366,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
{
ndpi_int_raknet_add_connection(ndpi_struct, flow);
} else {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
}
return;
}
Expand All @@ -326,7 +377,7 @@ static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
break;

default: /* Invalid RakNet packet */
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
exclude_proto(ndpi_struct, flow);
return;
}

Expand Down
Binary file added tests/cfgs/default/pcap/roblox.pcapng
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ Patricia protocols: 2/2 (search/found)
CustomProtocolA 3 222 1
CustomProtocolB 2 148 1

1 TCP 192.168.1.245:56866 -> 3.3.3.3:443 [proto: 91.352/TLS.CustomProtocolA][IP: 352/CustomProtocolA][Encrypted][Confidence: Unknown][DPI packets: 1][cat: Web/5][3 pkts/222 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][3.05 sec][Risk: ** Unidirectional Traffic **][Risk Score: 10][Risk Info: No server to client traffic][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
2 TCP 192.168.1.245:59682 -> 3.3.3.3:444 [proto: 353/CustomProtocolB][IP: 353/CustomProtocolB][ClearText][Confidence: Unknown][DPI packets: 1][2 pkts/148 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][1.02 sec][Risk: ** Unidirectional Traffic **][Risk Score: 10][Risk Info: No server to client traffic][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
1 TCP 192.168.1.245:56866 -> 3.3.3.3:443 [proto: 91.353/TLS.CustomProtocolA][IP: 353/CustomProtocolA][Encrypted][Confidence: Unknown][DPI packets: 1][cat: Web/5][3 pkts/222 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][3.05 sec][Risk: ** Unidirectional Traffic **][Risk Score: 10][Risk Info: No server to client traffic][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
2 TCP 192.168.1.245:59682 -> 3.3.3.3:444 [proto: 354/CustomProtocolB][IP: 354/CustomProtocolB][ClearText][Confidence: Unknown][DPI packets: 1][2 pkts/148 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][1.02 sec][Risk: ** Unidirectional Traffic **][Risk Score: 10][Risk Info: No server to client traffic][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
Loading

0 comments on commit 6a58c46

Please sign in to comment.