故事是这样的,看到一个知乎专栏名为一段鬼畜的代码的分析文章,超想要感受一下,所以顺着作者的思路来梳理一遍。


第一段代码:输出当前时间

根据原作者的描述,这段代码来自国际 C 语言混乱代码大赛,即 IOCCC,找了一番却并没有找到,以下是源代码。

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

长这么大我还从没分析过长这么奇怪的代码,能成功编译已经让我备感诧异了,然后不出所料,运行结果如下图:

ioccc第一段代码运行结果.png

顺着作者的逻辑,添加头文件和返回值,规范 main 函数,并展开源代码,函数形参不指定类型时默认为 int,我们用 int x 替换 _,得到如下结果:

#include <stdio.h>
int main(int x) {
    x ^ 448 && main(-~x);
    putchar(--x % 64 ? 32 | -~7[__TIME__ - x / 8 % 8][">'txiZ^(~z?" - 48] >> ";;;====~$::199"[x * 2 & 8 | x / 64] / (x & 2 ? 1 : 8) % 8 & 1 : 10);
    return 0;
}

展开第三行,x ^ 448 && main(-~x); 包含一个判断语句,即:

if (x ^ 448)
    main(-~x);

第四行的 putchar 函数的参数也包含了判断逻辑,将 ? : 运算符展开成 if ... else ... 得到如下:

#include <stdio.h>
int main(int x) {
    if (x ^ 448)
        main(-~x);
    if (--x % 64)
        putchar(32 | -~7[__TIME__ - x / 8 % 8][">'txiZ^(~z?" - 48] >> ";;;====~$::199"[x * 2 & 8 | x / 64] / (x & 2 ? 1 : 8) % 8 & 1);
    else
        putchar(10);
    return 0;
}

第六行 putchar 中包含的逻辑依然过于复杂,我们拆成两个变量来保存:

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

这里有个很隐蔽的变换,取下标运算符例如 x[i] 本质是 x 指向的地址加上偏移量 i,即 *(x+i),因此 i[x] == x[i],则上面的第一行代码就可以变换成:

char a = -~(__TIME__ - x / 8 % 8)[7][">'txiZ^(~z?" - 48];

继续分析,补码按位取反的规律为 ~x = -(x+1),则 -~x 即为 x+1putchar(10) 中输出的 10 对应的 ascii 为 \nx ^ 448 隐含了只要 x != 448 时都满足条件,代码如下:

#include <stdio.h>
int main(int x) {
    if (x != 448)
        main(x + 1);
    if (--x % 64) {
        char a = 1 + (__TIME__ - x / 8 % 8)[7][">'txiZ^(~z?" - 48];
        char b = ";;;====~$::199"[x * 2 & 8 | x / 64] / (x & 2 ? 1 : 8) % 8;
        putchar(32 | a >> b & 1);
    }
    else
        putchar('\n');
    return 0;
}

函数参数的输入值 x1,3-4 行代码表示 x 的取值范围为 [1, 448],但是由于递归,5-12 行代码实际是从 x=448 倒过来执行,即转换为循环后,x = [448, 1],又由于第 5 行代码先执行了 --x,所以循环应该从 447 开始执行到 0,每轮自减 1

#include <stdio.h>
int main() {
    int x;
    for (x = 447; x >= 0; x--) {
        if (x % 64 != 0) {
            char a = 1 + (__TIME__ - x / 8 % 8)[7][">'txiZ^(~z?" - 48];
            char b = ";;;====~$::199"[x * 2 & 8 | x / 64] / (x & 2 ? 1 : 8) % 8;
            putchar(32 | a >> b & 1);
        }
        else
            putchar('\n');
    }
    return 0;
}

根据输出结果显示,输出长度为 64 个字符,一共输出了 7 行,因此我们可以推测,循环的 448 个字符正是输出结果,一共有三种类型的字符被输出,即 \n! 。这样我们就可以推测出,循环中的判断就是当输完了一行 64 个字符后,就换一行继续。接下来我们分析剩下的三行代码。

char a = 1 + (__TIME__ - x / 8 % 8)[7][">'txiZ^(~z?" - 48];
char b = ";;;====~$::199"[x * 2 & 8 | x / 64] / (x & 2 ? 1 : 8) % 8;
putchar(32 | a >> b & 1);

第三行 a >> b & 1 的结果为 01,可以修改为:

char c = a >> b;
if (c % 2 == 0)
    putchar(' ');
else
    putchar('!');

根据 i[x] == x[i] 的变换规则再变换,得到:

char a = (">'txiZ^(~z?" - 48)[(__TIME__ - x / 8 % 8)[7]] + 1;
char b = (";;;====~$::199")[x * 2 & 8 | x / 64] / (x & 2 ? 1 : 8) % 8;
char c = a >> b;
if (c % 2 == 0)
    putchar(' ');
else
    putchar('!');

然后然后,然后就分析不下去了,这都是什么鬼???

mmp5348534fh3.png

上面提到 i[x] == x[i],那么可以联想到 (i+j)[x] == (x+j)[i],因为 (x+j)[i] 等价于 *(i+j+x),则将 (__TIME__ - x / 8 % 8)[7] 转换为 __TIME__[7 - x / 8 % 8],得到如下内容:

char a = ">'txiZ^(~z?"[__TIME__[7 - x / 8 % 8] - 48] + 1;

char b= 一行后面的 ? : 改写成 if ... 语句,至此得到如下完整的代码:

#include <stdio.h>
int main() {
    int x;
    for (x = 447; x >= 0; x--) {
        if (x % 64 != 0) {
            char t = __TIME__[7 - x / 8 % 8];
            char a = ">'txiZ^(~z?"[t - 48] + 1;
            char s = x * 2 & 8 | x / 64;
            char b = ";;;====~$::199"[s];
            if (x & 2 == 0)
                b /= 8;
            b %= 8;
            char c = a >> b;
            if (c % 2 == 0)
                putchar(' ');
            else
                putchar('!');
        }
        else
            putchar('\n');
    }
    return 0;
}

接下来我们分析 ">'txiZ^(~z?";;;====~$::199。前者有 10 个字符,且不重复,推测这 10 个字符分别对应到数字 0-9。输出的每行长度为 64 个字符,__TIME__ 宏使用格式 HH:MM:ss,可以分析出 7 - x / 8 % 8 的区间 0-7 正好对应到 __TIME__ 宏值的下标。测试如下代码,得出运行结果:

int main() {
    for (int i=447; i >= 0; i--) {
        int j = 7 - i / 8 % 8;
        if (i % 64)
            printf("%d", j);
        else
            printf("\n");
    }
    return 0;
}

result:
000000001111111122222222333333334444444455555555666666667777777
000000001111111122222222333333334444444455555555666666667777777
000000001111111122222222333333334444444455555555666666667777777
000000001111111122222222333333334444444455555555666666667777777
000000001111111122222222333333334444444455555555666666667777777
000000001111111122222222333333334444444455555555666666667777777
000000001111111122222222333333334444444455555555666666667777777

到这一步可以知道,char t = __TIME__[7 - x / 8 % 8]; 每一次取出对应 HH:MM:ss 位置的值。继续分析,对于代码段:

char a = ">'txiZ^(~z?"[t - 48] + 1;

t 是取出的时间字符,480 的 ASCII 码,0123456789: 正好对应到 ASCII 的 48-58,这样就实现了映射:

char t      0    1    2    3    4    5    6    7    8    9    :
            >    '    t    x    i    Z    ^    (    ~    z    ?
char a      ?    (    u    y    j    [    _    )   127   {    @
ascii      63   40   117  121  106  91   95   41   127  123  64

分析第二个 bitmap,char b = ";;;====~$::199"[s];,可以推测出 s 的取值范围,我们仍然测试取值,运行如下代码得到结果:

int main() {
    for (int i=447; i >= 0; i--) {
        int j = i * 2 & 8 | i / 64;
        if (i % 64)
            printf("%x", j);
        else
            printf("\n");
    }
    return 0;
}

result:
eeee6666eeee6666eeee6666eeee6666eeee6666eeee6666eeee6666eeee666
dddd5555dddd5555dddd5555dddd5555dddd5555dddd5555dddd5555dddd555
cccc4444cccc4444cccc4444cccc4444cccc4444cccc4444cccc4444cccc444
bbbb3333bbbb3333bbbb3333bbbb3333bbbb3333bbbb3333bbbb3333bbbb333
aaaa2222aaaa2222aaaa2222aaaa2222aaaa2222aaaa2222aaaa2222aaaa222
999911119999111199991111999911119999111199991111999911119999111
888800008888000088880000888800008888000088880000888800008888000

建立映射表:

 0    1    2    3    4    5    6    7    8    9    10    11    12    13
 ;    ;    ;    =    =    =    =    ~    $    :    :     1     9     9
59   59   59   61   61   61   61   126   36   58   58    49    57    57
 3    3    3    5    5    5    5    6    4    2    2     1     1     1

这样就得出了 ab 的映射值,如下表:

a=  [63, 40, 117, 121, 106, 91, 95, 41, 127, 123, 64]
b=  [3, 5, 6, 4, 2, 1, 0]

建立每个绘制单元的矩阵:

00005577
11775577
11775577
11665577
22773377
22773377
44443377

其中 7 将被直接打印为 ' ',剔除后我们可以得到如下的 bitmap0-6 分别控制一个边的显示:

000055
11  55
11  55
116655
22  33
22  33
444433

最后分析 char c = a >> b;,将 a 的取值写成二进制:

char  ascii   binary
0,    63,     00111111
1,    40,     00101000
2,    117,    01110101
3,    121,    01111001
4,    106,    01101010
5,    91,     01011011
6,    95,     01011111
7,    41,     00101001 
8,    127,    01111111
9,    123,    01111011
:,    64,     01000000

例如对于 20:19,根据 a >> b % 2 可以算出对应掩码的显示状态:

2 = 01110101
0 = 00111111
: = 01000000
1 = 00101000
9 = 01111011

b =       0  1  2  3  4  5  6
2 =       T  F  T  F  T  T  T
0 =       T  T  T  T  T  T  F
: =       F  F  F  F  F  F  T
1 =       F  F  F  T  F  T  F
9 =       T  T  F  T  T  T  T

将掩码的结果填充到 bitmap 中就可以得到如下的结果:

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

解毕。


第二段代码:字符转换为 ASCII

代码如下:

char O,o[];main(l){for(;~l;O||puts(o))O=(O[o]=~(l=getchar())?4<(4^l>>5)?l:46:0)?-~O&printf("%02x ",l)*5:!O;}

运行结果:
ioccc第二段代码运行结果.png

有了第一段代码的分析经验,第二段代码首先格式化,补充头文件做调整得到:

#include <stdio.h>
char O, o[];
int main(void) {
    for(int l=1; ~l; O || puts(o))
        O = (O[o]= ~(l=getchar()) ? 4<(4^l>>5) ? l : 46 : 0) ? -~O & printf("%02x ",l)*5 : !O;
    return 0;
}

根据 ~l == -(l+1)-~l == l+1O[o] == o[O] 做变换,将 ? : 修改为 if ... else ... 的形式得到:

#include <stdio.h>
char O, o[];
int main(void) {
    for(int l=1; -(l+1) != 0; O || puts(o)) {
        l = getchar();
        if (-(l+1) != 0) {
            if (4 ^ l >> 5 > 4)
                o[O] = l;
            else
                o[O] = 46;
        }
        else
            o[O] = 0;
        if (o[O] != 0) {
            O = (O+1) & printf("%02x ", l)*5;
        }
        else
            O = !O;
    }
    return 0;
}

O || puts(o) 修改为 if 语句,将 4 ^ l >> 5 > 4 修改为 4 ^ l > 36,调整逻辑的顺序得到:

#include <stdio.h>
char O, o[];
int main(void) {
    for(int l=1; l != -1; ) {
        l = getchar();
        if (l == -1)
            O = !O;
        else {
            if (4 ^ l > 36)
                o[O] = l;
            else
                o[O] = 46;
            O = (O+1) & printf("%02x ", l)*5;
        }
        if (!O)
            puts(o);
    }
    return 0;
}

进一步地,输入的 l 为正数,且当 l > 2 时就能满足条件 4 ^ l > 36,因此将该条件修改为:

if (l > 2)
    o[O] = l;

又因为可输入的字符一定满足 l>2 的条件,所以代码可以进一步修改为:

#include <stdio.h>
char O, o[];
int main(void) {
    for(int l=1; l != -1; ) {
        l = getchar();
        o[O] = l;
        O = (O+1) & printf("%02x ", l)*5;
        if (!O)
            puts(o);
    }
    return 0;
}

函数 printf 返回输出的字符数,这里返回 3,建立 O 的取值映射表:

O = (O+1) & 15;
O = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

这样通过 o[O] 的操作,数组实际操作的容量为 16,每处理完 16 个字符,就通过 puts(o) 将缓冲区全部输出。

解毕。


第三段代码:鬼知道这代码是干啥的

  #include   /*recall-the\    /-good--old-\    /IOCCC-days!\    */<unistd.h>
   typedef  unsigned/*int*/  short U;U(main)  [32768],n,r[8];  __attribute__((
  # define  R(x)       A(r[  7-(n       >>x&  7)],       (n>>  x>>3       )%8)
  #define   C(x)       (U*)  ((/*             |IO|             -dpd
  */char*)  main       +(x)  )/*|             |CC|             ll*/
  # define  A(v,       i)(i  ?i<2             ?C(v             ):i\
  -4?v+=2,  C(i-       6?v-  2:v+       *C(v  -2))       :C(v  -=2)       :&v)
  /*lian*/  constructor))U(  x)(){for(;;*r+=  2,*r+=!n?_exit(  write(2,"Illeg"
  "al ins"   "truction ;-"    "(\n",24)),0:     n>>8==001?(      signed char

                 )n*2   :548==n>>    6&&usleep     /**/(10
                 )+n%  64==   4?0*  write  (r[7   /**/],C(
                 *C(*  r)),   *C(*  r+2)   )+4:  /**/ n>>9
                 ==63   &&--r[7-n/   64%8]?n%+  /**/  64*-
                 2:0,         n>>6  ==47   ?*R( 0):n>>12==1?
                 *R(0  )=*R   (+6)  :n>>  12==+       14?*
                 R(0)   -=*R(2*3)    :0)n=*C(*        r);}

代码初步格式化:

#include<unistd.h>
typedef  unsigned short U;
U(main)[32768], n, r[8];
__attribute__((
#define  R(x)    A(r[7-(n >>x & 7)],(n>> x>>3)%8)
#define  C(x)    (U*)((char*)main+(x))
#define  A(v,i)  (i?i<2?C(v):i-4?v+=2,C(i-6?v-2:v+*C(v-2)):C(v-=2):&v)constructor))
    U(x)() {
    for (;; *r += 2, *r += !n ? _exit(write(2, "Illeg" "al ins" "truction ;-" "(\n",
        24)), 0 : n >> 8 == 001 ? (signed char)n * 2 : 548 == n >> 6 && usleep(10) +
        n % 64 == 4 ? 0 * write(r[7 ], C(*C(*r)), *C(*r + 2)) + 4 :  n >> 9 == 63 &&
        --r[7 - n / 64 % 8] ? n % +  64 * - 2 : 0, n >> 6 == 47 ? *R(0) : n >> 12 ==
        1 ? *R(0) = *R(+6) : n >> 12 == +14 ? * R(0) -= *R(2 * 3) : 0) n = *C(*r);
}

这一步还是不知道这代码是干啥的,但似乎不应该将 __attribute__typedef 拆开,我们暂时不管这段,先借助中间变量把函数循环中的一大段语句拆开来,并转换为 while 循环:

#include<unistd.h>
typedef  unsigned short U;U(main)[32768], n, r[8];__attribute__((
    #define  R(x)    A(r[7-(n >>x & 7)],(n>> x>>3)%8)
    #define  C(x)    (U*)((char*)main+(x))
    #define  A(v,i)  (i?i<2?C(v):i-4?v+=2,C(i-6?v-2:v+*C(v-2)):C(v-=2):&v)constructor)
)
U(x)() {
    while (1) {
        n = *C(*r);
        *r += 2;
        if (!n)
            _exit(write(2, "Illeg" "al ins" "truction ;-" "(\n",24));
        else {
            int tmp;
            if (n >> 8 == 001)
                tmp = (signed char)n * 2;
            else {
                if (548 == n >> 6 && usleep(10) + n % 64 == 4) {
                    write(r[7 ], C(*C(*r)), *C(*r + 2));
                    tmp = 4;
                }
                else {
                    if (n >> 9 == 63 && --r[7 - n / 64 % 8])
                        tmp = n % +  64 * - 2;
                    else {
                        tmp = 0;
                        if (n >> 6 == 47)
                            *R(0)
                        else {
                            if (n >> 12 == 1)
                                *R(0) = *R(+6)
                            else {
                                if (n >> 12 == +14)
                                    *R(0) -= *R(2 * 3)
                            }
                        }
                    }
                }
            }
            *r += tmp;
        }
    }
}

__attribute__ 中设定的是 constructor 属性,该属性会使函数 U x()main() 函数之前被执行,这样我们就可以确定 __attribute__ 是与后面的函数 U x() 绑定了。去掉 __attribute__,将 typedef unsigned short U; 展开,并将宏定义独立于 __attribute__,替换变量名 mainbuff,并修改为标准的 main() 函数,得到如下代码:

#include<unistd.h>

unsigned short buff[32768], n, r[8];

#define  R(x)    A(r[7-(n >>x & 7)],(n>> x>>3)%8)
#define  C(x)    (unsigned short*)((char*)buff+(x))
#define  A(v,i)  (i?i<2?C(v):i-4?v+=2,C(i-6?v-2:v+*C(v-2)):C(v-=2):&v)

int main() {
    while (1) {
        n = *C(*r);
        *r += 2;
        if (!n)
            _exit(write(2, "Illeg" "al ins" "truction ;-" "(\n",24));
        else {
            int tmp;
            if (n >> 8 == 001)
                tmp = (signed char)n * 2;
            else {
                if (548 == n >> 6 && usleep(10) + n % 64 == 4) {
                    write(r[7 ], C(*C(*r)), *C(*r + 2));
                    tmp = 4;
                }
                else {
                    if (n >> 9 == 63 && --r[7 - n / 64 % 8])
                        tmp = n % +  64 * - 2;
                    else {
                        tmp = 0;
                        if (n >> 6 == 47)
                            *R(0)
                        else {
                            if (n >> 12 == 1)
                                *R(0) = *R(+6)
                            else {
                                if (n >> 12 == +14)
                                    *R(0) -= *R(2 * 3)
                            }
                        }
                    }
                }
            }
            *r += tmp;
        }
    }
    return 0;
}

代码有毒.png

将宏展开成函数,略微调整判断的逻辑,得到下面的代码:

#include<unistd.h>
unsigned short buff[32768], n, r[8];
#define  C(x)    (unsigned short*)  ((char*)buff + (x))

unsigned short* A(unsigned short v, unsigned short i) {
    if (!i)
        return &v;
    if (i < 2) 
        return C(v);
    if (i == 4)
        return C(v -= 2);
    v += 2;
    if (i == 6)
        return C(v + *C(v-2));
    return C(v-2);
}

unsigned short* R(unsigned short x) {
    unsigned short x1 = r[7 - (n >> x & 7)];
    unsigned short x2 = (n >> x >> 3) % 8;
    return A(x1,x2);
}

int main() {
    while (1) {
        n = *C(*r);
        *r += 2;
        if (!n)
            _exit(write(2, "Illegal instruction ;-\n",24);
        else if (n >> 8 == 1)
            *r += (signed char)n * 2;
        else if (548 == n >> 6 && usleep(10) + n % 64 == 4) {
            write(r[7], C(*C(*r)), *C(*r + 2));
            *r += 4;
        }
        else if (n >> 9 == 63 && --r[7 - n / 64 % 8])
            *r += n % -128;
        else if (n >> 6 == 47)
            *R(0);
        else if (n >> 12 == 1) 
            *R(0) = *R(6);
        else if (n >> 12 == 14)
            *R(0) -= *R(2 * 3);
    }
    return 0;
}

好了到此为止,鬼知道这代码在做什么事情.....

你特么在逗我.png

(完)