Notatka ta została napisana w 2014 roku, ale wówczas padłem ofiarą represji na Habra, więc nigdy nie ujrzała światła dziennego. Podczas banicji zapomniałem o niej, a teraz znalazłem w szkicach. Myślałem, że ją usunę, ale może się komuś przyda.

Generalnie, mała piątkowa lektura administracyjna na temat poszukiwania «włączonego» LD_PRELOAD.
1. Krótkie wprowadzenie dla tych, którzy nie znają zastępowania funkcji
Pozostali mogą przejść od razu do pkt. 2.
Zacznijmy od klasycznego przykładu:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand (time(NULL));
for(int i=0; i<5; i++){
printf ("%dn", rand()%100);
}
}
Komplilujemy bez żadnych flag:
$ gcc ./ld_rand.c -o ld_rand
I, jak można się było spodziewać, otrzymujemy 5 losowych liczb mniejszych niż 100:
$ ./ld_rand
53
93
48
57
20
Ale wyobraźmy sobie, że nie mamy kodu źródłowego programu, a jego zachowanie trzeba zmienić.
Stworzymy naszą bibliotekę z własnym prototypem funkcji, na przykład:
int rand(){
return 42;
}
$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so
I teraz nasz losowy wybór jest całkowicie przewidywalny:
# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42
Trik ten wygląda jeszcze bardziej imponująco, gdy najpierw wyeksportujemy naszą bibliotekę przez
$ export LD_PRELOAD=$PWD/ld_rand.so
lub wcześniej wykonamy
# echo "$PWD/ld_rand.so" > /etc/ld.so.preload
a następnie uruchomimy program w zwykłym trybie. Nie zmieniliśmy ani linijki w kodzie samego programu, ale jego zachowanie teraz zależy od maleńkiej funkcji w naszej bibliotece. Co więcej, w momencie pisania programu fałszywy rand nawet nie istniał.
Co więc spowodowało, że nasz program użył fałszywego rand? Разберем по шагам.
Kiedy aplikacja jest uruchamiana, ładują się określone biblioteki, które zawierają funkcje potrzebne programowi. Możemy je zobaczyć, używając ) i tommath (:
# ldd ./ld_rand
linux-vdso.so.1 (0x00007ffc8b1f3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe3da8af000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe3daa7e000)
Ta lista może się różnić w zależności od wersji OS, ale musi się tam znajdować plik libc.so. To właśnie ta biblioteka zapewnia wywołania systemowe i podstawowe funkcje, takie jak open, malloc, printf itd. Nasz rand również wchodzi w ich skład. Sprawdźmy to:
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand
Zobaczmy, czy zestaw bibliotek się nie zmieni przy użyciu LD_PRELOAD
# LD_PRELOAD=$PWD/ld_rand.so ldd ./ld_rand
linux-vdso.so.1 (0x00007ffea52ae000)
/scripts/c/ldpreload/ld_rand.so (0x00007f690d3f9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f690d230000)
/lib64/ld-linux-x86-64.so.2 (0x00007f690d405000)
Okazuje się, że ustawiona zmienna LD_PRELOAD sprawia, że nasz ld_rand.so zostaje załadowana, mimo że program sam jej nie wymaga. A ponieważ nasza funkcja «rand» ładowana jest wcześniej niż rand od libc.so, to ona rządzi.
Ok, udało nam się zastąpić rodzimą funkcję, ale jak usprawnić jej funkcjonalność i dodać pewne działania. Modyfikujemy nasz los:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
typedef int (*orig_rand_f_type)(void);
int rand()
{
/* Выполняем некий код */
printf("Evil injected coden");
orig_rand_f_type orig_rand;
orig_rand = (orig_rand_f_type)dlsym(RTLD_NEXT,"rand");
return orig_rand();
}
Tutaj jako naszą «dodatek» jedynie drukujemy jeden wiersz tekstu, po czym tworzymy wskaźnik na oryginalną funkcję. randAby uzyskać adres tej funkcji, potrzebujemy dlsym — to funkcja z biblioteki libdl, która znajdzie nasze rand w stosie dynamicznych bibliotek. Następnie wywołamy tę funkcję i zwrócimy jej wartość. W związku z tym będziemy musieli dodać «-ldl» podczas kompilacji:
$ gcc -ldl -shared -fPIC . /o_rand_evil.c -o ld_rand_evil.so
$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Evil injected code
66
Evil injected code
28
Evil injected code
93
Evil injected code
93
Evil injected code
95
A nasz program używa «native» rand, wcześniej wykonując pewne nieodpowiednie działania.
2. Męka poszukiwania
Znając potencjalne zagrożenie, chcemy wykryć, że preload został wykonany. Oczywiście, najlepszy sposób detekcji — to włożyć to do jądra, ale interesowały mnie szczególnie opcje wykrywania w przestrzeni użytkownika.
Poniżej przedstawimy pary rozwiązań do wykrywania oraz ich obalania.
2.1. Zacznijmy od prostego
Jak wspomniano wcześniej, aby wskazać ładowaną bibliotekę, można to zrobić za pomocą zmiennej LD_PRELOAD lub wpisując ją w pliku /etc/ld.so.preload. Stwórzmy dwa najprostsze detektory.
Pierwszy — do sprawdzenia ustawionej zmiennej środowiskowej:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
char* pGetenv = getenv("LD_PRELOAD");
pGetenv != NULL ?
printf("LD_PRELOAD (getenv) [+]n"):
printf("LD_PRELOAD (getenv) [-]n");
}
Drugi — do sprawdzenia otwarcia pliku:
#include <stdio.h>
#include <fcntl.h>
int main()
{
open("/etc/ld.so.preload", O_RDONLY) != -1 ?
printf("LD_PRELOAD (open) [+]n"):
printf("LD_PRELOAD (open) [-]n");
}
Załadujmy biblioteki:
$ export LD_PRELOAD=$PWD/ld_rand.so
$ echo "$PWD/ld_rand.so" > /etc/ld.so.preload
$ ./detect_base_getenv
LD_PRELOAD (getenv) [+]
$ ./detect_base_open
LD_PRELOAD (open) [+]
Tutaj i dalej [+] oznacza udane wykrycie.
Odpowiednio, [-] oznacza obejście detekcji.
Jak skuteczny jest taki detektor? Najpierw zajmiemy się zmienną środowiskową:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
char* (*orig_getenv)(const char *) = NULL;
char* getenv(const char *name)
{
if(!orig_getenv) orig_getenv = dlsym(RTLD_NEXT, "getenv");
if(strcmp(name, "LD_PRELOAD") == 0) return NULL;
return orig_getenv(name);
}
$ gcc -shared -fpic -ldl ./ld_undetect_getenv.c -o ./ld_undetect_getenv.so
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_base_getenv
LD_PRELOAD (getenv) [-]
Podobnie eliminujemy również sprawdzenie open:
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <errno.h>
int (*orig_open)(const char*, int oflag) = NULL;
int open(const char *path, int oflag, ...)
{
char real_path[256];
if(!orig_open) orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0){
errno = ENOENT;
return -1;
}
return orig_open(path, oflag);
}
$ gcc -shared -fpic -ldl ./ld_undetect_open.c -o ./ld_undetect_open.so
$ LD_PRELOAD=./ld_undetect_open.so ./detect_base_open
LD_PRELOAD (open) [-]
Tak, mogą być używane inne sposoby dostępu do pliku, takie jak, open64, stat i inne, ale w zasadzie do ich oszukania potrzebne są te same 5-10 linii kodu.
2.2. Przechodzimy dalej
Powyżej użyliśmy getenv() do pobrania wartości LD_PRELOAD, ale istnieje bardziej „niskopoziomowy” sposób dotarcia do ENV-zmiennych. Nie będziemy używać funkcji pośrednich, lecz odwołamy się do tablicy **environ, w której przechowywana jest kopia środowiska:
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char **argv) {
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++)
{
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL)
{
printf("LD_PRELOAD (**environ) [+]n");
return 0;
}
}
printf("LD_PRELOAD (**environ) [-]n");
return 0;
}
Ponieważ tutaj odczytujemy dane bezpośrednio z pamięci, taki wywołanie nie może zostać przechwycone, a nasz undetect_getenv już nie przeszkadza w wykrywaniu inwazji.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
Wydawałoby się, że na tym problem jest rozwiązany? To dopiero początek.
Po uruchomieniu programu, wartość zmiennej LD_PRELOAD w pamięci już nie jest potrzebna hakerom, więc można ją uznać za zbędną i usunąć przed wykonaniem jakichkolwiek instrukcji. Oczywiście, modyfikowanie tablic w pamięci to przynajmniej zły styl programowania, ale czy to może powstrzymać kogoś, kto i tak nie ma dobrych zamiarów?
W tym celu będziemy musieli stworzyć naszą fałszywą funkcję init(), w której przechwycimy ustaloną LD_PRELOAD i przekażemy ją naszemu linkerowi:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdlib.h>
extern char **environ;
char *evil_env;
int (*orig_execve)(const char *path, char *const argv[], char *const envp[]) = NULL;
// Создаём фейковую версию init
// которая будет вызвана при загрузке программы
// до выполнения каких-либо инструкций
void evil_init()
{
// Сначала сохраним текущее значение LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
// Избавляемся от текущего LD_PRELOAD
unsetenv(env);
break;
}
}
}
int execve(const char *path, char *const argv[], char *const envp[])
{
int i = 0, j = 0, k = -1, ret = 0;
char** new_env;
if(!orig_execve) orig_execve = dlsym(RTLD_NEXT,"execve");
// Проверям не существует ли других установленных LD_PRELOAD
for(i = 0; envp[i]; i++){
if(strstr(envp[i], "LD_PRELOAD")) k = i;
}
// Если LD_PRELOAD не было установлено до нас, то добавим его
if(k == -1){
k = i;
i++;
}
// Создаём новое окружение
new_env = (char**) malloc((i+1)*sizeof(char*));
// Копируем старое окружение, за исключением LD_PRELOAD
for(j = 0; j < i; j++) {
// перезаписываем или создаём LD_PRELOAD
if(j == k) {
new_env[j] = (char*) malloc(256);
strcpy(new_env[j], "LD_PRELOAD=");
strcat(new_env[j], evil_env);
}
else new_env[j] = (char*) envp[j];
}
new_env[i] = NULL;
ret = orig_execve(path, argv, new_env);
free(new_env[k]);
free(new_env);
return ret;
}
Wykonujemy, sprawdzamy:
$ gcc -shared -fpic -ldl -Wl,-init,evil_init ./ld_undetect_environ.c -o ./ld_undetect_environ.so
$ LD_PRELOAD=./ld_undetect_environ.so ./detect_environ
LD_PRELOAD (**environ) [-]
2.3. /proc/self/
Jednak pamięć to nie ostatnie miejsce, gdzie można wykryć podmianę LD_PRELOAD, jest jeszcze /proc/. Zacznijmy od oczywistego /proc/{PID}/environ.
W rzeczywistości istnieje uniwersalne rozwiązanie dla niedetekcji **environ i /proc/self/environ. Problem tkwi w "niewłaściwym" zachowaniu unsetenv(env).
prawidłowa wersja
void evil_init()
{
// Najpierw zachowamy bieżącą wartość LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
// Pozbywamy się bieżącego LD_PRELOAD
//unsetenv(env);
// Zamiast unset po prostu zerujemy naszą zmienną
for(int j = 0; environ[i][j] != ' '; j++) environ[i][j] = ' ';
break;
}
}
}
$ gcc -shared -fpic -ldl -Wl,-init,evil_init ./ld_undetect_environ_2.c -o ./ld_undetect_environ_2.so
$ (LD_PRELOAD=./ld_undetect_environ_2.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
Ale wyobraźmy sobie, że go nie znaleźliśmy i /proc/self/environ zawiera "problemowe" dane.
Na początku spróbujemy z naszą poprzednią "maskowaniem":
$ (LD_PRELOAD=./ld_undetect_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
LD_PRELOAD=./ld_undetect_environ.so
cat używa do otwierania pliku wciąż tej samej open(), dlatego rozwiązanie jest podobne do tego, co już zostało zrobione w punkcie 2.1, ale teraz tworzymy tymczasowy plik, do którego kopiujemy wartości prawdziwej pamięci bez linii zawierających LD_PRELOAD.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#define BUFFER_SIZE 256
int (*orig_open)(const char*, int oflag) = NULL;
char *soname = "fakememory_preload.so";
char *sstrstr(char *str, const char *sub)
{
int i, found;
char *ptr;
found = 0;
for(ptr = str; *ptr != ' '; ptr++) {
found = 1;
for(i = 0; found == 1 && sub[i] != ' '; i++){
if(sub[i] != ptr[i]) found = 0;
}
if(found == 1)
break;
}
if(found == 0)
return NULL;
return ptr + i;
}
void fakeMaps(char *original_path, char *fake_path, char *pattern)
{
int fd;
char buffer[BUFFER_SIZE];
int bytes = -1;
int wbytes = -1;
int k = 0;
pid_t pid = getpid();
int fh;
if ((fh=orig_open(fake_path,O_CREAT|O_WRONLY))==-1) {
printf("LD: Cannot open write-file [%s] (%d) (%s)n", fake_path, errno, strerror(errno));
exit (42);
}
if((fd=orig_open(original_path, O_RDONLY))==-1) {
printf("LD: Cannot open read-file.n");
exit(42);
}
do
{
char t = 0;
bytes = read(fd, &t, 1);
buffer[k++] = t;
//printf("%c", t);
if(t == ' ') {
//printf("n");
if(!sstrstr(buffer, "LD_PRELOAD")) {
if((wbytes = write(fh,buffer,k))==-1) {
//printf("write errorn");
}
else {
//printf("writed %dn", wbytes);
}
}
k = 0;
}
}
while(bytes != 0);
close(fd);
close(fh);
}
int open(const char *path, int oflag, ...)
{
char real_path[PATH_MAX], proc_path[PATH_MAX], proc_path_0[PATH_MAX];
pid_t pid = getpid();
if(!orig_open)
orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
snprintf(proc_path, PATH_MAX, "/proc/%d/environ", pid);
if(strcmp(real_path, proc_path) == 0) {
snprintf(proc_path, PATH_MAX, "/tmp/%d.fakemaps", pid);
realpath(proc_path_0, proc_path);
fakeMaps(real_path, proc_path, soname);
return orig_open(proc_path, oflag);
}
return orig_open(path, oflag);
}
I ten etap został zakończony:
$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
Następne oczywiste miejsce — /proc/self/maps. Nie ma sensu na nim się zatrzymywać. Rozwiązanie jest absolutnie identyczne z poprzednim: kopiujemy dane z pliku z wyłączeniem linii między libc.so i ld.so.
2.4. Wariant od Chokepoint
To rozwiązanie szczególnie mi się podoba z powodu swojej prostoty. Porównujemy adresy funkcji, ładowanych bezpośrednio z libc, i adresy "NEXT".
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#define LIBC "/lib/x86_64-linux-gnu/libc.so.6"
int main(int argc, char *argv[]) {
void *libc = dlopen(LIBC, RTLD_LAZY); // Open up libc directly
char *syscall_open = "open";
int i;
void *(*libc_func)();
void *(*next_func)();
libc_func = dlsym(libc, syscall_open);
next_func = dlsym(RTLD_NEXT, syscall_open);
if (libc_func != next_func) {
printf("LD_PRELOAD (syscall - %s) [+]n", syscall_open);
printf("Libc address: %pn", libc_func);
printf("Next address: %pn", next_func);
}
else {
printf("LD_PRELOAD (syscall - %s) [-]n", syscall_open);
}
return 0;
}
Ładujemy bibliotekę z przechwytywaniem "open()" i sprawdzamy:
$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc address: 0x7fa86893b160
Next address: 0x7fa868a26135
Odmowa okazała się jeszcze prostsza:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
extern void * _dl_sym (void *, const char *, void *);
void * dlsym (void * handle, const char * symbol)
{
return _dl_sym (handle, symbol, dlsym);
}
# LD_PRELOAD=./ld_undetect_chokepoint.so ./detect_chokepoint
LD_PRELOAD (syscall - open) [-]
2.5. Wywołania systemowe
Wydawałoby się, że na tym koniec, ale spróbujmy jeszcze. Jeśli skierujemy wywołanie systemowe bezpośrednio do jądra, pozwoli to ominąć cały proces przechwytywania. Rozwiązanie poniżej jest oczywiście zależne od architektury (x86_64). Spróbujmy zaimplementować to dla wykrycia otwierania ld.so.preload.
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFER_SIZE 256
int syscall_open(char *path, long oflag)
{
int fd = -1;
__asm__ (
"mov $2, %%rax;" // Open syscall number
"mov %1, %%rdi;" // Address of our string
"mov %2, %%rsi;" // Open mode
"mov $0, %%rdx;" // No create mode
"syscall;" // Straight to ring0
"mov %%eax, %0;" // Returned file descriptor
:"=r" (fd)
:"m" (path), "m" (oflag)
:"rax", "rdi", "rsi", "rdx"
);
return fd;
}
int main()
{
syscall_open("/etc/ld.so.preload", O_RDONLY) > 0 ?
printf("LD_PRELOAD (open syscall) [+]n"):
printf("LD_PRELOAD (open syscall) [-]n");
}
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
I to zadanie ma swoje rozwiązanie. Wydruk z man‘a:
ptrace - to narzędzie, które pozwala procesowi rodzicielskiemu obserwować i kontrolować działanie innego procesu, przeglądać i zmieniać jego dane oraz rejestry. Zwykle ta funkcja jest używana do tworzenia punktów przerwania w programie debuggowaniu oraz śledzeniu wywołań systemowych.
Proces rodzicielski może rozpocząć śledzenie, najpierw wywołując funkcję fork(2), a następnie nowo utworzony proces potomny może wykonać PTRACE_TRACEME, po którym (zwykle) następuje wykonanie exec(3). Z drugiej strony, proces rodzicielski może rozpocząć debugowanie istniejącego procesu za pomocą PTRACE_ATTACH.
Podczas śledzenia proces potomny zatrzymuje się za każdym razem, gdy otrzymuje sygnał, nawet jeśli ten sygnał jest ignorowany. (Wyjątkiem jest SIGKILL, który działa w normalny sposób.) Proces rodzicielski zostanie o tym powiadomiony w czasie wywołania wait(2), po którym może przeglądać i zmieniać zawartość procesu potomnego przed jego uruchomieniem. Następnie proces rodzicielski pozwala potomnemu kontynuować pracę, w niektórych przypadkach ignorując wysłany sygnał lub wysyłając zamiast tego inny sygnał).
W ten sposób rozwiązanie polega na śledzeniu procesu, zatrzymując go przed każdym wywołaniem systemowym i, jeśli to konieczne, przekierowując strumień do funkcji pułapki.
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <asm/unistd.h>
#if defined(__x86_64__)
#define REG_SYSCALL ORIG_RAX
#define REG_SP rsp
#define REG_IP rip
#endif
long NOHOOK = 0;
long evil_open(const char *path, long oflag, long cflag)
{
char real_path[PATH_MAX], maps_path[PATH_MAX];
long ret;
pid_t pid;
pid = getpid();
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0)
{
errno = ENOENT;
ret = -1;
}
else
{
NOHOOK = 1; // Entering NOHOOK section
ret = open(path, oflag, cflag);
}
// Exiting NOHOOK section
NOHOOK = 0;
return ret;
}
void init()
{
pid_t program;
// Форкаем дочерний процесс
program = fork();
if(program != 0) {
int status;
long syscall_nr;
struct user_regs_struct regs;
// Подключаемся к дочернему процессу
if(ptrace(PTRACE_ATTACH, program) != 0) {
printf("Failed to attach to the program.n");
exit(1);
}
waitpid(program, &status, 0);
// Отслеживаем только SYSCALLs
ptrace(PTRACE_SETOPTIONS, program, 0, PTRACE_O_TRACESYSGOOD);
while(1) {
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
if(WIFEXITED(status) || WIFSIGNALED(status)) break;
else if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP|0x80) {
// Получаем номер системного вызова
syscall_nr = ptrace(PTRACE_PEEKUSER, program, sizeof(long)*REG_SYSCALL);
if(syscall_nr == __NR_open) {
// Читаем слово из памяти дочернего процесса
NOHOOK = ptrace(PTRACE_PEEKDATA, program, (void*)&NOHOOK);
// Перехватываем вызов
if(!NOHOOK) {
// Копируем регистры дочернего процесса
// в переменную regs родительского
ptrace(PTRACE_GETREGS, program, 0, ®s);
// Push return address on the stack
regs.REG_SP -= sizeof(long);
// Копируем слово в память дочернего процесса
ptrace(PTRACE_POKEDATA, program, (void*)regs.REG_SP, regs.REG_IP);
// Устанавливаем RIP по адресу evil_open
regs.REG_IP = (unsigned long) evil_open;
// Записываем состояние регистров процесса
ptrace(PTRACE_SETREGS, program, 0, ®s);
}
}
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
}
}
exit(0);
}
else {
sleep(0);
}
}
Sprawdzamy:
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]
+0-0=5
Ogromne podziękowania
, których artykuły, źródła i komentarze przyczyniły się znacznie bardziej ode mnie, aby ta uwaga mogła się tutaj pojawić.
Źródło: habr.com
