Notatka ta została napisana w 2014 roku, ale dopiero co spotkałam się z represjami wobec Habrégo i nie ujrzała ona światła dziennego. W czasie zakazu o tym zapomniałem, ale teraz znalazłem to w draftach. Myślałem o usunięciu go, ale może komuś się przyda.

Ogólnie rzecz biorąc, mała piątkowa lektura administratora na temat wyszukiwania „wliczonych” LD_PRELOAD.
1. Krótka dygresja dla nieobeznanych z podstawieniem funkcji
Reszta może przejść od razu do s.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);
}
}
Kompilujemy bez żadnych flag:
$ gcc ./ld_rand.c -o ld_rand
I zgodnie z oczekiwaniami otrzymujemy 5 liczb losowych mniejszych niż 100:
$ ./ld_rand
53
93
48
57
20
Ale wyobraźmy sobie, że nie mamy kodu źródłowego programu, ale musimy zmienić jego zachowanie.
Stwórzmy własną bibliotekę z własnym prototypem funkcji, na przykład:
int rand(){
return 42;
}
$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so
A teraz nasz losowy wybór jest dość przewidywalny:
# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42
Ta sztuczka wygląda jeszcze bardziej imponująco, jeśli najpierw wyeksportujemy naszą bibliotekę za pośrednictwem
$ export LD_PRELOAD=$PWD/ld_rand.so
albo zrobimy to pierwsi
# echo "$PWD/ld_rand.so" > /etc/ld.so.preload
a następnie uruchom program jak zwykle. Nie zmieniliśmy ani jednej linii kodu w samym programie, ale jego zachowanie zależy teraz od małej funkcji w naszej bibliotece. Co więcej, w momencie pisania programu rand nawet nie istniał.
Co sprawiło, że nasz program użył fałszywki rand? Przeanalizujmy to krok po kroku.
Po uruchomieniu aplikacji ładowane są określone biblioteki zawierające funkcje potrzebne programowi. Możemy je przeglądać za pomocą ldd:
# 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 systemu operacyjnego, ale musi tam znajdować się plik libc.so. To właśnie ta biblioteka udostępnia wywołania systemowe i podstawowe funkcje, takie jak koncepcja, malloc, printf itp. Nasz rand jest także wśród nich. Upewnijmy się co do tego:
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand
Zobaczmy, czy zestaw bibliotek zmienia się podczas użycia 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 zmienna jest ustawiona LD_PRELOAD zmusza nas do załadowania ld_rand.so nawet pomimo tego, że sam program tego nie wymaga. A ponieważ nasza funkcja "skraj" ładuje się wcześniej niż rand od libc.so, wtedy ona rządzi grzędą.
Ok, udało nam się zastąpić natywną funkcję, ale jak możemy się upewnić, że jej funkcjonalność zostanie zachowana i dodane zostaną pewne akcje. Zmodyfikujmy naszą losowość:
#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” drukujemy tylko jedną linijkę tekstu, po czym tworzymy wskaźnik do oryginalnej funkcji rand. Aby uzyskać adres tej funkcji, potrzebujemy dlsym jest funkcją z biblioteki bibliotekaktóry znajdzie nasze rand w stosie bibliotek dynamicznych. Po czym wywołamy tę funkcję i zwrócimy jej wartość. W związku z tym będziemy musieli dodać „-ldl” podczas montażu:
$ 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 „natywnego” rand, dokonując wcześniej pewnych nieprzyzwoitych działań.
2. Ból poszukiwania
Wiedząc o potencjalnym zagrożeniu, chcemy je wykryć preload Zostało wykonane. Oczywiste jest, że najlepszym sposobem na wykrycie jest wepchnięcie go do jądra, ale interesowały mnie opcje wykrywania w przestrzeni użytkownika.
Następnie rozwiązania dotyczące wykrywania i ich obalania będą pojawiać się parami.
2.1. Zacznijmy prosto
Jak wspomniano wcześniej, możesz określić bibliotekę do załadowania za pomocą zmiennej LD_PRELOAD lub zapisując go w pliku /etc/ld.so.preload. Stwórzmy dwa proste detektory.
Pierwszym z nich jest sprawdzenie 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");
}
Drugim jest sprawdzenie, czy plik jest otwarty:
#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 poniżej [+] oznacza pomyślne wykrycie.
Odpowiednio, [-] oznacza ominięcie wykrywania.
Jak skuteczny jest taki detektor? Przyjrzyjmy się najpierw zmiennej środowiskowej:
#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 pozbywamy się czeku koncepcja:
#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, można tutaj zastosować inne sposoby dostępu do pliku, np. open64, stan itp., ale tak naprawdę potrzeba tych samych 5-10 linii kodu, aby ich oszukać.
2.2. Przejdźmy dalej
Powyżej użyliśmy getenv() aby uzyskać wartość LD_PRELOAD, ale istnieje również bardziej „niski” sposób dotarcia do tego miejsca ENV-zmienne. Nie będziemy używać funkcji pośrednich, ale odniesiemy się do tablicy **otaczać, który przechowuje kopię ś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, takie połączenie nie może zostać przechwycone, a nasze undetect_getenv nie przeszkadza już w identyfikacji włamania.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
Wydawałoby się, że ten problem został rozwiązany? To wciąż dopiero początek.
Po uruchomieniu programu wartość zmiennej LD_PRELOAD hakerzy nie potrzebują już ich w pamięci, to znaczy mogą je odczytać i usunąć przed wykonaniem jakichkolwiek instrukcji. Oczywiście edytowanie tablicy w pamięci jest co najmniej złym stylem programowania, ale jak może to powstrzymać kogoś, kto i tak naprawdę nie życzy nam dobrze?
Aby to zrobić musimy stworzyć własną fałszywą funkcję w tym(), w którym przechwytujemy zainstalowane LD_PRELOAD i przekaż go do naszego linkera:
#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 i 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/
Pamięć nie jest jednak ostatnim miejscem, w którym można znaleźć substytut LD_PRELOAD, jest również / proc /. Zacznijmy od oczywistości /proc/{PID}/środowisko.
W rzeczywistości istnieje uniwersalne rozwiązanie niewykryty **otaczać и /proc/self/środowisko. Problemem jest „złe” zachowanie unsetenv(env).
poprawna opcja
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);
// Вместо unset просто обнулим нашу переменную
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 tego nie znaleźliśmy i /proc/self/środowisko zawiera „problematyczne” dane.
Najpierw spróbujmy z naszym poprzednim „przebraniem”:
$ (LD_PRELOAD=./ld_undetect_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
LD_PRELOAD=./ld_undetect_environ.so
jak używa tego samego do otwarcia pliku otwarty(), więc rozwiązanie jest podobne do tego, co zostało już zrobione w sekcji 2.1, ale teraz tworzymy plik tymczasowy, w którym 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ł zaliczony:
$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
Kolejnym oczywistym miejscem jest /proc/self/maps. Nie ma sensu się nad tym rozwodzić. Rozwiązanie jest całkowicie identyczne z poprzednim: skopiuj dane z pliku minus linie pomiędzy nimi libc.so и ld.so.
2.4. Opcja od Chokepoint
To rozwiązanie szczególnie przypadło mi do gustu ze względu na jego prostotę. Porównujemy adresy funkcji ładowanych bezpośrednio z libci „DALEJ” adresy.
#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;
}
Ładowanie biblioteki z przechwyceniem "otwarty()" i zaznacz:
$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc address: 0x7fa86893b160
Next address: 0x7fa868a26135
Zaprzeczenie okazało się jeszcze prostsze:
#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 to wszystko, ale nadal będziemy flądrować. Jeśli wyślemy wywołanie systemowe bezpośrednio do jądra, ominie to cały proces przechwytywania. Poniższe rozwiązanie zależy oczywiście od architektury (x86_64). Spróbujmy to zaimplementować, aby wykryć otwarcie ld.so.wczytaj wstępnie.
#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 ten problem ma rozwiązanie. Fragment mężczyzna'A:
ptrace to narzędzie, które pozwala procesowi nadrzędnemu obserwować i kontrolować postęp innego procesu, przeglądać i zmieniać jego dane i rejestry. Zazwyczaj ta funkcja służy do tworzenia punktów przerwania w programie debugującym i monitorowania wywołań systemowych.
Proces nadrzędny może rozpocząć śledzenie, wywołując najpierw fork(2), a następnie powstały proces potomny może wykonać PTRACE_TRACEME, a następnie (zwykle) wykonać exec(3). Z drugiej strony proces nadrzędny może rozpocząć debugowanie istniejącego procesu za pomocą PTRACE_ATTACH.
Podczas śledzenia proces potomny zatrzymuje się za każdym razem, gdy otrzyma sygnał, nawet jeśli sygnał zostanie zignorowany. (Wyjątkiem jest SIGKILL, który działa normalnie.) Proces nadrzędny zostanie o tym powiadomiony poprzez wywołanie funkcji Wait(2), po czym będzie mógł przeglądać i modyfikować zawartość procesu potomnego przed jego uruchomieniem. Proces nadrzędny pozwala następnie dziecku kontynuować działanie, w niektórych przypadkach ignorując wysłany do niego sygnał lub zamiast tego wysyłając inny sygnał).
Rozwiązaniem jest zatem monitorowanie procesu, zatrzymywanie go przed każdym wywołaniem systemowym i w razie potrzeby przekierowanie wątku do funkcji przechwytującej.
#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);
}
}
Sprawdzanie:
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]
+0-0=5
Dziękuję bardzo
, którego artykuły, źródła i komentarze zrobiły znacznie więcej niż ja, aby ta notatka pojawiła się tutaj.
Źródło: www.habr.com
