最近CoolShell博主做了一個很有意思的在線puzzle,這些謎題很有趣同時也有一定的難度。由於水平有限,我並沒有通關,我覺得這些題還是很值得一做的,從中可以學到很多東西。
例如其中的第二題:
題目中給出了一個鍵盤和一行看不懂的字符串。我們發現這個鍵盤的鍵盤布局和現在通用的鍵盤(QWERTY鍵盤)不一樣,它叫做Dvorak鍵盤。這里就不多作解釋了,詳細的可以去Google。鍵盤圖片明顯在提示我們:要通過兩種鍵盤的布局映射,將給出的字符串轉換成QWERTY鍵盤下的輸出。當然,你可以自己一對一寫出來,不過在線轉換工具更方便。
macb() ? lpcbyu(&gbcq/_\021%ocq\012\0_=w(gbcq)/_dak._=}_ugb_[0q60)s+轉換之后得到:
main() { printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60);}這是1987年 國際C語言混亂代碼大賽(The International Obfuscated C Code Contest, IOCCC)一等獎的獲獎代碼,由貝爾實驗室的David Korn提交。當然平時我們不會寫出這么復雜難懂的代碼,但是分析這樣的代碼卻可以擴展我們的知識。
int main() { /* unix被編譯器內定為一個宏 * 相當於#define unix 1 */ printf("unix=%d\n", unix); /* =1 */ /* 打印字符串"un",因為"fun"是個字符數組 * "fun"+1相當於字符指針右移,指向"un" */ printf("%s\n","fun"+1); /* "have"是個字符數組,"have"[1]即字符a * 輸出97,即第二個字符'a'的ASCII值。*/ printf("%d\n", "have"[1]); printf("%d\n", 'a'); /* 在C語言中,x[1] = 1[x] */ printf("%d\n", (1)["have"]); /* 97 - 96 = 0x61 - 0x60 = 1 */ printf("%d\n", (1)["have"] - 0x60); /* 所以 "fun"+((1)["have"]-0x60) 相當於"fun"+1,輸出"un" */ printf("%s\n", "fun" + ((1)["have"] - 0x60)); /* 將其中的1用unix代替 */ printf("%s\n", (unix)["have"]+"fun"-0x60); /* 以上為后半部分 = "un" */ /* 下面兩個都輸出"bcde", 因為指針都是從'b'開始 */ printf("%s\n", "abcde" + 1); printf("%s\n", &"abcde"[1]); /* &"abcde"[1] == &(1)["abcde"] 輸出一樣 */ printf("%s\n", &(1)["abcde"]); /* 1用unix代替 */ printf("%s\n", &unix["abcde"]); /* 下面輸出"%six" 並換行 */ printf("%s", &"?%six\n"[1]); /* 注意: \012 = 0x0a = \n, 第一個字符 \021 被跳過 \0 是空字符 */ /* 同樣輸出"%six" 並換行 */ printf("%s", &"\021%six\012\0"[1]); /* 相當於這樣 */ printf("%s", &unix["\021%six\012\0"]); /* 把字符串"%six\n"當作格式,輸出"ABix" */ printf(&unix["\021%six\012\0"], "AB"); /* 相當於這樣 */ printf("%six\n", "AB"); /* 所以下面的可以輸出"unix" */ printf("%six\n", (unix)["have"]+"fun"-0x60); /* 至此,問題解決!!!輸出"unix" */ printf(&unix["\021%six\012\0"],(unix)["have"]+"fun"-0x60); return 0; }
這段代碼主要用到了x[a]和指針運算的一些知識,相信上面的步驟和注釋已經很清楚了,最終結果就是輸出unix
。