Maison > développement back-end > Tutoriel Python > Obscurcir « Bonjour tout le monde ! » obscurcir sur Python

Obscurcir « Bonjour tout le monde ! » obscurcir sur Python

Linda Hamilton
Libérer: 2025-01-04 03:37:44
original
203 Les gens l'ont consulté

Obfuscating “Hello world!” obfuscate on Python

créez le programme obscurci le plus étrange qui imprime la chaîne « Hello world ! ». J'ai décidé d'écrire une explication de comment ça marche. Alors, voici l'entrée, en Python 2.7 :

(lambda _, __, ___, ____, _____, ______, _______, ________:
    getattr(
        __import__(True.__class__.__name__[_] + [].__class__.__name__[__]),
        ().__class__.__eq__.__class__.__name__[:__] +
        ().__iter__().__class__.__name__[_____:________]
    )(
        _, (lambda _, __, ___: _(_, __, ___))(
            lambda _, __, ___:
                chr(___ % __) + _(_, __, ___ // __) if ___ else
                (lambda: _).func_code.co_lnotab,
            _ << ________,
            (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __)
            - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ <<
            __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______
            << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) <<
            ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) <<
            __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______
            << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) +
            _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) <<
            (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ <<
            _))) + (_____ << ______) + (_ << ___)
        )
    )
)(
    *(lambda _, __, ___: _(_, __, ___))(
        (lambda _, __, ___:
            [__(___[(lambda: _).func_code.co_nlocals])] +
            _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else []
        ),
        lambda _: _.func_code.co_argcount,
        (
            lambda _: _,
            lambda _, __: _,
            lambda _, __, ___: _,
            lambda _, __, ___, ____: _,
            lambda _, __, ___, ____, _____: _,
            lambda _, __, ___, ____, _____, ______: _,
            lambda _, __, ___, ____, _____, ______, _______: _,
            lambda _, __, ___, ____, _____, ______, _______, ________: _
        )
    )
)

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Les littéraux de chaîne n'étaient pas autorisés, mais j'ai défini d'autres restrictions pour m'amuser : il devait s'agir d'une seule expression (donc pas d'instruction d'impression) avec une utilisation intégrée minimale et aucun littéral entier.
Pour commencer

Comme nous ne pouvons pas utiliser print, nous pouvons écrire dans l'objet fichier stdout :

import sys
sys.stdout.write("Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Mais utilisons quelque chose de niveau inférieur : os.write(). Nous avons besoin du descripteur de fichier de stdout, qui est 1 (vous pouvez vérifier avec print sys.stdout.fileno()).

import os
os.write(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous voulons une seule expression, nous allons donc utiliser import() :

__import__("os").write(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous voulons également pouvoir masquer write(), nous allons donc ajouter un getattr() :

getattr(__import__("os"), "write")(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

C'est le point de départ. À partir de maintenant, tout obscurcira les trois chaînes et l'int.
Enchaîner les ficelles

"os" et "write" sont assez simples, nous allons donc les créer en joignant des parties des noms de différentes classes intégrées. Il existe de nombreuses façons différentes de procéder, mais j'ai choisi la suivante :

"o" from the second letter of bool: True.__class__.__name__[1]
"s" from the third letter of list: [].__class__.__name__[2]
"wr" from the first two letters of wrapper_descriptor, an implementation detail in CPython found as the type of some builtin classes’ methods (more on that here): ().__class__.__eq__.__class__.__name__[:2]
"ite" from the sixth through eighth letters of tupleiterator, the type of object returned by calling iter() on a tuple: ().__iter__().__class__.__name__[5:8]
Copier après la connexion
Copier après la connexion

Nous commençons à faire des progrès !

getattr(
    __import__(True.__class__.__name__[1] + [].__class__.__name__[2]),
    ().__class__.__eq__.__class__.__name__[:2] +
    ().__iter__().__class__.__name__[5:8]
)(1, "Hello world!\n")

Copier après la connexion
Copier après la connexion

"Hello world!n" est plus compliqué. Nous allons l'encoder sous la forme d'un grand entier, qui sera formé du code ASCII de chaque caractère multiplié par 256 à la puissance de l'index du caractère dans la chaîne. Autrement dit, la somme suivante :
∑n=0L−1cn(256n)

où L
est la longueur de la chaîne et cn est le code ASCII du n

ème caractère de la chaîne. Pour créer le numéro :

>>> codes = [ord(c) for c in "Hello world!\n"]
>>> num = sum(codes[i] * 256 ** i for i in xrange(len(codes)))
>>> print num
802616035175250124568770929992
Copier après la connexion
Copier après la connexion

Nous avons maintenant besoin du code pour reconvertir ce nombre en chaîne. Nous utilisons un algorithme récursif simple :

>>> def convert(num):
...     if num:
...         return chr(num % 256) + convert(num // 256)
...     else:
...         return ""
...
>>> convert(802616035175250124568770929992)
'Hello world!\n'
Copier après la connexion
Copier après la connexion

Réécriture en une ligne avec lambda :

convert = lambda num: chr(num % 256) + convert(num // 256) if num else ""

Copier après la connexion
Copier après la connexion

Maintenant, nous utilisons la récursion anonyme pour transformer cela en une expression unique. Cela nécessite un combinateur. Commencez par ceci :

>>> comb = lambda f, n: f(f, n)
>>> convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else ""
>>> comb(convert, 802616035175250124568770929992)
'Hello world!\n'
Copier après la connexion
Copier après la connexion

Maintenant, nous substituons simplement les deux définitions dans l'expression, et nous avons notre fonction :

>>> (lambda f, n: f(f, n))(
...     lambda f, n: chr(n % 256) + f(f, n // 256) if n else "",
...     802616035175250124568770929992)
'Hello world!\n'
Copier après la connexion
Copier après la connexion

Maintenant, nous pouvons coller cela dans notre code d'avant, en remplaçant certains noms de variables en cours de route (f → , n → _) :

getattr(
    __import__(True.__class__.__name__[1] + [].__class__.__name__[2]),
    ().__class__.__eq__.__class__.__name__[:2] +
    ().__iter__().__class__.__name__[5:8]
)(
    1, (lambda _, __: _(_, __))(
        lambda _, __: chr(__ % 256) + _(_, __ // 256) if __ else "",
        802616035175250124568770929992
    )
)

Copier après la connexion
Copier après la connexion

Internes des fonctions

Il nous reste un "" dans le corps de notre fonction de conversion (rappelez-vous : pas de chaîne littérale !), et un grand nombre que nous devrons cacher d'une manière ou d'une autre. Commençons par la chaîne vide. Nous pouvons en créer un à la volée en examinant les composants internes d'une fonction aléatoire :

(lambda _, __, ___, ____, _____, ______, _______, ________:
    getattr(
        __import__(True.__class__.__name__[_] + [].__class__.__name__[__]),
        ().__class__.__eq__.__class__.__name__[:__] +
        ().__iter__().__class__.__name__[_____:________]
    )(
        _, (lambda _, __, ___: _(_, __, ___))(
            lambda _, __, ___:
                chr(___ % __) + _(_, __, ___ // __) if ___ else
                (lambda: _).func_code.co_lnotab,
            _ << ________,
            (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __)
            - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ <<
            __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______
            << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) <<
            ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) <<
            __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______
            << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) +
            _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) <<
            (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ <<
            _))) + (_____ << ______) + (_ << ___)
        )
    )
)(
    *(lambda _, __, ___: _(_, __, ___))(
        (lambda _, __, ___:
            [__(___[(lambda: _).func_code.co_nlocals])] +
            _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else []
        ),
        lambda _: _.func_code.co_argcount,
        (
            lambda _: _,
            lambda _, __: _,
            lambda _, __, ___: _,
            lambda _, __, ___, ____: _,
            lambda _, __, ___, ____, _____: _,
            lambda _, __, ___, ____, _____, ______: _,
            lambda _, __, ___, ____, _____, ______, _______: _,
            lambda _, __, ___, ____, _____, ______, _______, ________: _
        )
    )
)

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ce que nous faisons réellement ici, c'est examiner la table des numéros de ligne de l'objet code contenu dans la fonction. Comme c’est anonyme, il n’y a pas de numéro de ligne, donc la chaîne est vide. Remplacez le 0 par _ pour le rendre plus déroutant (cela n'a pas d'importance, puisque la fonction n'est pas appelée) et collez-le. Nous allons également refactoriser le 256 en un argument qui sera transmis à notre convert() obscurci. avec le numéro. Cela nécessite d'ajouter un argument au combinateur :

import sys
sys.stdout.write("Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Un détour

Abordons un peu un problème différent. Nous voulons un moyen de masquer les nombres dans notre code, mais il sera fastidieux (et pas particulièrement intéressant) de les recréer à chaque fois qu'ils seront utilisés. Si nous pouvons implémenter, disons, range(1, 9) == [1, 2, 3, 4, 5, 6, 7, 8], alors nous pouvons envelopper notre travail actuel dans une fonction qui prend des variables contenant les nombres de 1 à 8, et remplacez les occurrences de littéraux entiers dans le corps par ces variables :

import os
os.write(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

Même si nous devons également former 256 et 802616035175250124568770929992, ceux-ci peuvent être créés en utilisant des opérations arithmétiques sur ces huit nombres « fondamentaux ». Le choix de 1 à 8 est arbitraire, mais semble être un bon compromis.

On peut obtenir le nombre d'arguments qu'une fonction prend via son objet code :

__import__("os").write(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

Créez un tuple de fonctions avec des nombres d'arguments compris entre 1 et 8 :

getattr(__import__("os"), "write")(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

En utilisant un algorithme récursif, nous pouvons transformer cela en sortie de range(1, 9) :

"o" from the second letter of bool: True.__class__.__name__[1]
"s" from the third letter of list: [].__class__.__name__[2]
"wr" from the first two letters of wrapper_descriptor, an implementation detail in CPython found as the type of some builtin classes’ methods (more on that here): ().__class__.__eq__.__class__.__name__[:2]
"ite" from the sixth through eighth letters of tupleiterator, the type of object returned by calling iter() on a tuple: ().__iter__().__class__.__name__[5:8]
Copier après la connexion
Copier après la connexion

Comme auparavant, nous convertissons cela en forme lambda :

getattr(
    __import__(True.__class__.__name__[1] + [].__class__.__name__[2]),
    ().__class__.__eq__.__class__.__name__[:2] +
    ().__iter__().__class__.__name__[5:8]
)(1, "Hello world!\n")

Copier après la connexion
Copier après la connexion

Puis, sous forme anonyme-récursive :

>>> codes = [ord(c) for c in "Hello world!\n"]
>>> num = sum(codes[i] * 256 ** i for i in xrange(len(codes)))
>>> print num
802616035175250124568770929992
Copier après la connexion
Copier après la connexion

Pour nous amuser, nous allons prendre en compte l'opération argcount dans un argument de fonction supplémentaire et masquer certains noms de variables :

>>> def convert(num):
...     if num:
...         return chr(num % 256) + convert(num // 256)
...     else:
...         return ""
...
>>> convert(802616035175250124568770929992)
'Hello world!\n'
Copier après la connexion
Copier après la connexion

Il y a un nouveau problème maintenant : nous avons encore besoin d'un moyen de masquer 0 et 1. Nous pouvons les obtenir en examinant le nombre de variables locales dans des fonctions arbitraires :

convert = lambda num: chr(num % 256) + convert(num // 256) if num else ""

Copier après la connexion
Copier après la connexion

Même si les corps des fonctions se ressemblent, _ dans la première fonction n'est pas un argument, et n'est pas non plus défini dans la fonction, donc Python l'interprète comme une variable globale :

>>> comb = lambda f, n: f(f, n)
>>> convert = lambda f, n: chr(n % 256) + f(f, n // 256) if n else ""
>>> comb(convert, 802616035175250124568770929992)
'Hello world!\n'
Copier après la connexion
Copier après la connexion

Cela se produit même si _ est réellement défini dans la portée globale.

Mettre cela en pratique :

>>> (lambda f, n: f(f, n))(
...     lambda f, n: chr(n % 256) + f(f, n // 256) if n else "",
...     802616035175250124568770929992)
'Hello world!\n'
Copier après la connexion
Copier après la connexion

Maintenant, nous pouvons remplacer la valeur de funcs, puis utiliser * pour transmettre la liste d'entiers résultante sous forme de huit variables distinctes, nous obtenons ceci :

getattr(
    __import__(True.__class__.__name__[1] + [].__class__.__name__[2]),
    ().__class__.__eq__.__class__.__name__[:2] +
    ().__iter__().__class__.__name__[5:8]
)(
    1, (lambda _, __: _(_, __))(
        lambda _, __: chr(__ % 256) + _(_, __ // 256) if __ else "",
        802616035175250124568770929992
    )
)

Copier après la connexion
Copier après la connexion

Déplacement des bits

Presque là ! Nous remplacerons les n{1..8} variables par , _, , _, etc., car cela crée une confusion avec les variables utilisées dans nos fonctions intérieures. Cela ne pose pas de problèmes réels, puisque les règles de cadrage signifient que les bonnes seront utilisées. C'est également l'une des raisons pour lesquelles nous avons refactorisé 256 là où _ fait référence à 1 au lieu de notre fonction convert() obscurcie. Ça devient long, donc je ne collerai que la première moitié :

(lambda _, __, ___, ____, _____, ______, _______, ________:
    getattr(
        __import__(True.__class__.__name__[_] + [].__class__.__name__[__]),
        ().__class__.__eq__.__class__.__name__[:__] +
        ().__iter__().__class__.__name__[_____:________]
    )(
        _, (lambda _, __, ___: _(_, __, ___))(
            lambda _, __, ___:
                chr(___ % __) + _(_, __, ___ // __) if ___ else
                (lambda: _).func_code.co_lnotab,
            _ << ________,
            (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __)
            - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ <<
            __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______
            << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) <<
            ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) <<
            __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______
            << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) +
            _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) <<
            (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ <<
            _))) + (_____ << ______) + (_ << ___)
        )
    )
)(
    *(lambda _, __, ___: _(_, __, ___))(
        (lambda _, __, ___:
            [__(___[(lambda: _).func_code.co_nlocals])] +
            _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else []
        ),
        lambda _: _.func_code.co_argcount,
        (
            lambda _: _,
            lambda _, __: _,
            lambda _, __, ___: _,
            lambda _, __, ___, ____: _,
            lambda _, __, ___, ____, _____: _,
            lambda _, __, ___, ____, _____, ______: _,
            lambda _, __, ___, ____, _____, ______, _______: _,
            lambda _, __, ___, ____, _____, ______, _______, ________: _
        )
    )
)

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Il ne reste plus que deux choses. Nous allons commencer par le plus facile : 256. 256=28

, nous pouvons donc le réécrire comme 1 << 8 (en utilisant un décalage de bit vers la gauche), ou _ << ________ avec nos variables obscurcies.

Nous utiliserons la même idée avec 802616035175250124568770929992. Un simple algorithme diviser pour régner peut le diviser en sommes de nombres qui sont eux-mêmes des sommes de nombres décalés ensemble, et ainsi de suite. Par exemple, si nous avions 112, nous pourrions le diviser en 96 16 puis (3 << 5) (2 << 3). J'aime utiliser les décalages de bits car le << me rappelle std::cout << "foo" en C ou print chevron (print >>) en Python, qui sont tous deux des harengs rouges impliquant d'autres façons d'effectuer des E/S.

Le nombre peut être décomposé de différentes manières ; aucune méthode n'est correcte (après tout, nous pourrions simplement la diviser en (1 << 0) (1 << 0) ..., mais ce n'est pas intéressant). Nous devrions avoir une quantité substantielle d'imbrication, tout en utilisant la plupart de nos variables numériques. Évidemment, faire cela à la main n’est pas amusant, nous allons donc proposer un algorithme. En pseudocode :

import sys
sys.stdout.write("Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

L'idée de base ici est que nous testons diverses combinaisons de nombres dans une certaine plage jusqu'à ce que nous trouvions deux nombres, base et shift, tels que base << shift est aussi proche que possible de num (c'est-à-dire que nous minimisons leur différence absolue, diff). Nous utilisons ensuite notre algorithme diviser pour régner pour diviser best_base et best_shift, puis répétons la procédure sur diff jusqu'à ce qu'elle atteigne zéro, en additionnant les termes en cours de route.

L'argument de range(), span, représente la largeur de l'espace de recherche. Cela ne peut pas être trop grand, sinon nous finirons par avoir num comme base et 0 comme décalage (car diff est zéro), et comme la base ne peut pas être représentée comme une variable unique, elle se répétera, de manière récurrente à l'infini. . S'il est trop petit, nous nous retrouverons avec quelque chose comme le (1 << 0) (1 << 0) ... mentionné ci-dessus. En pratique, nous voulons que la durée diminue à mesure que la profondeur de récursion augmente. Par essais et erreurs, j’ai trouvé que cette équation fonctionnait bien :
span=⌈log1.5|num|⌉ ⌊24−profondeur⌋

En traduisant le pseudocode en Python et en apportant quelques ajustements (prise en charge de l'argument de profondeur et quelques mises en garde concernant les nombres négatifs), nous obtenons ceci :

(lambda _, __, ___, ____, _____, ______, _______, ________:
    getattr(
        __import__(True.__class__.__name__[_] + [].__class__.__name__[__]),
        ().__class__.__eq__.__class__.__name__[:__] +
        ().__iter__().__class__.__name__[_____:________]
    )(
        _, (lambda _, __, ___: _(_, __, ___))(
            lambda _, __, ___:
                chr(___ % __) + _(_, __, ___ // __) if ___ else
                (lambda: _).func_code.co_lnotab,
            _ << ________,
            (((_____ << ____) + _) << ((___ << _____) - ___)) + (((((___ << __)
            - _) << ___) + _) << ((_____ << ____) + (_ << _))) + (((_______ <<
            __) - _) << (((((_ << ___) + _)) << ___) + (_ << _))) + (((_______
            << ___) + _) << ((_ << ______) + _)) + (((_______ << ____) - _) <<
            ((_______ << ___))) + (((_ << ____) - _) << ((((___ << __) + _) <<
            __) - _)) - (_______ << ((((___ << __) - _) << __) + _)) + (_______
            << (((((_ << ___) + _)) << __))) - ((((((_ << ___) + _)) << __) +
            _) << ((((___ << __) + _) << _))) + (((_______ << __) - _) <<
            (((((_ << ___) + _)) << _))) + (((___ << ___) + _) << ((_____ <<
            _))) + (_____ << ______) + (_ << ___)
        )
    )
)(
    *(lambda _, __, ___: _(_, __, ___))(
        (lambda _, __, ___:
            [__(___[(lambda: _).func_code.co_nlocals])] +
            _(_, __, ___[(lambda _: _).func_code.co_nlocals:]) if ___ else []
        ),
        lambda _: _.func_code.co_argcount,
        (
            lambda _: _,
            lambda _, __: _,
            lambda _, __, ___: _,
            lambda _, __, ___, ____: _,
            lambda _, __, ___, ____, _____: _,
            lambda _, __, ___, ____, _____, ______: _,
            lambda _, __, ___, ____, _____, ______, _______: _,
            lambda _, __, ___, ____, _____, ______, _______, ________: _
        )
    )
)

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Maintenant, lorsque nous appelons convert(802616035175250124568770929992), nous obtenons une belle décomposition :

import sys
sys.stdout.write("Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Collez-le en remplacement du 802616035175250124568770929992 et assemblez toutes les pièces :

import os
os.write(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et voilà.
Addendum : Prise en charge de Python 3

Depuis la rédaction de cet article, plusieurs personnes ont posé des questions sur le support de Python 3. Je n'y avais pas pensé à l'époque, mais comme Python 3 continue de gagner du terrain (et merci pour cela !), cet article aurait clairement dû être mis à jour depuis longtemps.

Heureusement, Python 3 (au moment de la rédaction, 3.6) ne nous oblige pas à beaucoup changer :

__import__("os").write(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

Voici la version complète de Python 3 :

getattr(__import__("os"), "write")(1, "Hello world!\n")
Copier après la connexion
Copier après la connexion
Copier après la connexion

Merci d'avoir lu ! Je continue d’être étonné par la popularité de cet article.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal