998 Stimmen

Obfuscated C Code Contest 2006. Bitte erklären Sie sykes2.c

Wie funktioniert dieses C-Programm?

main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}

Es kompiliert wie es ist (getestet auf gcc 4.6.3 ). Es gibt die Zeit aus, wenn es kompiliert wird. Auf meinem System:

    !!  !!!!!!              !!  !!!!!!              !!  !!!!!! 
    !!  !!  !!              !!      !!              !!  !!  !! 
    !!  !!  !!              !!      !!              !!  !!  !! 
    !!  !!!!!!    !!        !!      !!    !!        !!  !!!!!! 
    !!      !!              !!      !!              !!  !!  !! 
    !!      !!              !!      !!              !!  !!  !! 
    !!  !!!!!!              !!      !!              !!  !!!!!!

Quelle: sykes2 - Eine Uhr in einer Linie , sykes2 Autor Hinweise

Einige Hinweise: Standardmäßig keine Kompilierwarnungen. Kompiliert mit -Wall werden die folgenden Warnungen ausgegeben:

sykes2.c:1:1: warning: return type defaults to ‘int’ [-Wreturn-type]
sykes2.c: In function ‘main’:
sykes2.c:1:14: warning: value computed is not used [-Wunused-value]
sykes2.c:1:1: warning: implicit declaration of function ‘putchar’ [-Wimplicit-function-declaration]
sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of ‘|’ [-Wparentheses]
sykes2.c:1:1: warning: suggest parentheses around arithmetic in operand of ‘|’ [-Wparentheses]
sykes2.c:1:1: warning: control reaches end of non-void function [-Wreturn-type]

1835voto

nneonneo Punkte 162093

Entschärfen wir sie.

Einrücken:

main(_) {
    _^448 && main(-~_);
    putchar(--_%64
        ? 32 | -~7[__TIME__-_/8%8][">'txiZ^(~z?"-48] >> ";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1
        : 10);
}

Einführung von Variablen, um dieses Durcheinander zu entwirren:

main(int i) {
    if(i^448)
        main(-~i);
    if(--i % 64) {
        char a = -~7[__TIME__-i/8%8][">'txiZ^(~z?"-48];
        char b = a >> ";;;====~$::199"[i*2&8|i/64]/(i&2?1:8)%8;
        putchar(32 | (b & 1));
    } else {
        putchar(10); // newline
    }
}

Beachten Sie, dass -~i == i+1 wegen des Zweierkomplements. Daher haben wir

main(int i) {
    if(i != 448)
        main(i+1);
    i--;
    if(i % 64 == 0) {
        putchar('\n');
    } else {
        char a = -~7[__TIME__-i/8%8][">'txiZ^(~z?"-48];
        char b = a >> ";;;====~$::199"[i*2&8|i/64]/(i&2?1:8)%8;
        putchar(32 | (b & 1));
    }
}

Beachten Sie nun, dass a[b] ist dasselbe wie b[a] und wenden Sie die -~ == 1+ wieder ändern:

main(int i) {
    if(i != 448)
        main(i+1);
    i--;
    if(i % 64 == 0) {
        putchar('\n');
    } else {
        char a = (">'txiZ^(~z?"-48)[(__TIME__-i/8%8)[7]] + 1;
        char b = a >> ";;;====~$::199"[(i*2&8)|i/64]/(i&2?1:8)%8;
        putchar(32 | (b & 1));
    }
}

Die Rekursion in eine Schleife umwandeln und ein wenig mehr Vereinfachung einbauen:

// please don't pass any command-line arguments
main() {
    int i;
    for(i=447; i>=0; i--) {
        if(i % 64 == 0) {
            putchar('\n');
        } else {
            char t = __TIME__[7 - i/8%8];
            char a = ">'txiZ^(~z?"[t - 48] + 1;
            int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
            if((i & 2) == 0)
                shift /= 8;
            shift = shift % 8;
            char b = a >> shift;
            putchar(32 | (b & 1));
        }
    }
}

Dabei wird pro Iteration ein Zeichen ausgegeben. Nach jedem 64. Zeichen wird ein Zeilenumbruch ausgegeben. Andernfalls werden zwei Datentabellen verwendet, um herauszufinden, was ausgegeben werden soll, und es wird entweder Zeichen 32 (ein Leerzeichen) oder Zeichen 33 (ein ! ). Die erste Tabelle ( ">'txiZ^(~z?" ) ist ein Satz von 10 Bitmaps, die das Aussehen der einzelnen Zeichen beschreiben, und die zweite Tabelle ( ";;;====~$::199" ) wählt das entsprechende Bit in der Bitmap aus, das angezeigt werden soll.

Die zweite Tabelle

Betrachten wir zunächst die zweite Tabelle, int shift = ";;;====~$::199"[(i*2&8) | (i/64)]; . i/64 ist die Zeilennummer (6 bis 0) und i*2&8 ist 8, wenn i ist 4, 5, 6 oder 7 mod 8.

if((i & 2) == 0) shift /= 8; shift = shift % 8 wählt entweder die hohe Oktalziffer (für i%8 = 0,1,4,5) oder die niedrige Oktalziffer (für i%8 = 2,3,6,7) des Tabellenwertes. Die Verschiebungstabelle sieht am Ende wie folgt aus:

row col val
6   6-7 0
6   4-5 0
6   2-3 5
6   0-1 7
5   6-7 1
5   4-5 7
5   2-3 5
5   0-1 7
4   6-7 1
4   4-5 7
4   2-3 5
4   0-1 7
3   6-7 1
3   4-5 6
3   2-3 5
3   0-1 7
2   6-7 2
2   4-5 7
2   2-3 3
2   0-1 7
1   6-7 2
1   4-5 7
1   2-3 3
1   0-1 7
0   6-7 4
0   4-5 4
0   2-3 3
0   0-1 7

oder in tabellarischer Form

00005577
11775577
11775577
11665577
22773377
22773377
44443377

Beachten Sie, dass der Autor für die ersten beiden Tabelleneinträge den Nullterminator verwendet hat (raffiniert!).

Diese ist nach dem Vorbild einer Sieben-Segment-Anzeige gestaltet, mit 7 s als Leerzeichen. Die Einträge in der ersten Tabelle müssen also die Segmente definieren, die beleuchtet werden.

Die erste Tabelle

__TIME__ ist ein spezielles, vom Präprozessor definiertes Makro. Es expandiert zu einer String-Konstante, die die Zeit enthält, zu der der Präprozessor ausgeführt wurde, und zwar in der Form "HH:MM:SS" . Beachten Sie, dass er genau 8 Zeichen enthält. Beachten Sie, dass 0-9 die ASCII-Werte 48 bis 57 und : hat den ASCII-Wert 58. Die Ausgabe umfasst 64 Zeichen pro Zeile, so dass 8 Zeichen pro Zeichen von __TIME__ .

7 - i/8%8 ist somit der Index von __TIME__ die gerade ausgegeben wird (die 7- wird benötigt, weil wir eine Iteration durchführen i nach unten). Also, t ist der Charakter von __TIME__ ausgegeben werden.

a ergibt je nach Eingabe den folgenden Binärwert t :

0 00111111
1 00101000
2 01110101
3 01111001
4 01101010
5 01011011
6 01011111
7 00101001
8 01111111
9 01111011
: 01000000

Jede Zahl ist eine Bitmap die die Segmente beschreiben, die in unserer Sieben-Segment-Anzeige aufleuchten. Da es sich bei den Zeichen um 7-Bit-ASCII-Zeichen handelt, wird das High-Bit immer gelöscht. Somit, 7 in der Segmenttabelle wird immer als Leerzeichen gedruckt. Die zweite Tabelle sieht so aus, mit dem 7 s als Leerzeichen:

000055  
11  55  
11  55  
116655  
22  33  
22  33  
444433  

So zum Beispiel, 4 es 01101010 (die Bits 1, 3, 5 und 6 sind gesetzt), die wie folgt ausgedruckt wird

----!!--
!!--!!--
!!--!!--
!!!!!!--
----!!--
----!!--
----!!--

Um zu zeigen, dass wir den Code wirklich verstehen, passen wir die Ausgabe mit dieser Tabelle ein wenig an:

  00  
11  55
11  55
  66  
22  33
22  33
  44

Dies wird verschlüsselt als "?;;?==? '::799\x07" . Zu künstlerischen Zwecken fügen wir zu einigen der Zeichen 64 hinzu (da nur die niedrigen 6 Bits verwendet werden, wirkt sich dies nicht auf die Ausgabe aus); dies ergibt "?{{?}}?gg::799G" (Beachten Sie, dass das 8. Zeichen unbenutzt ist, so dass wir es eigentlich beliebig gestalten können). Einfügen unserer neuen Tabelle in den ursprünglichen Code:

main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>"?{{?}}?gg::799G"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}

erhalten wir

          !!              !!                              !!   
    !!  !!              !!  !!  !!  !!              !!  !!  !! 
    !!  !!              !!  !!  !!  !!              !!  !!  !! 
          !!      !!              !!      !!                   
    !!  !!  !!          !!  !!      !!              !!  !!  !! 
    !!  !!  !!          !!  !!      !!              !!  !!  !! 
          !!              !!                              !!   

wie wir es erwartet hatten. Es sieht nicht so solide aus wie das Original, was erklärt, warum der Autor sich für die Tabelle entschieden hat, die er verwendet hat.

105voto

chmeee Punkte 3568

Lassen Sie uns das zur besseren Lesbarkeit formatieren:

main(_){
  _^448&&main(-~_);
  putchar((--_%64) ? (32|-(~7[__TIME__-_/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[_*2&8|_/64]/(_&2?1:8)%8&1):10);
}

Wenn man es also ohne Argumente ausführt, ist _ (argc konventionell) 1 . main() wird sich selbst rekursiv aufrufen und dabei das Ergebnis von -(~_) (negatives bitweises NICHT von _ ), so dass es in Wirklichkeit 448 Rekursionen gibt (einzige Bedingung, bei der _^448 == 0 ).

Daraus ergeben sich 7 Zeilen mit 64 Zeichen Breite (die äußere ternäre Bedingung, und 448/64 == 7 ). Schreiben wir es also etwas sauberer um:

main(int argc) {
  if (argc^448) main(-(~argc));
  if (argc % 64) {
    putchar((32|-(~7[__TIME__-argc/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[argc*2&8|argc/64]/(argc&2?1:8)%8&1));
  } else putchar('\n');
}

Jetzt, 32 ist dezimal für ASCII-Raum. Es wird entweder ein Leerzeichen oder ein '!' (33 ist '!', daher das ' &1 ' am Ende). Konzentrieren wir uns auf den Klecks in der Mitte:

-(~(7[__TIME__-argc/8%8][">'txiZ^(~z?"-48]) >>
     (";;;====~$::199"[argc*2&8|argc/64]) / (argc&2?1:8) % 8

Wie ein anderer Poster sagte, __TIME__ ist die Kompilierzeit für das Programm und ist eine Zeichenkette, so dass es einige String-Arithmetik geht, sowie unter Ausnutzung eines Arrays subscript ist bidirektional: a[b] ist das gleiche wie b[a] für Zeichen-Arrays.

7[__TIME__ - (argc/8)%8]

Damit wird eines der ersten 8 Zeichen in __TIME__ . Dies wird dann indiziert in [">'txiZ^(~z?"-48] (0-9 Zeichen entsprechen 48-57 Dezimalstellen). Die Zeichen in dieser Zeichenfolge müssen aufgrund ihrer ASCII-Werte ausgewählt worden sein. Diese Manipulation des ASCII-Codes wird im Ausdruck fortgesetzt, so dass je nach Position innerhalb der Zeichenglyphe entweder ein ' ' oder '!' gedruckt wird.

51voto

Thomas Song Punkte 717

Ergänzend zu den anderen Lösungen, -~x ist gleich x+1 denn ~x ist gleichbedeutend mit (0xffffffff-x) . Dies ist gleichbedeutend mit (-1-x) im 2s-Komplement, also -~x es -(-1-x) = x+1 .

7voto

Lefteris E Punkte 2687

Ich habe die Modulo-Arithmetik so weit wie möglich entschärft und die Rekursion entfernt

int pixelX, line, digit ;
for(line=6; line >= 0; line--){
  for (digit =0; digit<8; digit++){
    for(pixelX=7;pixelX > 0; pixelX--){ 
        putchar(' '| 1 + ">'txiZ^(~z?"["12:34:56"[digit]-'0'] >> 
          (";;;====~$::199"[pixel*2 & 8  | line] / (pixelX&2 ? 1 : 8) ) % 8 & 1);               
    }
  }
  putchar('\n');
}

Ich erweitere es noch ein wenig:

int pixelX, line, digit, shift;
char shiftChar;
for(line=6; line >= 0; line--){
    for (digit =0; digit<8; digit++){
        for(pixelX=7;pixelX >= 0; pixelX--){ 
            shiftChar = ";;;====~$::199"[pixelX*2 & 8 | line];
            if (pixelX & 2)
                shift = shiftChar & 7;
            else
                shift = shiftChar >> 3;     
            putchar(' '| (">'txiZ^(~z?"["12:34:56"[digit]-'0'] + 1) >> shift & 1 );
        }

    }
    putchar('\n');
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X