Pourquoi ce code donne-t-il la sortie C++Sucks
? Quel est le concept derrière cela?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Testez-le ici .
c
deobfuscation
codeslayer1
la source
la source
skcuS++C
.Réponses:
Le nombre
7709179928849219.0
a la représentation binaire suivante en 64 bitsdouble
:+
montre la position du signe;^
de l'exposant et-
de la mantisse (c'est-à-dire la valeur sans l'exposant).Puisque la représentation utilise l'exposant binaire et la mantisse, doubler le nombre incrémente l'exposant de un. Votre programme le fait précisément 771 fois, donc l'exposant qui a commencé à 1075 (représentation décimale de
10000110011
) devient 1075 + 771 = 1846 à la fin; représentation binaire de 1846 est11100110110
. Le motif résultant ressemble à ceci:Ce motif correspond à la chaîne que vous voyez imprimée, uniquement à l'envers. Dans le même temps, le deuxième élément du tableau devient zéro, fournissant un terminateur nul, ce qui rend la chaîne appropriée pour passer à
printf()
.la source
7709179928849219
valeur et récupéré la représentation binaire.Version plus lisible:
Il appelle récursivement
main()
771 fois.Au début
m[0] = 7709179928849219.0
, qui se dresse pourC++Suc;C
. À chaque appel,m[0]
est doublé, pour "réparer" les deux dernières lettres. Dans le dernier appel,m[0]
contient une représentation de caractères ASCIIC++Sucks
etm[1]
contient uniquement des zéros, il a donc un terminateur nul pour laC++Sucks
chaîne. Tous sous l'hypothèse quim[0]
est stockée sur 8 octets, donc chaque caractère prend 1 octet.Sans récursivité et illégal
main()
appel cela ressemblera à ceci:la source
Avertissement: Cette réponse a été publiée sous la forme originale de la question, qui ne mentionnait que C ++ et comprenait un en-tête C ++. La conversion de la question en C pur a été effectuée par la communauté, sans contribution du demandeur initial.
Formellement parlant, il est impossible de raisonner sur ce programme car il est mal formé (c'est-à-dire qu'il n'est pas légal en C ++). Il viole C ++ 11 [basic.start.main] p3:
Ceci mis à part, il repose sur le fait que sur un ordinateur grand public typique, a
double
fait 8 octets de long et utilise une certaine représentation interne bien connue. Les valeurs initiales du tableau sont calculées de telle sorte que lorsque "l'algorithme" est exécuté, la valeur finale du premierdouble
sera telle que la représentation interne (8 octets) sera les codes ASCII des 8 caractèresC++Sucks
. Le deuxième élément du tableau est alors0.0
, dont le premier octet est0
dans la représentation interne, ce qui en fait une chaîne de style C valide. Il est ensuite envoyé à la sortie en utilisantprintf()
.L'exécution de cela sur HW où certains des éléments ci-dessus ne tiennent pas entraînerait à la place un texte incorrect (ou peut-être même un accès hors limites).
la source
basic.start.main
3.6.1 / 3 avec le même libellé.main()
, ou de le remplacer par un appel API pour formater le disque dur, ou autre chose.La façon la plus simple de comprendre le code est peut-être de travailler à l'envers. Nous allons commencer par une chaîne à imprimer - pour l'équilibre, nous utiliserons "C ++ Rocks". Point crucial: tout comme l'original, il fait exactement huit caractères. Puisque nous allons faire (à peu près) comme l'original et l'imprimer dans l'ordre inverse, nous allons commencer par le mettre dans l'ordre inverse. Pour notre première étape, nous allons simplement afficher ce motif binaire comme un
double
, et imprimer le résultat:Cela produit
3823728713643449.5
. Donc, nous voulons manipuler cela d'une manière qui n'est pas évidente, mais qui est facile à inverser. Je vais semi-arbitrairement choisir la multiplication par 256, ce qui nous donne978874550692723072
. Maintenant, nous avons juste besoin d'écrire du code obscurci pour diviser par 256, puis d'imprimer les octets individuels de celui-ci dans l'ordre inverse:Maintenant, nous avons beaucoup de cast, passant des arguments à (récursif)
main
qui sont complètement ignorés (mais l'évaluation pour obtenir l'incrémentation et la décrémentation est absolument cruciale), et bien sûr ce nombre à la recherche complètement arbitraire pour couvrir le fait que ce que nous faisons est vraiment assez simple.Bien sûr, étant donné que tout le problème est l'obscurcissement, si nous en avons envie, nous pouvons également prendre plus de mesures. Par exemple, nous pouvons profiter de l'évaluation des courts-circuits, pour transformer notre
if
déclaration en une seule expression, de sorte que le corps de main ressemble à ceci:Pour quelqu'un qui n'est pas habitué au code obscurci (et / ou au golf de code), cela commence à sembler assez étrange en effet - calculer et rejeter la logique
and
d'un certain nombre à virgule flottante sans signification et la valeur de retour demain
, qui ne renvoie même pas un valeur. Pire encore, sans réaliser (et réfléchir) au fonctionnement de l'évaluation des courts-circuits, il n'est peut-être même pas immédiatement évident comment elle évite une récursion infinie.Notre prochaine étape serait probablement de séparer l'impression de chaque caractère de la recherche de ce caractère. Nous pouvons le faire assez facilement en générant le bon caractère comme valeur de retour
main
et en imprimant ce quimain
retourne:Au moins pour moi, cela semble assez obscurci, alors je vais en rester là.
la source
Il s'agit simplement de créer un double tableau (16 octets) qui - s'il est interprété comme un tableau de caractères - crée les codes ASCII pour la chaîne "C ++ Sucks"
Cependant, le code ne fonctionne pas sur chaque système, il s'appuie sur certains des faits non définis suivants:
la source
Le code suivant s'imprime
C++Suc;C
, donc toute la multiplication ne concerne que les deux dernières lettresla source
Les autres ont expliqué la question de manière assez approfondie, je voudrais ajouter une note qu'il s'agit d' un comportement indéfini selon la norme.
C ++ 11 3.6.1 / 3 Fonction principale
la source
Le code pourrait être réécrit comme ceci:
Ce qu'il fait, c'est produire un ensemble d'octets dans le
double
tableaum
qui correspondent aux caractères «C ++ Sucks» suivi d'un terminateur nul. Ils ont obscurci le code en choisissant une valeur double qui, lorsqu'elle est doublée 771 fois, produit, dans la représentation standard, cet ensemble d'octets avec le terminateur nul fourni par le deuxième membre du tableau.Notez que ce code ne fonctionnerait pas sous une représentation endienne différente. De plus, les appels
main()
ne sont pas strictement autorisés.la source
f
retour est-il unint
?int
retour dans la question. Permettez-moi de résoudre ce problème.Rappelons d'abord que les nombres en double précision sont stockés dans la mémoire au format binaire comme suit:
(i) 1 bit pour le signe
(ii) 11 bits pour l'exposant
(iii) 52 bits pour la grandeur
L'ordre des bits décroît de (i) à (iii).
Tout d'abord, le nombre décimal fractionnaire est converti en nombre binaire fractionnaire équivalent, puis il est exprimé sous forme d'ordre de grandeur en binaire.
Ainsi, le numéro 7709179928849219.0 devient
Maintenant, tout en considérant les bits de magnitude 1. est négligé car toute la méthode de l'ordre de grandeur doit commencer par 1.
Donc, la partie magnitude devient:
Maintenant, la puissance de 2 est 52 , nous devons lui ajouter un nombre de biais comme 2 ^ (bits pour l'exposant -1) -1, c'est-à-dire 2 ^ (11 -1) -1 = 1023 , donc notre exposant devient 52 + 1023 = 1075
Maintenant, notre code multiplie le nombre par 2 , 771 fois ce qui fait que l'exposant augmente de 771
Donc, notre exposant est (1075 + 771) = 1846 dont l'équivalent binaire est (11100110110)
Maintenant, notre nombre est positif, donc notre bit de signe est 0 .
Notre numéro modifié devient donc:
signe bit + exposant + amplitude (concaténation simple des bits)
puisque m est converti en pointeur char, nous diviserons le motif binaire en morceaux de 8 à partir du LSD
(dont l'équivalent Hex est :)
Lequel de la carte de caractères comme indiqué est:
Maintenant, une fois que cela a été fait, m [1] est 0, ce qui signifie un caractère NULL
Supposons maintenant que vous exécutez ce programme sur une machine petit-boutiste (le bit de poids faible est stocké dans une adresse inférieure). ) et printf () s'arrête lorsqu'il rencontre 00000000 dans le dernier chunck ...
Ce code n'est cependant pas portable.
la source