Date post: | 16-Apr-2017 |
Category: |
Technology |
Upload: | codemotion |
View: | 277 times |
Download: | 0 times |
Hearthstone: an analysis of game network protocols
Andrea Del Fiandra & Marco Cuciniello
MILAN 25-26 NOVEMBER 2016
What are google protocol buffers?
• data serialization format• flexible and efficient• easily portable
How do protocol buffers work?message Person { required string name = 1; required int32 id = 2; optional string email = 3;
enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; }
message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; }
repeated PhoneNumber phone = 4;}
Games using protocol buffers
github.com/Armax/Pokemon-GO-node-api
github.com/Armax/Elix
How to extract .proto files
github.com/HearthSim/proto-extractor
Packets used during a Hearthstone match{ "1": "GetGameState", "2": "ChooseOption", "3": "ChooseEntities", "11": "Concede", "13": "EntitiesChosen", "14": "AllOptions", "15": "UserUI", "16": "GameSetup", "17": "EntityChoices", "19": "PowerHistory", "24": "SpectatorNotify", "115": "Ping", "116": "Pong", "168": "Handshake"}
Packets used during a Hearthstone matchHandshake
message Handshake {enum PacketID {
ID = 168;}
required int32 game_handle = 1;required string password = 2;required int64 client_handle = 3;optional int32 mission = 4;required string version = 5;required PegasusShared.Platform platform = 7;
}
Packets used during a Hearthstone matchGameSetup
message GameSetup {enum PacketID {
ID = 16;}
required int32 board = 1;required int32 max_secrets_per_player = 2;required int32 max_friendly_minions_per_player = 3;optional int32 keep_alive_frequency_seconds = 4;optional int32 disconnect_when_stuck_seconds = 5;
}
Packets used during a Hearthstone matchPing & Pong
message Ping {enum PacketID {
ID = 115;}
}
message Pong {enum PacketID {
ID = 116;}
}
Packets used during a Hearthstone match
PowerHistory
message PowerHistory {enum PacketID {
ID = 19;}
repeated PowerHistoryData list = 1;}
message PowerHistoryData {optional PowerHistoryEntity full_entity = 1;optional PowerHistoryEntity show_entity = 2;optional PowerHistoryHide hide_entity = 3;optional PowerHistoryTagChange tag_change = 4;optional PowerHistoryCreateGame create_game = 5;optional PowerHistoryStart power_start = 6;optional PowerHistoryEnd power_end = 7;optional PowerHistoryMetaData meta_data = 8;optional PowerHistoryEntity change_entity = 9;
}
Packets used during a Hearthstone match
PowerHistoryEntity
message PowerHistoryEntity {required int32 entity = 1;required string name = 2;repeated Tag tags = 3;
}
Packets used during a Hearthstone match
EntityChoices
message EntityChoices {enum PacketID {
ID = 17;}
required int32 id = 1;required int32 choice_type = 2;required int32 count_min = 4;required int32 count_max = 5;repeated int32 entities = 6 [packed = true];optional int32 source = 7;required int32 player_id = 8;
}
// Modulesvar ProtoBuf = require('protobufjs');var PegasusPacket = require('./pegasuspacket').PegasusPacket;var net = require('net');var client = new net.Socket();var builder = ProtoBuf.loadProtoFile(__dirname + '/proto/game.proto');var PegasusGame = builder.build();var serverIp = "127.0.0.1"var handshake = "qAAAAFkAAAAIpoKkChIGY0RnZ2xvGLmgmQIgggIqBjI4OTYwNjo6CAIQBBoOTWFjQm9va1BybzExLDUqJEJBQTVGNzVGLTdDMTItNTdDMS1BOEJELUI2QTk0RDFFODFBMw==";
handshake.game_handle = 1;
// Crafting ping packetvar ping = new PegasusGame.PegasusGame.Ping();var encoded_ping = PegasusGame.PegasusGame.Ping.encode(ping);var type = PegasusGame.PegasusGame.Ping.PacketID.ID;var pegasus_ping = new PegasusPacket();var encoded_pegasus_ping = pegasus_ping.Encode(encoded_ping, type);
// Craft USERUI packetvar userui = new PegasusGame.PegasusGame.UserUI();userui.mouse_info = null;userui.emote = 4;userui.player_id = null;var encoded_userui = PegasusGame.PegasusGame.UserUI.encode(userui);var type_userui = PegasusGame.PegasusGame.UserUI.PacketID.ID;var pegasus_userui = new PegasusPacket();var encoded_pegasus_userui = pegasus_ping.Encode(encoded_userui, type_userui);
client.connect(3724, serverIp, function() {client.write(Buffer.from(args.options.handshake, 'base64'));
setInterval(function() { console.log("[+] ping sent");
client.write(encoded_pegasus_ping); }, 5000);
setInterval(function() { client.write(encoded_pegasus_userui); }, 1000);});
net.createServer(function(sock) { console.log('[+] connection from: ' + sock.remoteAddress +':'+ sock.remotePort); sock.on('data', function(data) {
var pegasuspacket = new PegasusPacket();var bytes_decoded = pegasuspacket.Decode(data, 0, data.length);if(bytes_decoded >= 4) {
var decoded = Hearthnode.Decode(pegasuspacket);if(decoded != null && decoded != "unimplemented") {
// Handlingconsole.log(pegasuspacket.Type);switch(pegasuspacket.Type) {
// Handshakecase 168:
console.log("[i] Received handshake from client");console.log(decoded);// Reply with GameSetupvar GameSetup = new PegasusGame.PegasusGame.GameSetup();GameSetup.board = 6;GameSetup.max_secrets_per_player = 5;GameSetup.max_friendly_minions_per_player = 7;GameSetup.keep_alive_frequency_seconds = 5;GameSetup.disconnect_when_stuck_seconds = 25;var encoded_GameSetup = PegasusGame.PegasusGame.GameSetup.encode(GameSetup);var type = PegasusGame.PegasusGame.GameSetup.PacketID.ID;var pegasus_GameSetup = new PegasusPacket();var encoded_pegasus_GameSetup = pegasus_GameSetup.Encode(encoded_GameSetup, type);sock.write(encoded_pegasus_GameSetup);break;
case 115:// Crafting ping packetconsole.log("[i] Received ping from client");console.log(decoded)var Pong = new PegasusGame.PegasusGame.Pong();var encoded_Pong = PegasusGame.PegasusGame.Pong.encode(Pong);var type = PegasusGame.PegasusGame.Pong.PacketID.ID;var pegasus_Pong = new PegasusPacket();var encoded_pegasus_Pong = pegasus_ping.Encode(encoded_Pong, type);sock.write(encoded_pegasus_Pong);break;
case 15:// Received UserUIconsole.log("[i] Received UserUI");console.log(decoded);break;
Thanks for your attention
Marco Cuciniello - [email protected] Del Fiandra - [email protected]