diff --git a/docs/Server-configuration.md b/docs/Server-configuration.md index 73e6e03e..aeb63dcd 100644 --- a/docs/Server-configuration.md +++ b/docs/Server-configuration.md @@ -40,6 +40,7 @@ Impostor has an Anticheat that makes it possible to kick cheaters from games aut | **EnableOwnershipChecks** | `true` | Enables checks that check if players are allowed to perform a certain action on themself or another player. | | **EnableRoleChecks** | `true` | Enables checks that check if players have the correct role when performing certain role abilities like venting or murdering. | | **EnableTargetChecks** | `true` | Enables checks that check if certain packets to everyone that should only have been sent to certain players or vice versa. This includes sending votes or network objects. | +| **EnableIdentifierChecks** | `true` | Enables checks that verify if the friendcode and puid a host claims for a player in their PlayerInfo match the ones known for that player. | | **ForbidProtocolExtensions** | `true` | If disabled allows players to send network packets that go beyond the network packets sent by the vanilla game. This is necessary for most mods that need all players to install it. | | **EnablePacketSizeChecks** | `true` | Enables checks that verify if network messages exceed the maximum allowed size. | | **PacketSizeLimit** | `1203` | The maximum allowed size (in bytes) for a network message. The official servers of Among Us requires that a Hazel packet is < 1204 bytes, excluding headers. | diff --git a/src/Impostor.Api/CheatCategory.cs b/src/Impostor.Api/CheatCategory.cs index f9e6f635..cecfbc4a 100644 --- a/src/Impostor.Api/CheatCategory.cs +++ b/src/Impostor.Api/CheatCategory.cs @@ -35,9 +35,12 @@ public enum CheatCategory /// A packet was sent that exceeded the maximum allowed RPC size. PacketSize, + /// Legacy category for unsorted anticheat checks. + Other, + /// A packet was sent with more items than possible in the game. ItemLimits, - /// Legacy category for unsorted anticheat checks. - Other, + /// A packet contained an identifier (friendcode or puid) that does not match the one known for that player. + Identifiers, } diff --git a/src/Impostor.Api/Config/AntiCheatConfig.cs b/src/Impostor.Api/Config/AntiCheatConfig.cs index 904ed745..160d019e 100644 --- a/src/Impostor.Api/Config/AntiCheatConfig.cs +++ b/src/Impostor.Api/Config/AntiCheatConfig.cs @@ -30,6 +30,8 @@ namespace Impostor.Api.Config public bool EnableTargetChecks { get; set; } = true; + public bool EnableIdentifierChecks { get; set; } = true; + public bool ForbidProtocolExtensions { get; set; } = true; public bool EnablePacketSizeChecks { get; set; } = true; diff --git a/src/Impostor.Api/Net/IClient.cs b/src/Impostor.Api/Net/IClient.cs index 0732c0cc..39d68328 100644 --- a/src/Impostor.Api/Net/IClient.cs +++ b/src/Impostor.Api/Net/IClient.cs @@ -60,6 +60,22 @@ namespace Impostor.Api.Net /// IDictionary Items { get; } + /// + /// Gets or sets the friendcode of the player. + /// + /// + /// Impostor's HTTP server does not resolve friendcodes, so this value will remain null unless a plugin has written a value in this field. + /// + string? FriendCode { get; set; } + + /// + /// Gets or sets the puid of the player. + /// + /// + /// Impostor's HTTP server does not resolve puids, so this value will remain null unless a plugin has written a value in this field. + /// + string? Puid { get; set; } + /// /// Gets the current game data of the . /// diff --git a/src/Impostor.Api/Net/IClientPlayer.cs b/src/Impostor.Api/Net/IClientPlayer.cs index f11c8737..156b133d 100644 --- a/src/Impostor.Api/Net/IClientPlayer.cs +++ b/src/Impostor.Api/Net/IClientPlayer.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Impostor.Api.Games; using Impostor.Api.Net.Inner; using Impostor.Api.Net.Inner.Objects; diff --git a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs index eb3654a1..e3cf3604 100644 --- a/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs +++ b/src/Impostor.Api/Net/Inner/Objects/IInnerPlayerInfo.cs @@ -43,5 +43,21 @@ namespace Impostor.Api.Net.Inner.Objects DateTimeOffset LastMurder { get; } uint PlayerLevel { get; } + + /// + /// Gets the friendcode of the player. + /// + /// + /// Impostor's HTTP server does not resolve friendcodes, so this value will remain null unless a plugin has written a value on the player's client. + /// + string? FriendCode { get; } + + /// + /// Gets the puid of the player. + /// + /// + /// Impostor's HTTP server does not resolve puids, so this value will remain null unless a plugin has written a value on the player's client. + /// + string? Puid { get; } } } diff --git a/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs index 3c89cfb5..4d8ee9cb 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message01JoinGameS2C.cs @@ -19,10 +19,8 @@ namespace Impostor.Api.Net.Messages.S2C writer.Write(player.Client.Name); player.Client.PlatformSpecificData.Serialize(writer); writer.WritePacked(player.Character?.PlayerInfo?.PlayerLevel ?? 1); - - // ProductUserId and FriendCode are not yet known, so set them to an empty string - writer.Write(string.Empty); - writer.Write(string.Empty); + writer.Write(player.Client.FriendCode ?? string.Empty); + writer.Write(player.Client.Puid ?? string.Empty); writer.EndMessage(); } diff --git a/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs b/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs index 9ddeb030..9950515f 100644 --- a/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs +++ b/src/Impostor.Api/Net/Messages/S2C/Message07JoinedGameS2C.cs @@ -23,10 +23,8 @@ namespace Impostor.Api.Net.Messages.S2C writer.Write(ply.Client.Name); ply.Client.PlatformSpecificData.Serialize(writer); writer.WritePacked(ply.Character?.PlayerInfo?.PlayerLevel ?? 1); - - // ProductUserId and FriendCode are not yet known, so set them to an empty string - writer.Write(string.Empty); - writer.Write(string.Empty); + writer.Write(ply.Client.FriendCode ?? string.Empty); + writer.Write(ply.Client.Puid ?? string.Empty); } writer.EndMessage(); diff --git a/src/Impostor.Server/Net/Client.cs b/src/Impostor.Server/Net/Client.cs index 4a837227..a944658b 100644 --- a/src/Impostor.Server/Net/Client.cs +++ b/src/Impostor.Server/Net/Client.cs @@ -80,6 +80,7 @@ namespace Impostor.Server.Net CheatCategory.Ownership => _antiCheatConfig.EnableOwnershipChecks, CheatCategory.Role => _antiCheatConfig.EnableRoleChecks, CheatCategory.Target => _antiCheatConfig.EnableTargetChecks, + CheatCategory.Identifiers => _antiCheatConfig.EnableIdentifierChecks, CheatCategory.HostOnlyExtension => _antiCheatConfig.AllowHostOnlyExtensions switch { CheatingHostMode.Always => false, CheatingHostMode.IfRequested => !GameVersion.HasDisableServerAuthorityFlag, diff --git a/src/Impostor.Server/Net/ClientBase.cs b/src/Impostor.Server/Net/ClientBase.cs index 1aee622d..6553ff9d 100644 --- a/src/Impostor.Server/Net/ClientBase.cs +++ b/src/Impostor.Server/Net/ClientBase.cs @@ -40,6 +40,10 @@ namespace Impostor.Server.Net public ClientPlayer? Player { get; set; } + public string? FriendCode { get; set; } + + public string? Puid { get; set; } + public ColorType? PreviousColor { get; set; } = null; IClientPlayer? IClient.Player => Player; diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs index 8241f1ab..fb6d6557 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Impostor.Api; using Impostor.Api.Events.Managers; using Impostor.Api.Games; using Impostor.Api.Innersloth; @@ -59,6 +60,10 @@ namespace Impostor.Server.Net.Inner.Objects public DeathReason LastDeathReason { get; internal set; } + public string? FriendCode { get; internal set; } + + public string? Puid { get; internal set; } + public List Tasks { get; internal set; } = new List(0); public DateTimeOffset LastMurder { get; set; } @@ -128,12 +133,13 @@ namespace Impostor.Server.Net.Inner.Objects Tasks[i].Serialize(writer); } - writer.Write(string.Empty); // FriendCode - writer.Write(string.Empty); // PUID + // Resolved by the matchmaker and pushed onto the player by a plugin + writer.Write(FriendCode ?? string.Empty); + writer.Write(Puid ?? string.Empty); return new ValueTask(true); } - public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) { PlayerId = reader.ReadByte(); ClientId = reader.ReadPackedInt32(); @@ -175,11 +181,29 @@ namespace Impostor.Server.Net.Inner.Objects Tasks[i].Deserialize(reader); } - // Impostor doesn't expose fields that aren't properly validated - reader.ReadString(); // FriendCode - reader.ReadString(); // PUID + var friendCode = reader.ReadString(); + var puid = reader.ReadString(); - return ValueTask.CompletedTask; + if (Game.GetClientPlayer(ClientId) is { } owner) + { + FriendCode ??= owner.Client.FriendCode; + Puid ??= owner.Client.Puid; + } + + await ValidateIdentifiersAsync(sender, friendCode, puid); + } + + private async ValueTask ValidateIdentifiersAsync(IClientPlayer sender, string? friendCode, string? puid) + { + var mismatch = (!string.IsNullOrEmpty(friendCode) && FriendCode != null && FriendCode != friendCode) + || (!string.IsNullOrEmpty(puid) && Puid != null && Puid != puid); + + if (!mismatch) + { + return; + } + + await sender.Client.ReportCheatAsync(CheatContext.Deserialize, CheatCategory.Identifiers, $"Claimed identifiers for player {ClientId} do not match the values known for that player"); } public override async ValueTask HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) diff --git a/src/Impostor.Server/Net/State/Game.Data.cs b/src/Impostor.Server/Net/State/Game.Data.cs index ad63380c..7fcaf128 100644 --- a/src/Impostor.Server/Net/State/Game.Data.cs +++ b/src/Impostor.Server/Net/State/Game.Data.cs @@ -496,6 +496,8 @@ namespace Impostor.Server.Net.State playerInfo.NetId = _nextNetId++; playerInfo.OwnerId = ServerOwned; playerInfo.ClientId = sender.Client.Id; + playerInfo.FriendCode = sender.Client.FriendCode; + playerInfo.Puid = sender.Client.Puid; playerInfo.PlayerId = GameNet.GameData.GetNextAvailablePlayerId(); // If player played a previous game, restore their color diff --git a/src/Impostor.Server/config.json b/src/Impostor.Server/config.json index cdd37908..69d98b56 100644 --- a/src/Impostor.Server/config.json +++ b/src/Impostor.Server/config.json @@ -21,6 +21,7 @@ "EnableOwnershipChecks": true, "EnableRoleChecks": true, "EnableTargetChecks": true, + "EnableIdentifierChecks": true, "ForbidProtocolExtensions": true, "EnablePacketSizeChecks": true, "PacketSizeLimit": 1203