Op zoek naar LD_PRELOAD

Dit aantekening werd in 2014 geschreven, maar ik viel net onder repressies op Habr en het is nooit gepubliceerd. Tijdens de ban vergat ik erover en vond het nu in mijn concepten terug. Ik dacht eerst om het te verwijderen, maar misschien kan het nuttig zijn voor iemand.

Op zoek naar LD_PRELOAD

Kortom, een kleine vrijdagse admin-leesvoer over de zoektocht naar "ingeschakeld" LD_PRELOAD.

1. Een kleine afwijking voor degenen die niet bekend zijn met functie-vervanging

Anderen kunnen meteen doorgaan naar p.2.

Laten we beginnen met een klassiek voorbeeld:

#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);
  }
}

We compileren zonder enige vlaggen:

$ gcc ./ld_rand.c -o ld_rand

En, zoals te verwachten, krijgen we 5 willekeurige getallen onder de 100:

$ ./ld_rand
53
93
48
57
20

Maar stel je voor dat we de broncode van het programma niet hebben, maar we moeten het gedrag veranderen.

We creëren onze eigen bibliotheek met onze eigen prototypefunctie, bijvoorbeeld:

int rand(){
  return 42;
}

$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so

En nu is onze willekeurige keuze behoorlijk voorspelbaar:

# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42

Truc lijkt nog indrukwekkender als we eerst onze bibliotheek exporteren via

$ export LD_PRELOAD=$PWD/ld_rand.so

of eerst uitvoeren

# echo "$PWD/ld_rand.so" > /etc/ld.so.preload

en vervolgens het programma in de normale modus starten. We hebben geen regel in de code van het programma gewijzigd, maar het gedrag ervan hangt nu af van een kleine functie in onze bibliotheek. Bovendien bestond op het moment van schrijven het vervalsing rand niet eens.

Wat heeft onze programma doen besluiten de vervalsing te gebruiken? rand? Разберем по шагам.
Wanneer de applicatie wordt gestart, worden bepaalde bibliotheken geladen die functies bevatten die nodig zijn voor het programma. We kunnen ze bekijken met zou 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)

Deze lijst kan variëren afhankelijk van de versie van het besturingssysteem, maar er moet altijd een bestand aanwezig zijn libc.so. Deze bibliotheek zorgt voor systeemaanroepen en basisfuncties zoals open, malloc, printf enz. Onze rand maakt daar ook deel van uit. Laten we dat verifiëren:

# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand

Kijken of de set bibliotheken verandert bij het gebruik van 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)

Blijkbaar zorgt de geïnstalleerde variabele LD_PRELOAD ervoor dat onze ld_rand.so zelfs wordt geladen, hoewel het programma het niet vereist. En omdat onze functie "rand" eerder wordt geladen dan rand van libc.so, heeft deze de leiding.

Oké, we zijn erin geslaagd om de originele functie te vervangen, maar hoe kunnen we er ook voor zorgen dat de functionaliteit behouden blijft en er enkele acties worden toegevoegd? We modificeren onze random:

#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();
}

Hier printen we slechts één regel tekst als onze "toevoeging", waarna we een pointer naar de oorspronkelijke functie creëren. randOm het adres van deze functie te verkrijgen, hebben we nodig dlsym – dit is een functie uit de bibliotheek libdl, die onze rand in de dynamische bibliothekenstack zal vinden. Vervolgens zullen we deze functie aanroepen en de waarde teruggeven. Daarom moeten we toevoegen "-ldl" bij het compileren:

$ gcc -ldl -shared -fPIC ./o_rand_evil.c -o ld_rand_evil.so

$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Kwaadaardige geïnjecteerde code
66
Kwaadaardige geïnjecteerde code
28
Kwaadaardige geïnjecteerde code
93
Kwaadaardige geïnjecteerde code
93
Kwaadaardige geïnjecteerde code
95

En ons programma gebruikt "native", rand, nadat bepaalde ongepaste handelingen zijn uitgevoerd.

2. Zoekellende

Wetende van de potentiële bedreiging willen we ontdekken dat preload is uitgevoerd. Het is duidelijk dat de beste detectiemethode is om het in de kernel te stoppen, maar ik was geïnteresseerd in de mogelijkheden om het in user space te identificeren.

Hierna komen een paar oplossingen voor detectie en hun weerlegging.

2.1. Laten we beginnen met het eenvoudige

Zoals eerder vermeld, kan de te laden bibliotheek worden aangegeven met behulp van de variabele LD_PRELOAD of door deze in het bestand /etc/ld.so.preloadte zetten. Laten we twee eenvoudige detectoren maken.

De eerste - om de ingestelde omgevingsvariabele te controleren:

#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");
}

De tweede - om het openen van het bestand te controleren:

#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");
}

Laten we de bibliotheken laden:

$ 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) [+]

Hier en verder geeft [+] aan dat detectie succesvol is.
Bijgevolg betekent [-] om detectie te omzeilen.

Hoe effectief is zo'n detector? Laten we eerst de omgevingsvariabele aanpakken:

#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) [-]

Evenzo ontdoen we ons van de controle 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) [-]

Ja, hier kunnen andere manieren om toegang tot het bestand te krijgen worden gebruikt, zoals open64, stat enzovoort, maar in feite zijn dezelfde 5-10 regels code nodig om ze te bedriegen.

2.2. Laten we verder gaan

Hierboven hebben we gebruikt getenv() om de waarde te verkrijgen LD_PRELOAD, maar er zijn ook “lager niveau” manieren om bij ENV-variabelen te komen. We zullen geen tussenliggende functies gebruiken, maar direct naar de array **environ, waarin een kopie van de omgeving wordt opgeslagen:

#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;
}

Aangezien we hier gegevens rechtstreeks uit het geheugen lezen, kan deze aanroep niet worden onderschept, en onze undetect_getenv verstoort al niet de identificatie van de inbraak.

$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]

Het lijkt erop dat het probleem hiermee is opgelost? Dit is nog maar het begin.

Nadat het programma is gestart, is de waarde van de variabele LD_PRELOAD in het geheugen al niet meer nodig voor de hackers, dus kunnen we deze als verwijderd beschouwen voordat we enige instructies uitvoeren. Natuurlijk is het aanpassen van een array in het geheugen op zijn minst een slechte programmeerstijl, maar kan dit degene die ons geen goed wil toekennen, echt stoppen?

Hiervoor moeten we onze eigen nepfunctie creëren init(), waarin we de ingestelde LD_PRELOAD omleiden en deze doorgeven aan onze linker:

#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;
}

Uitvoeren, controleren:

$ 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/

Echter, geheugen is niet de laatste plek waar we vervalsing kunnen ontdekken LD_PRELOAD, er is ook nog /proc/. Laten we beginnen met het voor de hand liggende /proc/{PID}/environ.

Er is eigenlijk een universele oplossing voor undetect'er **environ en /proc/self/environ. Het probleem ligt in het "onjuiste" gedrag unsetenv(env).

de juiste optie

void evil_init()
{
  // Eerst slaan we de huidige waarde van LD_PRELOAD op
  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) {
        // Verwijder de huidige LD_PRELOAD 
        //unsetenv(env);
        // In plaats van unset, stellen we gewoon onze variabele in op nul
        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
$

Maar stel je voor dat we het niet hebben gevonden en /proc/self/environ bevat "probleemdata".

Laten we eerst proberen met onze eerdere "maskering":

$ (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 gebruikt voor het openen van bestanden dezelfde open(), dus de oplossing is vergelijkbaar met wat al gedaan is in par.2.1, maar nu creëren we een tijdelijk bestand waarin we de waarden van het echte geheugen zonder de strings die bevatten 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);
}

En deze fase is voorbij:

$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$

De volgende voor de hand liggende plek is /proc/self/maps. Er is geen reden om er langer bij stil te staan. De oplossing is volledig identiek aan de vorige: we kopiëren de gegevens uit het bestand zonder de regels tussen libc.so en ld.so.

2.4. Variant van Chokepoint

Deze oplossing vond ik vooral aantrekkelijk vanwege de eenvoud. We vergelijken de adressen van functies die rechtstreeks vanuit worden geladen libc, en de "VOLGENDE"-adressen.

#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;
}

We laden de bibliotheek met het onderscheppen van "open()" en controleren:

$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc-adres: 0x7fa86893b160
Volgend adres: 0x7fa868a26135

De ontkrachting bleek nog eenvoudiger:

#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. Syscalls

Het lijkt erop dat dit alles is, maar laten we nog wat verder graven. Als we de systeemaanroep rechtstreeks naar de kernel sturen, kan dit het gehele onderscheppingsproces omzeilen. De oplossing hieronder is uiteraard architectuurafhankelijk (x86_64). Laten we proberen het te implementeren voor het detecteren van openingen 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) [+]

En deze taak heeft een oplossing. Uittreksel uit man‘a:

ptrace is een hulpmiddel waarmee een ouderproces een ander proces kan observeren en controleren, de gegevens en registers ervan kan bekijken en wijzigen. Deze functie wordt meestal gebruikt om breakpoint in een debugprogramma te plaatsen en systeemaanroepen te volgen.

Het ouderproces kan tracing starten door eerst de functie fork(2) aan te roepen, waarna het resulterende kindproces PTRACE_TRACEME kan uitvoeren, gevolgd door (meestal) de uitvoering van exec(3). Aan de andere kant kan het ouderproces een bestaand proces debuggen met PTRACE_ATTACH.

Tijdens het traceren wordt het kindproces elke keer gestopt wanneer het een signaal ontvangt, zelfs als dit signaal genegeerd wordt. (Een uitzondering is SIGKILL, dat normaal functioneert.) Het ouderproces wordt hierover geïnformeerd bij het aanroepen van wait(2), waarna het de inhoud van het kindproces kan bekijken en wijzigen voordat het opnieuw wordt gestart. Vervolgens staat het ouderproces het kindproces toe om door te gaan, waarbij in sommige gevallen het signaal dat naar het kindproces wordt gestuurd wordt genegeerd of in plaats daarvan een ander signaal wordt gestuurd).

De oplossing is dus om het proces te volgen door het voor elke systeemaanroep te stoppen en, indien nodig, de stroom naar een trapfunctie te omleiden.

#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, &regs);
                        // 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, &regs);
                    }
                }
                ptrace(PTRACE_SYSCALL, program, 0, 0);
                waitpid(program, &status, 0);
            }
        }
        exit(0);
    }
    else {
        sleep(0);
    }
}

We controleren:

$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]

+0-0=5

Een grote dank aan

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, wiens artikelen, broncodes en opmerkingen veel meer hebben gedaan dan ik om deze opmerking hier te laten verschijnen.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster