加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > Linux > 正文

如何在二进制执行期间挂钩所有linux系统调用

发布时间:2020-12-14 01:53:33 所属栏目:Linux 来源:网络整理
导读:我试图修改 linux系统调用的默认行为.目前我正试图在实际调用它们之前挂钩并添加一个简单的print语句.我知道GCC链接器的标准’wrap’选项以及它如何用于挂钩包装器 Link to GCC Linker options.这完全适用于open(),fstat(),fwrite()等(我实际上挂钩了libc包
我试图修改 linux系统调用的默认行为.目前我正试图在实际调用它们之前挂钩并添加一个简单的print语句.我知道GCC链接器的标准’wrap’选项以及它如何用于挂钩包装器 Link to GCC Linker options.这完全适用于open(),fstat(),fwrite()等(我实际上挂钩了libc包装器).

更新:

限制是并非所有系统调用都与此方法相关联.
为了说明这一点,让我们采用一个简单的静态编译二进制.当我们尝试添加包装器时,它们会受到我们在main()之后引入的调用的影响(请参阅下面显示的strace输出)

> strace ./sample 

execve("./sample",["./sample"],[/* 72 vars */]) = 0
uname({sys="Linux",node="kumar",...})   = 0
brk(0)                                  = 0x71f000
brk(0x7201c0)                           = 0x7201c0
arch_prctl(ARCH_SET_FS,0x71f880)       = 0
readlink("/proc/self/exe","/home/admin/sample"...,4096) = 41
brk(0x7411c0)                           = 0x7411c0
brk(0x742000)                           = 0x742000
access("/etc/ld.so.nohwcap",F_OK)      = -1 ENOENT (No such file or directory)
fstat(1,{st_mode=S_IFCHR|0620,st_rdev=makedev(136,4),...}) = 0
mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0) = 0x7fbcc54d1000
write(1,"Hello from the wrapped readlink "...,36Hello from the wrapped readlink :з
) = 36
readlink("/usr/bin/gnome-www-browser","/etc/alternatives/gnome-www-brow"...,255) = 35
write(1,36/etc/alternatives/gnome-www-browser
) = 36
exit_group(36)                          = ?
+++ exited with 36 +++

如果我们仔细地注意到二进制文件,则第一个“未拦截的”调用readlink()(系统调用89,即0x59)来自这些行 – 一些链接器相关的代码部分(即_dl_get_origin)为其功能执行readlink().这些隐式的系统调用(虽然存在于二进制代码中)永远不会被我们的“包装”方法所吸引.

000000000051875c <_dl_get_origin>:
  51875c:       b8 59 00 00 00          mov    $0x59,%eax
  518761:       55                      push   %rbp
  518762:       53                      push   %rbx
  518763:       48 81 ec 00 10 00 00    sub    $0x1000,%rsp
  51876a:       48 89 e6                mov    %rsp,%rsi
  51876d:       0f 05                   syscall

如何将包装思想扩展到readlink()等系统调用(包括所有被调用的隐式调用)?

解决方法

ld有一个包装选项,引用 from manual:

–wrap symbol

Use a wrapper function for symbol. Any undefined reference to symbol will be resolved to __wrap_symbol. Any undefined reference to __real_symbol will be resolved to symbol. This can be used to provide a wrapper for a system function. The wrapper function should be called __wrap_symbol. If it wishes to call the system function,it should call __real_symbol.

它也适用于系统调用.这是readlink的一个例子:

#include <stdio.h>
#include <string.h>
#include <unistd.h>

ssize_t __real_readlink(const char *path,char *buf,size_t bufsiz);

ssize_t __wrap_readlink(const char *path,size_t bufsiz) {
    puts("Hello from the wrapped readlink :з");
    __real_readlink(path,buf,bufsiz);
}

int main(void) {
    const char testLink[] = "/usr/bin/gnome-www-browser";
    char buf[256];
    memset(buf,sizeof(buf));
    readlink(testLink,sizeof(buf)-1);
    puts(buf);
}

要从编译器将选项传递给链接器,请使用-Wl选项:

$gcc test.c -o a -Wl,--wrap=readlink
$./a
Hello from the wrapped readlink :з
/etc/alternatives/gnome-www-browser

这个想法是__wrap_func是你的函数包装器. __real_func链接器将链接到实际函数func.并且每次对代码中的func的调用都将替换为__wrap_func.

UPD:有人可能会注意到,静态编译的二进制文件会调用另一个未被截获的readlink.要理解原因,只需做一点实验 – 将代码编译到目标文件,并列出符号,如:

$gcc test.c -c -o a.o -Wl,--wrap=readlink
$nm a.o
0000000000000037 T main
                 U memset
                 U puts
                 U readlink
                 U __real_readlink
                 U __stack_chk_fail
0000000000000000 T __wrap_readlink

这里有趣的是,你不会看到在进入main函数之前对strace一起看到的一堆函数的引用 – 例如uname(),brk(),access()等等.这是因为main函数不是二进制文件中调用的第一个代码. bit of research with objdump将向您显示第一个名为_start的函数.

现在,让我们再做一个例子 – 覆盖_start函数:

$cat test2.c
#include <stdio.h>
#include <unistd.h>

void _start() {
        puts("Hello");
        _exit(0);
}
$gcc test2.c -o a -nostartfiles
$strace ./a
execve("./a",["./a"],[/* 69 vars */]) = 0
brk(0)                                  = 0x150c000
access("/etc/ld.so.nohwcap",F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL,8192,0) = 0x7f3ece55d000
access("/etc/ld.so.preload",R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache",O_RDONLY|O_CLOEXEC) = 3
fstat(3,{st_mode=S_IFREG|0644,st_size=177964,177964,PROT_READ,MAP_PRIVATE,3,0) = 0x7f3ece531000
close(3)                                = 0
access("/etc/ld.so.nohwcap",F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6",O_RDONLY|O_CLOEXEC) = 3
read(3,"177ELF2113>1320372"...,832) = 832
fstat(3,{st_mode=S_IFREG|0755,st_size=1840928,3949248,PROT_READ|PROT_EXEC,MAP_PRIVATE|MAP_DENYWRITE,0) = 0x7f3ecdf78000
mprotect(0x7f3ece133000,2093056,PROT_NONE) = 0
mmap(0x7f3ece332000,24576,MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE,0x1ba000) = 0x7f3ece332000
mmap(0x7f3ece338000,17088,MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS,0) = 0x7f3ece338000
close(3)                                = 0
mmap(NULL,0) = 0x7f3ece530000
mmap(NULL,0) = 0x7f3ece52e000
arch_prctl(ARCH_SET_FS,0x7f3ece52e740) = 0
mprotect(0x7f3ece332000,16384,PROT_READ) = 0
mprotect(0x600000,PROT_READ)     = 0
mprotect(0x7f3ece55f000,PROT_READ) = 0
munmap(0x7f3ece531000,177964)          = 0
fstat(1,{st_mode=S_IFCHR|0600,10),0) = 0x7f3ece55c000
write(1,"Hellon",6Hello
)                  = 6
exit_group(0)                           = ?
+++ exited with 0 +++
$

它以前如何?!我们只是重写了二进制文件中的第一个函数,仍然看到系统调用 – 为什么?

实际上,这是因为调用不是由应用程序执行的,而是在应用程序加载到内存之前由内核执行,并允许运行.

UPD:正如我们之前看到的,您的应用程序不会调用这些函数.老实说,在shell调用execve为你的应用程序后,我找不到为静态二进制文件做的事情,但是从列表中看起来你看到的每个调用都是由内核本身完成的 – 没有任何辅助应用程序,比如动态链接器不需要静态二进制文件(因为有像brk这样的函数可以处理数据段).

无论如何,你肯定无法轻易修改这种行为,你需要一些黑客攻击.因为如果您可以轻松覆盖在二进制运行之前执行的代码的函数 – 即来自其他二进制文件 – 它将是安全性中的一个大黑洞,想象一下:一旦您需要root权限,您可以覆盖函数用一个来执行你的代码,并等待一些具有root权限的守护进程执行一个脚本,从而触发你的代码.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读