Cifrado del canal de red de Counter-Strike: Global Offensive

From Valve Developer Community
Jump to: navigation, search
English
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.
Counter-Strike: Global Offensive

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 ) &copyData ) )
                {
                                ::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