0% ont trouvé ce document utile (0 vote)
16 vues12 pages

Obfuscation de Code

Linux - Rendre son code source illisible

Transféré par

alarue
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
16 vues12 pages

Obfuscation de Code

Linux - Rendre son code source illisible

Transféré par

alarue
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF ou lisez en ligne sur Scribd
LIBS & MODULES JOUONS AVEC L'OBFUSCATION méthodes dobscurcissement et elles ne sont pas toutes aussi efficaces les tunes que les autres. Dans cet article, nous aborderons done plusieurs tech- niques, en partant des plus simples pour aller vers des solutions un peu plus complexes. NOTE Tenter d’obscurcir un code est un exercice dautant plus intéressant en Python que le langage a été ‘congu justement pour que le code soit le plus lisible possible. Rendreson codeillisible lorsque l’on préche pour Popen source, c’est le mal incarné. Toutefois, il est possiblede 1, LE CODE DE concevoir que dans certains cas, on souhaite obscurcir, DEPART des parties d’un code et; surtout d’un point de vue paupexpérimenteciftentes ech purement technique, cela peut se révéler intéressant, niques dobfuscation, il nous faut un code a la fois simple, mais sutfisam- ‘obfuscation est un angli- _ a/en comprendre le sens, alors que lament conséquent pour ne pas ete cisme signifiant « obscur- | machine pourra le lire et linterpréter vial. De plus, ce code doit &tre parf cissement ». Obscurcir un. sanspeine. Que ce soitpourde bonnes tement lsible et done respecter le plus code, cest le rendre ilisble | ou de mauvaises raisons, Fobfuscation possible les bonnes pratiques, ce qui eny ajoutant une couche de complexi- reste un mécanisme intéressant a étu- nous permettra de comparer plus sim- {ételle quelle cerveau humain peinera der, cariln’y apasune, mais plusieurs plement les résultats obtenus. 56 © GNU/Linux Magazine France N°242 [Link] Jouons avec obfuscation de code Nous allons utiliser le script suivant, stocké dans [Link]. Ce pro- gramme attend en paramere le nom d'un fichier, !ouvre et affiche son contenu import sys from os import path def getFilename() -> str: "1" Récupération des paramitres : 1 seul paranétre c'est le nom de fichier non il y @ une erreur sraises Exception: Le nonbre de paranétres est incorrect :return: Le premier (et seul) parandtre représentant le non du fichier rrtype: str 4 Si le nombre de paramatres 4 exception est levée if len([Link]) |= raise Excoption('Syntaxe : roadFile ') return [Link](1] diftérent de 1 alors une def readFile(filenane : str) > None: "1" Afichage du fichier dont le non est passé en paranatre sparam filename: Non du fichier a lire rtype filename: str return: None ztype: None 4 Verification do 1'existence du fichier if [Link](filenane) print E"Lecture ds fichier (filename) :") # Parcours du fichier ‘with open (filename, ‘r') as fie: # Affichage de chaque Ligne for Line in fic: print(£'> (1ine}', ende'") Except ion (f'Tapo else: raise Exception(f'Le fichier (filenane} n\'existe pas!) Le d\‘ouvrir (filename)') # Programe principal Af nana (main F Récuparation du nom de fichier dans les paranétres filenane = gotFilenana() # Lecture et affichage du fichier readFile filenane) Ce programme est tr8s simple. On peut noter + une fonction getFilename() qui teste le nombre de parametres transmis au script et qui renvoie le premier (et unique) paramétre + une fonction readFite() qui lit et affiche le fichier dont le nom est passé en paramere ; * un programme principal ui fait ap- ‘pel aux deux fonctions précédentes. Ce code est abondamment com- menté et respecte les bonnes pratiques de nommage des variables, d’espace- ment, etc. Voyons maintenant com- ment Fobscurcir. 2. OBFUSCATION MANUELLE Lobfuscation manuelle nexiste pas réellement, 8 part pour certains déve- loppeurs chez qui c'est une « qualité » innée et qui sont capables de mettre ‘en pratique Fensemble des anti-pat- terns sans avoir a réfléchirne serait-ce qu'une seconde. Nousallons ici volon- tairement obscurcir notre code pasa pas (on peut dire aussi le « pourrir ») de maniére & mettre en lumiére la dé- gradation progressive du code. 2.1 Suppression des commentaires La premitre modification qui vient 4 Vesprit est de supprimer les commentaires import sys from os import path def getFilenane() -> str: [Link] GNU/Linux Magazine France N°242 Ey LIBS & MODULES PYTHON, if lon([Link]) '= 2: MOTE, cicniesst ttt McePtion('Sytane : readrile None: ter les sauts de ligne, if [Link] (filename) : print (f'tectore du fichier (filename) with open(filenane, 'r') as fic: for line in fic: 2.3 Renommage des identifiants print(f!> {Line}', end="") Les noms des variables et des fonctions respectent les Exception(f'le fichier {filename} | _ conventions PEP8 et sont tres lisibles, Nous allons les mo- difier en aXXXXXX ob XXXXAX sera le numéro apparition de Videntifiant dans le code en le lsant de bas en haut : import sys from os import path def 2000001 ( if lon([Link]) !=2: raise Exception('Syntaxe : readFile ') return [Link](1] def 2000002 (2000003) : Lf path. exists (2000003) : print (f"Lecture du fichier {4000003} :") with open(a000003, 'r')as fic: for 2000004 in fic: Print(f'> (a000004)', if _nane “Filename = getFilename() readFile (filename) On ne peut pas dite que le code ait vraiment perdu en lisibilite, mais i y a d6ja nettement moins d'informations. 2.2 Suppression du typage et des espacements ‘Le typage donne des informations surle type attendu pour les parameétres des fonctions ainsi que pour les valeurs de retour de ces demiéres. Nous allons donc les supprimer, de méme que les sauts de ligne et les espacements non indispensables end="") else: raise Exception(f'Lo fichier {a000003) n\'existe pas’) inert 7 cepts if _name_=='_main_': from os import "a000003=2000001 () def getFilenane(): 2000002 (000003) Af Len([Link])!=2: socbuea wo eet yoke § cand Kien Fisted? Petit & petit, notre code ressemble de plus en plus 2 ot roadrie(tilerano): ‘une mélasse infme. if path. xists(filenano) print ('Lecture du fichier {filename} :*) with open (filename, 'r')as fi 2.4 Le probléme des chaines de for Line in fic: Eint(f'> (Line}', end="") caractares, al Les chaines de caractéres restent lsbles et ce rest pas ,, ise Exception f'Le fichier (filename) n\’existe | qy meilleur effet | Comme nous sommes dans une op- OY ae a iss tique d'obfuscation manuelle, nous allons nous créer une Tlvtoaagiactiesty petite fonction de codage en base64 a Textérieur de notre readFile(Eilenane) script. Cette fonction va nous permettre de récupérer les chaines encodées et de les insérer dans le programme Voila qui est un peu mieux. (avec le code permettant de les décoder, bien entendu). 58 © GNU/Linux Magazine France N°242 [Link] Jouons avec obfuscation de code Voici donc la fonction dencodage en base64, stockée dans [Link] import base64 import sys Af pane == '_ain_': GE Len([Link]) print ('Syntax: encode ') exit (2) nag = [Link]{1] ‘usg_encoded = [Link] ("ascii") b64_bytes = based. bé4encode (nsg_encoded) print (b64_bytes..decode('ascii')) TTestons ce programme sur l'une des chatnes de carac~ teres de readFile. py : oe ae ' prety Potrcr es Gut custome ert Voila qui est prometteur ! Pour décoder, sur une chaine msg il faucra utiliser base64.b64decode(nsg). decode(‘ascii'). Pour utiliser cette technique dans les lignes contenant des Fstrings, il va falloir les réécrire de maniére 2 utils format(). Ainsi, nous allons modifier [[frscctertecture au fichier (2000003) = En: [frsicc( tecture da fichier (} + tomat(2000003)) Puisque nous y sommes, nous passerons aussi les en- tiers en hexadécimaux. Lopération est tres simple puisqu'il suffit de préfixer un nombre par 8x pour qu'il soit consi- ‘déré comme hexadécimal. Ic, comme nous avons que Let 2 lécriture sera triviale (Ox et @x2), mais apportera tune pinoée supplémentaire de complexié Regardons le script initial modifié import sys from os import path import base64 def 2000001 ( if Len([Link]) !=0x2: raise Exception (base64.b64decod @ ('U31udGF4ZSA6IHJ1YWRGaWx1IDxub21£2m13 aGl1ej4=") .decode('ascii')) return [Link] [Oxi] et 2000002 (2000003) : if path. exists (a000003) print (base64.bS4decode ('TGVjdkVyZSEKdsBna¥No anvylit9tDe=') dacoda ascii) .format (2000003) with open (3000003, base64. béddecode (‘cg="). decode('ascii"))as fic: for 2000004 in fie: print (base64.b64decode ('PiB7EQ==") decode ascii") format (2000004) , end="') else: raise Exception (base64.b64decode ("6Ughnl jaG LeiBTESSuIZVEa¥NOESBWIIM=' ) decode ("ascii") ‘format (a000003)) Lf — name_=sbase64. béddecode (’X19ttluxi8=") decide ("ascii"): }00003=2000001 () 2000002 (000003) ‘Gayest, on ne comprend plus ren. Mais estcesuffisant ? 2.5 La touche finale Pour faire vraiment du code sale, nous allons utiliser des syntaxes que tout développeur Python ne devrait ja- mais avoir a utiliser + Te point-virgule pour chainer des instructions sur une ligne + les tests conditionnels sur une ligne = instruction_si_true if condition else instruction_si_False ; + les boucles sur une ligne avec la compréhension de listes : [ instruction for variable in ensemble ] ‘Tout ceci donne sur notre code import sys, base64 from os import path def a0o0005(): if Len (sys. argv) !=0x2:raise Exception (base64.bé Adecode ('U31udGB42SA6THIIYWRGaWx1TDxub21£2n1 jaG1i fds") .decode (‘aseii')) retum [Link] [0x1] et 2000002 (2000003) : if path. exists(a000003) Drint (base64, b64decode ( TGVjdHvyZSEkdSBuaRN cawvyTHE9ID0=") .decode ("ascii") . format (a000003) ) with open (2000003,base6é. b6édecode( 'cg==") decode ('ascii'))as fic: [print (base64 64decode('PiB7EQ==') decode’ ascii) format (a000004) ,end="') for 4000004 in fic] [Link] CGNUA nox Magazine France N24 az LIBS & MODULES PYTHON, else: raise Exception (base64. b64decode ("TGUgzml jaG11ciB7£SBuJ2V4aXNOZSBWYXM=") . decode ("ascii") . format (2000003) ) .a000003=a000003 () ;a000002(a000003) if nane__=-base64 .b6ddecode ("x19tYW1uX1 decoda('ascii') else exit (0) Si vous étes d'humeur joueuse, vous pouvez maintenant ajouter des espaces de-ci de-la import sys, base64 fron 08 det 000002 ( 1 if Len([Link] raise xception (base64. 64decode ('U31udGF4RSA6THI1Y¥RGaNeL Daub ‘2AflaljaGllo}4=") dacode (‘aseii")) return [Link] (xa 1 def 2000002 (2000003); if path. exists( 000003 ) print (base6¢ béddecods "TOV aavy2smidsma allfoalvy IHt9TDo=" ) decode ascii" ) format (2000003) with open ( 3000003, base64. Détdecode "cg=" ).deoode( ——aseii"))as fic: [print (base64, b64dacode 'PiB7EQ==") .dacode ('ascii'). format (2000004) end="') for a000006 in fic} else:raise Exception (base6s,bb¢decode ( ‘nedgdnl jaGl1oiB7SBuJZVUaNNDESBWNM") decode | format (2000003 ) ) 2000034000001 (); 2000002 (2000003) if eli") __nane_= ‘base6b6ldecode "X19e1WLux18=" Yedecoda(*ascii") else exit( 9) NOTE Au lieu de laisser les imports en début de fichier, nous, urions également pu les disséminer dans le code en fonction de leur utilisation. Cerise sur le gateau, nous pouvons générer et enregis- trer le bytecode correspondant a notre programme $ python3 -m py compile [Link] Dans le répertoire __pycache__ se trouve un fichier [Link] (jutilise Python 3.8). Le re rnommer en [Link] ninterférera pas avec son bon fonctionnement EL coe ee Le contenu du fichier est maintenant complétement ilisible serve: prrernenrmnnnennenteratetnanianatal tt 1247dlit7da0 2580 Lee awdO’ dOkrNawOre drondse) O7777) opaiher aI INI Ns, Trryordie"teodoeodooor}8757)80777, VALLE 1SAGTRULINRGakaL Tvab2iftal jal Lojé=Oaschs@772) lent syseargr? Excoptionasesl® bétdecade? decoieet fe ‘re readrle bt praoooirearne7 ‘20? 3022220 | 2@rVELOdoOdo Siren reueegeeo sso pvenrsngorncce deodee reeed2s?) Ne TOK} anyasekaStnaiNoaty taibowretiaopmeteteeerea vorereerreet??eg7 Ith) trtedreedeo | @ddeeqS?) 218719907) bend) @printe ‘227@format)®.0Za000006r re rene Listoomp> [Link]> ‘2 $TGUgzal jaGl1ciB7 fSBuJ2V4aXNO LSB) sa penieten? 7 nee re opent 272) a00000sttier ‘2e7e777a00000229%5 ara RISEYWLuKN8=r299) P22 roar t227IMT Maumee 17 areexite me amnerMocaodul>???8 2 Et pourtant, le fichier reste exécutable ! 3. POURQUOI LA COMPILATION EN BYTECODE NE SUFFIT-ELLE PAS ? (On pourrait étre tenté de se dire que tout le travail ef fectué précédemment ne sert strictement rien : pourquoi ne pas partir directement du fichier original et l'enregis- ‘rer en bytecode ? La réponse est simple : ver le fichier .py depuis le fichier .pye. 60 © GNU/Linux Magazine France N°242 [Link] Jouons avec obfuscation de code Reprenons donc note fichier de départ sans obfuscation et compilons-le errr bytecode ca Poo Eo ee for line in fic: peer eo aren Dans __pyeache__nous retrouvons bien notte fichier u Boer prereset) Oem teres eet ay core fee SO ee Nous allons maintenant installer Uncompyle6, un utiltaire qui va nous per- mettre de décompiler le code $ pip install unconpy! peer En ese ee) oem Sood Langons cet outil sur le fichier pye Phone Finalement, notre travail Cobtusca Se ee eRe tion nétait pas si inutile, non ? 4 Deconpiled fron: 2ython 3.8.2 (default, Jul 16 2020, 14:00:26) CEO reeset tay 4 compiled at: 2020-09-14 17:34:49 eet beens ory Perera Nous avons réalisé notre obfus Pa ee eer cation manuellement, ce qui nous a La venreccren iret tity permis daller assez loin dans | = si 1 seul parandtre c'est le non de fichier di erercer arent fications. Toutelois, cela n'est pas utilsable sur des programmes vole ; mineux. Pour cla, fut sot usr ate NR a cca une solution existante, sit écrit un return: Le preaier (et seul) paranatce représentant Je non | ercesce travail que nous avons effectué dans ard la section 2. Af Len([Link]) != 2: raise Exception('Syntaxe ; readFile ') : Eeiretetss] eee eee ee ; Protec rC ee Stee ern ri : pyminifier est outil sinstallant avec pip et permettant de réduire la place coccupée par un code, de Fobscurcit es et de le compresser. oo eens be nletiprntod neato En reprenant le code original, nous print f"Lecture du fichier (filename) :*) —aeee Fee cc rtoe ett eee rena sd [Link] GNU/Linux Magazine France N°242 rere tres oer aay erase besiod precre ) st eS ee fichier>') oer rrgel pratt retire beso prarcswere asters) peers Naseer ect SaaS) ae Se tO meena for line in fie: pseriraectes eo) Cos pret escne crs toe in irced frsseereey is) ry raise Exception(f'Le fichier (#ilenane} err eras) eee eee Ce Eee ee sree) periretistrere) [Ree beeen Cem Cr cn ay preys) Nous voyons que cette opération retire les espaces et lignes « inutiles » ainsi que les commentaires. Grace a option dobscurcir le code obfuscate, nous pouvons également eer tend pear) c= patty peed eee teeny peer eethtns co aeliess ere Tt Rs EC Peete] Pearce ns Frea) Agee ere coey) readFile ') try GtmcC mar oescy Se ees Eran tttss except: raise F(£'Inpossible d\'ouvrir (filenane}') Ber 64 GNU/Linux Magazine France N°242 een ee era) ra i) oo) ng eCcs ci Ccr i esate) Crest de Vobfuscation « gentillette », car il ne sera pas 18s compliqué d'écrire un programme remplacant les va- riables de départ par leur valeur, de maniére a retrouver tun code plus lisibl. Liajout de option --gzip permet d'ajouter la couche de compression § pyminifier --obfuscate --gzip [Link] Copter) peter oes emee ee ive Pear creea tear ste eee eer er enter Ste string Peete Cotes et men ar cen at Lee eras tos Seamer Tepe) Ge sane ae od Rea nate eee ssce Sra ac) Fr ce Leen aD ee crise) # Created by pyninifier (https: //[Link]/ Liftoft/pyinifier) Cette fois, le code est nettement plus obscurci il nen reste pas moins généré par un outil connu et il ne sera pas trés compliqué de retrouver un code plus lisible, pyminifier est donc la solution de facilité, mais n'est pas {8s satisfaisant. Tournons-nous alors vers une automati sation de la méthode manuelle présentée précédemment. Nous allons écrire un programme [Link] pre nant en parametre un nom de fichier et réalisant diverses tiches visant & obscurcir le code. Voici le code de base import sys from os import path from typing import List def getFilename() -> str www [Link] Jouons avec obfuscation de code AE Jen([Link]) |= raise Exception('Syntane : readfile List{ste]: Af path. exists (filenane) : print (f'Lectore du fichier (filenane) :") ey: with open(filenane, 'r') as fic: return [Link]() except: aise Bxception(f' Impossible \‘ouvri (filenane)’) ception (fla fichier {#4lenane} 4 Programe principal if _nano main! "F Recupération du nom de fichier dans les paranatres ‘filenane = get¥ilenane () # lecture et affichage du fichier data = readPile (filenana) Ce programme a la méme structure de fonction que [Link]. La fonction readFile() est légérement dif ‘érente puisqu‘lle lit le fichier en entier et le stocke dans tne liste de chaines de caractéres (méthode readLines_()). Crest cette liste, écupétée dans data, qui sera modifiée et qui conduira & Fobscurcissement du programme pas- sé en paramétre. 4.2.1 Approche ni La suppression des lignes vides lignes ne contenant que \n) est simple, mais nécessite un minimum d'attention ‘def delmmptyLines (data : List[str]) -> None: for Line in reversed (data) if Line == "\a": data. renove (Line) Nous devons travaller surla liste inversée, car nous mo- difions le contenu de la liste a chaque itération :travailler sur la liste non inversée entratnerait le fait de ne pas tes- ter toutes les lignes. Cette méthode va devenir de plus en plus complexe au fur et a mesure de nos traitements : suppression des docstrings, modification des identifiants, etc. II faut trou- ver quelque chose de plus efficace ! 4.2.2 Pourquoi ne pas traiter tous les cas en une passe? Nous pourrions utiliser des expressions réguligres, ce- pendant il existe une méthode un peu plus technique, mais pilus efticace : analyser le code a aide du module tokenize qui fourit la liste des tokens des données qui ui sont trans- mises, Notre programme nia pas vocation a étre utilisé en production, il sagira d'une ébauche qui souttrira nécessai- rement de certaines restrictions et devra étre améliorée, Par ‘exemple, nous ne gérerons pas le cas des fstring et notre -estion des tokens sera balbutiante (un traitement plus pous- 6 nécessiterait de trop nombreuses lignes de code). Inféressons-nous pour commencer au code permettant dafficher les tokens : from typing import Listyemextro import tokenize, token import keyword def readFile(filenane : strjiverboseerboole= False) -> List[st Af [Link] print (£'Lecture du fichier (filenane} :') try: with open (filename, 'r') as fic: ‘return:analyze (fic; verbose) except: ‘raise Exception(f! Impossible 4\'ouvrir {filenane}') raise Exception(f'Le fichier (filename) a\'existe pas') def analyze(source : TextI0, verbose : bool = False) -> List[str! ‘prev_toktype = token. INDENT prov_ttext =" tokgen = tokenize.generate_tokens source. readline) [Link]

Vous aimerez peut-être aussi