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

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
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;
}
Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016
Games using protocol buffers
github.com/Armax/Pokemon-GO-node-api
github.com/Armax/Elix
Back to Hearthstone
halp pls
Time for decompilation
Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016
Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016
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 match
Handshake
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 match
GameSetup
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 match
Ping & 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;
}
Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016
Implementation
Demo Time
What we learnt
Encryption
Encryption
Encryption
Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016
Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016
// Modules
var 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 = "qAAAAFkAAAAIpoKkChIGY0RnZ2xvGLmgmQIgggIqBjI4OTYwNjo6CAIQBBoOTWFjQm9va1BybzExLDUqJEJBQTVGNzV
handshake.game_handle = 1;
// Crafting ping packet
var 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 packet
var 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") {
// Handling
console.log(pegasuspacket.Type);
switch(pegasuspacket.Type) {
// Handshake
case 168:
console.log("[i] Received handshake from client");
console.log(decoded);
// Reply with GameSetup
var 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 packet
console.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 UserUI
console.log("[i] Received UserUI");
console.log(decoded);
Demo Time
Server / Client
Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016
Q & A Time!
Thanks for your attention
Marco Cuciniello - arm4x@becreatives.com
Andrea Del Fiandra - delfioh@gmail.com
1 sur 35

Contenu connexe

En vedette(20)

GAMERS LEAGUE 2015GAMERS LEAGUE 2015
GAMERS LEAGUE 2015
SPODIA663 vues

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

Serialization in GoSerialization in Go
Serialization in GoAlbert Strasheim
21.3K vues47 diapositives
NetworkNetwork
Networkboybuon205
274 vues25 diapositives

Similaire à Hearthstone: an analysis of game network protocols - Marco Cuciniello, Andrea Del Fiandra - Codemotion Milan 2016(20)

Plus de Codemotion(20)

Dernier(20)

Green Leaf Consulting: Capabilities DeckGreen Leaf Consulting: Capabilities Deck
Green Leaf Consulting: Capabilities Deck
GreenLeafConsulting170 vues
[2023] Putting the R! in R&D.pdf[2023] Putting the R! in R&D.pdf
[2023] Putting the R! in R&D.pdf
Eleanor McHugh34 vues

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

  • 1. Hearthstone: an analysis of game network protocols Andrea Del Fiandra & Marco Cuciniello MILAN 25-26 NOVEMBER 2016
  • 4. What are google protocol buffers? • data serialization format • flexible and efficient • easily portable
  • 5. 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; }
  • 7. Games using protocol buffers github.com/Armax/Pokemon-GO-node-api github.com/Armax/Elix
  • 9. halp pls Time for decompilation
  • 12. How to extract .proto files github.com/HearthSim/proto-extractor
  • 13. 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" }
  • 14. Packets used during a Hearthstone match Handshake 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; }
  • 15. Packets used during a Hearthstone match GameSetup 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; }
  • 16. Packets used during a Hearthstone match Ping & Pong message Ping { enum PacketID { ID = 115; } } message Pong { enum PacketID { ID = 116; } }
  • 17. 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;
  • 18. Packets used during a Hearthstone match PowerHistoryEntity message PowerHistoryEntity { required int32 entity = 1; required string name = 2; repeated Tag tags = 3; }
  • 19. 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; }
  • 29. // Modules var 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 = "qAAAAFkAAAAIpoKkChIGY0RnZ2xvGLmgmQIgggIqBjI4OTYwNjo6CAIQBBoOTWFjQm9va1BybzExLDUqJEJBQTVGNzV handshake.game_handle = 1; // Crafting ping packet var 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 packet var 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);
  • 30. 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") { // Handling console.log(pegasuspacket.Type); switch(pegasuspacket.Type) { // Handshake case 168: console.log("[i] Received handshake from client"); console.log(decoded); // Reply with GameSetup var 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 packet console.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 UserUI console.log("[i] Received UserUI"); console.log(decoded);
  • 34. Q & A Time!
  • 35. Thanks for your attention Marco Cuciniello - arm4x@becreatives.com Andrea Del Fiandra - delfioh@gmail.com