Obfuscated C Code Contest 2006. Proszę wyjaśnić sykes2.c

Jak działa ten program C?

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

Kompiluje się tak ,jak jest (testowane na gcc 4.6.3). Wyświetla czas kompilacji. Na moim systemie:

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

Źródło: sykes2-zegar w jednej linii, sykes2 autor podpowiedzi

Kilka wskazówek: domyślnie brak ostrzeżeń kompilacji. Zestawione z -Wall, emitowane są następujące ostrzeżenia:

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]
Author: iCodez, 2013-03-13

4 answers

Odkamieniajmy to.

Wcięcia:

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

Wprowadzenie zmiennych do rozwikłania tego bałaganu:

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

Zauważ, że -~i == i+1 z powodu dwójki-dopełniacza. Dlatego mamy

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

Zauważ, że a[b] jest taki sam jak b[a], i zastosuj ponownie -~ == 1+ zmianę:

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

Konwertowanie rekurencji do pętli i skradanie się w nieco większym uproszczeniu:

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

Wyświetla jeden znak na iterację. Co 64 znak, wyświetla nową linię. W przeciwnym razie używa pary tabel danych, aby dowiedzieć się, co ma być wyprowadzone, i umieszcza znak 32 (spacja) lub znak 33 (a !). Pierwsza tabela (">'txiZ^(~z?") jest zbiorem 10 bitmap opisujących wygląd każdego znaku, a druga tabela (";;;====~$::199") wybiera odpowiedni bit do wyświetlenia z mapy bitowej.

Druga tabela

Zacznijmy od analizy drugiej tabeli, int shift = ";;;====~$::199"[(i*2&8) | (i/64)];. i/64 jest numerem linii (6 do 0) i i*2&8 jest 8 iff i jest 4, 5, 6 lub 7 mod 8.

if((i & 2) == 0) shift /= 8; shift = shift % 8 wybiera wysoką ósemkową cyfrę (dla i%8 = 0,1,4,5) lub niską ósemkową cyfrę (dla i%8 = 2,3,6,7) wartości tabeli. Tabela shift wygląda następująco:

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

Lub w formie tabelarycznej

00005577
11775577
11775577
11665577
22773377
22773377
44443377

Zauważ, że autor użył terminatora null dla dwóch pierwszych wpisów tabeli (sneaky!).

[[46]} jest to zaprojektowane po wyświetlaczu siedmiosegmentowym, z 7S jako puste. Tak więc wpisy w pierwszej tabeli muszą określać segmenty, które się rozświetlają.

Pierwsza tabela

__TIME__ jest specjalnym makrem zdefiniowanym przez preprocesor. Rozszerza się do stałej łańcuchowej zawierającej czas, w którym preprocesor został uruchomiony, w postaci "HH:MM:SS". Zauważ, że zawiera dokładnie 8 znaków. Zauważ, że 0-9 ma wartości ASCII od 48 do 57, a : ma wartość ASCII 58. Wyjście To 64 znaki na linię, więc pozostawia 8 znaków na znak __TIME__.

7 - i/8%8 jest zatem indeksem __TIME__ to jest obecnie wyjście (7- jest potrzebne, ponieważ iteracja i w dół). Tak więc {[36] } jest znakiem __TIME__ będącym wyjściem.

a w zależności od wejścia t:

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

Każda liczba jest bitmapą opisującą segmenty, które są oświetlone na naszym wyświetlaczu siedmiosegmentowym. Ponieważ wszystkie znaki są 7-bitowe ASCII, wysoki bit jest zawsze czyszczony. Tak więc 7 w tabeli segmentów zawsze drukuje jako puste. Druga tabela wygląda tak z 7 s jako puste:

000055  
11  55  
11  55  
116655  
22  33  
22  33  
444433  

Więc, na przykład, 4 jest 01101010 (ustawione bity 1, 3, 5 i 6), które wypisują jako

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

Aby pokazać, że naprawdę rozumiemy kod, dostosujmy nieco wyjście za pomocą tej tabeli:

  00  
11  55
11  55
  66  
22  33
22  33
  44

To jest zakodowane jako "?;;?==? '::799\x07". Dla celów artystycznych dodamy 64 do kilku znaków (ponieważ używane są tylko niskie 6 bitów, nie wpłynie to na wynik); daje to "?{{?}}?gg::799G" (zauważ, że 8. postać jest nieużywana, więc możemy ją robić co tylko chcemy). Umieszczenie naszej nowej tabeli w oryginalnym kodzie:

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

Otrzymujemy

          !!              !!                              !!   
    !!  !!              !!  !!  !!  !!              !!  !!  !! 
    !!  !!              !!  !!  !!  !!              !!  !!  !! 
          !!      !!              !!      !!                   
    !!  !!  !!          !!  !!      !!              !!  !!  !! 
    !!  !!  !!          !!  !!      !!              !!  !!  !! 
          !!              !!                              !!   
Tak jak się spodziewaliśmy. Nie jest tak solidny jak oryginał, co wyjaśnia, dlaczego autor zdecydował się użyć tabeli, którą zrobił.
 1772
Author: nneonneo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:50

Sformatujmy to dla łatwiejszego czytania:

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

Więc, uruchamiając go bez argumentów, _ (argc konwencjonalnie) jest 1. main() wywoła się rekurencyjnie, przekazując wynik -(~_) (ujemny bit nie z _), więc tak naprawdę przejdzie 448 rekurencji (tylko warunek gdzie _^448 == 0).

Biorąc to, wydrukuje 7 64-znakowych szerokich linii(zewnętrzny warunek trójkowy i 448/64 == 7). Więc przepiszmy to trochę czyściej:

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

Teraz, 32 jest dziesiętne dla ASCII miejsce. Albo drukuje spację albo a '!"(33 is"!", stąd "&1 " na końcu). Skupmy się na blobie w środku:

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

Jak powiedział inny plakat, __TIME__ jest czasem kompilacji programu i jest ciągiem znaków, więc dzieje się arytmetyka łańcuchów, a także wykorzystanie indeksu dolnego tablicy dwukierunkowego: a[b] jest tym samym, co b[a] dla tablic znaków.

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

Spowoduje wybranie jednego z pierwszych 8 znaków w __TIME__. To jest następnie indeksowane do [">'txiZ^(~z?"-48] (0-9 znaków to 48-57 dziesiętnych). Znaki w tym łańcuchu muszą być wybrane dla ich wartości ASCII. Ta sama manipulacja kodem ASCII jest kontynuowana przez wyrażenie, co powoduje wydrukowanie albo ""albo"!"w zależności od miejsca w glifie postaci.

 98
Author: chmeee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-13 21:11:25

Dodanie do pozostałych rozwiązań, {[0] } jest równe x+1, Ponieważ ~x jest równoważne (0xffffffff-x). Jest ona równa (-1-x) w dopełniaczu 2s, więc -~x jest -(-1-x) = x+1.

 46
Author: Thomas Song,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-21 21:44:31

Zdekodowałem arytmetykę modulo tak bardzo, jak mogłem i usunąłem rekurencję

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

Rozszerzenie go nieco bardziej:

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');
}
 3
Author: Lefteris E,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-21 16:34:56