Illustration Burning Crusade Illidan

Il s'agit d'un vieux tuto publié en 2010 ressorti par pure nostalgie (sortie du film oblige) d'un temps que les moins de 20 ans ne peuvent pas pas connaître et où l'émulation de World of Warcraft (Vanilla & Burning Crusade) battait son plein (de bugs).

TL;DR: Ne perdez pas votre temps à jouer, perdez le à comprendre à quoi vous jouez.

Article en cours de relecture

Sommaire

Contexte

À l'époque, des lacunes dans le code du serveur étaient utilisées pour manipuler le comportement des serveurs de jeu. Ces bugs qui faisaient accessoirement (et fréquemment) crasher les royaumes étaient les conséquences visibles de 2 maladies atteignant les développeurs:

  • Faire confiance aux utilisateurs*,
  • Ne pas faire attention au débordement de mémoire (buffer overflows).

Faire confiance aux utilisateurs équivaut à ne pas faire toutes les vérifications nécessaires côté serveur sur les données reçues.

Ne pas gérer le débordement de mémoire c'est ne pas analyser les fuites potentielles de mémoire via un profilage et utiliser des fonctions dépréciées ou non sécurisées des librairies standard.

*: C'est une remarque facile critiquant le projet gigantesque qu'est le reverse engineering d'un jeu aussi complexe que WoW (cf les dépôts monstrueux MaNGOS, ArcEmu) et TrinityCore qui atteignaient déjà le demi million de lignes à l'époque), mais néanmoins toujours d'actualité dans toutes les branches de l'informatique où le pattern "interaction utilisateur <-> programme" est présent...

Matériel et méthodes

La mémoire

Il s'agit de faire simple et rapide. Les gens qui ont déjà touché à des langages comme le C, le savent tous, accéder à la mémoire c'est comme accéder à des cases de rangement. Dans chaque case il y a une unité de mémoire (un octet), où chaque octet est subdivisé en 8 cases que sont les bits. PS : Notez qu'il est rare de manipuler les bits...

Chaque type de variable occupe un ensemble de cases. Par exemple le type int occupera 32 bits soit, 4 octets sur la plupart des systèmes. Le type char occupera 1 octet, etc.

Toutefois, on a souvent besoin de manipuler des variables plus "souples", capables d'avoir une taille pouvant varier dans le temps. Les chaines de caractères en sont un exemple. Par exemple, contrairement à ce que laissent penser des langages haut niveau comme Python, Java et même C++, la concaténation de chaines de caractères n'est pas une chose triviale. Le programmeur doit parfois (souvent en C) indiquer la quantité de mémoire requise pour stocker ses variables selon un processus nommé : l'allocation dynamique.

Exemple (en C) :

/* Note: ce code est simplifié, aucun test de n'est fait pour savoir si les opérations se déroulent bien.
 * C'est typiquement un code de merde.
 */
char * dest_string   = NULL;
char string_to_copy  = "coucou";
char string_to_copy2 = "ca va ?";

// Copy string_to_copy to dest_string
dest_string = malloc((strlen(string_to_copy + 1) * sizeof(*dest_string));
strcpy(dest_string, string_to_copy);

// Concatenate string_to_copy2 with dest_string
dest_string = realloc(dest_string, (strlen(dest_string) + strlen(string_to_copy2) + 1) * sizeof(*dest_string));
strcat(dest_string, string_to_copy2);

// Free memory
free(dest_string);

Exemple (en Python):

string_to_copy = "coucou";
string_to_copy2 = "ca va ?"

dest_string = string_to_copy + string_to_copy2

Il faut avoir en tête les conséquences engendrées si ce processus ne fait pas l'objet de toutes les attentions :

  • Requérir de la mémoire est couteux en temps,
  • Requérir plus de mémoire qu'il n'en faut ne sert à rien,
  • Réquérir trop peu de mémoire mène à des phénomènes totalement imprévisibles se terminant la plupart du temps par un segmentation fault ou out of memory error, Ces erreurs sont dues à un accès en lecture/écriture à une zone de la mémoire que le programme n'a pas le droit de manipuler (car n'en a pas fait la demande). Les droits sont gérés par le système d'exploitation qui joue le rôle de dernier garde-fou.
  • Oublier de libérer la mémoire demandée mène à ce que l'on appelle des fuites mémoire ou memory leaks, se traduisant par une consommation croissante de mémoire sans raison apparente et ce, de manière proportionnelle au temps d'exécution. Exemples objectifs: Chrome, Internet Explorer, Windows, Atom, Sublime Text, Spyder, tous les programmes en Java.


<Interlude imagé :>

Serveur Java troll Langage Java troll

Source : https://www.luc-damas.fr/.

Chrome troll

</Fin de l'interlude imagé>


Les développeurs Python ont un petit aperçu de ce que donne un accès mémoire à une zone non allouée:

>>> alphabet = ['a', 'b', 'c']
>>> print(alphabet[3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range

Résumé à l'attention des rageux (un certain Gui.L m'indique qu'il se sent concerné): Si vous pleurez à cause de la complexité de Python ou de Java, allez vous arracher les cheveux et tout le reste sur du C pour vite revenir et ne plus jamais vous plaindre...

Winsock Packet Editor (WPE)

Winsock Packet Editor (WPE) est un outil permettant la capture et l'édition de paquets de données circulant sur le réseau au niveau de la couche de transport du modèle OSI (Open Systems Interconnection).

L'utilisateur sélectionne un processus à "sniffer" et peut créer des filtres qui modifieront les données à la volée avant qu'elles atteignent leur destination.

Dans le cas présent c'est le client du jeu qui est ciblé. De nombreuses captures permettent de deviner progressivement (avec beaucoup de temps) l'architecture des paquets envoyés/reçus suite à des actions en jeu.

Les items de l'inventaire

Dans WoW, un avatar possède un nombre invariable de slots de base (fixes) : le sac à dos, les emplacements complémentaires réservés aux sacs, la page principale des items en banque, et les slots d'équipement; ces slots sont traités de la même façon par le serveur (comme un gros sac avec des contraintes de déplacement gérées par le client), et sont accessibles quelque-soit la localisation du personnage en jeu.

À ces slots s'ajoutent des slots complémentaires : les multiples sacs achetés par le joueur.

Le protocole de communication est simple : Le glisser/déposer doit informer le serveur des positions de départ et d'arrivée de l'item sélectionné. Chaque position (arrivée/départ) est définie par l'ID du sac et l'ID de la case concernée dans ce sac, sauf dans le cas de déplacement entre slots fixes où seul l'ID de la case concernée est transmis.

Voici une explication en images :

items banque items equipement

Liens grand format: Lien 1, Lien 2.

Légende:

  • Vert: position d'arrivée de l'item,
  • Rouge: position de départ de l'item,
  • Chiffres blancs: id des cases hors équipement de base,
  • Chiffres bleus: id des slots de base du perso,
  • Chiffres au dessus des flèches: Position de l'id dans le paquet TCP, visible dans l'interface de WPE,
    • Si le glisser/déposer se fait entre slots fixes, chaque position est constituée d'un seul ID : celui de la case. Le paquet envoyé a une taille de 8 octets. La case source est dans le 7ième octet, la case de destination est dans le 8ième octet.
    • Si le glisser/déposer implique un slot complémentaire (un sac acheté par le joueur), chaque position est obligatoirement constituée des 2 ID. Le paquet envoyé a une taille de 10 octets. La source est stockée dans les octets 7 et 8, la destination est stockée dans les octets 9 et 10.
  • Les flèches jaunes décrivent les paquets échangés lors de trajets normaux.
  • La flèche cyan décrit le trajet modifié par le filtre : L'utilisateur croit déplacer un item vers la première case (VIDE) en haut à gauche (en 17) du sac à dos (en FF); Le filtre corrige la destination et l'envoie vers la banque (en FF 34).


Pour la culture générale, voici une liste plus complète des ID de slots accessibles en jeu :

Type Slots
Equipement 00 => 16
Sac à dos 17 => 26
Onglet principal de banque 27 => 42
Slots de sacs en banque 43 => 49
Trousseau de clés 56 => 75


À propos du trousseau : Celui-ci est extensible à 32 slots max, avec un filtre adéquat on peut arriver à une configuration curieuse avec seulement une clé placée tout en bas (cf image ci-dessus).

La preuve de concept est faite. Voyons ce que l'on peut/pouvait faire d'intéressant.

Les hacks

elfe de sang burning crusade

Oui, cette illustration est presque hors contexte...

Au début de l'émulation de WoW, peu de vérifications étaient faites côté serveur. Seul le client refusait en jeu les actions de l'utilisateur; cette limitation n'était vraiment pas un problème en éditant les paquets transmis sur le réseau.

La banque omniprésente

Comme cela a été démontré plus haut, des filtres adéquats permettaient l'accès à la banque quelque-soit la localisation du personnage. La commande .bank accessible depuis un compte VIP sur certains serveurs privés attestaient de cette fonctionnalité.

Ceci est une découverte personnelle que j'appelais le bug de la "mini-stargate" portable.

Équiper n'importe quoi n'importe où

Dans les premiers temps il était possible de mettre n'importe quel item dans n'importe quel slot d'équipement (tête, épaules, etc.).

Certains joueurs portaient donc plusieurs versions d'un même item surpuissant pour améliorer leurs stats. On pouvait même empiler plusieurs items théoriquement non empilables sur le même emplacement d'équipement...

La duplication d'items

Avec un filtre WPE, il suffisait de forcer l'arrivée d'un item vers un slot n'existant pas (ex: FF FF).

Avant les premiers patchs, le serveur essayait d'écrire dans un emplacement mémoire non autorisé et plantait lamentablement. Selon les tests réalisés, ce buffer overflow du pauvre faisait crasher imanquablement les royaumes. Il suffisait que quelques joueurs fassent des tests pour que les crashs à répétition rendent l'expérience de jeu déplorable.

Par la suite, des correctifs ont été appliqués pour limiter les plantages. Mais un mécanisme bien plus interessant a été mis en place; le déplacement erroné d'un item était détecté; afin d'éviter toute corruption de l'inventaire le joueur était déconnecté et recevait une copie de l'item fautif par la poste. Or l'item originel qui n'avait jamais vraiment été déplacé, était conservé dans les sacs.

C'était ça la duplication :p

Le spam de sorts

En étudiant les paquets échangés lors de l'exécution de sorts, et en remplaçant les ID des sorts légitimes par d'autres plus puissants, les joueurs pouvaient exécuter des sorts qui n'étaient pas activés dans leur arbre de talent, ou rejouer des sorts en passant outre les cooldowns.

La compréhension des langues de la faction adverse

Encore une découverte personnelle, due cette fois, à l'étude des paquets échangés lorsque les joueurs s'exprimaient.

Dans WoW, les messages sont encodés de sorte que les factions adverses ne peuvent communiquer entre elles. De plus, certaines races maîtrisent des langages qui leurs sont propres. Or les opérations d'encodage et de décodage sont réalisées côté client. Le serveur ne fait que relayer les messages en clair !

Voici la structure de ces paquets:

Numérotation des octets:
01  02  03  04  05  06  07  08  09  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32

envoi (/w) ou dire (/d):
AA  F7  9B  6F  7B  37  01  00  00  00  07  00  00  00  MESS.
op. code                |type de mess   |langue         |
                        01=>dire        07=>commun
                        06=>crier       01=>orc
                        07=>whisper     21=>bas parler
                                        03=>taurahe
                                        0E=>troll
                                        0A=>thalassien
                                        23=>draenai
                                        00=>MJ/par def

répondre (r):
19  0E  FF  CB  57  BB  07  00  00  00  07  00  00  00  nom_joueur.MESS.


machin chuchote:
96  A1  07  00  00  00  00  A2  5B  04  00  00  00  00  00  00  00  00  00  A2  5B  04  00  00  00  00  00  05  00  00  00  MESS..
        |whispé|langue      |ID joueur                      |langue doublon?|ID joueur                      |taille mess+1  |
à machin:
00  12  09  00  00  00  00  A2  5B  04  00  00  00  00  00  00  00  00  00  A2  5B  04  00  00  00  00  00  05  00  00  00  MESS..
        |whisper|langue     |ID joueur                      |langue doublon?|ID joueur                      |taille mess+1  |
ici le commun devient par defaut


machin dit:
1E  8A  01  07  00  00  00  A2  5B  04  00  00  00  00  00  07  00  00  00  A2  5B  04  00  00  00  00  00  05  00  00  00  MESS..
        |typ|langue         |ID joueur                      |langue doublon?|ID joueur                      |taille mess+1  |

On voit que lors d'un envoi, un ID sert à identifier le type de message envoyé :

01 => dire
06 => crier
07 => whisper

Le type de langue utilisé est quant à lui envoyé dans la séquence suivante, voici ces ID :

07 => commun
01 => orc
21 => bas parler
03 => taurahe
0E => troll
0A => thalassien
23 => draenai
00 => MJ/par def

Précision: La langue ayant l'ID 00 est la langue utilisée par les maitres de jeu; or tout le monde comprend ces avatars... L'astuce était là.

Parler la langue universelle

Le contact interfaction est une option des serveurs à configurer.

Sur les serveurs où ce contact était autorisé, les paquets envoyés ne contenaient jamais d'identification de langue (00). Sur les serveurs normaux où le contact interfaction était interdit, quand un membre chuchotait à un autre membre de la même faction, la séquence reçue ne contenait pas non plus d'identification de langue.

N'importe qui pouvait donc comprendre ET potentiellement parler le langage universel ayant l'ID 00.

Exemple de chuchotement (les 2 premiers octets sont des opcodes dont la compréhension est obscure et non essentielle):

96  A1  |07 |00 00  00  00 ......

Il suffisait de corriger l'ID de langue envoyé (07 pour le langage commun de l'alliance) en le remplaçant par 00 pour être compris par tous (astuce peu discrète évidemment; il s'agissait d'un excellent moyen de se faire dénoncer et bannir) !

Comprendre toutes les langues

Pour comprendre une langue, il suffisait de changer l'ID de langue reçu.

Exemple :

Si un H2 parlait, un A2 recevait :

1E  8A  01  01  00  00  00  XX  XX  04  00  00  00  00  00  07  00  00  00  XX  XX  04  00  00  00  00  00  05  00  00  00  MESS..

Pour afficher le message en clair il suffisait de corriger le paquet reçu comme ceci :

1E  8A  01  07  00  00  00
ou:
1E  8A  01  00  00  00  00

La lutte

Avec le temps les serveurs privés ont commencé à bénéficier de patchs plus conséquents les rapprochant toujours plus du fonctionnement des serveurs officiels.

Le client de World of Warcraft contient un module que l'on appelle le Warden. Sur demande régulière de la part du serveur (~40 sec), le client retourne un rapport complet de son état de santé sur une brève période.

Dès 2012, le code gérant le Warden a commencé à être stable et fut utilisé massivement (voir le code source de TrinityCore). À partir de cette date, parmis les 800+ codes d'erreurs, on retrouve la détection des dll injectées (processus par lequel les éditeur de paquets fonctionnent), les erreurs de collisions (passage à travers les murs), la marche sur l'eau, la détection des patchs "Language patch (speak all)", la détection du wall climb, l'absence des dégats de chute, les bizarreries liées au mouvement (vitesse de déplacement erronée, téléportation), etc.

À chaque élément détecté correspond un comportement à adopter (kick, ban, etc.).

Bref, ne jouez pas avec le feu; que cela soit sur privé et à plus forte raison sur officiel :p

MJ Allopass

MJ Allopass prêt à allopasser