Ieškote LD_PRELOAD

Šis užrašas buvo parašytas 2014 m., bet aš ką tik patyriau represijas prieš Habré ir jis neišvydo dienos šviesos. Per draudimą tai pamiršau, bet dabar radau juodraščiuose. Galvojau ištrinti, bet gal kam nors pravers.

Ieškote LD_PRELOAD

Apskritai, mažas penktadienio administratorius skaito temą, kaip ieškoti „įtraukta“ LD_IŠANKSTINIO ĮKAUTI.

1. Trumpas nukrypimas tiems, kurie nėra susipažinę su funkcijų pakeitimu

Likusieji gali eiti tiesiai į 2 p.

Pradėkime nuo klasikinio pavyzdžio:

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

Kompiliuojame be jokių vėliavėlių:

$ gcc ./ld_rand.c -o ld_rand

Ir, kaip ir tikėtasi, gauname 5 atsitiktinius skaičius, mažesnius nei 100:

$ ./ld_rand
53
93
48
57
20

Bet įsivaizduokime, kad mes neturime programos šaltinio kodo, bet turime pakeisti elgesį.

Sukurkime savo biblioteką su savo funkcijos prototipu, pavyzdžiui:

int rand(){
  return 42;
}

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

Ir dabar mūsų atsitiktinis pasirinkimas yra gana nuspėjamas:

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

Šis triukas atrodo dar įspūdingesnis, jei pirmą kartą eksportuosime savo biblioteką per

$ export LD_PRELOAD=$PWD/ld_rand.so

arba mes tai padarysime pirmi

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

ir paleiskite programą kaip įprasta. Pačioje programoje nepakeitėme nė vienos kodo eilutės, bet dabar jos elgsena priklauso nuo mažytės funkcijos mūsų bibliotekoje. Be to, rašant programą, eilė net neegzistavo.

Dėl ko mūsų programa buvo naudojama netikra eilė? Paimkime tai žingsnis po žingsnio.
Kai programa paleidžiama, įkeliamos tam tikros bibliotekos, kuriose yra programai reikalingų funkcijų. Galime juos peržiūrėti naudodami 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)

Šis sąrašas gali skirtis priklausomai nuo OS versijos, tačiau jame turi būti failas libc.taip. Būtent ši biblioteka teikia sistemos iškvietimus ir pagrindines funkcijas, pvz atidaryti, malloc, printf ir tt Mūsų eilė taip pat yra tarp jų. Įsitikinkime tuo:

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

Pažiūrėkime, ar bibliotekų rinkinys keičiasi naudojant LD_IŠANKSTINIO ĮKAUTI

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

Pasirodo, kintamasis nustatytas LD_IŠANKSTINIO ĮKAUTI verčia mus krauti ld_rand.so net nepaisant to, kad pati programa to nereikalauja. Ir nuo mūsų funkcijos "rand" pakrauna anksčiau nei eilė nuo libc.taip, tada ji valdo barą.

Gerai, mums pavyko pakeisti savąją funkciją, bet kaip užtikrinti, kad jos funkcionalumas būtų išsaugotas ir kai kurie veiksmai būtų pridėti. Pakeiskime savo atsitiktinumą:

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

Čia kaip „papildymas“ spausdiname tik vieną teksto eilutę, po kurios sukuriame žymeklį į pradinę funkciją eilė. Norėdami gauti šios funkcijos adresą, mums reikia dlsym yra bibliotekos funkcija libdlkuris ras mūsų eilė dinaminių bibliotekų krūvoje. Po to iškviesime šią funkciją ir grąžinsime jos reikšmę. Atitinkamai turėsime pridėti "-ldl" surinkimo metu:

$ 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

Ir mūsų programa naudoja „gimtąją“ eilė, prieš tai atlikęs kai kuriuos nepadorius veiksmus.

2. Ieškojimo skausmas

Žinodami apie galimą grėsmę, norime ją aptikti preload Jis buvo atliktas. Aišku, kad geriausias būdas aptikti yra įstumti jį į branduolį, bet mane domino aptikimo galimybės userspace.

Toliau aptikimo ir jų paneigimo sprendimai bus pateikti poromis.

2.1. Pradėkime nuo paprasto

Kaip minėta anksčiau, naudodami kintamąjį galite nurodyti įkeliamą biblioteką LD_IŠANKSTINIO ĮKAUTI arba įrašydami jį į failą /etc/ld.so.preload. Sukurkime du paprastus detektorius.

Pirmiausia reikia patikrinti nustatytą aplinkos kintamąjį:

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

Antrasis yra patikrinti, ar failas atidarytas:

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

Įkelkime bibliotekas:

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

Čia ir žemiau [+] rodo sėkmingą aptikimą.
Atitinkamai [-] reiškia aptikimo apėjimą.

Kiek efektyvus toks detektorius? Pirmiausia pažvelkime į aplinkos kintamąjį:

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

Panašiai atsikratome čekio atidaryti:

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

Taip, čia galima naudoti kitus būdus pasiekti failą, pvz., atviras64, stat ir pan., bet, tiesą sakant, norint juos apgauti, reikia tų pačių 5-10 kodo eilučių.

2.2. Eikime toliau

Aukščiau naudojome getenv () gauti vertę LD_IŠANKSTINIO ĮKAUTI, bet yra ir „žemesnio lygio“ būdas pasiekti ENV- kintamieji. Mes nenaudosime tarpinių funkcijų, bet nurodysime masyvą **aplinka, kurioje saugoma aplinkos kopija:

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

Kadangi čia mes skaitome duomenis tiesiai iš atminties, toks skambutis negali būti perimtas, o mūsų undetect_getenv jis nebetrukdo nustatyti įsibrovimo.

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

Atrodytų, kad ši problema išspręsta? Tai dar tik prasideda.

Paleidus programą, kintamojo reikšmė LD_IŠANKSTINIO ĮKAUTI įsilaužėliams jo nebereikia atmintyje, tai yra, jie gali jį perskaityti ir ištrinti prieš vykdydami bet kokias instrukcijas. Žinoma, masyvo redagavimas atmintyje yra bent jau blogas programavimo stilius, bet ar tai gali sustabdyti žmogų, kuris vis tiek mums nelinki gero?

Norėdami tai padaryti, turime sukurti savo netikrą funkciją init (), kuriame perimame įdiegtą LD_IŠANKSTINIO ĮKAUTI ir perduokite jį mūsų nuorodai:

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

Vykdome ir tikriname:

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

Tačiau atmintis nėra paskutinė vieta, kur galite rasti pakaitalą LD_IŠANKSTINIO ĮKAUTI, taip pat yra /proc/. Pradėkime nuo akivaizdžių dalykų /proc/{PID}/environ.

Tiesą sakant, yra universalus sprendimas neaptikti **aplinka и /proc/self/environ. Problema yra „neteisingas“ elgesys unsetenv(env).

teisingas variantas

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
$

Bet įsivaizduokime, kad neradome ir /proc/self/environ yra „probleminių“ duomenų.

Pirmiausia pabandykime su ankstesniu „masuku“:

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

kaip naudoja tą patį failui atidaryti atviras(), todėl sprendimas yra panašus į tai, kas jau buvo padaryta 2.1 skyriuje, tačiau dabar sukuriame laikiną failą, kuriame nukopijuojame tikrosios atminties reikšmes be eilučių, kuriose yra LD_IŠANKSTINIO ĮKAUTI.

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

Ir šis etapas praėjo:

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

Kita akivaizdi vieta yra /proc/self/maps. Nėra prasmės ties tuo gyvent. Sprendimas yra visiškai identiškas ankstesniam: nukopijuokite duomenis iš failo atėmus eilutes tarp libc.taip и ld.taip.

2.4. Pasirinkimas iš Chokepoint

Šis sprendimas man ypač patiko dėl savo paprastumo. Mes lyginame tiesiogiai iš įkeltų funkcijų adresus libc, ir „KITAS“ adresai.

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

Įkeliama biblioteka su perėmimu "atviras()" ir patikrinkite:

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

Paneigimas pasirodė dar paprastesnis:

#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

Atrodytų, tai ir viskas, bet vis tiek plekšnėsim. Jei nukreipsime sistemos iškvietimą tiesiai į branduolį, tai apeis visą perėmimo procesą. Toliau pateiktas sprendimas, žinoma, priklauso nuo architektūros (x86_64). Pabandykime tai įgyvendinti, kad aptiktume angą 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) [+]

Ir ši problema turi sprendimą. Ištrauka iš vyras'A:

ptrace – tai įrankis, leidžiantis pirminiam procesui stebėti ir kontroliuoti kito proceso eigą, peržiūrėti ir keisti jo duomenis bei registrus. Paprastai ši funkcija naudojama derinimo programoje sukurti lūžio taškus ir stebėti sistemos skambučius.

Pirminis procesas gali pradėti sekti pirmiausia iškviesdamas fork(2), o tada gautas antrinis procesas gali vykdyti PTRACE_TRACEME, po kurio (paprastai) vykdant exec(3). Kita vertus, pirminis procesas gali pradėti derinti esamą procesą naudodamas PTRACE_ATTACH.

Sekimas sustabdo vaiko procesą kiekvieną kartą, kai jis gauna signalą, net jei signalas ignoruojamas. (Išimtis yra SIGKILL, kuris veikia normaliai.) Pirminis procesas bus apie tai informuotas iškviečiant palaukti(2), po kurio jis gali peržiūrėti ir keisti antrinio proceso turinį prieš jam prasidėjus. Tada tėvų procesas leidžia vaikui toliau bėgioti, kai kuriais atvejais ignoruojant jam siunčiamą signalą arba vietoj jo siunčiant kitą signalą).

Taigi, sprendimas yra stebėti procesą, sustabdant jį prieš kiekvieną sistemos iškvietimą ir, jei reikia, nukreipti giją į kablio funkciją.

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

Mes patikriname:

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

+0-0=5

Thank you so much

Charlesas Hubainas
užspringimo taškas
ValdikSS
Philippe'as Teuwenas
derhasas

, kurios straipsniai, šaltiniai ir komentarai padarė daug daugiau nei aš, kad šis užrašas atsirastų čia.

Šaltinis: www.habr.com

Добавить комментарий