2009-07-21 20 views
7

Objetivo: Cargue .so o ejecutable que se haya verificado que está firmado (o verificado contra un algoritmo arbitrario).Verifique la firma del objeto compartido de Linux antes de la carga

Quiero ser capaz de verificar un .so/ejecutable y luego cargar/ejecutar ese .so/ejecutable con dlopen/...

La llave de esto es que no parece haber ninguna manera programática a check-then-load. Uno podría verificar el archivo manualmente y luego cargarlo después ... sin embargo, hay una ventana de oportunidad dentro de la cual alguien podría intercambiar ese archivo por otro.

Una posible solución que se me ocurre es cargar el binario, verificar la firma y luego actualizar/ejecutar el /proc/$PID/fd ... sin embargo, no sé si esa es una solución viable.

Como los bloqueos del sistema de archivos son avisos en Linux, no son tan útiles para este propósito ... (bueno, hay mount -o mand ... pero esto es algo para uso en el nivel de usuario, no en el de root).

+0

Parece que el problema general es imposible de resolver sin la intervención a nivel de kernel: -/ segmentos se pueden sobrescribir cuando verificados ... ptrace pura y simple puede cambiar la forma de las cosas están funcionando ... A la espera de respuestas que pueden hacer algo 'mágico' ... parece que no hay forma de hacerlo sin privilegios de nivel raíz y alguna forma de desactivar la depuración externa. – harningt

Respuesta

1

El problema es esencialmente insoluble en la forma que ha dado, porque los objetos compartidos son cargados por mmap() ing para procesar el espacio de la memoria. Entonces, incluso si podría asegurarse de que el archivo que dlopen() funcionó fue el que examinó y declaró OK, cualquiera que pueda escribir en el archivo puede modificar el objeto cargado en cualquier tiempo después de que haya lo cargué.(Esta es la razón por la cual no actualiza los binarios en ejecución al escribirles; en su lugar, elimina y luego instala, porque escribir en ellos probablemente bloqueará las instancias en ejecución).

Su mejor opción es asegurarse de que solo el usuario que está ejecutando pueda escribir en el archivo, luego examinarlo y luego dlopen(). Su usuario (o raíz) aún puede introducir código diferente, pero los procesos con esos permisos podrían simplemente indicarle() que haga su oferta de todos modos.

+1

Bueno, 'mmap (,,, MAP_COPY ,,)' daría una asignación que no se ve afectada por más cambios en el archivo en el disco, pero no se implementa ampliamente. En Linux y en la mayoría de los demás sistemas, se usa 'mmap (,,, MAP_PRIVATE ,,)'; POSIX no especifica si los cambios en el archivo cambian la asignación, pero generalmente lo hacen a menos que la página ya haya sido copiada en escritura. – ephemient

+0

Ah, buena idea de eso ... y ptrace hace que todo esto sea inútil, ¿no? -/ Hace que DigSig parezca la única opción ... o un sistema de archivos que ofrece acceso de solo lectura a los datos que son firmas verificado ... – harningt

+0

Marcado como la "respuesta" ya que no parece existir ninguna alternativa que impida que un usuario raíz/actual se deshaga de cosas. – harningt

1

This project supuestamente resuelve esto en el nivel de kernel.

DigSig ofrece actualmente:

  • tiempo de ejecución de verificación de firma de binarios ELF y bibliotecas compartidas.
  • soporte para la revocación de firmas del archivo.
  • un mecanismo de caché de firma para mejorar el rendimiento.
+0

Impresionante, lamentablemente falta el uso del usuario común sin derechos de root ... – harningt

6

Muchos enlazadores dinámicas (incluyendo de Glibc) apoyo ajuste variable de LD_AUDIT ambiente a una lista separada por dos puntos de bibliotecas compartidas. Estas bibliotecas pueden enlazarse en varias ubicaciones en el proceso dinámico de carga de la biblioteca.

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

Compilar esto con cc -shared -fPIC -o test.so test.c o similar.

Puedes ver glibc/elf/tst-auditmod1.c o latrace para ver más ejemplos, o leer el Linkers and Libraries Guide.


Muy muy específico a los interiores de Glibc, pero todavía se puede enganchar en libdl en tiempo de ejecución.

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

Por desgracia, mis investigaciones me están conduciendo a la conclusión de que incluso si usted podría enganchar en glibc/elf/dl-load.c:open_verify() (que no se puede), no es posible hacer esta carrera libre contra alguien escribir sobre segmentos de tu biblioteca

+0

Dulce, este aspecto se parece a lo que quiero ... excepto que es una de esas variables de entorno que solo se verifican al inicio: -/ El proyecto que necesita esta característica es uno que a menudo se carga como un complemento a otro producto ... sin embargo LD_AUDIT parece algo útil de manejar cuando se usa en nuestras aplicaciones controladas ... – harningt

+0

¡Impresionante! Le daría a este el fragmento de información más útil y lo marcaría como la respuesta, excepto en el caso en que alguien hizo un agujero en la teoría. – harningt

Cuestiones relacionadas