Obfuscating Python 16


Python est un langage qu’il est difficile de rendre illisible. Difficile, mais pas impossible.

D’abord, les techniques habituelles de substitutions de lettres par leurs codes ASCII sont toujours valables. Le fameux easter egg import this en fait d’ailleurs usage; si vous ouvrez le fichier dont le chemin est contenu dans this.__file__, vous trouverez :

s = """Gur Mra bs Clguba, ol Gvz Crgref
 
Ornhgvshy vf orggre guna htyl.
Rkcyvpvg vf orggre guna vzcyvpvg.
Fvzcyr vf orggre guna pbzcyrk.
Pbzcyrk vf orggre guna pbzcyvpngrq.
Syng vf orggre guna arfgrq.
Fcnefr vf orggre guna qrafr.
Ernqnovyvgl pbhagf.
Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.
Nygubhtu cenpgvpnyvgl orngf chevgl.
Reebef fubhyq arire cnff fvyragyl.
Hayrff rkcyvpvgyl fvyraprq.
Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.
Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.
Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.
Abj vf orggre guna arire.
Nygubhtu arire vf bsgra orggre guna *evtug* abj.
Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.
Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.
Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!"""
 
d = {}
for c in (65, 97):
    for i in range(26):
        d[chr(i+c)] = chr((i+13) % 26 + c)
 
print "".join([d.get(c, c) for c in s])

Ensuite, il y a le fait qu’il est possible de spécifier (en Python 2.7, mais plus Python 3), l’encoding du module en ROT13, base64 ou même zip. Au lieux de mettre # -*- coding: utf8 -*-, vous pouvez faire :

# -*- coding: rot13 -*-
 
cevag h'Fnz rg Znk, cnepr dhr obver frhy rfg zbvaf qebyr dhr pbqre n cyhfvrhef'

Mais au dela de ces petites astuces, il y a bien entendu la véritable offuscation, celle qui utilise des imbrications d’instructions capilotractées avec de multiples niveaux de nesting dans des onliners qui s’étendent sur des kilomètres en incluant des noms de variables alambiqués le tout organisé dans des structures syntaxiques volontairement obscures en détournant des capacités du langage pour en faire une indigeste bouillie immonde dont la lecture provoquera des saignement durant la phrase de vomi. Comme cette phrase.

Généralement ça passe par un usage massif des features que Guido déteste comme les lambdas ou les ;.

Il y a les classiques onliners à base de map / reduce, par exemples les 1000 premiers nombres … premiers :

print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))

Ensuite il y a ceux qui aiment se la jouer “mes variables ont des noms de code Fortran” :

print (lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24)

Ce qui va afficher cette belle composition de Mandelbrot (si votre terminal fait 80 colonnes de large) :

Mandelbrot dans un terminal

La génération procédurale : ou comment faire de Zolies choZes avec 1ko de code.

D’ailleurs, pour l’offuscation, on adore les trucs de maths parce que ça donne l’air intelligent et compliqué, et Mandelbrot est un truc très prisé puisqu’avec peu de lignes on peut outputer du Dali Période Guimauve automatiquement :

_                                      =   (
                                        255,
                                      lambda
                               V       ,B,c
                             :c   and Y(V*V+B,B,  c
                               -1)if(abs(V)<6)else
               (              2+c-4*abs(V)**-0.4)/i
                 )  ;v,      x=1500,1000;C=range(v*x
                  );import  struct;P=struct.pack;M,\
            j  ='<QIIHHHH',open('M.bmp','wb').write
for X in j('BM'+P(M,v*x*3+26,26,12,v,x,1,24))or C:
            i  ,Y=_;j(P('BBB',*(lambda T:(T*80+T**9
                  *i-950*T  **99,T*70-880*T**18+701*
                 T  **9     ,T*i**(1-T**45*2)))(sum(
               [              Y(0,(A%3/3.+X%v+(X/v+
                               A/3/3.-x/2)/1j)*2.5
                             /x   -2.7,i)**2 for  \
                               A       in C
                                      [:9]])
                                        /9)
                                       )   )

Notez cette fois que le code, en plus d’être particulièrement imbuvable, a été gentiment indenté pour ressembler à la figure qu’il va lui même pondre dans le fichier M.bmp que voici :

Mais bien entendu il y a des gens qui arrivent à faire des trucs moches sans entrainement. Par exemple des codeurs JS qui vous font ça:

if "longue chaine".find('chaine') != -1:

Au lieu de :

if 'chaine' in 'longue chaine':

Ou alors les programmeurs C qui font :

for x in range(0, len(ma_list)):
    print ma_list[x]

Alors qu’on a :

for x in ma_list:
    print x

Et au besoin:

for i, x in enumerate(ma_list):
    print i, x

Et à peu près la moitié des programmeurs d’autres langages qui font :

if truc == True:
    if not len(ma_list):

Alors que:

if truc:
     if not ma_list:

Marche très bien.

Je vous passe les isintance() que nous font les programmeurs Java, les check au lieu des gestions des exceptions et les gens qui écrivent les variables en camelCase. Des gens très compétents font du code horrible.

Certains trouvent que le créateur du langage a été un nazi pour avoir forcé les gens à utiliser l’indentation et les sauts de ligne ou pour avoir limité les lambdas. Quand je lis le code de certains, j’ai tendance au contraire à trouver la politique actuelle proche de l’anarchisme hippie. Interdisons les tabs. Les “;” plus de 3 fois d’affilé. Les nombres de saut de ligne qui ne sont pas standard face au PEP8. En fait faisons des syntax errors sur le PEP8. Et les syntaxes errors déclencheront des chocs électriques via le clavier.

Et on enverra tous ceux qui sont pas d’accord dans des camps d’entraînement pour qu’ils puissent tous se concentrer un peu plus sur leur code.

Sinon à quoi ça sert d’être un BDFL ?

16 thoughts on “Obfuscating Python

  • truxs

    Le problème amha c’est que la plupart du coté “batteries incluses” qui font le plus de python ne sont pas mise en avant dans les cours.

    Ne serait qu’inverser un string à coup de a[::-1] au lieu d’appeler une méthode

  • Sam Post author

    @truxs: en général les enseignants qui font des cours sur Python ne savent pas coder en Python. C’est un langage qu’ils comprennent car simple à prendre en main, alors ils ont accepter de faire un cours dessus, mais ils n’ont jamais rien produit de concret dans ce langage. Du coup ils ne connaissent pas les bonnes pratiquent et enseignent un Python très dégradé.

  • JoJo

    “Interdisons les tabs” … mais qu’est-ce que vous avez tous contre les tabulations bordel ! :D

    la PEP8 c’est super … sauf l’indentation par espaces, je m’y ferai jamais, ni en C, ni en Python, ni sur ma feuille d’imposition :(

  • Sam Post author

    Ca ferait une super chanson ta dernière phrase.

    Ni en C,
    Ni en Python,
    Ni sur ma feuille d’imposition.
    Non, non, non, non,
    Je n’aime que les tabulations !

  • Symen

    la PEP8 c’est super … sauf l’indentation par espaces, je m’y ferai jamais, ni en C, ni en Python, ni sur ma feuille d’imposition :(

    D’ailleurs quel est l’intérêt de l’indentation par espaces, en dehors du fait qu’elle est très utilisée (et donc recommandée par la PEP8) ?
    Ça me parait pourtant plus sain d’utiliser les tabulations: pas de “doublons” sémantiquement inutiles et plus facile d’adapter la taille par colonne.

  • Sam Post author

    Le débat tab VS space a été refait mille fois, je crois qu’on a pas besoin de le relancer en comment, tout a été dit.

    @Romain: le PEP8, tout simplement.

  • kontre

    @romain
    Pour être un tantinet plus précis (ou enculeur de mouches, c’est selon), le camelcase est utilisé en python, pour les noms des classes. Du coup, la justification c’est de voir tout de suite si un nom représente une classe ou une variable autre.

  • Réchèr

    J’ai aussi eu droit à quelques perles python de la part d’ex-collègues.

    for i in [ a for a in range(10) ]:
        # du code utilisant i
    a = int("0x12AB", 16)
    # au lieu de, tout connement :
    a = 0x12AB
    # (C'était, de toutes façons, une valeur définie en dur)

    etc.

    Sinon, à partir du python 3, il y a Ellipsis, pour obfusquer du code.
    http://blog.brush.co.nz/2009/05/ellipsis/

  • TitraxX

    Mais bien entendu il y a des gens qui arrivent à faire des trucs moches sans entrainement. Par exemple
    if "longue chaine".find('chaine') != -1:

    Et merde…

  • Nicolargo

    Petite boulette dans l’exemple du programmeur C qui pour le coups aura une belle erreur de syntaxe:

    for x in range(0, len((ma_list)):

    à corriger en:

    for x in range(0, len((ma_list))):

  • Sam Post author

    Bien vu. J’ai viré une parenthèse plutot que d’en rajouter une, pour éviter l’effet lisp.

Leave a comment

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

Utilisez <pre lang='python'>VOTRE CODE</pre> pour insérer un block de code coloré

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