混淆C $ C $ç大赛2006请解释sykes2.c [英] Obfuscated C Code Contest 2006. Please explain sykes2.c
问题描述
请问这个C程序工作?
<$p$p><$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:警告:返回类型默认为'诠释'[-Wreturn型]
sykes2.c:在函数'主':
sykes2.c:1:14:警告:不使用计算值[-Wunused值]
sykes2.c:1:1:警告:函数'的putchar'隐式声明[-Wimplicit功能声明]
sykes2.c:1:1:警告:在操作数建议各地算术括号'|'[-Wparentheses]
sykes2.c:1:1:警告:在操作数建议各地算术括号'|'[-Wparentheses]
sykes2.c:1:1:警告:控制到达非void函数[-Wreturn型]结束
让我们去混淆了。
缩进:
主(_){
_ ^ 448安培;&安培;主( - 〜_);
的putchar(--_ 64%
? 32 | - 〜7 [__ TIME __-_ / 8%8] - →[48&GT'txiZ ^(〜Z?];&gt;中;;; ====〜$ :: 199[_ * 2及8 | _ / 64] /(_和2 1:8?)%8和1
:10);
}
引入变量来解开这个烂摊子:
的main(int i)以{
如果(I ^ 448)
主( - 〜I);
如果( - 则i%64){
所以char a = - 〜7 [__ TIME __- I / 8%8] [&GT;'txiZ ^(〜Z? - 48];
炭B = A&GT;&GT; ;;; ====〜$ 199 ::[我* 2和8 | I / 64] /(I和2?1:8)8%;
的putchar(32 |(B&安培; 1));
}其他{
的putchar(10); // 新队
}
}
注意 - 〜我== I + 1,因为二进制补码
。因此,我们有
的main(int i)以{
如果(我!= 448)
主要第(i + 1);
一世 - ;
如果(ⅰ%64 == 0){
的putchar('\\ n');
}其他{
所以char a = - 〜7 [__ TIME __- I / 8%8] [&GT;'txiZ ^(〜Z? - 48];
炭B = A&GT;&GT; ;;; ====〜$ 199 ::[我* 2和8 | I / 64] /(I和2?1:8)8%;
的putchar(32 |(B&安培; 1));
}
}
现在,请注意, A [B]
相同 b [A]
,并应用 - 〜== 1 +
再次更改:
的main(int i)以{
如果(我!= 448)
主要第(i + 1);
一世 - ;
如果(ⅰ%64 == 0){
的putchar('\\ n');
}其他{
所以char a =(&GT;'txiZ ^(〜Z? - 48)[(__ TIME __- I / 8%8)[7] + 1;
炭B = A&GT;&GT; ;;; ====〜$ :: 199[(我* 2及8)|的i / 64] /(I和2 1:8)%8;
的putchar(32 |(B&安培; 1));
}
}
递归转换到一个循环,并偷偷在更多的简化:
//请不要传递任何命令行参数
主(){
INT I;
对于(i = 447; I&GT; = 0;我 - ){
如果(ⅰ%64 == 0){
的putchar('\\ n');
}其他{
焦炭T = __TIME __ [7 - I / 8%8]。
所以char a =&GT;'txiZ ^(〜Z?[T - 48] + 1;
INT转变=;;; ====〜$ 199 ::[(我* 2和8)| (I / 64)];
如果((ⅰ和2)== 0)
移动/ = 8;
移移= 8%;
炭B = A&GT;&GT;转移;
的putchar(32 |(B&安培; 1));
}
}
}
这个输出每次迭代一个字符。每64个字符,它输出一个换行符。否则,它使用一对数据表来找出输出,并提出要么32字符(空格)或字符33(A !
)。第一个表(&GT;'txiZ ^(〜Z
)是一组10位图描述每个角色的外观,和第二个表(;;; ====〜$ 199 ::
)选择合适的位从位图显示。
第二个表格
让我们通过检查第二个表, INT偏移开始=;;; ====〜$ 199 ::[(我* 2和8)| (I / 64);
。 I / 64
是行号(6比0)和 I * 2和8
8 IFF I
4,5,6或7 MOD 8。
IF((I和2)== 0)移位/ = 8;移移= 8%
或者选择高八进制数字(为 I%8
= 0,1,4,5)或低八进制数字(对于 I%8
= 2,3,6,7)表的价值。这种转变表最终看起来是这样的:
排山坳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 5月2日至3日
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 6月4日至5日
3月2日至3日5
3 0-1 7
2月6日至7日2
二月四日至五日7
2 2-3 3
2 0-1 7
1月6日至7日2
1 7月4日至五日
1次2-3 3
1 7 0-1
0 6-7 3
0 4-5 4
0 3月2日至三日
0 0-1 7
或以表格形式
00005577
11775577
11775577
11665577
22773377
22773377
44443377
请注意,作者使用的空终止前两个表项(偷偷摸摸的!)。
这是一个七段显示器后设计的,与 7
取值为空白。因此,在第一个表中的条目必须定义得到点亮的部分。
第一个表
7 - I / 8%8
是这样的指数 __ TIME __
是presently被输出( 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
能够像空格:
000055
11 55
11 55
116655
22 33
22 33
444433
因此,例如, 4
是 01101010
(位1,3,5,6集),它打印为
---- !! -
!! - !! -
!! - !! -
!!!!!! -
---- !! -
---- !! -
---- !! -
要展示我们真正理解code,让我们来调整输出有点与此表:
00
11 55
11 55
66
22 33
22 33
44
这是EN codeD为?;;?==?':: 799 \\ X07
。对艺术目的,我们将添加64到几个字符(因为只有低6位,这将不会影响输出);这给?{{}}??GG :: 799G
(注意第8字符是未使用的,所以我们实际上可以把它无论我们想要的)。把我们的新表中原来的code:
我们得到
! ! !
! ! ! ! ! ! ! ! !
! ! ! ! ! ! ! ! !
! ! ! !
! ! ! ! ! ! ! ! !
! ! ! ! ! ! ! ! !
! ! !
正如我们的预期。它不是固体面色原来,这解释了为什么笔者选择了用他做的表。
How does this C program work?
main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}
It compiles as it is (tested on gcc 4.6.3
). It prints the time when compiled. On my system:
!! !!!!!! !! !!!!!! !! !!!!!!
!! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !!
!! !!!!!! !! !! !! !! !! !!!!!!
!! !! !! !! !! !! !!
!! !! !! !! !! !! !!
!! !!!!!! !! !! !! !!!!!!
Source: sykes2 - A clock in one line, sykes2 author hints
Some hints: No compile warnings per default. Compiled with -Wall
, the following warnings are emitted:
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]
Let's de-obfuscate it.
Indenting:
main(_) {
_^448 && main(-~_);
putchar(--_%64
? 32 | -~7[__TIME__-_/8%8][">'txiZ^(~z?"-48] >> ";;;====~$::199"[_*2&8|_/64]/(_&2?1:8)%8&1
: 10);
}
Introducing variables to untangle this mess:
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
}
}
Note that -~i == i+1
because of twos-complement. Therefore, we have
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));
}
}
Now, note that a[b]
is the same as b[a]
, and apply the -~ == 1+
change again:
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));
}
}
Converting the recursion to a loop and sneaking in a bit more simplification:
// 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));
}
}
}
This outputs one character per iteration. Every 64th character, it outputs a newline. Otherwise, it uses a pair of data tables to figure out what to output, and puts either character 32 (a space) or character 33 (a !
). The first table (">'txiZ^(~z?"
) is a set of 10 bitmaps describing the appearance of each character, and the second table (";;;====~$::199"
) selects the appropriate bit to display from the bitmap.
The second table
Let's start by examining the second table, int shift = ";;;====~$::199"[(i*2&8) | (i/64)];
. i/64
is the line number (6 to 0) and i*2&8
is 8 iff i
is 4, 5, 6 or 7 mod 8.
if((i & 2) == 0) shift /= 8; shift = shift % 8
selects either the high octal digit (for i%8
= 0,1,4,5) or the low octal digit (for i%8
= 2,3,6,7) of the table value. The shift table ends up looking like this:
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
or in tabular form
00005577
11775577
11775577
11665577
22773377
22773377
44443377
Note that the author used the null terminator for the first two table entries (sneaky!).
This is designed after a seven-segment display, with 7
s as blanks. So, the entries in the first table must define the segments that get lit up.
The first table
__TIME__
is a special macro defined by the preprocessor. It expands to a string constant containing the time at which the preprocessor was run, in the form "HH:MM:SS"
. Observe that it contains exactly 8 characters. Note that 0-9 have ASCII values 48 through 57 and :
has ASCII value 58. The output is 64 characters per line, so that leaves 8 characters per character of __TIME__
.
7 - i/8%8
is thus the index of __TIME__
that is presently being output (the 7-
is needed because we are iterating i
downwards). So, t
is the character of __TIME__
being output.
a
ends up equalling the following in binary, depending on the input t
:
0 00111111
1 00101000
2 01110101
3 01111001
4 01101010
5 01011011
6 01011111
7 00101001
8 01111111
9 01111011
: 01000000
Each number is a bitmap describing the segments that are lit up in our seven-segment display. Since the characters are all 7-bit ASCII, the high bit is always cleared. Thus, 7
in the segment table always prints as a blank. The second table looks like this with the 7
s as blanks:
000055
11 55
11 55
116655
22 33
22 33
444433
So, for example, 4
is 01101010
(bits 1, 3, 5, and 6 set), which prints as
----!!--
!!--!!--
!!--!!--
!!!!!!--
----!!--
----!!--
----!!--
To show we really understand the code, let's adjust the output a bit with this table:
00
11 55
11 55
66
22 33
22 33
44
This is encoded as "?;;?==? '::799\x07"
. For artistic purposes, we'll add 64 to a few of the characters (since only the low 6 bits are used, this won't affect the output); this gives "?{{?}}?gg::799G"
(note that the 8th character is unused, so we can actually make it whatever we want). Putting our new table in the original code:
main(_){_^448&&main(-~_);putchar(--_%64?32|-~7[__TIME__-_/8%8][">'txiZ^(~z?"-48]>>"?{{?}}?gg::799G"[_*2&8|_/64]/(_&2?1:8)%8&1:10);}
we get
!! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !! !! !! !! !! !! !!
!! !! !!
just as we expected. It's not as solid-looking as the original, which explains why the author chose to use the table he did.
这篇关于混淆C $ C $ç大赛2006请解释sykes2.c的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!