2009-07-21 11 views
7

Cel: Załaduj plik .so lub plik wykonywalny, który został zweryfikowany do podpisu (lub zweryfikowany względem dowolnego algorytmu).Sprawdź sygnaturę obiektu współużytkowanego Linux przed ładowaniem.

Chcę być w stanie zweryfikować .so/plik wykonywalny, a następnie załadować/execute że .so/wykonywalny z dlopen/...

Klucz w tym jest to, że wydaje się, że nie sposób programowy sprawdź i załaduj. Można ręcznie sprawdzić plik, a następnie załadować go po .. jednak istnieje okno możliwości, w którym ktoś może zamienić ten plik na inny.

Jednym z możliwych rozwiązań, które mogę wymyślić jest załadowanie pliku binarnego, sprawdzenie podpisu, a następnie dlopen/execvt /proc/$PID/fd .... jednak nie wiem, czy jest to wykonalne rozwiązanie.

Ponieważ blokady systemu plików są doradcze w Linuksie, nie są tak przydatne w tym celu ... (no, jest mount -o mand ... ale to jest coś dla poziomu użytkownika, a nie root).

+0

Wygląda ogólnego problemu jest nierozwiązywalny bez interwencji na poziomie jądra: -/ Segmenty mogą być nadpisane raz zweryfikowane ... ptrace może wręcz zmienić sposób wszystko działa ... oczekiwaniu żadnych odpowiedzi, że może coś zrobić "magiczne" ... wygląda na to, że nie da się tego zrobić bez uprawnień na poziomie rootów i sposobu wyłączenia zewnętrznego debugowania. – harningt

Odpowiedz

1

Problem jest zasadniczo nierozwiązywalny w podanej formie, ponieważ obiekty wspólne są ładowane przez mmap() ing w celu przetworzenia miejsca w pamięci. Więc nawet jeśli może być upewnij się, że plik, na którym działał dlopen() był tym, który zbadałeś i zadeklarowałeś OK, każdy, kto może zapisywać do pliku, może zmodyfikować załadowany obiekt pod dowolną datę po załadowałem to.(Dlatego nie uaktualniasz uruchamiania plików binarnych, pisząc do nich - zamiast tego usuwasz, a następnie instalujesz, ponieważ zapisanie do nich prawdopodobnie spowoduje awarię wszystkich działających instancji).

Najlepiej jest upewnić się, że tylko użytkownik, którego używasz, może pisać do pliku, a następnie go sprawdzić, a następnie dlopen(). Twój użytkownik (lub root) nadal może przekradać się do innego kodu, ale procesy z tymi uprawnieniami mogą po prostu ptrace(), aby wykonać ich licytację w każdym razie.

+1

Cóż, 'mmap (,,, MAP_COPY ,,)' dałoby mapowanie, na które nie wpływają dalsze zmiany w pliku na dysku, ale nie jest szeroko stosowane. W Linuksie i większości innych systemów używa się 'mmap (,,, MAP_PRIVATE ,,); nie jest określone przez POSIX, czy zmiany w pliku zmieniają mapowanie, ale generalnie mają one miejsce, chyba że strona została już skopiowana przy zapisie. – ephemient

+0

Ah, dobry haczyk na tym ... i ptrace czyni to wszystko bezużytecznym, czy nie: -/ Sprawia, że ​​DigSig wygląda jak jedyna opcja ... lub system plików, który oferuje dostęp tylko do odczytu do danych, które same są sygnaturami zweryfikowano ... – harningt

+0

Oznaczono to jako "odpowiedź", ponieważ wydaje się, że nie ma alternatywy, która uniemożliwiałaby rootom/obecnym użytkownikom fałszowanie rzeczy. – harningt

1

This project podobno rozwiązuje to na poziomie jądra.

DigSig obecnie oferuje:

  • prowadzony weryfikację metrum ELF plików binarnych i bibliotek współdzielonych.
  • obsługa odwołania podpisu pliku.
  • Mechanizm buforowania podpisów w celu zwiększenia wydajności.
+0

Niesamowite, niestety brakuje powszechnego użytku użytkownika bez praw root ... – harningt

6

wielu dynamicznych łączniki (włącznie) glibc za wsparcie ustawiając zmienną środowiskową LD_AUDIT do oddzielonej dwukropkami listy bibliotek współdzielonych. Biblioteki te mogą łączyć się z różnymi lokalizacjami w procesie ładowania biblioteki dynamicznej.

#define _GNU_SOURCE 
#include <dlfcn.h> 
#include <link.h> 
unsigned int la_version(unsigned int v) { return v; } 
unsigned int la_objopen(struct link_map *l, Lmid_t lmid, uintptr_t *cookie) { 
    if (!some_custom_check_on_name_and_contents(l->l_name, l->l_addr)) 
     abort(); 
    return 0; 
} 

Skompiluj to z cc -shared -fPIC -o test.so test.c lub podobnym.

Możesz zobaczyć glibc/elf/tst-auditmod1.c lub latrace po więcej przykładów lub przeczytać Linkers and Libraries Guide.


Bardzo bardzo specyficzne wewnętrzne glibc, ale nadal można zaczepić do libdl przy starcie.

#define _GNU_SOURCE 
#include <dlfcn.h> 
#include <stdio.h> 

extern struct dlfcn_hook { 
    void *(*dlopen)(const char *, int, void *); 
    int (*dlclose)(void *); 
    void *(*dlsym)(void *, const char *, void *); 
    void *(*dlvsym)(void *, const char *, const char *, void *); 
    char *(*dlerror)(void); 
    int (*dladdr)(const void *, Dl_info *); 
    int (*dladdr1)(const void *, Dl_info *, void **, int); 
    int (*dlinfo)(void *, int, void *, void *); 
    void *(*dlmopen)(Lmid_t, const char *, int, void *); 
    void *pad[4]; 
} *_dlfcn_hook; 
static struct dlfcn_hook *old_dlfcn_hook, my_dlfcn_hook; 

static int depth; 
static void enter(void) { if (!depth++) _dlfcn_hook = old_dlfcn_hook; } 
static void leave(void) { if (!--depth) _dlfcn_hook = &my_dlfcn_hook; } 

void *my_dlopen(const char *file, int mode, void *dl_caller) { 
    void *result; 
    fprintf(stderr, "%s(%s, %d, %p)\n", __func__, file, mode, dl_caller); 
    enter(); 
    result = dlopen(file, mode); 
    leave(); 
    return result; 
} 

int my_dlclose(void *handle) { 
    int result; 
    fprintf(stderr, "%s(%p)\n", __func__, handle); 
    enter(); 
    result = dlclose(handle); 
    leave(); 
    return result; 
} 

void *my_dlsym(void *handle, const char *name, void *dl_caller) { 
    void *result; 
    fprintf(stderr, "%s(%p, %s, %p)\n", __func__, handle, name, dl_caller); 
    enter(); 
    result = dlsym(handle, name); 
    leave(); 
    return result; 
} 

void *my_dlvsym(void *handle, const char *name, const char *version, void *dl_caller) { 
    void *result; 
    fprintf(stderr, "%s(%p, %s, %s, %p)\n", __func__, handle, name, version, dl_caller); 
    enter(); 
    result = dlvsym(handle, name, version); 
    leave(); 
    return result; 
} 

char *my_dlerror(void) { 
    char *result; 
    fprintf(stderr, "%s()\n", __func__); 
    enter(); 
    result = dlerror(); 
    leave(); 
    return result; 
} 

int my_dladdr(const void *address, Dl_info *info) { 
    int result; 
    fprintf(stderr, "%s(%p, %p)\n", __func__, address, info); 
    enter(); 
    result = dladdr(address, info); 
    leave(); 
    return result; 
} 

int my_dladdr1(const void *address, Dl_info *info, void **extra_info, int flags) { 
    int result; 
    fprintf(stderr, "%s(%p, %p, %p, %d)\n", __func__, address, info, extra_info, flags); 
    enter(); 
    result = dladdr1(address, info, extra_info, flags); 
    leave(); 
    return result; 
} 

int my_dlinfo(void *handle, int request, void *arg, void *dl_caller) { 
    int result; 
    fprintf(stderr, "%s(%p, %d, %p, %p)\n", __func__, handle, request, arg, dl_caller); 
    enter(); 
    result = dlinfo(handle, request, arg); 
    leave(); 
    return result; 
} 

void *my_dlmopen(Lmid_t nsid, const char *file, int mode, void *dl_caller) { 
    void *result; 
    fprintf(stderr, "%s(%lu, %s, %d, %p)\n", __func__, nsid, file, mode, dl_caller); 
    enter(); 
    result = dlmopen(nsid, file, mode); 
    leave(); 
    return result; 
} 

static struct dlfcn_hook my_dlfcn_hook = { 
    .dlopen = my_dlopen, 
    .dlclose = my_dlclose, 
    .dlsym = my_dlsym, 
    .dlvsym = my_dlvsym, 
    .dlerror = my_dlerror, 
    .dladdr = my_dladdr, 
    .dlinfo = my_dlinfo, 
    .dlmopen = my_dlmopen, 
    .pad  = {0, 0, 0, 0}, 
}; 

__attribute__((constructor)) 
static void init(void) { 
    old_dlfcn_hook = _dlfcn_hook; 
    _dlfcn_hook = &my_dlfcn_hook; 
} 

__attribute__((destructor)) 
static void fini(void) { 
    _dlfcn_hook = old_dlfcn_hook; 
} 
 
$ cc -shared -fPIC -o hook.so hook.c 
$ cat > a.c 
#include <dlfcn.h> 
int main() { dlopen("./hook.so", RTLD_LAZY); dlopen("libm.so", RTLD_LAZY); } 
^D 
$ cc -ldl a.c 
$ ./a.out 
my_dlopen(libm.so, 1, 0x80484bd) 

Niestety, moje badania prowadzą mnie do wniosku, że nawet jeśli można zaczepić do glibc/elf/dl-load.c:open_verify() (czego nie można), to nie jest możliwe, aby ten wyścig wolne przed kimś pisanie na segmentach twoja biblioteka.

+0

Słodko, ten rodzaj wyglądu wygląda dokładnie tak, jak chcę ... oprócz tego, że jest jedną z tych zmiennych środowiskowych sprawdzanych tylko przy starcie: -/ Projekt wymagający tej funkcji jest często ładowany jako wtyczka do innego produktu ... jednak LD_AUDIT wygląda jak coś przydatnego do obsługi, gdy jest używane w naszych kontrolowanych aplikacjach ... – harningt

+0

Awesome! Dałbym tę jedną z najbardziej pożytecznych drobiazgów informacji i oznaczyłem ją jako odpowiedź, z wyjątkiem przypadku, w którym ktoś wytykał dziurę w teorii. – harningt