Как работает эта C-программа?
main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}
Компилируется как есть (проверено gcc 4.6.3
). Он печатает время при компиляции. В моей системе:
!! !!!!!! !! !!!!!! !! !!!!!!
!! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !!
!! !!!!!! !! !! !! !! !! !!!!!!
!! !! !! !! !! !! !!
!! !! !! !! !! !! !!
!! !!!!!! !! !! !! !!!!!!
Источник: sykes2 - часы в одну строку , намеки автора sykes2
Некоторые подсказки: нет предупреждений компиляции по умолчанию. Скомпилированы -Wall
следующие предупреждения:
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
шероховатый
источник
источник
printf("%d", _);
в началоmain
распечаток: pastebin.com/HHhXAYdJint
./a.out $(seq 0 447)
Ответы:
Давайте де-запутать это.
Отступ:
Введение переменных, чтобы распутать этот беспорядок:
Обратите внимание, что
-~i == i+1
из-за двойного дополнения. Поэтому мы имеемТеперь обратите внимание, что
a[b]
это то же самоеb[a]
, и примените-~ == 1+
изменение снова:Преобразование рекурсии в цикл и пробежка в немного упрощенном виде:
Это выводит один символ за итерацию. Каждый 64-й символ выводит новую строку. В противном случае он использует пару таблиц данных, чтобы выяснить, что выводить, и помещает либо символ 32 (пробел), либо символ 33 (а
!
). Первая таблица (">'txiZ^(~z?"
) представляет собой набор из 10 битовых карт, описывающих внешний вид каждого символа, а вторая таблица (";;;====~$::199"
) выбирает соответствующий бит для отображения из битовой карты.Второй стол
Давайте начнем с изучения второй таблицы
int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
.i/64
это номер строки (от 6 до 0) иi*2&8
равен 8, еслиi
4, 5, 6 или 7 mod 8.if((i & 2) == 0) shift /= 8; shift = shift % 8
выбирает либо восьмеричную цифру (дляi%8
= 0,1,4,5), либо восьмеричную цифру (дляi%8
= 2,3,6,7) табличного значения. Таблица сдвига в итоге выглядит так:или в табличной форме
Обратите внимание, что автор использовал нулевой терминатор для первых двух записей таблицы (подлый!).
Это разработано после семисегментного дисплея с
7
s в качестве пробелов. Таким образом, записи в первой таблице должны определять сегменты, которые загораются.Первый стол
__TIME__
это специальный макрос, определенный препроцессором. Он расширяется до строковой константы, содержащей время запуска препроцессора в форме"HH:MM:SS"
. Обратите внимание, что он содержит ровно 8 символов. Обратите внимание, что 0-9 имеют значения ASCII от 48 до 57 и:
имеют значение ASCII 58. Выходные данные составляют 64 символа в строке, так что остается 8 символов на символ__TIME__
.7 - i/8%8
таким образом, индекс того,__TIME__
что в данный момент выводится (7-
необходим, потому что мы выполняем итерациюi
вниз). Итак,t
характер__TIME__
выхода.a
в результате получается двоичное значение, в зависимости от входных данныхt
:Каждое число представляет собой растровое изображение, описывающее сегменты, которые подсвечиваются на нашем семисегментном дисплее. Поскольку все символы являются 7-битными ASCII, старший бит всегда очищается. Таким образом,
7
в таблице сегментов всегда печатается как пустой. Вторая таблица выглядит так с7
пробелами s:Так, например,
4
есть01101010
(биты 1, 3, 5 и 6 установлены), который печатает какЧтобы показать, что мы действительно понимаем код, давайте немного скорректируем вывод с помощью этой таблицы:
Это закодировано как
"?;;?==? '::799\x07"
. В художественных целях мы добавим 64 к нескольким символам (поскольку используются только младшие 6 битов, это не повлияет на вывод); это дает"?{{?}}?gg::799G"
(обратите внимание, что 8-й символ не используется, поэтому мы можем сделать его как угодно). Положим нашу новую таблицу в исходный код:мы получили
как мы и ожидали. Это не так хорошо выглядит, как оригинал, что объясняет, почему автор решил использовать таблицу, которую он сделал.
источник
*
(разыменование) и a+
: PДавайте отформатируем это для удобства чтения:
Таким образом, запустив его без аргументов, _ (условно argc) есть
1
.main()
будет рекурсивно вызывать себя, передавая результат-(~_)
(отрицательный побитовый НЕ_
), так что на самом деле будет идти 448 рекурсий (только условие, где_^448 == 0
).Принимая это, он напечатает 7 64-символьных широких линий (внешнее троичное условие и
448/64 == 7
). Итак, давайте перепишем это немного чище:Теперь
32
является десятичным для ASCII-пространства. Он либо печатает пробел, либо '!' (33 - это «!», Следовательно, «&1
» в конце). Давайте сосредоточимся на пятне в середине:Как сказал другой автор,
__TIME__
это время компиляции для программы и является строкой, так что происходит некоторая строковая арифметика, а также использование преимуществ двунаправленного нижнего индекса массива: a [b] совпадает с b [a] для массивов символов.Это выберет один из первых 8 символов в
__TIME__
. Затем он индексируется в[">'txiZ^(~z?"-48]
(0-9 символов, 48-57 десятичных). Символы в этой строке должны быть выбраны для их значений ASCII. Эта же символьная манипуляция кодом ASCII продолжается через выражение, что приводит к выводу либо '', либо '!' в зависимости от расположения в глифе персонажа.источник
Добавление к другим решениям
-~x
равно,x+1
потому что~x
эквивалентно(0xffffffff-x)
. Это равно(-1-x)
в дополнении 2s, так и-~x
есть-(-1-x) = x+1
.источник
Я как можно больше обфускировал арифметику по модулю и удалил рекурсию
Расширяя это немного больше:
источник