Cifrado del canal de red de Counter-Strike: Global Offensive
January 2024
Esta página recoge las nociones básicas sobre cómo funciona el cifrado del canal de red de CS:GO y cómo los terceros pueden proporcionar extensiones de servidores y software del cliente para asegurar la comuniación entre el cliente y el servidor del juego mediante claves de cifrado. Es necesario tener una versión actualizada de CS:GO 1.35.6.0 o superior para usar el protocolo de claves de cifrado de canales de red de terceros.
La comuniación del canal de red de CS:GO puede cifrarse con una clave binaria de 16 byte. Initial "connectionless" packets starting with four bytes 0xFF 0xFF 0xFF 0xFF
and followed by either A2S_GETCHALLENGE 'q'
byte or C2S_CONNECT 'k'
byte and are always sent unencrypted in the clear. After the client connection is accepted by the game server, the network channel is established and must be used. If the network channel must use the encryption key then the key configured on the client side and on the server side must match for communication to succeed.
Cambiando claves de cifrado
Los terceros pueden implementar administración de claves de cifrado para sus jugadores de distintas formas, así como controlar que clientes necesitan clave y cuáles no, pudiendo conectarse sin necesidad de ella. If the client requires an encryption key then typically 3rd party backend will have a secure connection with client software to inform the 3rd party client software in a secure way of the required encryption key. Secure implementation of backend to client protocol is required because this channel is the most likely target for man-in-the-middle attack by malicious clients with the goal of extracting the encryption key. 3rd party client software will forward the encryption key to the CS:GO game client using machine-local communication. At the same time 3rd party backend will communicate the client's encryption key to the 3rd party game server that will be hosting the match for this client. By the time the client connects to the game server they can both use the same established encryption key and benefit from encrypted network channel communication.
Establecer clave de cifrado del cliente
El siguiente código muestra un ejemplo de clave de cifrado del cliente de CS:GO por parte de un software de terceros. El formato el una dirección de servidor remota, a la que el cliente debe utilizar una clave para conectarse, el símbolo de "mayor qué" (>)
, la clave de 32 bit debe contener must contain non-zero in the upper word ( ( idx & 0xFFFF0000 ) != 0 )
to avoid conflicting with built-in encryption keys, the 16-byte plaintext key that the client will use to encrypt messages, and non-zero amount up to 256 bytes of 3rd party defined payload that client will send to the server over the wire unencrypted so shouldn't contain the encryption key because man-in-the-middle attacker will be able to extract it.
void TestSettingEngineCryptKey() { const HWND hwndEngine = FindWindow( "Valve001", NULL ); if ( !hwndEngine ) { ::MessageBox( NULL, "Failed to find CS:GO HWND", "Encryption Key", MB_OK | MB_ICONEXCLAMATION ); return; } // // Data description for encrypted channel // char const * szServerAddressPrefix = "172.16.10.197:27016>"; int32 nClientKeyIndex = 0x00010102; byte arrPlainTextClientEncryptionKey[16] = { 0x01, 0x02, 0x03, 0x04, 0x15, 0x16, 0x17, 0x00, 0x10, 0x20, 0x30, 0x40, 0xA8, 0xB8, 0xC8, 0x00 }; byte arrEncryptedCookieToSendToServerOverWire[64] = { 0xDD, 0xEE, 0xFF, 0x00, 0x15, 0x16, 0x17, 0x00, 0xAA, 0xBB, 0xCC, 0x00, 0xA8, 0xB8, 0xC8, 0x00, 0xDD, 0xEE, 0xFF, 0x00, 0x15, 0x16, 0x17, 0x00, 0xAA, 0xBB, 0xCC, 0x00, 0xA8, 0xB8, 0xC8, 0x00, 0xDD, 0xEE, 0xFF, 0x00, 0x15, 0x16, 0x17, 0x00, 0xAA, 0xBB, 0xCC, 0x00, 0xA8, 0xB8, 0xC8, 0x00, 0xDD, 0xEE, 0xFF, 0x00, 0x15, 0x16, 0x17, 0x00, 0x00, 0xBB, 0xCC, 0x00, 0xA8, 0xB8, 0xC8, 0x00 }; // // Fill out the data structure to send to the engine. // COPYDATASTRUCT copyData; copyData.dwData = 0x43525950; // CRYP copyData.cbData = strlen( szServerAddressPrefix ) + sizeof( nClientKeyIndex ) + sizeof( arrPlainTextClientEncryptionKey ) + sizeof( arrEncryptedCookieToSendToServerOverWire ); byte *pbBufferForEngine = ( byte * ) stackalloc( copyData.cbData ); copyData.lpData = pbBufferForEngine; // Put all the data into the buffer V_memcpy( pbBufferForEngine, szServerAddressPrefix, strlen( szServerAddressPrefix ) ); V_memcpy( pbBufferForEngine + strlen( szServerAddressPrefix ), &nClientKeyIndex, sizeof( nClientKeyIndex ) ); V_memcpy( pbBufferForEngine + strlen( szServerAddressPrefix ) + sizeof( nClientKeyIndex ), arrPlainTextClientEncryptionKey, sizeof( arrPlainTextClientEncryptionKey ) ); V_memcpy( pbBufferForEngine + strlen( szServerAddressPrefix ) + sizeof( nClientKeyIndex ) + sizeof( arrPlainTextClientEncryptionKey ), arrEncryptedCookieToSendToServerOverWire, sizeof( arrEncryptedCookieToSendToServerOverWire ) ); // Send the message to the engine if ( !SendMessageA( hwndEngine, WM_COPYDATA, 0, ( LPARAM ) ©Data ) ) { ::MessageBox( NULL, "CS:GO Engine Denied Request", "Encryption Key", MB_OK | MB_ICONEXCLAMATION ); return; } ::MessageBox( NULL, "CS:GO Engine Accepted Request", "Encryption Key", MB_OK | MB_ICONEXCLAMATION ); }
Establecer clave de cifrado del servidor
Se necesita una extensión del servidor de terceros para establecer la clave de cifrado del servidor, el cual debe estar ejecutándose con el comando -externalnetworkcryptkey
para activar dicha característica. La extensión debe implementar la interfaz IServerPluginCallbacks
v4 "ISERVERPLUGINCALLBACKS004"
. La interfaz v4 se ha ampliado de dos modos adicionales en comparación a la v3: BNetworkCryptKeyCheckRequired
y BNetworkCryptKeyValidate
. Toda la información de la interfaz en C++ está aquí.
#define INTERFACEVERSION_ISERVERPLUGINCALLBACKS "ISERVERPLUGINCALLBACKS004" abstract_class IServerPluginCallbacks { public: // Initialize the plugin to run // Return false if there is an error during startup. virtual bool Load( CreateInterfaceFn interfaceFactory, CreateInterfaceFn gameServerFactory ) = 0; // Called when the plugin should be shutdown virtual void Unload( void ) = 0; // called when a plugins execution is stopped but the plugin is not unloaded virtual void Pause( void ) = 0; // called when a plugin should start executing again (sometime after a Pause() call) virtual void UnPause( void ) = 0; // Returns string describing current plugin. e.g., Admin-Mod. virtual const char *GetPluginDescription( void ) = 0; // Called any time a new level is started (after GameInit() also on level transitions within a game) virtual void LevelInit( char const *pMapName ) = 0; // The server is about to activate virtual void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) = 0; // The server should run physics/think on all edicts virtual void GameFrame( bool simulating ) = 0; // Called when a level is shutdown (including changing levels) virtual void LevelShutdown( void ) = 0; // Client is going active virtual void ClientActive( edict_t *pEntity ) = 0; // Client is fully connected ( has received initial baseline of entities ) virtual void ClientFullyConnect( edict_t *pEntity ) = 0; // Client is disconnecting from server virtual void ClientDisconnect( edict_t *pEntity ) = 0; // Client is connected and should be put in the game virtual void ClientPutInServer( edict_t *pEntity, char const *playername ) = 0; // Sets the client index for the client who typed the command into their console virtual void SetCommandClient( int index ) = 0; // A player changed one/several replicated cvars (name etc) virtual void ClientSettingsChanged( edict_t *pEdict ) = 0; // Client is connecting to server ( set retVal to false to reject the connection ) // You can specify a rejection message by writing it into reject virtual PLUGIN_RESULT ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) = 0; // The client has typed a command at the console virtual PLUGIN_RESULT ClientCommand( edict_t *pEntity, const CCommand &args ) = 0; // A user has had their network id setup and validated virtual PLUGIN_RESULT NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) = 0; // This is called when a query from IServerPluginHelpers::StartQueryCvarValue is finished. // iCookie is the value returned by IServerPluginHelpers::StartQueryCvarValue. // Added with version 2 of the interface. virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) = 0; // added with version 3 of the interface. virtual void OnEdictAllocated( edict_t *edict ) = 0; virtual void OnEdictFreed( const edict_t *edict ) = 0; // // Allow plugins to validate and configure network encryption keys (added in Version 4 of the interface) // Game server must run with -externalnetworkcryptkey flag, and 3rd party client software must set the // matching encryption key in the client game process. // // BNetworkCryptKeyCheckRequired allows the server to allow connections from clients or relays that don't have // an encryption key. The function must return true if the client encryption key is required, and false if the client // is allowed to connect without an encryption key. It is recommended that if client wants to use encryption key // this function should return true to require it on the server side as well. // Any plugin in the chain that returns true will flag connection to require encryption key for the engine and check // with other plugins will not be continued. // If no plugin returns true to require encryption key then the default implementation will require encryption key // if the client wants to use it. virtual bool BNetworkCryptKeyCheckRequired( uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, bool bClientWantsToUseCryptKey ) = 0; // BNetworkCryptKeyValidate allows the server to validate client's over the wire encrypted payload cookie and return // false if the client cookie is malformed to prevent connection to the server. If this function returns true then // the plugin allows the client to connect with the encryption key, and upon return the engine expects the plugin // to have copied 16-bytes of client encryption key into the buffer pointed at by pbPlainTextKeyForNetChan. That key // must match the plaintext key supplied by 3rd party client software to the client game process, not the client cookie // transmitted unencrypted over the wire as part of the connection packet. // Any plugin in the chain that returns true will stop evaluation of other plugins and the 16-bytes encryption key // copied into pbPlainTextKeyForNetchan will be used. If a plugin returns false then evaluation of other plugins will // continue and buffer data in pbPlainTextKeyForNetchan will be preserved from previous calls. // If no plugin returns true and the encryption key is required then the client connection will be rejected with // an invalid certificate error. virtual bool BNetworkCryptKeyValidate( uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, int nEncryptionKeyIndexFromClient, int numEncryptedBytesFromClient, byte *pbEncryptedBufferFromClient, byte *pbPlainTextKeyForNetchan ) = 0; };
An example implementation of the server plugin is provided here.
bool CServerPluginExample::BNetworkCryptKeyCheckRequired( uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, bool bClientWantsToUseCryptKey ) { // Example server plugin implementation always requires encryption return true; } bool CServerPluginExample::BNetworkCryptKeyValidate( uint32 unFromIP, uint16 usFromPort, uint32 unAccountIdProvidedByClient, int nEncryptionKeyIndexFromClient, int numEncryptedBytesFromClient, byte *pbEncryptedBufferFromClient, byte *pbPlainTextKeyForNetchan ) { // // Example server plugin implementation: // byte arrPlainTextClientEncryptionKey[ 16 ] = { 0x01, 0x02, 0x03, 0x04, 0x15, 0x16, 0x17, 0x00, 0x10, 0x20, 0x30, 0x40, 0xA8, 0xB8, 0xC8, 0x00 }; V_memcpy( pbPlainTextKeyForNetchan, arrPlainTextClientEncryptionKey, 16 ); return true; }
Enlaces externos
January 2024