2006年混淆C代码竞赛。请解释sykes 2. c

这个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上测试)。它打印编译时的时间。在我的系统上:

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

来源:Sykes 2-打卡一行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]
83625 次浏览

让我们去混淆它。

缩进:

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

引入变量来解开这个混乱:

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

注意-~i == i+1是因为二补码。因此,我们有

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

现在,注意#0与#1相同,并再次应用-~ == 1+更改:

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

将递归转换为循环并偷偷进行更多简化:

// please don't pass any command-line argumentsmain() {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));}}}

这每次迭代输出一个字符。每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)。移位表最终如下所示:

row col val6   6-7 06   4-5 06   2-3 56   0-1 75   6-7 15   4-5 75   2-3 55   0-1 74   6-7 14   4-5 74   2-3 54   0-1 73   6-7 13   4-5 63   2-3 53   0-1 72   6-7 22   4-5 72   2-3 32   0-1 71   6-7 21   4-5 71   2-3 31   0-1 70   6-7 40   4-5 40   2-3 30   0-1 7

或以表格形式

00005577117755771177557711665577227733772277337744443377

请注意,作者在前两个表条目中使用了空终止符(偷偷摸摸!)。

这是在七段显示之后设计的,以7为空白。因此,第一个表中的条目必须定义被点亮的段。

第一张桌子

__TIME__是预处理器定义的特殊宏。它以"HH:MM:SS"的形式扩展为包含预处理器运行时间的字符串常量。观察到它正好包含8个字符。请注意0-9的ASCII值为48到57,:的ASCII值为58。输出为每行64个字符,因此__TIME__的每个字符留下8个字符。

因此,7 - i/8%8是当前正在输出的__TIME__的索引(需要7-是因为我们正在向下迭代i)。所以,t__TIME__被输出的字符。

a最终在二进制中等于以下内容,具体取决于输入t

0 001111111 001010002 011101013 011110014 011010105 010110116 010111117 001010018 011111119 01111011: 01000000

每个数字都是一个位图,描述了在我们的七段显示中点亮的段。由于字符都是7位ASCII,高位总是被清除。因此,段表中的7总是打印为空白。第二个表看起来像这样,7是空白:

00005511  5511  5511665522  3322  33444433

因此,例如,401101010(位1,3,5和6集),它打印为

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

为了表明我们真正理解了代码,让我们用这个表稍微调整一下输出:

  0011  5511  556622  3322  3344

这被编码为"?;;?==? '::799\x07"。出于艺术目的,我们将为一些字符添加64(因为只使用了低6位,这不会影响输出);这给出了"?\{\{?}}?gg::799G"(请注意,第8个字符未使用,所以我们实际上可以将其设为任何我们想要的)。将我们的新表放入原始代码中:

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

我们得到

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

正如我们预期的那样。它不像原版那样坚固,这就解释了为什么作者选择使用他所做的桌子。

让我们格式化它以便于阅读:

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

所以,在没有参数的情况下运行它,_(常规的argc)是1main()将递归调用自己,传递-(~_)的结果(_的负位非),所以实际上它将进行448次递归(只有条件_^448 == 0)。

以此为例,它将打印7个64个字符的宽行(外部三元条件和448/64 == 7)。所以让我们把它重写得更干净一点:

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

现在,32是ASCII空间的十进制。它要么打印一个空格,要么打印一个'!'(33是'!',因此末尾是'&1')。让我们关注中间的blob:

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

正如另一个海报所说,__TIME__是程序的编译时间,并且是一个字符串,所以有一些字符串算术正在进行,以及利用数组下标是双向的:字符数组的a[b]与b[a]相同。

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

这将选择__TIME__中的前8个字符中的一个。然后将其索引到[">'txiZ^(~z?"-48](0-9个字符为48-57个十进制)。此字符串中的字符必须为其ASCII值选择。相同的字符ASCII代码操作继续通过表达式,以导致打印''或'!',具体取决于字符字形中的位置。

加上其他解,-~x等于x+1,因为~x等价于(0xffffffff-x)。这等于2s补码中的(-1-x),所以-~x-(-1-x) = x+1

我尽可能地消除了模算术的混淆,并消除了隐藏

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

再扩展一点:

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;elseshift = shiftChar >> 3;putchar(' '| (">'txiZ^(~z?"["12:34:56"[digit]-'0'] + 1) >> shift & 1 );}
}putchar('\n');}