前陣子某 geek 朋友過生日,所以想用程式給他一個小驚喜。原本打算送給他一個假的 patch,讓他在編譯時收到訊息,不過弄半天還是沒搞懂 #pragma 怎麼用,後來改用程式碼混淆的方式製作卡片。
步驟大概如下:
- 做卡片
- 轉成程式碼
- 製造大亂
卡片就是 ASCII art 畫一下,看得出來是生日祝賀就好 XD
畫好後,找個方法轉成 C 語言。看了看內容,發現全部都是由「-」、「/」等幾個字元組成,所以要用程式畫出來只要控制要印出的字元以及迴圈次數即可。這時先假設有個函式叫做 pt() ,傳入二個參數,一個是使用的字元,一個是重複次數:
void pt( int ascii, int count ){
int i;
for( i = 0; i < count; i++ ){
printf("%c", ascii);
}
}
按照順序計算字元重複次數以後,就可以把卡片轉換成程式:
#include <stdio.h>
void pt(int ascii, int count){
if( count > 0 ){
printf("%c", ascii);
pt( ascii, count-1 );
}
}
int main(){
pt(10, 1); // n
pt(32, 23); // sapce
pt(47, 1); // "/"
pt(92, 1); // ""
pt(32, 23);
pt(47, 1);
.....
return 0;
}
接下來開始製造大亂。
平常都在找讓程式碼可讀性增加、降低維護成本的方法,反而要倒過來就不太會了,好在之前 CoolShell.cn 上看到一篇文章「如何写出无法维护的代码」,有個方法可以參考。
先來處裡比較簡單的,程式碼中數字只要執行時期表達方式在執行時期的結果相同即可,所以可以把 pt() 參數的數字改成 16 進位,或是用運算式改寫。
pt(0xA, 10+8-17); // n
main() 中 pt() 重複次數太高,即使取代名稱以後看起來還是井然有序。所以複製出多個功能完全一模一樣的函式,並且建立另一個函式專門呼叫 printf。
void pt( int ascii, int count ){
if( count > 0 ){
printChar( ascii );
pt( ascii, count-1 ); // Can call pt2() and other, too
}
} ;
void pt2( int ascii, int count ){ ... } ;
void pt3( int ascii, int count ){ ... } ;
void pt4( int ascii, int count ){ ... } ;
void printChar( int ascii ){
printf("%c", ascii );
}
隨機把 main() 中的函式呼叫名稱換掉以後就差不多夠亂了,最後把變數名稱、函式名稱隨便換一下就大功告成了。
後來看了 IOCCC 和 Just Another Perl Hacker 後,覺得實在還太嫩,以後誰生日再來找其他方法惡搞 XD
後記:
gcc -S 參數可以將 C 語言轉成組合語言:
gcc -S -o code.asm source.c
Javascript 有現成的工具可以玩:aaencode!