Concorso del codice C offuscato 2006. Spiegare sykes2.c


975

Come funziona questo programma C?

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

Si compila così com'è (testato su gcc 4.6.3). Stampa l'ora durante la compilazione. Sul mio sistema:

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

Fonte: sykes2 - Un orologio su una riga , suggerimenti dell'autore sykes2

Alcuni suggerimenti: nessun avviso di compilazione per impostazione predefinita. Compilati con -Wall, vengono emessi i seguenti avvisi:

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]

6
Debug: aggiunta printf("%d", _);all'inizio delle mainstampe: pastebin.com/HHhXAYdJ
banale

Integer, per impostazione predefinita ogni variabile non tipizzata èint
drahnr

18
Hai letto il suggerimento? ioccc.org/2006/sykes2/hint.text
nhahtdh


Se lo esegui in questo modo si blocca:./a.out $(seq 0 447)
SS Anne

Risposte:


1819

De-offuscarlo.

Rientro:

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

Presentazione delle variabili per districare questo casino:

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
    }
}

Si noti che a -~i == i+1causa del complemento a due. Pertanto, abbiamo

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));
    }
}

Ora, nota che a[b]è lo stesso dib[a] e applica -~ == 1+nuovamente la modifica:

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));
    }
}

Convertire la ricorsione in un ciclo e semplificare un po 'di più:

// 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));
        }
    }
}

Questo genera un carattere per iterazione. Ogni 64 ° carattere, genera una nuova riga. In caso contrario, utilizza una coppia di tabelle di dati per capire cosa produrre e inserisce il carattere 32 (uno spazio) o il carattere 33 (a !). La prima tabella ( ">'txiZ^(~z?") è un insieme di 10 bitmap che descrivono l'aspetto di ciascun carattere e la seconda tabella ( ";;;====~$::199") seleziona il bit appropriato da visualizzare dalla bitmap.

Il secondo tavolo

Iniziamo esaminando la seconda tabella, int shift = ";;;====~$::199"[(i*2&8) | (i/64)];. i/64è il numero di riga (da 6 a 0) ed i*2&8è 8 sef iè 4, 5, 6 o 7 mod 8.

if((i & 2) == 0) shift /= 8; shift = shift % 8seleziona la cifra ottale alta (per i%8= 0,1,4,5) o la cifra ottale bassa (per i%8= 2,3,6,7) del valore della tabella. La tabella dei turni si presenta così:

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

o in forma tabulare

00005577
11775577
11775577
11665577
22773377
22773377
44443377

Si noti che l'autore ha utilizzato il terminatore null per le prime due voci della tabella (subdolo!).

Questo è progettato dopo un display a sette segmenti, con 7s come spazi vuoti. Pertanto, le voci nella prima tabella devono definire i segmenti che si illuminano.

Il primo tavolo

__TIME__è una macro speciale definita dal preprocessore. Si espande in una costante di stringa contenente l'ora in cui è stato eseguito il preprocessore, nel modulo "HH:MM:SS". Osserva che contiene esattamente 8 caratteri. Si noti che 0-9 ha valori ASCII da 48 a 57 e :ha valore ASCII 58. L'output è di 64 caratteri per riga, quindi lascia 8 caratteri per carattere di __TIME__.

7 - i/8%8è quindi l'indice di __TIME__quello che viene attualmente prodotto ( 7-è necessario perché stiamo iterando iverso il basso). Quindi, tè il carattere di __TIME__essere prodotto.

afinisce per eguagliare quanto segue in binario, a seconda dell'input t:

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

Ogni numero è una bitmap che descrive i segmenti illuminati nel nostro display a sette segmenti. Poiché i caratteri sono tutti ASCII a 7 bit, il bit alto viene sempre azzerato. Pertanto, 7nella tabella dei segmenti viene sempre stampato come vuoto. La seconda tabella si presenta così con la 7s come spazi vuoti:

000055  
11  55  
11  55  
116655  
22  33  
22  33  
444433  

Quindi, per esempio, 4è 01101010(set di bit 1, 3, 5 e 6), che stampa come

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

Per mostrare che capiamo davvero il codice, regoliamo un po 'l'output con questa tabella:

  00  
11  55
11  55
  66  
22  33
22  33
  44

Questo è codificato come "?;;?==? '::799\x07". Per scopi artistici, aggiungeremo 64 ad alcuni dei caratteri (poiché vengono utilizzati solo i 6 bit bassi, ciò non influirà sull'output); questo dà "?{{?}}?gg::799G"(nota che l'ottavo personaggio è inutilizzato, quindi possiamo effettivamente farlo come vogliamo). Mettendo la nostra nuova tabella nel codice originale:

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

noi abbiamo

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

proprio come ci aspettavamo. Non è così solido come l'originale, il che spiega perché l'autore abbia scelto di usare il tavolo che ha fatto.


2
@drahnr - Tecnicamente è sia un *(dereference) che un +: P
detly

18
@ АртёмЦарионов: circa 30 minuti, ma sono tornato e l'ho modificato un po '. Uso molto C e ho già fatto alcune deobuscole IOCCC per interesse personale (l'ultimo che ho fatto, solo per interesse personale, è stato questo bellissimo raytracer ). Se vuoi chiedere come funziona, sarei felice di obbligare;)
nneonneo

5
@ АртёмЦарионов: circa un giorno IIRC (conta anche il tempo impiegato a comprendere la geometria del raytracer). Anche quel programma è molto intelligente, perché non usa parole chiave .
nneonneo,

178
C .. tutta la potenza del linguaggio assembly unito alla leggibilità del linguaggio assembly
wim

6
Per ulteriori informazioni in questo senso, controlla "Obfuscated C and Other Mysteries", di Don Libes. Insegna le tecniche C analizzando le voci del Concorso C offuscato.
Chris N,

102

Formattiamo questo per una lettura più semplice:

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

Quindi, eseguendolo senza argomenti, _ (argc convenzionalmente) lo è 1. main()chiamerà in modo ricorsivo se stesso, passando il risultato di -(~_)(NON bit a bit negativo di _), quindi in realtà andrà 448 ricorsioni (solo condizione dove _^448 == 0).

Prendendo ciò, stamperà 7 linee larghe di 64 caratteri (la condizione ternaria esterna e 448/64 == 7). Quindi riscriviamolo un po 'più pulito:

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');
}

Ora, 32è decimale per lo spazio ASCII. Stampa uno spazio o un "!" (33 è '!', Quindi il ' &1' alla fine). Concentriamoci sul blob nel mezzo:

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

Come ha detto un altro poster, __TIME__è il tempo di compilazione per il programma ed è una stringa, quindi c'è un po 'di aritmetica di stringa in corso, oltre a sfruttare un indice di matrice bidirezionale: a [b] è uguale a b [a] per array di caratteri.

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

Questo selezionerà uno dei primi 8 caratteri in __TIME__. Questo viene quindi indicizzato in [">'txiZ^(~z?"-48](0-9 caratteri sono 48-57 decimali). I caratteri in questa stringa devono essere stati scelti per i loro valori ASCII. La stessa manipolazione del codice ASCII del carattere continua attraverso l'espressione, per provocare la stampa di un '' o '!' a seconda della posizione all'interno del glifo del personaggio.


49

Aggiungendo alle altre soluzioni, -~xè uguale a x+1perché ~xequivale a (0xffffffff-x). Questo è uguale al (-1-x)complemento 2s, così -~xè -(-1-x) = x+1.


5
Interessante. So da un po 'che ~ x == -x - 1, ma non conoscevo il ragionamento matematico alla base.
Avvicinamento

3
Ey, Cole, (-1-x) è uguale a (-x-1), non è necessario "ripararlo" !!
Thomas Song

7
Lo stesso motivo per cui se qualcuno ha -1338, allora NON sono 1337.
Andrew Mao

4

Ho offuscato il più possibile l'aritmetica del modulo e ho rimosso la ricorsione

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');
}

Espandendolo un po 'di più:

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');
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.