混淆C代码大赛2006年。请解释sykes2.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
上测试)。 它打印编译时的时间。 在我的系统上:
!! !!!!!! !! !!!!!! !! !!!!!!
!! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !!
!! !!!!!! !! !! !! !! !! !!!!!!
!! !! !! !! !! !! !!
!! !! !! !! !! !! !!
!! !!!!!! !! !! !! !!!!!!
来源: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]
让我们去混淆它吧。
缩进:
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]
与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(a !
)。 第一个表( ">'txiZ^(~z?"
))是一组描述每个字符外观的10位图,第二个表( ";;;====~$::199"
)选择从位图显示适当的位。
第二张桌子
首先检查第二个表, int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
。 i/64
是行号(6到0),如果i
是4,5,6或7 mod 8,则i*2&8
是8。
if((i & 2) == 0) shift /= 8; shift = shift % 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
为空白。 所以,第一个表中的条目必须定义点亮的段。
第一张桌子
__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
这被编码为"?;;?==? '::799x07"
。 出于艺术目的,我们将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通常)是1
。 main()
会递归地调用它自己,传递-(~_)
的结果(负的_
不为_
),所以真的会执行448次递归(只有条件where _^448 == 0
)。
448/64 == 7
条件,它将打印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值选择。通过表达式继续进行操作,以根据角色的字形内的位置来打印“'或'!'。
添加到其他的解决方案, -~x
等于x+1
,因为~x
是相当于(0xffffffff-x)
这等于2s补码中的(-1-x)
,所以-~x
是-(-1-x) = x+1
。
上一篇: Obfuscated C Code Contest 2006. Please explain sykes2.c