LD_PRELOAD只能拦截libc调用,而不能拦截系统调用?

4

我的代码和malloc一起很好用,但是和mmap不兼容。以下是代码:

main.c

#include <stdio.h>
#include <stdlib.h>

int main(){
  int * p = (int*) malloc(sizeof(int));
  printf("in main(): value p = %d\n", *p);
  free(p);
}

preload.c

#define _GNU_SOURCE
#include <time.h>
#include <dlfcn.h>
#include <stdio.h>
#include <sys/types.h>

void *(*orig_malloc)(size_t size);
void *malloc(size_t size){
  printf("  Hooked(preload)! malloc:size:%lu\n", size);
  return orig_malloc(size);
}

void * (*orig_mmap)(void *start, size_t length, int prot, int flags, int fd, off_t offset);
void * mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset){
  printf("  Hooked(preload)! mmap:start:%p, length:%lu, prot:%d, flags:%p, fd:%p, offset:%d\n", start, length, prot, flags, fd, offset);
  return orig_mmap(start, length, prot, flags, fd, offset);
}

void
_init(void)
{
  printf("Loading hack.\n");
  orig_malloc = (void* (*)(size_t)) dlsym(RTLD_NEXT, "malloc");
  orig_mmap = (void* (*)(void*, size_t, int, int, int, off_t)) dlsym(RTLD_NEXT, "mmap");
}

编译它

gcc -Wall -fPIC -DPIC -c preload.c
ld -shared -o preload.so preload.o -ldl
gcc main.c

使用LD_PRELOAD运行它。

LD_PRELOAD=./preload.so ./a.out

使用strace来运行它

strace ./a.out 2>&1 | view -
LD_PRELOAD 的输出并不拦截 mmap 的调用,只会拦截对 malloc 的调用。与此同时,使用 strace 运行时,输出显示 mmap 被多次调用。
这个结果让我感到困惑;假设 mmap 确实被 main.c 调用了(我猜是通过 malloc),那么为什么 preload.c 不能拦截 mmap 呢?
PS:我的平台是 Ubuntu 14.04,Linux 内核版本为 3.13。
PS2: "syscall" 意味着 libc 中的系统调用包装器(不确定这是否对问题有影响)。

对于那些想要在系统调用中注入故障或伪造成功的人,请查找strace -e inject选项。 - Hubert Kario
4个回答

7
strace 打印的 mmap 调用是 glibc 内部的调用。 使用 LD_PRELOAD 是无法拦截 glibc 对 mmap 的内部调用的,因为 mmap 不在 /lib64/libc.so.6.plt 段中,而是直接从 glibc 中调用。
$ objdump -j .plt -d /lib64/libc.so.6 

/lib64/libc.so.6:     file format elf64-x86-64


Disassembly of section .plt:

000000000001f400 <*ABS*+0x8e3fb@plt-0x10>:
   1f400:   ff 35 02 ac 39 00       pushq  0x39ac02(%rip)        # 3ba008 <_GLOBAL_OFFSET_TABLE_+0x8>
   1f406:   ff 25 04 ac 39 00       jmpq   *0x39ac04(%rip)        # 3ba010 <_GLOBAL_OFFSET_TABLE_+0x10>
   1f40c:   0f 1f 40 00             nopl   0x0(%rax)

000000000001f410 <*ABS*+0x8e3fb@plt>:
   1f410:   ff 25 02 ac 39 00       jmpq   *0x39ac02(%rip)        # 3ba018 <_GLOBAL_OFFSET_TABLE_+0x18>
   1f416:   68 0b 00 00 00          pushq  $0xb
   1f41b:   e9 e0 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f420 <*ABS*+0xb8c10@plt>:
   1f420:   ff 25 fa ab 39 00       jmpq   *0x39abfa(%rip)        # 3ba020 <_GLOBAL_OFFSET_TABLE_+0x20>
   1f426:   68 0a 00 00 00          pushq  $0xa
   1f42b:   e9 d0 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f430 <realloc@plt>:
   1f430:   ff 25 f2 ab 39 00       jmpq   *0x39abf2(%rip)        # 3ba028 <_GLOBAL_OFFSET_TABLE_+0x28>
   1f436:   68 00 00 00 00          pushq  $0x0
   1f43b:   e9 c0 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f440 <malloc@plt>:
   1f440:   ff 25 ea ab 39 00       jmpq   *0x39abea(%rip)        # 3ba030 <_GLOBAL_OFFSET_TABLE_+0x30>
   1f446:   68 01 00 00 00          pushq  $0x1
   1f44b:   e9 b0 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f450 <__tls_get_addr@plt>:
   1f450:   ff 25 e2 ab 39 00       jmpq   *0x39abe2(%rip)        # 3ba038 <_GLOBAL_OFFSET_TABLE_+0x38>
   1f456:   68 02 00 00 00          pushq  $0x2
   1f45b:   e9 a0 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f460 <memalign@plt>:
   1f460:   ff 25 da ab 39 00       jmpq   *0x39abda(%rip)        # 3ba040 <_GLOBAL_OFFSET_TABLE_+0x40>
   1f466:   68 03 00 00 00          pushq  $0x3
   1f46b:   e9 90 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f470 <*ABS*+0x90f60@plt>:
   1f470:   ff 25 d2 ab 39 00       jmpq   *0x39abd2(%rip)        # 3ba048 <_GLOBAL_OFFSET_TABLE_+0x48>
   1f476:   68 09 00 00 00          pushq  $0x9
   1f47b:   e9 80 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f480 <_dl_find_dso_for_object@plt>:
   1f480:   ff 25 ca ab 39 00       jmpq   *0x39abca(%rip)        # 3ba050 <_GLOBAL_OFFSET_TABLE_+0x50>
   1f486:   68 04 00 00 00          pushq  $0x4
   1f48b:   e9 70 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f490 <calloc@plt>:
   1f490:   ff 25 c2 ab 39 00       jmpq   *0x39abc2(%rip)        # 3ba058 <_GLOBAL_OFFSET_TABLE_+0x58>
   1f496:   68 05 00 00 00          pushq  $0x5
   1f49b:   e9 60 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f4a0 <free@plt>:
   1f4a0:   ff 25 ba ab 39 00       jmpq   *0x39abba(%rip)        # 3ba060 <_GLOBAL_OFFSET_TABLE_+0x60>
   1f4a6:   68 06 00 00 00          pushq  $0x6
   1f4ab:   e9 50 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f4b0 <*ABS*+0xb8bc0@plt>:
   1f4b0:   ff 25 b2 ab 39 00       jmpq   *0x39abb2(%rip)        # 3ba068 <_GLOBAL_OFFSET_TABLE_+0x68>
   1f4b6:   68 08 00 00 00          pushq  $0x8
   1f4bb:   e9 40 ff ff ff          jmpq   1f400 <data.8467+0x1f390>

000000000001f4c0 <*ABS*+0x8ec70@plt>:
   1f4c0:   ff 25 aa ab 39 00       jmpq   *0x39abaa(%rip)        # 3ba070 <_GLOBAL_OFFSET_TABLE_+0x70>
   1f4c6:   68 07 00 00 00          pushq  $0x7
   1f4cb:   e9 30 ff ff ff          jmpq   1f400 <data.8467+0x1f390>
[m@localhost ~]$ 

在glibc中调用mmap时不会通过.plt入口进行调用,而是直接调用,因此无法拦截这些调用:

$ objdump -d /lib64/libc.so.6 | grep mmap
[...]
   81628:   e8 83 ad 07 00          callq  fc3b0 <mmap>
   8177c:   e8 2f ac 07 00          callq  fc3b0 <mmap>
00000000000fc3b0 <mmap>:
   fc3c0:   73 01                   jae    fc3c3 <mmap+0x13>
  13a267:   e8 44 21 fc ff          callq  fc3b0 <mmap>
$ 

00000000000fc3b0 <mmap>:
   fc3b0:   49 89 ca                mov    %rcx,%r10
   fc3b3:   b8 09 00 00 00          mov    $0x9,%eax
   fc3b8:   0f 05                   syscall 
   fc3ba:   48 3d 01 f0 ff ff       cmp    $0xfffffffffffff001,%rax
   fc3c0:   73 01                   jae    fc3c3 <mmap+0x13>
   fc3c2:   c3                      retq   
   fc3c3:   48 8b 0d 96 da 2b 00    mov    0x2bda96(%rip),%rcx        # 3b9e60 <_DYNAMIC+0x2e0>
   fc3ca:   f7 d8                   neg    %eax
   fc3cc:   64 89 01                mov    %eax,%fs:(%rcx)
   fc3cf:   48 83 c8 ff             or     $0xffffffffffffffff,%rax
   fc3d3:   c3                      retq   
   fc3d4:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
   fc3db:   00 00 00 
   fc3de:   66 90                   xchg   %ax,%ax

抱歉,但是libc的mmap()可以通过这种方式被hook!只需使用OP的代码和一个小型测试程序调用mmap()...结果如下所示:"Hooked(preload)! mmap:start:(nil), length:20, prot:3, flags:0x22, fd:0xffffffff, offset:0" - user2371524
是的,但不包括 glibc 对其 mmap 的调用。@FelixPalmen - 4566976
@FelixPalmen “我想你是指库内部调用……对吗?” 是的。 - 4566976
使用LD_PRELOAD无法捕获glibc内部调用的mmap,但是可以通过strace进行捕获。想知道这是如何实现的? - Richard
谢谢。尽管如此,strace基于ptrace,在运行时会切换到内核空间...这是我想避免的... - Richard
显示剩余2条评论

6

mmap是一个系统调用,而malloc则不是。

由于系统调用对程序的运行至关重要,它们必须在ld.so实际开始工作之前就要开始工作,它们位于一个在其他所有部分加载之前就被加载的区域中;它可以被动态链接,但是该映射(特定的“虚拟”动态对象的映射)是由内核本身完成的。这早在ld.so实际开始工作之前就已经完成了。


2
@Richard:不,libc中的syscall包装器绑定到PLT存根非常早。 早得LD_PRELOAD无法生效。 - datenwolf
1
@Richard:请看一下这篇文章,了解整个PLT/GOT的工作原理:https://www.technovelty.org/linux/plt-and-got-the-key-to-code-sharing-and-dynamic-libraries.html - datenwolf
不,libc中的系统调用包装器非常早就绑定到PLT存根上是错误的。Intra-libc mmap调用不通过PLT进行,这就是为什么它们无法被拦截的原因。相反,在其他库和可执行文件中的mmap调用是通过PLT进行的,可以像任何其他API一样进行拦截。"动态链接由内核本身执行"也是错误的,内核本身不执行任何动态链接。 - yugr
@yugr:我不是在谈论任意库的动态链接,而是指实现系统调用入口/出口的那个特殊代码的链接:http://man7.org/linux/man-pages/man7/vdso.7.html 特别是这部分:“vDSO”(虚拟动态共享对象)是一个小型共享库,__内核会自动映射__到所有用户空间应用程序的地址空间中。 可以在这里找到有关其功能和工作原理的详细说明:http://www.trilithium.com/johan/2005/08/linux-gate/ - datenwolf
好的,内核映射vDSO(它也映射可执行文件和ld.so),但不对其进行任何链接。即使是针对vDSO的所有动态链接也是在ld.so中完成的,而不是在内核中完成的。 - yugr
显示剩余4条评论

3

你的问题的标题实际上就是答案。

假设mmap确实是由main.c调用的(我猜是通过malloc)

所以说,你的main.c没有调用库函数mmap()?当然你不能以这种方式截取系统调用,那么你该怎么办呢?一些体系结构有一个syscall CPU指令,一些使用特殊的中断……有很多方法,但它完全不同于C调用约定。内核并不以某种方式与您的二进制文件链接,而是在您的用户空间进程执行某些“特殊”操作时接管控制(带有一些硬件辅助)。

如果您想知道如何拦截系统调用,这当然非常依赖于平台,但我建议您只需查看strace实用程序的源代码即可。您永远不会在strace中看到malloc(),因为这不是系统调用,malloc()使用mmap系统调用。

另一方面,如果您将您的库预加载到实际调用libc mmap()函数的二进制文件中,则会按预期工作。

简而言之:libc mmap()mmap系统调用的用户友好封装,具有以下主要内容:

#include <sys/mman.h>

int main()
{
    void *test = mmap(0, 20, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,
        -1, 0);
    return 0;
}

结果是:

加载hack。
Hooked(preload)! mmap:start:(nil), length:20, prot:3, flags:0x22,
fd:0xffffffff, offset:0


我只针对 x86_64 平台进行开发;看起来使用了 syscall 指令集。想知道是否可以通过简单地挂钩二进制文件中的所有 syscall 指令来拦截系统调用... - Richard
我认为... syscall 一方面是CPU指令(无法挂钩),另一方面是 glibc 中的最小包装器,允许程序使用C调用约定调用任何系统调用。后者可以使用LD_PRELOAD进行挂钩。 - user2371524
我相信可以通过二进制重写技术来实现。 - Richard
好的,理想情况下,如果syscall指令可以被替换为一个调用自己子程序的相同长度指令,那就太好了...但我猜它是一个不带数据的单字指令?啊,我从未学过x86指令集,所以我不知道了 ;) - user2371524
@yugr,问题中的“PS”是在我的回答之后添加的,但这并不重要;当libc调用自己的函数时,没有涉及到动态符号。 - user2371524
显示剩余2条评论

0

syscall_intercept库使用一些二进制重写技巧来拦截来自libc的系统调用。您可以使用此库编写的拦截逻辑进行LD_PRELOAD,它将拦截系统调用;您可以选择处理它们和/或将它们传递给内核。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接