web-dev-qa-db-ja.com

難読化された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]
944
corny

難読化を解除しましょう。

字下げ

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
    }
}

2の補数のため、-~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));
        }
    }
}

これは反復ごとに1文字を出力します。 64文字ごとに改行を出力します。それ以外の場合は、何を出力するかを判断するために一対のデータテーブルを使用し、文字32(スペース)または文字33(!)のいずれかを配置します。最初のテーブル(">'txiZ^(~z?")は各文字の外観を説明する10個のビットマップのセットで、2番目のテーブル(";;;====~$::199")はビットマップから表示する適切なビットを選択します。

2番目のテーブル

2番目のテーブルint shift = ";;;====~$::199"[(i*2&8) | (i/64)];を調べることから始めましょう。 iが4、5、6、または7 mod 8の場合、i/64は行番号(6から0)で、i*2&8は8です。

if((i & 2) == 0) shift /= 8; shift = shift % 8は、テーブル値の上位8進数字(i%8 = 0,1,4,5の場合)または下位8進数字(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

作成者が最初の2つのテーブルエントリにnullターミネータを使用したことに注意してください(卑劣!)。

これは7sをブランクとして、7セグメント表示の後に設計されています。そのため、最初のテーブルのエントリは、点灯するセグメントを定義する必要があります。

最初のテーブル

__TIME__ は、プリプロセッサによって定義された特別なマクロです。これは、プリプロセッサが実行された時刻を含む文字列定数に展開されます。形式は"HH:MM:SS"です。正確に8文字が含まれていることを確認してください。 0から9のASCIIの値は48から57、:の値はASCIIの値58です。出力は1行あたり64文字なので、1文字あたり8文字の__TIME__が残ります。

したがって7 - i/8%8は現在出力されている__TIME__のインデックスです(iを下方に繰り返しているので7-が必要です)。したがって、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セグメント表示で点灯しているセグメントを表します。文字はすべて7ビットASCIIなので、上位ビットは常にクリアされます。したがって、セグメントテーブルの7は常に空白として印刷されます。 2番目のテーブルは、7sをブランクにしたものです。

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

我々が得る

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

予想通り。原作ほど堅実に見えるわけではないので、作者が自分のした表を使用することを選択した理由を説明します。

1793
nneonneo

読みやすくするためにこれをフォーマットしましょう。

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

現在、ASCIIスペースの32は10進数です。スペースまたは '!'を印刷します。 (33は '!'なので最後の '&1'です)真ん中のブロブに注目しましょう。

-(~(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文字のうちの1つを選択します。これはそれから[">'txiZ^(~z?"-48]にインデックスされます(0-9文字は48-57 10進数です)。この文字列内の文字は、それらのASCII値に対して選択されている必要があります。これと同じ文字ASCIIコードの操作は式の中で続けられ、 ''または '!'のいずれかが出力されます。文字のグリフ内の位置によって異なります。

98
chmeee

他の解決策に加えて、-~xx+1と同等であるため、~x(0xffffffff-x)と同じです。これは2の補数の(-1-x)に等しいので、-~x-(-1-x) = x+1です。

47
Thomas Song

モジュロ演算をできる限り難読化解除し、再帰を削除しました

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