混淆 C 代码竞赛 2006。请解释一下 sykes2.c
Posted
技术标签:
【中文标题】混淆 C 代码竞赛 2006。请解释一下 sykes2.c【英文标题】:Obfuscated C Code Contest 2006. Please explain sykes2.c 【发布时间】:2013-03-01 20:49:17 【问题描述】:这个 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 - A clock in one line,sykes2 author hints
一些提示:默认情况下没有编译警告。使用-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]
【问题讨论】:
调试:将printf("%d", _);
添加到main
的开头打印:pastebin.com/HHhXAYdJ
整数,每个无类型变量默认为int
你读过提示吗? ioccc.org/2006/sykes2/hint.text
另请阅读***.com/questions/10321196/…
如果你这样运行它会崩溃:./a.out $(seq 0 447)
【参考方案1】:
让我们去混淆它。
缩进:
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));
现在,请注意 a[b]
is the same as b[a]
,并再次应用 -~ == 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 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));
每次迭代输出一个字符。每第 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 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
或以表格形式
00005577
11775577
11775577
11665577
22773377
22773377
44443377
请注意,作者对前两个表条目使用了空终止符(偷偷摸摸!)。
这是按照七段显示设计的,7
s 为空白。因此,第一个表中的条目必须定义被点亮的段。
第一张桌子
__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 00111111
1 00101000
2 01110101
3 01111001
4 01101010
5 01011011
6 01011111
7 00101001
8 01111111
9 01111011
: 01000000
每个数字都是一个位图,描述了在我们的七段显示器中点亮的段。由于字符都是 7 位 ASCII,所以总是清除高位。因此,段表中的7
始终打印为空白。第二个表格如下所示,7
s 为空白:
000055
11 55
11 55
116655
22 33
22 33
444433
因此,例如,4
是 01101010
(设置了第 1、3、5 和 6 位),打印为
----!!--
!!--!!--
!!--!!--
!!!!!!--
----!!--
----!!--
----!!--
为了表明我们真的理解代码,让我们用这张表稍微调整一下输出:
00
11 55
11 55
66
22 33
22 33
44
这被编码为"?;;?==? '::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);
我们得到
!! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !!
正如我们所料。看起来不像原版那么扎实,这就解释了为什么作者选择使用他做的桌子。
【讨论】:
@drahnr - 从技术上讲,它既是*
(取消引用)又是+
:P
@АртёмЦарионов:大约 30 分钟,但我已经回来并进行了相当多的编辑。我经常使用 C,而且我之前做过一些 IOCCC 去混淆处理,出于个人兴趣(我做的最后一个,只是为了个人兴趣,是this beautiful raytracer)。如果你想问它是如何工作的,我很乐意效劳;)
@АртёмЦарионов:大约一天 IIRC(也计算了理解光线追踪器几何所花费的时间)。该程序也非常聪明,因为它不使用关键字。
C.. 汇编语言的所有力量与汇编语言的可读性相结合
有关这方面的更多信息,请查看 Don Libes 的“Obfuscated C and Other Mysteries”。它通过分析混淆的 C 竞赛条目来教授 C 技术。【参考方案2】:
让我们将其格式化以便于阅读:
main(_)
_^448&&main(-~_);
putchar((--_%64) ? (32|-(~7[__TIME__-_/8%8])[">'txiZ^(~z?"-48]>>(";;;====~$::199")[_*2&8|_/64]/(_&2?1:8)%8&1):10);
因此,不带参数运行它,_(通常为 argc)是1
。 main()
将递归调用自身,传递 -(~_)
的结果(_
的负位 NOT),所以实际上它会进行 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 代码操作通过表达式继续,导致打印 ' ' 或 '!'取决于角色字形中的位置。
【讨论】:
【参考方案3】:添加到其他解决方案,-~x
等于 x+1
,因为 ~x
等于 (0xffffffff-x)
。这等于 (-1-x)
的 2s 补码,所以 -~x
是 -(-1-x) = x+1
。
【讨论】:
有趣。我早就知道 ~x == -x - 1,但我不知道它背后的数学推理。 Ey,Cole,(-1-x) 和 (-x-1) 一样,你不需要“修复”它!! 同理,如果某人是 -1338,那么他们不是 1337。【参考方案4】:我尽可能地对模运算进行了去混淆处理并删除了递归
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;
else
shift = shiftChar >> 3;
putchar(' '| (">'txiZ^(~z?"["12:34:56"[digit]-'0'] + 1) >> shift & 1 );
putchar('\n');
【讨论】:
以上是关于混淆 C 代码竞赛 2006。请解释一下 sykes2.c的主要内容,如果未能解决你的问题,请参考以下文章