Meklē LD_PRELOAD

Šī piezīme tika uzrakstīta 2014. gadā, bet es tikko nokļuvu represijās pret Habrē, un tā neredzēja dienasgaismu. Aizlieguma laikā par to aizmirsu, bet tagad atradu melnrakstos. Domāju dzēst, bet varbūt kādam noderēs.

Meklē LD_PRELOAD

Kopumā neliela piektdienas administratora lasīšana par tēmu “iekļauts” LD_IELĀDĒT.

1. Īsa atkāpe tiem, kas nav pazīstami ar funkciju aizstāšanu

Pārējais var doties tieši uz 2. lpp.

Sāksim ar klasisku piemēru:

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

Mēs apkopojam bez karodziņiem:

$ gcc ./ld_rand.c -o ld_rand

Un, kā gaidīts, mēs iegūstam 5 nejaušus skaitļus, kas mazāki par 100:

$ ./ld_rand
53
93
48
57
20

Bet iedomāsimies, ka mums nav programmas pirmkoda, bet mums ir jāmaina darbība.

Izveidosim savu bibliotēku ar savu funkciju prototipu, piemēram:

int rand(){
  return 42;
}

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

Un tagad mūsu nejaušā izvēle ir diezgan paredzama:

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

Šis triks izskatās vēl iespaidīgāks, ja vispirms eksportējam savu bibliotēku, izmantojot

$ export LD_PRELOAD=$PWD/ld_rand.so

vai arī mēs to darīsim vispirms

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

un pēc tam palaidiet programmu kā parasti. Mēs neesam mainījuši nevienu koda rindiņu pašā programmā, taču tās darbība tagad ir atkarīga no niecīgas funkcijas mūsu bibliotēkā. Turklāt programmas rakstīšanas laikā rinda pat neeksistēja.

Kas lika mūsu programmai izmantot viltojumu rinda? Ņemsim to soli pa solim.
Kad lietojumprogramma tiek startēta, tiek ielādētas noteiktas bibliotēkas, kurās ir programmai nepieciešamās funkcijas. Mēs varam tos apskatīt, izmantojot 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 saraksts var atšķirties atkarībā no OS versijas, taču tajā ir jābūt failam libc.so. Tieši šī bibliotēka nodrošina sistēmas izsaukumus un pamatfunkcijas, piemēram, atvērt, malloc, printf utt Mūsu rinda arī ir starp tiem. Pārliecināsimies par to:

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

Apskatīsim, vai bibliotēku kopa mainās, kad to lieto LD_IELĀDĒT

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

Izrādās, mainīgais ir iestatīts LD_IELĀDĒT liek mums ielādēt ld_rand.so pat neskatoties uz to, ka pati programma to neprasa. Un kopš mūsu funkcijas "rands" slodzes agrāk nekā rinda no libc.so, tad viņa pārvalda riestu.

Labi, mums izdevās aizstāt sākotnējo funkciju, bet kā mēs varam nodrošināt, ka tās funkcionalitāte tiek saglabāta un dažas darbības tiek pievienotas. Pārveidosim mūsu izlases veidu:

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

Šeit kā mūsu “papildinājums” mēs izdrukājam tikai vienu teksta rindiņu, pēc kuras izveidojam rādītāju uz sākotnējo funkciju rinda. Lai iegūtu šīs funkcijas adresi, mums ir nepieciešams dlsym ir bibliotēkas funkcija libdlkas atradīs mūsu rinda dinamisku bibliotēku kaudzē. Pēc tam mēs izsauksim šo funkciju un atgriezīsim tās vērtību. Attiecīgi mums būs jāpievieno "-ldl" montāžas laikā:

$ 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

Un mūsu programma izmanto “native” rinda, iepriekš veicis dažas neķītras darbības.

2. Meklēšanas sāpes

Zinot par iespējamu apdraudējumu, mēs vēlamies to atklāt pirmsielādēšana Tas tika izpildīts. Ir skaidrs, ka labākais veids, kā noteikt, ir iespiest to kodolā, bet mani interesēja noteikšanas iespējas userspace.

Tālāk risinājumi atklāšanai un to atspēkošanai būs pa pāriem.

2.1. Sāksim ar vienkāršu

Kā minēts iepriekš, varat norādīt ielādējamo bibliotēku, izmantojot mainīgo LD_IELĀDĒT vai ierakstot to failā /etc/ld.so.preload. Izveidosim divus vienkāršus detektorus.

Pirmais ir pārbaudīt iestatīto vides mainīgo:

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

Otrais ir pārbaudīt, vai fails ir atvērts:

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

Ielādēsim bibliotēkas:

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

Šeit un zemāk [+] norāda uz veiksmīgu noteikšanu.
Attiecīgi [-] nozīmē atklāšanas apiešanu.

Cik efektīvs ir šāds detektors? Vispirms apskatīsim vides mainīgo:

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

Līdzīgi mēs atbrīvojamies no čeka atvērt:

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

Jā, šeit var izmantot citus veidus, kā piekļūt failam, piemēram, open64, valsts utt., bet patiesībā, lai tos maldinātu, ir vajadzīgas tās pašas 5-10 koda rindas.

2.2. Ejam tālāk

Iepriekš mēs izmantojām getenv() lai iegūtu vērtību LD_IELĀDĒT, taču ir arī “zemāks” veids, kā nokļūt ENV- mainīgie. Mēs neizmantosim starpfunkcijas, bet atsauksimies uz masīvu ** vide, kurā tiek saglabāta vides 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;
}

Tā kā šeit mēs lasām datus tieši no atmiņas, šādu zvanu nevar pārtvert, un mūsu undetect_getenv tas vairs netraucē identificēt ielaušanos.

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

Šķiet, ka šī problēma ir atrisināta? Tas vēl tikai sākas.

Pēc programmas palaišanas mainīgā vērtība LD_IELĀDĒT hakeriem tas vairs nav vajadzīgs atmiņā, tas ir, viņi var to izlasīt un izdzēst pirms jebkādu norādījumu izpildes. Protams, masīva rediģēšana atmiņā ir vismaz slikts programmēšanas stils, bet kā tas var apturēt kādu, kurš tik un tā nevēlas mums labu?

Lai to izdarītu, mums ir jāizveido sava viltus funkcija tajā(), kurā mēs pārtveram instalēto LD_IELĀDĒT un nosūtiet to mūsu linkerim:

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

Izpildām un pārbaudām:

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

Tomēr atmiņa nav pēdējā vieta, kur var atrast aizstāšanu LD_IELĀDĒT, ir arī /proc/. Sāksim ar acīmredzamo /proc/{PID}/environ.

Patiesībā ir universāls risinājums neatklāt ** vide и /proc/self/environ. Problēma ir "nepareizā" uzvedība unsetenv(env).

pareizais variants

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 iedomāsimies, ka neatradām un /proc/self/environ satur "problemātiskus" datus.

Vispirms pamēģināsim ar mūsu iepriekšējo "maskējumu":

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

kaķis izmanto to pašu, lai atvērtu failu atvērt (), tāpēc risinājums ir līdzīgs tam, kas jau tika darīts sadaļā 2.1, bet tagad mēs izveidojam pagaidu failu, kurā kopējam patiesās atmiņas vērtības bez rindām, kas satur LD_IELĀDĒT.

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

Un šis posms ir izturēts:

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

Nākamā acīmredzamā vieta ir /proc/self/maps. Nav jēgas pie tā kavēties. Risinājums ir absolūti identisks iepriekšējam: kopējiet datus no faila, atskaitot līnijas starp tām libc.so и ld.tātad.

2.4. Opcija no Chokepoint

Šis risinājums man īpaši patika tā vienkāršības dēļ. Mēs salīdzinām to funkciju adreses, kas ielādētas tieši no libc, un “NEXT” adreses.

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

Notiek bibliotēkas ielāde ar pārtveršanu "atvērt ()" un pārbaudiet:

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

Atspēkojums izrādījās vēl vienkāršāks:

#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

Šķiet, ka tas ir viss, bet mēs tik un tā plosīsimies. Ja mēs novirzīsim sistēmas zvanu tieši uz kodolu, tas apies visu pārtveršanas procesu. Tālāk sniegtais risinājums, protams, ir atkarīgs no arhitektūras (x86_64). Mēģināsim to ieviest, lai atklātu atvērumu 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) [+]

Un šai problēmai ir risinājums. Izvilkums no vīrietis'A:

ptrace ir rīks, kas ļauj vecākajam procesam novērot un kontrolēt cita procesa gaitu, skatīt un mainīt tā datus un reģistrus. Parasti šo funkciju izmanto, lai izveidotu pārtraukuma punktus atkļūdošanas programmā un uzraudzītu sistēmas zvanus.

Vecākprocess var sākt izsekošanu, vispirms izsaucot fork(2), un pēc tam iegūtais pakārtotais process var izpildīt PTRACE_TRACEME, kam seko (parasti) izpildot exec(3). No otras puses, vecākais process var sākt esošā procesa atkļūdošanu, izmantojot PTRACE_ATTACH.

Izsekojot, bērna process apstājas katru reizi, kad tas saņem signālu, pat ja signāls tiek ignorēts. (Izņēmums ir SIGKILL, kas darbojas normāli.) Vecāku process tiks informēts par to, izsaucot gaidīšanas (2), pēc tam tas var skatīt un modificēt pakārtotā procesa saturu pirms tā palaišanas. Pēc tam vecāku process ļauj bērnam turpināt skriet, dažos gadījumos ignorējot viņam nosūtīto signālu vai tā vietā nosūtot citu signālu).

Tādējādi risinājums ir uzraudzīt procesu, apturot to pirms katra sistēmas izsaukuma un, ja nepieciešams, novirzīt pavedienu uz āķa funkciju.

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

Pārbaude:

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

+0-0=5

Liels tev paldies

Čārlzs Hubeins
Chokepoint
ValdikSS
Filips Teuvens
derhass

, kuras raksti, avoti un komentāri paveica daudz vairāk nekā es, lai šī piezīme šeit parādītos.

Avots: www.habr.com

Pievieno komentāru