Warum gibt dieser Code die Ausgabe aus C++Sucks
? Was ist das Konzept dahinter?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
Testen Sie es hier .
c
deobfuscation
Codeslayer1
quelle
quelle
skcuS++C
.Antworten:
Die Zahl
7709179928849219.0
hat die folgende binäre Darstellung als 64-Bitdouble
:+
zeigt die Position des Zeichens;^
des Exponenten und-
der Mantisse (dh des Wertes ohne Exponenten).Da die Darstellung einen binären Exponenten und eine Mantisse verwendet, erhöht das Verdoppeln der Zahl den Exponenten um eins. Ihr Programm macht es genau 771 Mal, so dass der Exponent, der bei 1075 begann (Dezimaldarstellung von
10000110011
), am Ende 1075 + 771 = 1846 wird; binäre Darstellung von 1846 ist11100110110
. Das resultierende Muster sieht folgendermaßen aus:Dieses Muster entspricht der Zeichenfolge, die Sie gedruckt sehen, nur rückwärts. Gleichzeitig wird das zweite Element des Arrays Null, wodurch ein Nullterminator bereitgestellt wird, wodurch die Zeichenfolge zum Übergeben an geeignet wird
printf()
.quelle
7709179928849219
Wert eingefügt und die binäre Darstellung zurückbekommen.Lesbarere Version:
Es wird
main()
771 Mal rekursiv aufgerufen.Am Anfang
m[0] = 7709179928849219.0
, das steht fürC++Suc;C
. Wird bei jedem Anrufm[0]
verdoppelt, um die letzten beiden Buchstaben zu "reparieren".m[0]
Enthält im letzten Aufruf die ASCII-Zeichendarstellung vonC++Sucks
undm[1]
enthält nur Nullen, sodass ein Nullterminator für dieC++Sucks
Zeichenfolge vorhanden ist. Alles unter der Annahme, dassm[0]
es auf 8 Bytes gespeichert ist, sodass jedes Zeichen 1 Byte benötigt.Ohne Rekursion und illegalen
main()
Anruf sieht es so aus:quelle
Haftungsausschluss: Diese Antwort wurde in der ursprünglichen Form der Frage veröffentlicht, in der nur C ++ erwähnt wurde und die einen C ++ - Header enthielt. Die Konvertierung der Frage in reines C wurde von der Community ohne Eingabe des ursprünglichen Fragestellers durchgeführt.
Formal ist es unmöglich, über dieses Programm nachzudenken, weil es schlecht geformt ist (dh es ist kein legales C ++). Es verstößt gegen C ++ 11 [basic.start.main] p3:
Abgesehen davon beruht es auf der Tatsache, dass auf einem typischen Consumer-Computer a
double
8 Byte lang ist und eine bestimmte bekannte interne Darstellung verwendet. Die Anfangswerte des Arrays werden so berechnet, dass bei Ausführung des "Algorithmus" der Endwert des erstendouble
so ist, dass die interne Darstellung (8 Bytes) die ASCII-Codes der 8 Zeichen sindC++Sucks
. Das zweite Element im Array ist dann0.0
, dessen erstes Byte sich0
in der internen Darstellung befindet, was dies zu einer gültigen Zeichenfolge im C-Stil macht. Dies wird dann mit an die Ausgabe gesendetprintf()
.Wenn Sie dies auf HW ausführen, wo einige der oben genannten Punkte nicht zutreffen, wird stattdessen Mülltext (oder möglicherweise sogar ein Zugriff außerhalb der Grenzen) ausgegeben.
quelle
basic.start.main
3.6.1 / 3 mit dem gleichen Wortlaut.main()
zu entfernen oder ihn durch einen API-Aufruf zum Formatieren der Festplatte oder was auch immer zu ersetzen.Der einfachste Weg, den Code zu verstehen, besteht darin, die Dinge in umgekehrter Reihenfolge durchzuarbeiten. Wir beginnen mit einem String zum Ausdrucken - zum Ausgleich verwenden wir "C ++ Rocks". Entscheidender Punkt: Genau wie das Original ist es genau acht Zeichen lang. Da wir das Original (ungefähr) mögen und es in umgekehrter Reihenfolge ausdrucken, werden wir es zunächst in umgekehrter Reihenfolge einfügen. In unserem ersten Schritt betrachten wir dieses Bitmuster einfach als
double
und drucken das Ergebnis aus:Dies erzeugt
3823728713643449.5
. Wir wollen das also auf eine Weise manipulieren, die nicht offensichtlich ist, aber leicht rückgängig zu machen ist. Ich werde halb willkürlich die Multiplikation mit 256 wählen, was uns gibt978874550692723072
. Jetzt müssen wir nur noch einen verschleierten Code schreiben, um ihn durch 256 zu teilen, und dann die einzelnen Bytes davon in umgekehrter Reihenfolge ausdrucken:Jetzt haben wir viele Castings, die Argumente an (rekursive) übergeben
main
, die völlig ignoriert werden (aber die Bewertung, um das Inkrement und Dekrement zu erhalten, sind äußerst wichtig), und natürlich diese völlig willkürlich aussehende Zahl, um die Tatsache zu vertuschen, was wir tun ist wirklich ziemlich einfach.Da der springende Punkt die Verschleierung ist, können wir natürlich auch weitere Schritte unternehmen, wenn wir Lust dazu haben. Zum Beispiel können wir die Kurzschlussbewertung nutzen, um unsere
if
Aussage in einen einzigen Ausdruck umzuwandeln, sodass der Hauptteil folgendermaßen aussieht:Für jeden, der nicht an verschleierten Code (und / oder Code Golf) gewöhnt ist, sieht dies in der Tat ziemlich seltsam aus - das Berechnen und Verwerfen der Logik
and
einer bedeutungslosen Gleitkommazahl und des Rückgabewerts vonmain
, der nicht einmal a zurückgibt Wert. Schlimmer noch, ohne zu erkennen (und darüber nachzudenken), wie die Kurzschlussbewertung funktioniert, ist es möglicherweise nicht sofort offensichtlich, wie eine unendliche Rekursion vermieden wird.Unser nächster Schritt wäre wahrscheinlich, das Drucken jedes Zeichens vom Finden dieses Zeichens zu trennen. Wir können das ziemlich einfach tun, indem wir das richtige Zeichen als Rückgabewert von generieren
main
und ausdrucken, wasmain
zurückgibt:Zumindest scheint mir das verschleiert genug zu sein, also lasse ich es dabei.
quelle
Es wird lediglich ein doppeltes Array (16 Byte) aufgebaut, das - wenn es als char-Array interpretiert wird - die ASCII-Codes für die Zeichenfolge "C ++ Sucks" aufbaut.
Der Code funktioniert jedoch nicht auf jedem System, sondern stützt sich auf einige der folgenden undefinierten Fakten:
quelle
Der folgende Code wird gedruckt
C++Suc;C
, sodass die gesamte Multiplikation nur für die letzten beiden Buchstaben giltquelle
Die anderen haben die Frage ziemlich gründlich erklärt. Ich möchte einen Hinweis hinzufügen, dass dies ein undefiniertes Verhalten gemäß dem Standard ist.
C ++ 11 3.6.1 / 3 Hauptfunktion
quelle
Der Code könnte folgendermaßen umgeschrieben werden:
Es erzeugt eine Reihe von Bytes im
double
Arraym
, die zufällig den Zeichen 'C ++ Sucks' entsprechen, gefolgt von einem Nullterminator. Sie haben den Code verschleiert, indem sie einen doppelten Wert gewählt haben, der, wenn er 771-mal verdoppelt wird, in der Standarddarstellung den Satz von Bytes mit dem vom zweiten Mitglied des Arrays bereitgestellten Nullterminator erzeugt.Beachten Sie, dass dieser Code unter einer anderen Endian-Darstellung nicht funktioniert. Auch Anrufe
main()
sind nicht unbedingt erlaubt.quelle
f
Rückkehr einint
?int
Rückgabe in der Frage hirnlos kopiert habe . Lassen Sie mich das beheben.Zunächst sollten wir daran erinnern, dass Zahlen mit doppelter Genauigkeit im Binärformat wie folgt im Speicher gespeichert sind:
(i) 1 Bit für das Vorzeichen
(ii) 11 Bits für den Exponenten
(iii) 52 Bits für die Größe
Die Reihenfolge der Bits nimmt von (i) auf (iii) ab.
Zuerst wird die dezimale Bruchzahl in eine äquivalente gebrochene Binärzahl umgewandelt und dann als binäre Größenordnungsform ausgedrückt.
So ist die Zahl 7709179928849219,0 wird
Nun wird unter Berücksichtigung der Größenbits 1 vernachlässigt, da alle Größenordnungsmethoden mit 1 beginnen sollen.
So wird der Magnitudenanteil:
Nun ist die Macht der 2 ist 52 , müssen wir Vorbelastungsnummer als hinzuzufügen -1 2 ^ (für Exponenten -1 Bits) , dh 2 ^ (11 -1) -1 = 1023 , so dass unsere Exponenten werden 52 + 1023 = 1075
Jetzt multipliziert unser Code die Zahl mit dem 2 , 771- fachen, wodurch sich der Exponent um 771 erhöht
Unser Exponent ist also (1075 + 771) = 1846, dessen binäres Äquivalent (11100110110) ist.
Jetzt ist unsere Zahl positiv, also ist unser Vorzeichenbit 0 .
So wird unsere modifizierte Nummer:
Vorzeichenbit + Exponent + Größe (einfache Verkettung der Bits)
Da m in einen Zeichenzeiger umgewandelt wird, teilen wir das Bitmuster in 8er-Blöcke vom LSD auf
(dessen Hex-Äquivalent ist :)
Welche aus der Charakterkarte wie gezeigt ist:
Sobald dies geschehen ist, ist m [1] 0, was ein NULL-Zeichen bedeutet
Angenommen, Sie führen dieses Programm auf einem Little-Endian- Computer aus (das Bit niedrigerer Ordnung wird in der unteren Adresse gespeichert), zeigen Sie also mit dem Zeiger m auf das Bit mit der niedrigsten Adresse und nehmen Sie dann Bits in Spannfuttern von 8 auf (als Typ, der in char * umgewandelt wurde) ) und das printf () stoppt, wenn 00000000 im letzten Chunck ...
Dieser Code ist jedoch nicht portierbar.
quelle