Linux 允许一个 动态链接库 (linux 上标准的叫法是 共享库)能被先加载到目标进程当中 提供了这个功能 就是 LD preload 通过设置 LD_PRELOAD 变量的值为一个共享库的地址,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。 然后启动的程序就会先加载这个dll 这样就运行我们来 hack 目标进程
先看看目标进程当中那些函数还没有定义
# nm -u a.out w _Jv_RegisterClasses w __gmon_start__ U __libc_start_main@@GLIBC_2.0 U strlen@@GLIBC_2.0 U write@@GLIBC_2.0
没有定义的函数 会在使用到的时候解析到正确的地址上 比如上面的 weite 函数 ,进程会加载 libc 这个共享库并得到正确的 write 函数的地址 但是我们使用LD_PRELOAD 指定的so 比 libc 还早加载 那么程序就会把 write 函数的地址解析到我们的 so 里面 ,如果我们的 so 也导出同样名字的函数 相当于劫持了。
下面写个程序看看
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <string.h> #include <dlfcn.h> char *msg = "I am sincoder\n"; static ssize_t (*pf_org_write)(int , const void *, size_t ) = NULL; static void init(void) { pf_org_write = dlsym(RTLD_NEXT, "write"); printf("get write addr : %x \n", (unsigned int)pf_org_write ); } ssize_t write(int fd, const void *buf, size_t count) { if (NULL == pf_org_write) init(); if (STDOUT_FILENO == fd || STDERR_FILENO == fd) { return pf_org_write(STDOUT_FILENO, msg, strlen(msg)); } return pf_org_write(fd, buf, count); }
其中要注意的就是 得到正确的 系统调用 地址的代码 dlsym(RTLD_NEXT, "write");
指定 RTLD_NEXT 是为了 让加载器不把这个函数解析到我们自己下面定义的 write 而是 解析到下一个 so 中的函数 下一个肯定是 libc 了。。。于是就获得了正确的地址 也就是 write 实际的地址。
测试程序:
#define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <string.h> #include <dlfcn.h> char *msg = "hello world"; int main() { return write(STDOUT_FILENO,msg,strlen(msg)); }
编译:
gcc test.c -fpic -ldl -shared -o test.so
gcc write.c
然后 export LD_PRELOAD=/root/test.so
执行下程序看看
# ./a.out
get write addr : 477ab7f0
I am sincoder
可见顺利的替换了 write
还可以在 /etc/ld.so.preload 中写入 so 地址 这样系统启动的时候就生效 而且对所有的用户
使用限制
这种方式虽然很酷,但却有一些限制。比如对于静态编译的程序是无效的。因为静态编译的程序不需要连接动态库的面的函数。而且,假如文件的SUID或SGID位被置1,加载的时候会忽略LD_PRELOAD(这是ld的开发者出于安全考虑做的)。
相关的应用--Jynx-Kit
标签:none