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 gcc 4.6.3
). Drukuje czas po skompilowaniu. W moim systemie:
!! !!!!!! !! !!!!!! !! !!!!!!
!! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !!
!! !!!!!! !! !! !! !! !! !!!!!!
!! !! !! !! !! !! !!
!! !! !! !! !! !! !!
!! !!!!!! !! !! !! !!!!!!
Źródło: sykes2 - Zegar w jednej linii , wskazówki autora sykes2
Kilka wskazówek: Brak domyślnych ostrzeżeń dotyczących kompilacji. Po kompilacji -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]
c
obfuscation
deobfuscation
staromodny
źródło
źródło
printf("%d", _);
na początkumain
wydruków: pastebin.com/HHhXAYdJint
./a.out $(seq 0 447)
Odpowiedzi:
Usuńmy z tego zaciemnienie.
Wcięcie:
Przedstawiamy zmienne, aby rozwiązać ten bałagan:
Zauważ,
-~i == i+1
że z powodu dwóch uzupełnień. Dlatego mamyTeraz zauważ, że
a[b]
to samob[a]
, i zastosuj-~ == 1+
zmianę ponownie:Przekształcanie rekurencji w pętlę i skradanie się w nieco większym uproszczeniu:
Daje to jeden znak na iterację. Co 64 znak wypisuje nowy wiersz. W przeciwnym razie używa pary tabel danych, aby dowiedzieć się, co wyprowadzić, i umieszcza znak 32 (spację) lub znak 33 (a
!
). Pierwsza tabela (">'txiZ^(~z?"
) to zestaw 10 map bitowych opisujących wygląd każdego znaku, a druga tabela (";;;====~$::199"
) wybiera odpowiedni bit do wyświetlenia z mapy bitowej.Drugi stół
Zacznijmy od zbadania drugiej tabeli
int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
.i/64
jest numerem linii (od 6 do 0) ii*2&8
wynosi 8 iffi
to 4, 5, 6 lub 7 mod 8.if((i & 2) == 0) shift /= 8; shift = shift % 8
wybiera albo wysoką liczbęi%8
ósemkową (dla = 0,1,4,5), albo niską liczbęi%8
ósemkową (dla = 2,3,6,7) wartości tabeli. Tabela zmian wygląda następująco:lub w formie tabelarycznej
Zauważ, że autor użył zerowego terminatora dla pierwszych dwóch wpisów tabeli (podstępne!).
Zostało to zaprojektowane po siedmiosegmentowym wyświetlaczu z
7
pustymi literami s. Tak więc wpisy w pierwszej tabeli muszą definiować podświetlane segmenty.Pierwszy stół
__TIME__
to specjalne makro zdefiniowane przez preprocesor. Rozwija się do stałej łańcuchowej zawierającej czas, w którym uruchomiono preprocesor, w formie"HH:MM:SS"
. Zauważ, że zawiera dokładnie 8 znaków. Zauważ, że 0-9 ma wartości ASCII od 48 do 57 i:
ma wartość ASCII 58. Dane wyjściowe to 64 znaki w wierszu, więc pozostawia 8 znaków na znak__TIME__
.7 - i/8%8
jest zatem wskaźnikiem tego,__TIME__
który jest obecnie generowany (7-
jest potrzebny, ponieważ iterujemy wi
dół). Więct
jest charakter__TIME__
bycia wyjściem.a
kończy się w następujący sposób binarnie, w zależności od danych wejściowycht
:Każda liczba jest bitmapą opisującą segmenty podświetlane na naszym siedmiosegmentowym wyświetlaczu. Ponieważ wszystkie znaki są 7-bitowymi kodami ASCII, wysoki bit jest zawsze usuwany. Tak więc
7
w tabeli segmentów zawsze drukuje się jako puste. Drugi stół wygląda tak z7
pustymi literami s:Na przykład
4
jest01101010
(zestaw bitów 1, 3, 5 i 6), który wypisuje jakoAby pokazać, że naprawdę rozumiemy kod, dostosujmy nieco wynik za pomocą tej tabeli:
Jest to zakodowane jako
"?;;?==? '::799\x07"
. Do 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 ósma postać jest nieużywana, więc możemy zrobić to, co chcemy). Umieszczenie naszej nowej tabeli w oryginalnym kodzie:dostajemy
tak jak się spodziewaliśmy. Nie jest tak solidnie wyglądający jak oryginał, co tłumaczy, dlaczego autor postanowił skorzystać ze stołu, który zrobił.
źródło
*
(dereferencja), jak i+
: PSformatujmy to dla łatwiejszego czytania:
Tak więc, uruchamiając go bez argumentów, _ (argc konwencjonalnie) jest
1
.main()
wywoła się rekurencyjnie, przekazując wynik-(~_)
(ujemne bitowe NIE_
), więc naprawdę przejdzie 448 rekurencji (tylko warunek gdzie_^448 == 0
).Biorąc to pod uwagę, wypisze 7 linii o szerokości 64 znaków (warunek zewnętrznej trójki i
448/64 == 7
). Więc przepiszmy to trochę czystsze:Teraz
32
jest dziesiętny dla przestrzeni ASCII. Drukuje spację lub „!” (33 to „!”, Stąd „&1
” na końcu). Skupmy się na kropli na środku:Jak powiedział inny plakat,
__TIME__
jest to czas kompilacji programu i jest on ciągiem, więc zachodzi pewna arytmetyka ciągu, a także wykorzystanie dwukierunkowego indeksu tablicowego: a [b] jest takie samo jak b [a] dla tablic postaci.Spowoduje to wybranie jednego z pierwszych 8 znaków w
__TIME__
. Jest to następnie indeksowane do[">'txiZ^(~z?"-48]
(0-9 znaków ma 48-57 miejsc dziesiętnych). Znaki w tym ciągu muszą zostać wybrane dla ich wartości ASCII. Ten sam znak manipulacji kodem ASCII jest kontynuowany przez wyrażenie, co powoduje wydrukowanie „” lub „!” w zależności od położenia w glifie postaci.źródło
Dodanie do innych rozwiązań
-~x
jest równe,x+1
ponieważ~x
jest równoważne z(0xffffffff-x)
. Jest to równe(-1-x)
uzupełnieniu do 2s, podobnie-~x
jest-(-1-x) = x+1
.źródło
Usunąłem zaciemnienie arytmetyki modulo, jak mogłem, i usunąłem rekursję
Rozwijając to trochę bardziej:
źródło