Analyse du fonctionnement de Kajedo

Date de publication : — dernière modification :

Il existe quelques sites web sur internet proposant de lire vos textes à l'aide d'une synthèse vocales. Il peut être parfois utile d'intégrer une synthèse vocale dans ses applications, malheureusement il est difficile de trouver une société proposant une API permettant d'intégrer facilement une solution de synthèse vocale à son application. Il est alors envisageable d'étudier la possibilité de reverse engineering d'un site proposant de la synthèse vocale.

Le site Kajedo.fr, que nous analyserons

Je traiterai volontairement d'un site web mort, Kajedo.fr, afin d'éviter d'inciter directement les gens à faire de même. Si vous êtes un nostalgique de Kajedo, ou que par simple curiosité vous voulez essayer leur voix, sachez qu'il y a une série d'applications pour Android, vous permettant de télécharger un beau fichier WAV ; ça s'achète ici.

Disclaimer : Ceci est un tutoriel à but éducatif, non dans le but de vous apprendre à ne pas respecter la propriété intellectuelle. Toujours demander la permission avant d'analyser le fonctionnement d'un site web.
Respectez aussi les conditions d'utilisations des services, par exemple dans le cadre d'une synthèse vocale, les fichiers audio générés ne peuvent pas être utilisés à d'autres fins que la démonstration du service.

Observer et analyser les requêtes

Chez Kajedo, il y a des interactions entre votre ordinateur et le serveur de Kajedo à chaque fois que vous demandez à lire un texte. Dans ce cas, il s'agit de requêtes HTTP. Pour observer ces requêtes, nous utiliserons Firebug pour Firefox, mais on peut aussi utiliser un autre outil de développement pour son navigateur web ; et si votre navigateur ne supporte pas de tel outil, il est toujours possible de recourir à Wireshark.

Ouvrez le module Firebug puis cliquez sur l'onglet "Réseau". Cet onglet va nous montrer toutes les requêtes HTTP du navigateur. Firebug va peut-être vous inviter recharger la page, sans quoi vous ne pourrez analyser le traffic. Cliquez sur le bouton "Effacer", pour effacer toutes les requêtes existantes. On va alors demander à Kajedo de lire du texte, ce qui génèrera quelques requêtes :

L'onglet réseau de Firebug, avec les requêtes effectuées à chaque lecture

On observe 3 requêtes. On a inspecter la première : on clique dessus, puis on sélectionne le sous-onglet "Réponse" pour voir la réponse du serveur :

La requête config.xml dans Firebug

Elle ne nous sera pas très intéressante. On pourra noter que les fichiers indiqués en réponse ne sont pas appellés par le navigateur.

La seconde requête est une requête de type POST, donc potentiellement celle qui va demander au serveur de générer le fichier audio :

La requête makenewsound dans Firebug

On a affaire à du XML, pour mieux le lire, il est préférable de l'indenter (plusieurs outils en ligne peuvent réaliser automatiquement la lourde tâche du formatage) :

<?xml version="1.0" encoding="UTF-8"?>
<KagedoSynthesis>
    <Identification>
        <codeAuth><!-- Privé ! --></codeAuth>
    </Identification>
    <Result>
        <ResultCode>200</ResultCode>
        <ErrorDetail/>
        <Duration>254</Duration>
    </Result>
    <MainData>
        <DialogList>
            <Dialog character="darkvadoor" url="http://webservice.kagedo.fr/mp3/sounds/11970949/sounds/3/c/b/7/837e789264a6970ab16ebe21d6a11362c6723c5/0.mp3" size="6687">Test</Dialog>
        </DialogList>
    </MainData>
</KagedoSynthesis>

On observe ici une URL d'un fichier MP3. Si on regarde les requêtes de tout à l'heure, on se rend compte que cette URL est appelée. On peut donc formuler comme hypothèse que la requête "makenewsound" est responsable de la création du fichier audio. Pour en avoir le coeur net, il faut regarder ce que le navigateur a envoyé au serveur. On clique sur l'onglet "Source" de la requête pour l'afficher :

La source de la requête makenewsound

On peut observer un formulaire avec un unique champ nommé "KagedoSynthesis" qui contient une (longue) chaîne de caractères. Si cette chaîne de caractères vous semble incompréhensible, c'est normal. Il s'agit d'une chaîne encodée pour être encapsulée dans une URL (plus d'infos ici). Cela évite tout conflit avec des caractères spéciaux. Pour la décoder, il existe de nombreuses fonctions dans des languages de programmation : URI.unescape en Ruby, urldecode en PHP, etc.

Une fois la requête décodée, on obtient ceci :

<KagedoSynthesis>
    <Identification>
        <codeAuth><!-- Privé --></codeAuth>
    </Identification>
    <Result>
        <ResultCode/>
        <ErrorDetail/>
    </Result>
    <MainData>
        <DialogList>
            <Dialog character="darkvadoor">Test</Dialog>
        </DialogList>
    </MainData>
</KagedoSynthesis>

On voit le texte que j'ai tapé ("Test"), la voix que j'ai demandé ("darkvadoor"). On sait à présent ce que le navigateur envoie aux serveurs de Kagedo. A un détail près : il y a un code d'authentification dans les requêtes que j'ai analysé (volontairement masquées), est-il généré d'une manière spéciale, ou est-ce un code fixe ?

Le code d'authentification

Le site Kajedo.fr utilise un fichier Flash pour envoyer les requêtes HTTP et lire le MP3. C'est donc lui qu'il va falloir inspecter. J'utilise Flash Decompiler pour regarder le code source du fichier. En inspectant le code source, on se rend compte assez rapidement que le code est une constante :

Le fichier flash dans le décompilateur

Problème résolu.

Simuler le navigateur

J'ai réalisé un substitut de l'interface web de Kajedo en Python :

import urllib2
from urllib import urlencode

# On prépare la contenu de la requête
io = raw_input("Tapez un texte : ")
voix = raw_input("Donnez une voix : ")
url = "http://webservice.kagedo.fr/nsynthesis/ws/makenewsound"
req = {"KagedoSynthesis" : "<KagedoSynthesis><Identification><codeAuth><!-- Privé ! --></codeAuth></Identification><Result><ResultCode/><ErrorDetail/></Result><MainData><DialogList><Dialog character=\"" + voix + "\">" + io + "</Dialog></DialogList></MainData></KagedoSynthesis>"}
data = urlencode(req)
header = { "User-agent" : "User agent"}

# On fait la requête
request = urllib2.Request(url,data,headers=header)
response = urllib2.urlopen(request)
returned = response.read()

# On affiche l'URL du MP3
print returned.split("\"")[7]

On notera la solution relativement crade pour récupérer l'URL du fichier MP3, qui a pour seul avantage d'être plus rapide qu'un parseur XML.