Blog of :/blog/weboob/DRM_de_NolifeTV.html

DRM de NolifeTV

Le site Nolife TV permet de visualiser les émissions de la chaîne de télévision du même nom via un player flash. Il existe un module Weboob pour se passer de logiciel propriétaire, mais comme on va le voir, ils ont mis en place un mécanisme afin d'empêcher le contournement. En vain.

Première version

Fin 2011 je suis tombé sur ce post de leur forum, qui inclus cette mention :

Merci de ne pas intégrer la lecture des vidéos de Nolife Online dans votre client NoAir sans passer par l'interface web de Nolife Online. De façon générale, ne développez pas de lecteur accédant directement aux vidéos de Nolife Online sans notre accord.

Y voyant une provocation au libriste que je suis, et bien que n'étant pas intéressé par les émissions de cette chaîne, je me suis mis en tête de pondre un module weboob. À des fins de recherche, évidemment.

L'analyse du site m'a permis de constater que le player flash faisait deux requêtes à /_newplayer/api/api_player.php :

skey=9fJhXtl%5D%7CFR%3FN%7D%5B%3A%5Fd%22%5F&connect=1&a=US

Celle-ci effectue un connect=1 pour s'annoncer, je ne sais pas trop pourquoi.

skey=9fJhXtl%5D%7CFR%3FN%7D%5B%3A%5Fd%22%5F&a=UEM%7CSEM&quality=0&id%5Fnlshow=1234

Cette requête renvoie entre autres l'URL du fichier vidéo. On constate dans les paramètres le champ id_nlshow qui contient l'ID de la vidéo. Rien de bien compliqué, donc, mis à part cette skey dont je n'arrivais pas à déterminer l'origine.

Après diverses recherches, j'ai fini par comprendre qu'il s'agissait d'une constante. Un peu déçu de ne pas y avoir pensé alors que c'est une « protection » commune, et sans comprendre comme d'habitude l'intérêt de faire ça, j'avais atteint une version fonctionnelle de mon module et savourais ma première victoire.

Seconde version

Il y a quelques mois, une nouvelle version du site de Nolife TV a été mise en ligne, cassant sans ménagement le module de weboob. N'ayant alors pas le courage de me replonger sur le code d'un module que je n'utilise pas, j'ai laissé les choses trainer… jusqu'à aujourd'hui.

Je n'ai pas été déçu du voyage. Après avoir corrigé l'authentification, la recherche et autres trivialités, je me suis alors penché sur la récupération de l'URL des vidéos.

Le player effectue maintenant non pas deux mais trois appels à /_nlfplayer/api/api_player.php :

skey=1b6cf46e6e484e03b370f528441357fd&a=MD5&timestamp=1351359574

Renvoie des paramètres qui ne semblent pas être utilisés par la suite, probablement l'équivalent du connect=1 de la précédente version.

a=EML&skey=893873357e374594eb6fac475846574b&id%5Fnlshow=30833&timestamp=1351359576

Récupère quelques méta-informations sur la vidéo.

quality=0&a=UEM%7CSEM%7CMEM%7CCH%7CSWQ&skey=005f9ae80d77db93b557cc14c404aa76&id%5Fnlshow=30833&timestamp=1351359579

Enfin, le graal, l'URL de la vidéo.

Facile me diriez-vous ? Ce n'est sans compter un petit détail : le paramètre skey change d'un appel à l'autre. On constate également la présence du paramètre timestamp, ce qui laisse immédiatement penser que la clef est calculée à partir de lui. Mais de quelle manière ?

C'est probablement un checksum MD5, et il y a fort à parier qu'une clef privée est utilisée comme salt. Et pour la retrouver, ça risque d'être coton.

Je me suis alors penché sur divers outils pour tenter d'analyser le player flash. swftools, flasm ou flare n'en sont pas venu à bout. J'ai juste été capable de décompresser le .swf, mais aucune information dans ce qui restait un binaire ne m'ont donné la solution.

C'est alors que je suis tombé sur ce site qui m'a sorti ce fichier parfaitement lisible.

On y découvre les lignes suivantes :

public function nl_dataloader(){
    this.loaded_data = new Object();
    super();
    salt = chaine([97, 53, 51, 98, 101, 49, 56, 53, 51, 55, 55, 48, 102, 48, 101, 98, 101, 48, 51, 49, 49, 100, 54, 57, 57, 51, 99, 55, 98, 99, 98, 101]);
}
public static function addKey(_arg1:URLVariables):URLVariables{
    _arg1.timestamp = new Date().time;
    _arg1.skey = getKey(_arg1.timestamp);
    return (_arg1);
}
public static function getKey(_arg1):String{
    return (MD5.encrypt((MD5.encrypt(String(_arg1)) + salt)));
}

À partir de là, la solution est évidente. Transposé en python dans le module weboob, ça donne le code suivant :

SALT = 'a53be1853770f0ebe0311d6993c7bcbe'
def genkey(self):
    timestamp = str(int(time.time()))
    skey = md5(md5(timestamp).hexdigest() + self.SALT).hexdigest()
    return skey, timestamp

Et ça marche. Le module NolifeTV fonctionne de nouveau. Le commit complet pour régler le problème est visible ici.

Conclusion

Comme toujours, les mesures de protection que tentent de mettre en place les sites de streaming video se contournent très facilement. Il n'est plus à démontrer que les seules personnes que ça emmerde, ce sont les utilisateurs honnêtes qui ne peuvent pas regarder leur contenu payant sur la plateforme de leur choix, ni de les voir offline.