logo

 

Alteration de reqûetes cryptées avec flash
Ou comment en faire beaucoup trop pour fausser les scores sur les jeux.

Dans les sites de jeux en flash (que je ne fréquente plus depuis au moins 2002), la transmission des scores depuis les applications flash se font souvent en clair, par simple GET ou POST.
Les arguments classiques sont l'identifiant de l'utilisateur ou son pseudo, et le score, en décimal. On peut très facilement voir passer et altérer ces requêtes en utilisant des plugins firefox comme Tamper Data.
Il suffit de remplacer les données par celles que l'on veut, et le serveur n'ira pas (et ne peut pas) vérifier.

Dans les sites payants, avec des concours pour gagner des lots, ou sur les jeux produits par des studios assez gros, les scores sont parfois cryptés pour décourager cette attaque simple.
On peut souvent utiliser un programme pour modifier les valeurs en RAM, comme Cheat Engine (merci [Kicoll]) mais seulement quand l'action est assez lente, ou assez contrôlable pour permettre de faire différentes recherches de valeurs. Et ça ne marche pas toujours, à cause de vérifications (dérivées de certaines variables, pour détecter les changements brutaux) qui se font justement au niveau de l'application flash.


Je prendrais exemple ici sur le jeu "Paf le Chien" d'Adictiz, trouvable sur Facebook, et sur lequel plusieurs de mes connaissances s'étaient acharnées pour dépasser le kilomètre.
La distance augmente rapidement et on ne peut pas mettre le jeu en pause pour filtrer les valeurs et ésperer trouver l'adresse de la variable.
On peut voir avec Tamper Data qu'une fois la vitesse tombée à 0, le score est immédiatement transmis à fb.adictiz.com/g.php par un POST:

Visiblement, les données ne sont pas transmises en clair. L'en-tête n'indique rien de spécial, mais le jeu de caractères utilisés pour coder l'information montre que le tout est en Base64.
On peut voir les codes %2B et %3D qui correspondent aux valeurs ASCII de "+" et "=", qui sont aussi typiques.

Le décodage Base64 vers Hexa ne donne rien de concluant. On aurait donc à faire a du cryptage, qui serait pris en charge directement par l'application flash.
Heureusement, l'ActionScript est parfaitement décompilable avec des utilitaires plus ou moins payants.
Encore une fois, le SWF du jeu peut être téléchargé avec Tamper Data, puisque l'adresse exacte n'est pas directement trouvable dans la source de la page (utilisation de scripts).


La liste des scripts dévoile une fonction interessante: HttpCrypt, qui n'aurait pas pu être mieux nommée.
Aussi, un dossier "hurlant", qui est le nom de la librairie de crypto ActionScript3 de Metal Hurlant.

Quand le jeu démarre (dans Stratosphere.as), la fonction HttpCrypt est appellée, où on voit que le jeu a absolument besoin de g.php pour transmettre ses données:

this.oHC = new HttpCrypt(this.sitePath + "g.php");

En éxaminant rapidement la suite du script, on reconnaît la fonction appellée une fois le lancer terminé:

private function finDuJeu()

A la fin de cette fonction, on trouve:

if (this.distance > 10) {
this.callFBJS("Share", [Math.round(this.distance)]);
this.oHC.ResetVars();
this.oHC.SetVar("score", String(Math.round(this.distance)));
this.oHC.SetVar("user", this.flashVars.fb_sig_user);
this.oHC.Send();
}

La distance n'est transmise que si elle excède 10m. Elle est arrondie (distance est en virgule flottante dans tous les scripts du jeu).
ResetVars() vide la variable outData.


La fonction SetVar se trouve dans HttpCrypt.as et assemble simplement les deux arguments qu'on lui passe.

public function SetVar(param1:String, param2:String) : void {
this.outData = this.outData + (param1 + ":" + param2 + ";");
return;
}

outData sera donc sous la forme "score:xxx;user:yyy;" juste avant que Send() soit apellé dans finDuJeu().

Facebook fournit une variable fb_sig_user, qui est un identifiant numérique pour chaque utilisateur. Ici j'utiliserais un identifiant quelconque, appartenant peut être déjà à quelqu'un.
Le jeu se sert de cette variable pour la renvoyer avec le score.

La fonction Send() dans HttpCrypt.as appèle encrypt() avec outData comme argument.

private function encrypt(param1:String) : String {
var _loc_2:ByteArray = null;
_loc_2 = Hex.toArray(Hex.fromString(param1));
var _loc_3:* = this.prepare();
_loc_3.encrypt(_loc_2);
return Base64.encodeByteArray(_loc_2);
}

On voit ici aussi clairement les étapes pour coder les données du POST. D'abord outData est converti en un tableau d'octets, la fonction de crypto est initialisée par l'appel de prepare(), et la valeur est retournée codée en Base64.


La fonction prepare() indique tout aussi clairement la méthode de cryptage, sa clé et son mode:

private function prepare() : ICipher {
var _loc_1:* = new PKCS5();
var _loc_2:* = Crypto.getCipher(this.encAndModeType, Hex.toArray(Hex.fromString(this.secretKey)), _loc_1);
_loc_1.setBlockSize(_loc_2.getBlockSize());
return _loc_2;
}

C'est là où on peut découvrir toute la grandeur de l'imagination des dévelopeurs, avec notament la clé de cryptage, ici secretKey (véridique, quoique pas si étonnant).

Leur chaîne outData est alors cryptée avec Blowfish en mode ECB, avec la clé "merdemerde". A partir de là, on a toutes les informations necéssaires pour fausser les données du POST.
Je reprend les étapes dans l'ordre:
-Composer une chaine sous la forme "score:xxx;user:yyy;"
-La crypter en Blowfish mode ECB, surement en blocs de 32 octets (deviné et confirmé par la suite).
-La passer en Base64.
-Modifier les caractères spéciaux pour qu'ils puissent être passés dans un POST.


L'identifiant Facebook est trouvable dans à peu près toutes leurs requêtes de pages, surtout quand on commente quelque chose. C'est un nombre entre 6 et 9 chiffres.
Il faut chercher des variables comme u, uid, userid... Surtout faire attention que c'est bien le notre en le cherchant sur Google (il doit trouver la page de votre profil, si il date un peu).

La petite difficulté est de pouvoir utiliser Blowfish avec le bon mode et avec les données qu'on veut.
Comme je n'ai trouvé aucun service en ligne marchant en ECB et acceptant des données en héxa, j'ai eu recours à la libraire mcrypt en php, que free.fr offre généreusement avec le plugin Blowfish.
Un simple script permet de réaliser les tests qu'on souhaite:

function bfcrypt($donnees, $cle) {
$td = mcrypt_module_open("blowfish", "", "ecb", "");
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $cle, $iv);
$crypted = mcrypt_generic($td, $donnees);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $crypted;
}

function str2hex($string) {
$hex="";
for ($i=0; $i < strlen($string); $i++)
$hex .= (strlen(dechex(ord($string[$i])))<2)? "0".dechex(ord($string[$i])): dechex(ord($string[$i]));
return $hex;
}


(A partir d'ici, les données ont été modifiées pour ne pas qu'on retrouve mon identifiant, alors c'est normal si les données ne sont pas bonnes, l'important c'est que ça puisse être compris).

Pour vérifier que la méthode est bonne, on peut essayer de retomber sur la valeur de outData à partir des données du POST. Par exemple, avec une distance de 62m, le jeu a envoyé:
data=WHqM1vNOxbeyFzKPxxX2iZzky72bZxJoKFJYS3uHfN0%3D (soit WHqM1vNOxbeyFzKPxxX2iZzky72bZxJoKFJYS3uHfN0=)
Ce qui donne en héxa: 587a8cd6f34ec5b7b217328fc715f6899ce4cbbd916712682852584b7b877cdd
Décrypté: score:62;user:1139855929; (suivi de 7 caractères de valeur $07, pour combler et arriver à une longueur de 32 octets, comme prévu.)

Il suffit alors d'utiliser le script PHP pour créer les données que l'on veut envoyer à la place du vrai score:

echo str2hex(bfcrypt("score:4597;user:1139855929;", "merdemerde"));

Donnera une suite d'octets qu'il faudra passer en Base64 par le biais d'un convertisseur en ligne (ce qui peut aussi être fait via PHP), qu'il faudra ajuster pour éviter les caractères spéciaux.
Il ne reste plus qu'à intercepter une nouvelle fois le POST émis par le jeu, et de remplacer les données par:

D103Gh3O9Pj6qxOA89x9PfhSatbwvx2xX5r3nXqbjvU%3D

Malheureusement, je ne peut pas montrer le résultat final car le jeu transmet ces données seulement vers adictiz.com, et pas vers Facebook. Facebook prend le score en clair, mais avec un checksum, dont on ne peut pas connaître l'algorithme de calcul... Ca ne devrait par contre pas rater pour d'autres sites.

Bon kikoololisme.

footer
symbol symbol symbol symbol symbol