不知道為什麼,這周感覺過得特別漫長 …。
音樂

這禮拜大部分聽的都是舊專輯,也沒有挖掘到想讓我大書特書的新專輯,就不寫心得了。
Hack The Box
這禮拜完成了 Footprinting 模組。基本上就是針對企業內部常見服務的枚舉與嗅探大全。涵蓋了 NFS, FTP, SMB, DNS, IMAP, SNMP 等等。學會了如何掃描這些服務、尋找設定錯誤的權限,或是預設憑證來當作突破口。這部分比較偏向體力活和記憶,就是把工具指令記熟。
Quine
我還記得大二下 OOP 教授說過他以前做過 Quine(也鼓勵我們自己做一個),當時讀 Ken Thompson 的經典論文 Reflections on Trusting Trust 時,也大概理解 Quine 的邏輯,但一直沒親手實作過,直到這禮拜 Tsoding 出這隻影片 Self-Reproducing Programs。
其實他的思路非常簡單,就是把程式分成「框架」和「數據」兩部分,重點在於如何用一個字串來表示程式自身,並將其格式化輸出。
char *self = "...";
int main() {
print("%s", self);
}最後就可以變成這樣:
#include <stdio.h>
#include <string.h>
int main() {
// 利用 `~` 當作佔位符表示自己(`self`)
// 推薦用魔法來複製和編輯程式骨架
// Emacs & Vim: Could your VSCode do this?
char *self = "#include <stdio.h>\n#include <string.h>\n\nint main() {\n char *self = \"~\";\n int n = strlen(self);\n\n for (int i = 0; i < n; i++) {\n if (self[i] == 126) {\n for (int j = 0; j < n; j++) {\n switch (self[j]) {\n case '\\n':\n printf(\"\\\\n\");\n break;\n case '\"':\n printf(\"\\\\\\\"\");\n break;\n case '\\\\':\n printf(\"\\\\\\\\\");\n break;\n default:\n printf(\"%c\", self[j]);\n }\n }\n } else {\n printf(\"%c\", self[i]);\n }\n }\n}\n";
int n = strlen(self);
// 透過遍歷 `self` 輸出 `main.c` 的程式框架
for (int i = 0; i < n; i++) {
// 當遇到 `~` 時,代表我們要一五一十的把整個字串輸出出來
if (self[i] == 126) {
for (int j = 0; j < n; j++) {
switch (self[j]) {
// 這是最簡單的情況,遇到 \n 不能換行,而是要輸出 \n,為了輸出 \n 就必須用到逃脫字元 \
case '\n':
printf("\\n");
break;
// 這很酷。在上面 `self` 時,我們如果要表示 ",就必須用逃脫字元 \。而在這裡我們為了輸出 \ 和 ",我們必須個別為他們添上逃脫字元,也就變成了 \\\"
case '"':
printf("\\\"");
break;
// 同理!
case '\\':
printf("\\\\");
break;
default:
printf("%c", self[j]);
}
}
} else {
printf("%c", self[i]);
}
}
}超酷的!還有其他的一些東東:
-
Wiki - Bloo:一個收藏各種語言的各種版本 Quine 實做的網站。
-
Github - Quine-Relay
一個 Ruby 程式,生成 Rust,再生成 Scala… 經歷 128 種語言後再變回原本的 Ruby 程式碼。藝術品 …
Tsoding
When I say that the programming language doesn’t matter, I hope you know where I’m coming from.
編譯器
這禮拜也看了些編譯器的東西。
- Github - Tiny-Compiler & Blog - TinyCompiler: a compiler in a week-end:週末手刻編譯器挑戰。
- Github - Chibicc:Rui Ueyama 的作品,透過精細的 git commit 歷史來教學,非常推薦。
- Gemini - 深入剖析 C99 編譯器架構與實作原理:我用 Gemini Deep Research 聊天了解我對編譯器的一些疑問。
總之我現在大概知道了編譯器的架構,分成前端、中端、後端。我原本對編譯器的理解只有停在詞法分析、語法分析、語義檢查的前端部份,中端和後端是最近才知道的。不過就我觀察,大部分自幹的編譯器,好像也都只專住在前端而已。
不需要分號的 C 編譯器
我好像變成 Tsoding 的 “Fanboy” 了 …。其實原理意外簡單,tinycc 的 Parser 在分析完一段 Statement 後會預期並檢查是否有分號,我們只要把那個「強制檢查」改成「可選」甚至是「略過」就好。
#include <stdio.h>
int main(){
printf("Hello World\n")
printf("Hello World\n")
printf("Hello World\n")
printf("Hello World\n")
printf("Hello World\n")
return 0
}$ ./tcc no-semi.c
no-semi.c:5: error: ';' expected (got 'printf')尋找 expected (got 是在哪裡報錯的。
ST_FUNC void skip(int c)
{
if (tok != c) {
char tmp[40];
pstrcpy(tmp, sizeof tmp, get_tok_str(c, &tokc));
tcc_error("'%s' expected (got '%s')", tmp, get_tok_str(tok, &tokc));
}
next();
}所以我們需要找到哪裡呼叫了 skip(';')。透過 grep 可以發現它藏在 block 函數處理 Expression 的地方。
static void block(int flags)
{
...
/* expression case */
if (t != ';') {
unget_tok(t);
expr:
if (flags & STMT_EXPR) {
vpop();
gexpr();
} else {
gexpr();
vpop();
}
skip(';');
}
...
}這就是處理 Expression 的地方,把它邏輯改成「如果遇到 ; 再跳過」就可以了。
if (tok == ';') skip(';');對於 return 也是如此。
...
} else if (t == TOK_RETURN) {
...
if (b)
gfunc_return(&func_vt);
if (tok == ';') skip(';');
/* jump unless last stmt in top-level block */
...
} else if (t == TOK_BREAK) {
...$ ./tcc no-semi.c
$ ./a.out
Hello World
Hello World
Hello World
Hello World
Hello World看!多奇妙啊!
寒假
寒假已經過了 5/9 個禮拜了天啊天啊天啊啊啊啊啊啊啊啊啊。
- 預習編譯器、資料庫的課程,主要是把書粗略看一遍就差不多了
- 剪輯演算法的影片,主要是動態規劃、貪婪演算法的部份(現在決定睡前剪)
- 狂幹 HackTheBox Academy,當然希望可以直接把 CPTS Path 的所有 Modules 都看完
- 制定專題《基於多維度漏洞挖掘之 IoT 設備安全審計與更新機制研究》的總體大綱