л.д. имеет возможность для упаковки, цитаты from manual:
--wrap символ
Используйте функцию обертки для символа. Любая неопределенная ссылка на символ будет разрешена __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()
и т. Д. Это потому, что основная функция - это не первый код, вызываемый в вашем двоичном файле. A 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: Как мы видели ранее, функции не вызываются приложением. Честно говоря, я не мог найти, что делается для статических двоичных файлов после того, как оболочка вызывает execve
для вашего приложения, но из списка это выглядит как каждый вызов, который вы видите, выполняемый самим ядром - без какого-либо побочного приложения, такого как динамический компоновщик которые не нужны для статических двоичных файлов (и потому, что существуют такие функции, как brk
, который работает с сегментами данных).
Независимо от того, что вы, конечно же, не можете легко изменить это поведение, вам понадобится взлом. Поскольку, если вы можете легко переопределить функцию для кода, который выполняется перед вашим двоичным запуском, то есть из другого двоичного файла, - это будет большая черная дыра в безопасности, представьте себе: как только вам понадобятся права root, вы переопределите функцию с одним, чтобы выполнить ваш код, и немного подождите, пока какой-то демон с правами root выполняет сценарий и, таким образом, запускает ваш код в игру.
Я делаю статические ссылки для простоты. Поэтому я думаю, что LD_PRELOAD мне не помогает. –