Fêtons le départ de Max 87


Je crois que tout le monde est au courant, Max quitte la bride de la France pour retrouver les bridés de Thaîlande. Il faut marquer le coup, non ?

Alors on a pondu un petit jeu :

Ouvrez le coffre !

Le principe est simple, le premier qui arrive au bout de la série d’énigmes a gagné.

Le jeu encourage la programmation, particulièrement Python, mais peut être résolu de plusieurs manières, certains plus élégantes que d’autres :) C’est très orienté nerd.

Dans tous les cas j’ai codé ça un peu à l’arrache car on a eu l’idée en milieu d’après-midi, alors si ça se trouve il y a des failles à exploiter pour gagner plus vite. Même un petit brute-force, ça se tente (même si je pense que le serveur mourra avant d’avoir progressé de 10% et que c’est celui du blog \o/).

Bref, y a pas de règle à part que normalement le jeu se désactive une fois que le premier joueur ouvre le coffre. Mais n’hésitez pas à mettre un com si vous trouvez un bug qui vous empêche la progression. Cela dit, si vous êtes bloqué, y a quand même plus de chance que ce soit parce que vous n’avez pas trouvé la solution. Et là, on vous aidera pas ^^

Sachez quand même que j’ai fais le jeu plusieurs fois, et pondu des scripts en conséquences pour tester que ça marche bien comme prévu.

Quand à savoir ce qu’il y a dans le coffre…


Le jeu est terminé, le code source est en ligne.

87 thoughts on “Fêtons le départ de Max

  • Sam Post author

    J’ai changé le lien de l’article a posteriori, sorry pour ceux qui l’ont reçu par mail.

  • Sam Post author

    Ah bah c’est malin, le jeu marche plus. Ca commence bien. Bon, je fix.

  • lezy

    Ah je suis trop dégouté, le temps de lancer le programme, le coffre a été ouvert…

    Sinon merci pour le petit jeu ;)

  • Sam Post author

    Nan le coffre a pas été ouvert, c’est notre installation qui merde grave. C’est ça le codage à l’arrache. On travaille dessus :-)

  • Pickle

    Ce serait pas mieux d’intégrer un timer pour ce genre de jeu plutôt que de faire ‘premier arrivé, premier servi’.

    Puis on dit que le gagnant serait celui qui finirait le jeu en un minimum de temps.

  • Sam Post author

    Bon, c’est réparé. Mais alors, à l’arrache de chez à l’arrache hein ^^ Vous pouvez y aller, je confirme que le coffre n’a pas été ouvert (la variable n’a pas été setté).

  • Sam Post author

    @Pickle : non, ça suppose de faire des comparaison, de retenir un compte pour chaque user, etc. C’est chiant. En plus, ça oblige les gens à se bouger, et ça favorise ceux qui suivent le blog souvent puisqu’ils ont plus de chance de voir l’article tôt (qui est volontairement omis sur twitter). En plus, ça met une petite part de hasard, ce qui rend le truc un peu vicieux. Un jeu trop juste, c’est moins fun à regarder pour le créateur !

  • Pocket Tiger

    Harg, je suis une couille en sécu-insertion-phyton-etc …. Dommage. J’aimerais bien savoir ce qu’il y à dans le coffre quand même =)

    Bon voyage Max.
    (Tien le trombone à pas réagit au mot couille =/ )

  • Sam Post author

    Logs:

    game.sametmax.com/?code=0	1
    game.sametmax.com/?code=123456	1
    game.sametmax.com/?code=AZZZ	1

    Bandes de pignoufs. Je suis parfois un boulet, mais quand même…

  • ohehoh

    Encore un prejuge ethno-racisto-colore ! Non ya pas que des brides en Thailande ! Ya aussi des gros-nez.. Comme votre serviteur.. Qui d’ailleurs ne vous sert pas a grand chose.. Mais quand meme !!!

  • Max

    Alors bande de tafioles !
    PAs un capable d’ouvrir ce tout petit coffre ? :)

  • Khertan

    Mouais l’image est pas approprié …

    C’est un “Metal Frontier Safe”, une tirelire coffre jouet pour enfant …

    C’est une tirelire coffre à deux combinaison … donc 4 chiffres …
    Je les ai tous testé, spa ça :p

  • Etienne

    Y’a peut-être quelque chose à tenter du côté de “Y U NO OPEN” (meme) et du “GET” de “I don’t GET it”…

    Moi j’ai ni le temps ni la patience

  • Recher

    J’ai essayé quelques trucs : tous les verbes HTML en majuscules, minuscules et titled-case. Quelques autres mots : open, code, safe, fuck…

    Et sinon, j’ai rien trouvé de frappant dans le code source de la page.

    Pour l’instant je sèche. Ça fait un peu mal à mon ego d’être bloqué sur la première énigme de la série (puisqu’il semblerait qu’il y en ait une série). Mais c’est pas grave. Je vais attendre un peu.

  • Etienne

    Et si on collaborait? Y’a peut-être l’un ou l’autre qui a trop d’idées a essayer pour un seul homme.

  • Krypted

    Y’a aussi deux autres sites sur les ports 8070 et 8080 mais je pense que c’était pour leurs tests

  • Pierre

    Vu que le message d’erreur de base dit “I don’t GET it”, j’ai essayé en passant par la méthode POST:

    curl --data "code=mescouillessurtonnez" http://game.sametmax.com/

    ça me retourne une nouvelle erreur :

    Error log : areyouhuman

    J’ai essayé de passer différents codes (yes, yesIam, ettasoeur, etc.), mais rien n’y fait.

  • Syl

    J’ai fait le même test que Pierre, et j’en suis au même point.
    En tout cas, je suis sur que c’est du POST et pas du GET.

    J’ai tenté avec le paramêtres “human”, “areyouhuman”, “robot”…le tout avec différentes réponses (‘True’, ‘true’, ‘False’, ‘false’, ‘yes’, ‘no’, 0, 1, “Evidemment que je suis un humain ducon!!!”)

    …mais rien n’y fait non plus.

    Je pense qu’il faut creuser de ce côté là…

  • Etienne

    @Pierre, Kertan

    Malin!

    PS:
    En passant, si c’est le serveur de Sametmax, alors c’est Apache

  • Sam Post author

    @foxmask: si ça s’appelle les mots fléchés. Ma grand-mère en fait.

    @Syl: d’après https://www.grc.com/haystack.htm, son espace de recherche est de 3.21 x 10^8 ce qui est très faible et il peut être bruteforcé en 3.72 jours max sur une attaque en ligne, en raison de 1000 essais à la seconde. Après, tu vas juste tuer le blog avec :-)

  • Sam Post author

    @Etienne: on a nginx sur ce serveur je crois.

    @Pierre: j’aurais du mettre une réaction sur “mes couilles sur ton nez”. Ce scénario était évident, je me sens bête de l’avoir négligé.

  • Syl

    @Sam: Merci pour cette indication, je crois que j’ai trouvé le pass! ;)

    …mais je sais pas comment le saisir.

  • Sam Post author

    Il faut s’amuser ^^ C’est là que Python va être utile.

  • Syl

    Je sens que je suis pas loin…mais je dois partir en réunion.
    En espérant que le coffre ne sera pas ouvert d’ici là!

  • manatlan

    je pars aussi en reunion …

    j’en suis là:

    # http://game.sametmax.com/areyouhuman
    import pickle
    from StringIO import StringIO
    NextUrlContainer = type(“NextUrlContainer”, (), {‘__init__': (lambda s, n: setattr(s, ‘next’, n))})

    r=”Y2NvcHlfcmVnCl9yZWNvbnN0cnVjdG9yCnAwCihjX19tYWluX18KTmV4dFVybENvbnRhaW5lcgpw MQpjX19idWlsdGluX18Kb2JqZWN0CnAyCk50cDMKUnA0CihkcDUKUyduZXh0JwpwNgpJMjA5NzM1 MQpzYi4=”””.decode(“base64″)
    o= pickle.load(StringIO(r))
    print o

  • Etienne

    Et moi, thanks to manatlan, j’en suis là

    import pickle
    import base64
    NextUrlContainer = type("NextUrlContainer", (), {'__init__': (lambda s, n: setattr(s, 'next', n))})
    next_url = pickle.loads(base64.b64decode("Y2NvcHlfcmVnCl9yZWNvbnN0cnVjdG9yCnAwCihjX19tYWluX18KTmV4dFVybENvbnRhaW5lcgpw MQpjX19idWlsdGluX18Kb2JqZWN0CnAyCk50cDMKUnA0CihkcDUKUyduZXh0JwpwNgpJMjA5NzM1 MQpzYi4= "))
    print next_url.next

    2097351

  • Etienne
    import pickle
    import base64
    NextUrlContainer = type("NextUrlContainer", (), {'__init__': (lambda s, n: setattr(s, 'next', n))})
     
    first_url = "Y2NvcHlfcmVnCl9yZWNvbnN0cnVjdG9yCnAwCihjX19tYWluX18KTmV4dFVybENvbnRhaW5lcgpw MQpjX19idWlsdGluX18Kb2JqZWN0CnAyCk50cDMKUnA0CihkcDUKUyduZXh0JwpwNgpJMjA5NzM1 MQpzYi4= "
     
     
    def next(pickled):
        next_url = pickle.loads(base64.b64decode(pickled))
        return next_url.next
     
    base_url = "http://game.sametmax.com/areyouhuman/{}"
    next_url = next(first_url)
     
    while True:
        resp = requests.get(base_url.format(next_url))
        if resp.status_code != 200:
            print resp.text
            break
        else:
            next_url = next(resp.text)
            print next_url
  • zanguu
    import pickle
    import base64
    from urllib2 import urlopen
     
    NextUrlContainer = type("NextUrlContainer", (), {'__init__': (lambda s, n: setattr(s, 'next', n))})
    previous = [];
     
    def loop(next_num="2097351"):
    	path = "http://game.sametmax.com/areyouhuman/" + str(next_num)
    	print path 
    	html = urlopen(path).read()
    	print html
    	next_url = pickle.loads(base64.b64decode(html))
    	if not(next_url.next in previous):
    		previous.append(next_url.next)
    		loop(next_url.next)
    	else:
    		print "boucle infinie !"
    		print "previous"
     
    loop()

    Moi j’ai ça.

    Et 666 url plus tard j’ai une 404 (impossible de l’avoir maintenant qu’il y a déjà un gagnant ?)

    pour les flemmards: le dernier numéro est 9664723

  • zanguu

    Auto-reponse
    1- mes balises codes ont merdé (ou j’ai zappé de les mettre), si un gentil blogeur pouvais réparer ça, ce serait gentil.
    2- ah non la 404 c’est parce que je tapais pas au bon endroit
    3- Lol, 666 clics aussi ?

  • Etienne

    Le dernier c’est le nom d’un fichier zippé.

    Je laisse la surprise à ceux qui n’y sont pas encore?

  • Sam Post author

    Je pense que ton boulot a un antivirus qui scan tous les zip, et celui-là, il va vitre montrer si l’anti-virus a été codé correctement \o/

  • Etienne

    @Syl
    J’ai utilisé un:

    import requests
    import zipfile
    import StringIO
    import os
     
    resp = requests.get("http://game.sametmax.com/wololo.zip")
     
    zipped = zipfile.ZipFile(StringIO.StringIO(resp.content))
    name = zipped.getinfo(zipped.namelist()[0])
    zip_file_path = zipped.extract(name)
     
    while True:
        zipped = zipfile.ZipFile(zip_file_path)
        namelist = zipped.namelist()
        if len(namelist) == 1 and namelist[0].endswith('.zip'):
            name = zipped.getinfo(zipped.namelist()[0])
            new_zip_file_path = zipped.extract(name)
            print new_zip_file_path
            os.remove(zip_file_path)
            zip_file_path = new_zip_file_path
        else:
            print namelist
            break

    Lancé dans un terminal, peut-être que ça passera.

  • Syl

    Comment ça? Ca risque de flinguer mon ordi votre fichier???

    Bon ben je vais devoir attendre ce soir…si c’est pas ouvert d’ici là!

  • Syl

    @Etienne: Merci! …mais ça passe pas. Faut croire qu’il est pas si pourri que ça cet AV (si vous saviez ce que c’est vous rigoleriez bien!).

  • Etienne

    Je viens d’essayer ça:

    import json
    d = json.loads(requests.get("http://game.sametmax.com/api.json").text)
    for k, v in d.iteritems():
        print k, len(v)

    qui donne ça:

    1
    1
    $ 2
    ( 3
    , 2
    0 2
    4 1
    8 3
    2
    B 1
    F 2
    J 1
    N 1
    R 2
    V 2
    Z 3
    ^ 2
    b 3
    f 3
    j 2
    n 20
    r 2
    v 2
    z 3
    ~ 2
    1

    3
    ! 1
    % 2
    ) 1
    – 2
    1 3
    5 2
    9 3
    = 2
    A 1
    E 3
    I 2
    M 1
    Q 3
    U 1
    Y 3
    ] 1
    a 50
    e 60
    i 30
    m 3
    q 2
    u 2
    y 3
    } 3

  • Sam Post author

    @Syl: le coffre a déjà été ouvert. Mais non, le zip va pas tout bousiller, il y que quelques centaines de niveau de nesting, c’est pas un zip infini.

  • Etienne

    Le dernier zip s’appelle “one_more_time_1000.zip” quand-même!

  • Max

    Alog de l’antivirus de Syl:

    if file.name.find('zip') != -1:
    Alert "Virus!"

    ^^

  • Max

    Ah merde! Bon ben tant pis!
    Bravo au gagnant! ^^

    c’est pas une raison pour pas continuer de chercher feignasse! :p

  • Max

    bon ben je vais aller poster le cadeau au gagnant, j’espère qu’il ne sera pas périmé en arrivant…

  • Syl

    @Max: Ben du coup, je continuerais à chercher ce soir, j’arrive pas à récuperer le fichier.

    (l’AV en question a récemment été décrié publiquement par son auteur initial…donc oui, c’est à peu près ça!)

  • Etienne

    @syl
    Te fatigue pas, le zip contient au final un fichier texte dans lequel on a:

    api.json

  • Etienne

    Je me dit que comme on a un dictionnaire comme réponse à api.json,
    et qu’un dictionnaire n’est pas ordonné, il faut l’ordonner sur quelque chose:

    import requests
    import json
    d = json.loads(requests.get("http://game.sametmax.com/api.json").text)
     
    liste_vals = sorted([(k, v) for k, v in d.iteritems()], key=lambda item: item[0])

    Après on peut extraire une série ordonnée d’entiers:

    print "".join([str(len(item[1])) for item in liste_vals])

    Mais bon, là j’en ai marre, faut que je mange quelque chose.

  • Recher

    Les clés du dictionnaire, c’est les caractères du code ASCII, de 9 à 13, puis de 32 à 126.
    C’est à dire, à peu de chose près, tous les caractères imprimables du code ascii.
    Et le nombre d’éléments du dictionnaire est pil poil 100.

    Y’a le “\\u000b” qui est un peu bizarre. Ça fait pas un caractère unique du code ASCII. Mais à mon avis c’est juste un détail. Je pense que cette clé est censé représenter le caractère ASCII numéro 11.

    Pour l’instant, je vois rien de plus.

  • Syl

    J’ai essayé de concaténer les valeurs numériques du dictionnaire en partant du principe que le mot de passe est “sammax” ou “SAMMAX” (d’après les indications de Sam en début de post.

    …mais ça ne donne rien non plus.

    Bon allez, c’est pas tout ça, mais j’ai du boulot moi!

  • Recher

    Trouvé ! (du moins, l’étape en cours).


    # d = dictionnaire récupéré avec l'url api.json
    for order in range(10, 90, 10):
    for key, val in d.items():
    if len(val) == order:
    print key

    ça donne les lettres “gnitaehc”
    ce qui fait “cheating” à l’envers.

    Et après :
    http://game.sametmax.com/cheating

    La suite ressemble au code de konami utilisé dans les jeux vidéo NES, pour tricher.

    À bientôt, avec moi ou quelqu’un d’autre.

  • Recher

    Ah bah c’est bon :

    http://game.sametmax.com:8080/konami

    Félicitations à la personne qui a trouvé tout ça en premier ! Ça ferait plaisir qu’elle vienne faire un petit coucou ici.

    Désolé pour le formatage pourri du code dans mon message précédent, je m’y suis pas encore habitué.
    Explication en langage humain : prendre la lettre dont la valeur du dico est 10 (10 fois l’élément “.”), puis la lettre dont la valeur est 20, puis 30, 40, 50, 60, 70, 80.

  • Etienne

    Ouf, c’est fini! Merci Recher!

    Et merci les deux tarés, je me suis bien amusé!

    Clair que tout seul j’y serais jamais arrivé.

    bravo à MrAaaah

  • Recher

    De rien !
    (Et je viens de m’apercevoir que le gagnant a fait coucou, mais dans l’article suivant. Donc merci à lui pour le coucoutage).

  • cyp

    Oui fallait le mériter, félicitation a ceux qui se sont accrocher jusqu’au bout y’avait du boulot quand même ^^

    Ca me rappelle la fac la grande époque des clones d’ouverture facile et de ses clones, certains y passait des journées entières (nuit comprise).
    Y’avait même eu quelque jeux avec des gros lots en cash à l’époque il me semble.

    Sinon dommage pour le bruteforce Konami était dans mon dico mais j’ai coupé au bout de 15mn (moitié lettre b) quand j’ai du partir de chez moi.
    Ceci dit ça n’aurait pas marché (à cause de la casse “Konami” et l’url http://game.sametmax.com:8080/?code=konami )

  • groug

    Bien joué au vainqueur !
    De toute façon, j’m’en fous, j’ai pas de Xbox360, j’ai une PS3 :p

  • Recher

    J’ai bien peur que le bruteforce que tu avais lancé n’aurait pas marché de toutes façons, quel que soit la casse.

    La bonne url c’est
    http://game.sametmax.com:8080/konami

    Sans le ” ?code= ”

    En gros, la zone de saisie de la page ne sert à rien, à part récupérer le tout premier indice “I Don’t GET it”.

  • Etienne

    Max écrivait plus ahut:

    j’espère qu’il ne sera pas périmé en arrivant…

    Ce n’est donc pas une poupée gonflable, ni une pute thaï, ni un billet d’avion pour Phuket.

    Qu’est-ce qui périme relativement vite? 10 kg de boudin noir artisanal? Des coupons de promotion pour le McDo, valables jusqu’au mois prochain?

  • Max

    héhé ça vous a fait fumer le cerveau hein :)

    @etienne, tu le sauras si le gagnant décide d’en faire un article :p

  • Recher

    @cyp : ah oui, désolé. J’ai mal lu ce que tu disais en fait.

    Pour info, y’avait pas forcément besoin de dézipper les 1000 fichiers “one_more_time_xxxx”.

    En ouvrant le one_more_time_1.zip avec un éditeur hexadécimal, les infos sont assez lisibles. On voit la suite de noms de fichiers, de 2 à 1000. Et à l’octet 50874, on a les caractères : “youdiditjonhy.txtapi.json”
    Ça ressemble à un nom de fichier .txt, suivi immédiatement de son contenu.

    La suite du fichier .zip, c’est les noms one_more_time_xxxx, mais numérotés à l’envers, de 1000 à 2. Un peu comme des parenthèses qu’on referme.

    Enfin je dis ça comme ça, je sais pas si j’aurais trouvé le “api.json” en ne le connaissant pas d’avance.

    Et étrangement, le tout premier fichier wololo.zip contient des données toutes garblés et ininterprétables. Je sais pas trop pourquoi. Mais je suis pas expert en .zip.

  • Syl

    Tiens, je vous que vous vous êtes bien inspiré de ce site pour le défi! ;)

  • Sam Post author

    Oui c’est un grand classique python-challenge. Je leur ai pique le zip et la redirection. J’ai ajouté le pickle / base64, et le api.json est inspiré d’un de leur count de string.

Leave a comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.