Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2034397
  • 博文数量: 369
  • 博客积分: 10093
  • 博客等级: 上将
  • 技术积分: 4271
  • 用 户 组: 普通用户
  • 注册时间: 2005-03-21 00:59
文章分类

全部博文(369)

文章存档

2013年(1)

2011年(2)

2010年(10)

2009年(16)

2008年(33)

2007年(146)

2006年(160)

2005年(1)

分类: LINUX

2006-07-03 22:53:46

好些天没有写技术文章了,心情日记已经占了一半的篇幅,大学生活给人留下的记忆还是蛮好的,尤其是临近毕业这段时间!
切入正题吧!:)
前面我谈到了利用LKM机制劫持系统调用的方法,其实,在用户空间,可以借助动态链接库直接劫持系统库,从而在一定程度上达到和劫持系统调用一样的目的。动态链接库相对于静态链接库,顾名思义就是链接动作是在程序加载的时候进行的。内核加载可执行文件时,如果发现此文件动态链接到另外的文件,那么系统将首先调用ld-xx.so(或者是ld-linux.so.x)动态执行链接的动作,也就是找到动态库并将其映射到内存,最后将可执行文件中的符号地址替换成动态链接库的地址,执行可执行文件。不难发现,动态链接程序(ld-xx.so)执行的就是动态地址解析的任务。如果我们通过某种方式,用自己的函数覆盖系统库提供的特定函数,我们也就截获了这个系统库。Linux的动态链接程序通过读取环境变量LD_PRELOAD设置的动态链接库预加载其中的库,从而可以覆盖原有的系统库函数,原有的库函数仍然可以通过函数dlsym()来取得,不过此时第一个参数须为RTLD_NEXT表示在以后加载的库中查找由第二个参数指定的符号。
下面我们将通过一个简单的例子来实际接触一下此种劫持办法:
#include
#include
#define __USE_GNU
#include
#undef __USE_GNU
#include

/* init functions. */
void _init(void);

/* export functions. */
pid_t fork(void);

/* old functions. */
static pid_t (*old_fork)(void);

void _init(void)
{
        printf("libtest starting ...\n");
        old_fork = dlsym(RTLD_NEXT, "fork");
        if(!old_fork){
                perror("can't find routine fork.\n");
                return;
        }
        printf("libtest is initialed.\n");
}

pid_t fork(void)
{
        printf("calling fork ...\n");

        return old_fork();
}
代码比较简单,只有_init函数需要解释一下,当一个库函数加载时,如果其中有void _init(void)函数,那么这个函数将被首先执行,本例子中用它来找到真正的fork函数。
编译方法如下:
$ gcc -c test.c -fPIC
$ ld -o libtest.so -shared -lc -ldl test.o
将新生成的动态链接库libtest.so加入到LD_PRELOAD环境变量中:
$ export LD_PRELOAD="./libtest.so"
之后你将发现你所执行的所有外部命令,都将初试化动态链接库libtest.so:
$ ls
libtest starting ...
libtest is initialed.
写个简单的测试程序测试一下:
#include
#include

int main(void)
{
        int retval;

        retval = fork();
        if(retval == 0){
                printf("I am child.\n");
        }else if(retval > 0){
                printf("I am parent.\n");
        }else{
                perror("fork");
        }

        return retval;
}
执行结果如下:
 $ ./a.out
libtest starting ...
libtest is initialed.
calling fork ...
I am child.
I am parent.
可以看到fork()已被成功截获了。
不过不要欣喜得过早,看看对于setuid的程序是否也能劫持呢?
$ ls -l /bin/passwd
libtest starting ...
libtest is initialed.
-rws--x--x 1 root root 29384 06-17 08:22 /bin/passwd
$ /bin/passwd
Changing password for xiaosuo
(current) UNIX password:
passwd:Authentication token manipulation error
很让人失望不是,libtest.so根本没有进行初始化,原来人们早就意识到这个可能存在的系统漏洞,强制所有setuid的程序对环境变量LD_PRELOAD的值不敏感,看来我们还是迟到了一步,不过也应该庆幸我们的系统很安全。glibc中的相关注释如下:
File: rtld.c
1617   /* We have two ways to specify objects to preload: via environment
1618      variable and via the file /etc/ld.so.preload.  The latter can also
1619      be used when security is enabled.  */
1620   assert (*first_preload == NULL);
1621   struct link_map **preloads = NULL;
1622   unsigned int npreloads = 0;
1623
1624   if (__builtin_expect (preloadlist != NULL, 0))
1625     {
1626       /* The LD_PRELOAD environment variable gives list of libraries
1627          separated by white space or colons that are loaded before the
1628          executable's dependencies and prepended to the global scope
1629          list.  If the binary is running setuid all elements
1630          containing a '/' are ignored since it is insecure.  */
1631       char *list = strdupa (preloadlist);
1632       char *p;
结论:
Linux的动态链接程序通过环境变量LD_PRELOAD使得劫持系统库成为了可能,极大的增强了系统的灵活性,不过凡事都有其两面性,希望它没有给我们留下更多漏洞。
阅读(1322) | 评论(0) | 转发(0) |
0

上一篇:毕业了!

下一篇:读写锁的一种实现

给主人留下些什么吧!~~