有些人不走寻常路
大家周末好,又到了周末時間,給分享一些輕松有趣的內容,希望大家喜歡。
去年鵝廠內部極客圈舉辦了第二次極客大賽,題目如下:
"實現一個世界上最小的程序來輸出自身的MD5"
作為極客圈一員的我也參加了比賽,比賽競爭激烈,為了爭奪一個字節的優勢,大家都拿出自己的絕活,通過參加比賽,學到很多知識,重新刷新了我底層知識的理解,計算機原理的理解:
題目介紹和常規解法:最小MD5挑戰賽,你敢來戰嗎?
大神解法: 頂級極客技術挑戰賽,你敢來挑戰嗎?| 大神登峰造極
接下來,給大家分享一些不走尋常路的野路子方案,歡迎大家點評討論,腦洞大開。
野路子解法1-md5碰撞原理及實現
人間正道是滄桑
一. 原理
已知A, 可以生成相同長度, 內容不同的X和Y, 使得md5(A+X)=md5(A+Y)
已知A, B, X, Y, md5(A)=md5(B), md5(A+X)=md5(A+Y), 則md5(A+X)=md5(B+X)=md5(A+Y)=md5(B+Y)
二. 構造步驟
通過工具
(http://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5_source.zip),?
可以構造出上面的X和Y:
$ echo Hello > A $ echo World > B$ ./fastcoll -p A -o AX1 AY1 MD5 collision generator v1.5 by Marc Stevens (http://www.win.tue.nl/hashclash/) Using output filenames: 'AX1' and 'AY1' Using prefixfile: 'A' Using initial value: 7be4ac1acaf95d7b8f73709af7db5c10 Generating first block: .. Generating second block: S01...... Running time: 4.53125 s$ cat AX1 | md5 dbbd16368b2eb0f6596004ace403e012 $ cat AY1 | md5 dbbd16368b2eb0f6596004ace403e012$ cat AX1 B | md5sum 8b6ddb8f03cd3f7fe63f288f6688018a $ cat AY1 B | md5sum 8b6ddb8f03cd3f7fe63f288f6688018a這樣我們就有了指定1個bit的能力 (同時文件也會增大128字節).
循環執行128次:
每次生成的2個文件, 取末尾128字節, 得到X[i]和Y[i];
然后就可以根據md5反推出Xi/Yi該選哪個了
三. Show Me The Code
test.c:
先編譯test.c得到test, 再想辦法填充里面的md5coll, chkpos, chkval, 就可以達到反推md5的效果
講解一下算法:
整個程序分為5部分:
elf頭等
reserve數組, 用來64字節對齊
md5coll數組, 長度128*128, 用來放上面算出來的X[i]/Y[i]
chkpos/chkval數組, 用來查md5coll, 反推出自身md5輸出
main函數及其它
階段一:
編譯確定1 2 5
階段二:
跑128次fastcoll, 對比X[i]和Y[i]的差異, 可以構造出Y[i][chkpos[i]] == chkval[i], 從而算出chkpos和chkval
階段三:
將X[i]填充到md5coll數組, 至此可執行程序的md5就不會變了
根據md5的二進制位, 填充對應的Xi或者Yi
這樣就可以構造出能算出自身md5的程序了。
野路子解法2-撿"漏"
始從正道,因久攻不克,遂智取
邪念初生
最初是題目描述中的一個細節提醒了我:
這里禁用socket,想必是為了防止對外通信,反過來說,如果能突破通信的限制,確實就可以不用自己辛苦計算結果了,通信的另一方直接把結果塞給它就好了。
投石問路
要想對外通信,不管用何種通信方式,都不免要通過一些系統調用的,所以先用一些不太常用的系統調用試探一波,看有沒有漏網的系統調用:
int main() {int sv[2];int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);if (ret != 0){*(int *)0 = 0;}printf("xxx\n");return 0; }這里有一點要說明一下,就是每次提交結果,如果不通過,系統會有比較詳細的反饋(大概有使用了禁止的系統調用、crash,結果錯誤、返回值不為0這幾個),這個有助于我們試探信息。注意到上面代碼第6行,這里會故意crash,以此區分socketpair在沒有被禁止的前提下,這個調用究竟有沒有成功。
上面程序的提交結果是Wrong Answer,表明這個調用成功了,證明確實有些系統調用沒有被禁止,此事可搞。
首戰不利
最初想到的是利用共享內存來傳遞信息:
準備兩個可執行程序A和B
先提交A,A的功能是把MD5(B)寫到共享內存
接著再提交B,B從共享內存讀取A留下的內容并輸出即可達到目的。
以下是A的代碼:
int main() {int id = shmget(0x32147658, 1024, IPC_CREAT | 0666);if (id == -1) *(int *)0 = 0;char *data = (char *)shmat(id, 0, 0);if (!data) return 2;memcpy(data, "9afd0fbf6a61da4d64e92095476716ec", 32);//這串即是B的md5int ret = shmdt(data);if (ret == -1)return 3;return 0; }然后是B的:
int main() {int id = shmget(0x32147658, 1024, IPC_EXCL);char *data = (char *)shmat(id, 0, 0);write(1, data, 32);_exit(0); }由于B做的事情及其簡單,所以體積很容易做得很小。
然而,想法是很美好,提交A的時候卻得到 ”非法系統調用“。很簡單,共享內存相關的系統調用被禁了,浪費了上面十幾行代碼。
再戰又不利
接著又想到利用文件來傳遞信息:
還是兩個可執行程序A和B
A把MD5(B)寫到一個文件
B讀出來輸出
想得依然很美,但試遍系統中所有可能成功的地方(/tmp /run /var /dev等),都無法找到一個可以創建文件的目錄,或者一個可讀可寫的文件,此路依然不通。
三戰還不利
這時還沒有放棄文件的思路,而且想了個比較粗暴的招(也是實在沒辦法了):
還是倒霉的A和同樣倒霉的B
A遍歷整個文件系統,找出可以創建文件的地方創建文件,或者找到一個可讀可寫的文件,寫入內容
B以同樣的方式遍歷文件系統,必能找到A之前留下的內容,讀之并輸出
試了一下提交A居然成功了,但提交B的時候顯示Wrong Answer。此時懷疑是踩到了/dev下的某些設備文件,它們是可讀可寫的,但語義不是普通文件的語義,讀出來的和寫入的內容不一樣,典型的如/dev/uramdom,寫入的語義是給系統的隨機數生成器貢獻隨機事件,而讀取則是獲取隨機數。
有錯就改,在A中添加校驗信息:
A先寫一個固定串TEST,再寫如MD5(B)
B在讀取的時候先看能不能讀到TEST,如果能的話,則繼續讀取后面的內容
加上校驗之后,A可以成功找到通過校驗的文件,而B則始終無法讀到校驗信息TEST。。。
這里終于開始懷疑判分系統除了禁用系統調用,還可能做了更深的隔離,并不會在同一個環境里測試不同的可執行文件,路路不通,一度打算放棄。
山窮水盡
放棄之前,照例是要再掙扎一番的,文件不行,再看看有沒有其它途徑,然后又試了另一種進程間通信方式fifo(命名管道),mknod(創建fifo的系統調用)系統調用確實沒有被禁,但fifo是存在于文件系統中的,無法創建文件,也就無法創建fifo,此法不靠譜,越想越絕望。
峰回路轉
此時大概只剩最后一種進程間通信方式了:消息隊列。死馬當活馬醫吧,反正這條路也快到頭了,不管結果怎么樣,能解脫是肯定的,直接上代碼,A的:
int main(int argc, char **argv) {int msqid;int key=0xDEADDEAD;msqid = msgget(key, 0600 | IPC_CREAT);char buf[TEXT_SIZE];strcpy(buf, "eb861d89e1c8e775c39dab7878216093");int flag = msgsnd(msqid, buf, TEXT_SIZE, 0);if (flag < 0){perror("send messag eerror");return -1;}return 0; }B的:
int main(int argc, char **argv) {int msqid;int key=0xDEADDEAD;msqid = msgget(key, 0600);char buf[TEXT_SIZE];int flag = msgrcv(msqid, buf, TEXT_SIZE, 0, 0);write(1, buf, TEXT_SIZE);_exit(0); }苦心人,天不負,就這樣,成功了,,,也就是說,系統禁了很多系統調用,唯獨忽略了消息隊列。
上面的這個程序,稍加裁剪便達到了310字節,成功到頂一游。
查漏補缺
印象中Linux的進程間通信方式普遍有多個版本,除了上面的消息隊列,會不會還有漏網之魚呢,再次翻閱系統調用表,果然發現了另一個版本的消息隊列:
mq_open
mq_timedreceive
mq_timedsend
這個應該也是可以利用的,不過沒有嘗試,連同上面的msgget一起,找組委會自首,請求原諒。。
看完記得一鍵三連在看,轉發,點贊
是對文章最大的贊賞,感謝
推薦閱讀
頂級極客技術挑戰賽,你敢來挑戰嗎?| 大神登峰造極
Linux Kernel TCP/IP Stack|Linux網絡硬核系列
頂級C程序員之路
總結
- 上一篇: 一篇漫画,看懂云计算!
- 下一篇: 高考结束了