我正在尝试修改linux系统调用的默认行为。目前我正试图在实际调用它们之前挂钩并添加一个简单的print语句。我知道GCC链接器的标准'wrap'选项以及它如何用于挂钩包装器Link to GCC Linker options。这完全适用于open(),fstat(),fwrite()等(我实际上是挂钩libc包装器)。
更新
限制是并非所有系统调用都与此方法相关联。 为了说明这一点,让我们采用一个简单的静态编译二进制。当我们尝试添加包装器时,它们会受到我们在 main()之后引入的调用的影响(请参阅下面显示的strace输出)
self.helper = FormHelper(self)
self.helper.attrs = {'data-validate':'parsley'}
如果我们仔细注意到二进制文件,第一个“未拦截”调用readlink()(系统调用89,即0x59)来自这些行 - 一些与链接器相关的代码部分(即 _dl_get_origin ) readlink()的功能。这些隐式的系统调用(虽然存在于二进制代码中)永远不会被我们的“包装”方法所吸引。
> 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, "/etc/alternatives/gnome-www-brow"..., 36/etc/alternatives/gnome-www-browser
) = 36
exit_group(36) = ?
+++ exited with 36 +++
如何将包装思想扩展到readlink()等系统调用(包括调用的所有隐式调用)?
答案 0 :(得分:1)
ld 有一个包装选项,引用from manual:
- 换行符号
使用符号的包装函数。任何未定义的符号引用都将解析为__wrap_symbol。对__real_symbol的任何未定义引用都将解析为符号。这可用于为系统功能提供包装器。包装函数应该被称为__wrap_symbol。如果它想调用系统函数,它应该调用__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, char *buf, 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, 0, sizeof(buf));
readlink(testLink, buf, 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
这里有趣的是,在进入主函数之前,你不会看到对使用strace看到的一堆函数的引用 - 例如uname()
,brk()
,access()
等等。这是因为主要功能不是二进制文件中调用的第一个代码。 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, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 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, ...}) = 0
mmap(NULL, 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, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\37\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1840928, ...}) = 0
mmap(NULL, 3949248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3ecdf78000
mprotect(0x7f3ece133000, 2093056, PROT_NONE) = 0
mmap(0x7f3ece332000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7f3ece332000
mmap(0x7f3ece338000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3ece338000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece530000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece52e000
arch_prctl(ARCH_SET_FS, 0x7f3ece52e740) = 0
mprotect(0x7f3ece332000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ) = 0
mprotect(0x7f3ece55f000, 4096, PROT_READ) = 0
munmap(0x7f3ece531000, 177964) = 0
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 10), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece55c000
write(1, "Hello\n", 6Hello
) = 6
exit_group(0) = ?
+++ exited with 0 +++
$
它是什么?!我们只是重写了二进制文件中的第一个函数,仍然看到系统调用 - 为什么?
实际上,这是因为调用不是由应用程序执行的,而是在应用程序加载到内存之前由内核执行,并允许运行。
UPD:如前所述,您的应用程序不会调用这些函数。老实说,在shell为您的应用调用execve
后,我无法找到正在完成的静态二进制文件,但从列表中看起来就像您看到的每个电话一样由内核本身完成 - 没有任何辅助应用程序,例如静态二进制文件不需要的动态链接器(以及因为brk
之类的函数与数据段一起工作)< / em>的
无论如何,你肯定无法轻易修改这种行为,你需要一些黑客攻击。因为如果您可以轻松覆盖二进制运行之前执行的代码的函数 - 即来自其他二进制文件 - 它将是安全性中的一个大黑洞,想象一下:一旦您需要root权限,您可以覆盖函数一个执行你的代码,并等待一些具有root权限的守护进程执行一个脚本,从而触发你的代码。