35
Hearthstone: an analysis of game network protocols Andrea Del Fiandra & Marco Cuciniello MILAN 25-26 NOVEMBER 2016

Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016

Embed Size (px)

Citation preview

Hearthstone: an analysis of game network protocols

Andrea Del Fiandra & Marco Cuciniello

MILAN 25-26 NOVEMBER 2016

Wow

Such surprise

Very unpredictable

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

Back to Hearthstone

halp pls

Time for decompilation

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;

}

Implementation

Demo Time

What we learnt

Encryption

Encryption

Encryption

// 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;

Demo Time

Server / Client

Q & A Time!

Thanks for your attention

Marco Cuciniello - [email protected] Del Fiandra - [email protected]