ChatGPT解决这个技术问题 Extra ChatGPT

混淆 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 - A clock in one linesykes2 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
如果你这样运行它会崩溃:./a.out $(seq 0 447)

C
Community

让我们去混淆它。

缩进:

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

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

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

第一张表

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

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

根据输入 ta 最终等于以下二进制:

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  

因此,例如,401101010(设置了第 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 技术。
c
chmeee

让我们对其进行格式化以便于阅读:

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() 将递归调用自己,传递 -(~_) 的结果(_ 的负位 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 代码操作通过表达式继续,导致打印 ' ' 或 '!'取决于角色字形中的位置。


n
nneonneo

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


有趣的。我已经知道 ~x == -x - 1 有一段时间了,但我不知道它背后的数学推理。
Ey,Cole,(-1-x) 和 (-x-1) 一样,你不需要“修复”它!!
如果某人是-1338,那么他们不是1337的原因相同。
L
Lefteris E

我尽可能地对模运算进行了去混淆,并删除了递归

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

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅