systemcall sistema reglas operativo manejo llamadas interfaz estructura calls linux gcc linker system-calls binutils

reglas - Cómo conectar TODAS las llamadas al sistema de Linux durante una ejecución binaria



reglas de llamadas al sistema (1)

Estoy tratando de modificar el comportamiento predeterminado de la llamada al sistema de Linux. En este momento estoy tratando de enganchar y agregar una declaración de impresión simple antes de que sean realmente invocados. Conozco la opción estándar ''wrap'' del enlazador GCC y cómo se puede usar para enganchar wrappers. Enlace a las opciones de GCC Linker . Esto funciona perfectamente para open (), fstat (), fwrite () etc (donde en realidad estoy enganchando las envolturas de libc).

ACTUALIZAR:

La limitación es que NO todas las llamadas al sistema se conectan con este enfoque. Para ilustrarlo, tomemos un binario simple compilado estáticamente. Cuando tratamos de agregar wrappers, están siendo afectados por las llamadas que presentamos después de main () (Por favor, vea la salida de strace que se muestra a continuación)

> strace ./sample execve("./sample", ["./sample"], [/* 72 vars */]) = 0 uname({sys="Linux", node="kumar", ...}) = 0 brk(0) = 0x71f000 brk(0x7201c0) = 0x7201c0 arch_prctl(ARCH_SET_FS, 0x71f880) = 0 readlink("/proc/self/exe", "/home/admin/sample"..., 4096) = 41 brk(0x7411c0) = 0x7411c0 brk(0x742000) = 0x742000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbcc54d1000 write(1, "Hello from the wrapped readlink "..., 36Hello from the wrapped readlink :з ) = 36 readlink("/usr/bin/gnome-www-browser", "/etc/alternatives/gnome-www-brow"..., 255) = 35 write(1, "/etc/alternatives/gnome-www-brow"..., 36/etc/alternatives/gnome-www-browser ) = 36 exit_group(36) = ? +++ exited with 36 +++

Si observamos cuidadosamente el binario, la primera llamada "no interceptada" readlink () (llamada al sistema 89 es decir, 0x59) proviene de estas líneas: alguna porción de código relacionada con el enlazador (es decir, _dl_get_origin ) hace un readlink () para su funcionamiento. Estos syscall implícitos (aunque están presentes en el código binario) nunca se enganchan con nuestro enfoque "wrap".

000000000051875c <_dl_get_origin>: 51875c: b8 59 00 00 00 mov $0x59,%eax 518761: 55 push %rbp 518762: 53 push %rbx 518763: 48 81 ec 00 10 00 00 sub $0x1000,%rsp 51876a: 48 89 e6 mov %rsp,%rsi 51876d: 0f 05 syscall

¿Cómo extender la idea de envoltura a las llamadas al sistema como readlink () (incluyendo todas las implícitas que se invocan)?


Tengo una opción para envolver, la cita del manual :

- símbolo de envoltura

Use una función de envoltura para el símbolo. Cualquier referencia indefinida al símbolo se resolverá en __wrap_symbol. Cualquier referencia indefinida a __real_symbol se resolverá en el símbolo. Esto se puede usar para proporcionar un contenedor para una función del sistema. La función de envoltura se debe llamar __wrap_symbol. Si desea llamar al sistema, debe llamar a __real_symbol.

Funciona bien con llamadas al sistema también. Aquí hay un ejemplo con readlink :

#include <stdio.h> #include <string.h> #include <unistd.h> ssize_t __real_readlink(const char *path, char *buf, size_t bufsiz); ssize_t __wrap_readlink(const char *path, char *buf, size_t bufsiz) { puts("Hello from the wrapped readlink :з"); __real_readlink(path, buf, bufsiz); } int main(void) { const char testLink[] = "/usr/bin/gnome-www-browser"; char buf[256]; memset(buf, 0, sizeof(buf)); readlink(testLink, buf, sizeof(buf)-1); puts(buf); }

Para pasar la opción al vinculador desde el compilador use la opción -Wl :

$ gcc test.c -o a -Wl,--wrap=readlink $ ./a Hello from the wrapped readlink :з /etc/alternatives/gnome-www-browser

La idea es que __wrap_func es tu contenedor de funciones. El enlazador __real_func se vincularía con la función real func . Y cada llamada a un func en el código sería reemplazada por __wrap_func .

UPD: uno puede notar que un ser binario compilado estático llama a otro readlink , que no está siendo interceptado. Para entender el motivo, solo haga un pequeño experimento: compile el código en el archivo objeto y liste los símbolos, como:

$ gcc test.c -c -o a.o -Wl,--wrap=readlink $ nm a.o 0000000000000037 T main U memset U puts U readlink U __real_readlink U __stack_chk_fail 0000000000000000 T __wrap_readlink

Lo interesante aquí es que no verá referencias a un conjunto de funciones que se ven con strace antes de ingresar a la función principal, por ejemplo, uname() , brk() , access() , etc. Esto se debe a que la función principal no es el primer código que se llama en tu binario. Un poco de investigación con objdump le mostrará que la primera función llamada _start .

Ahora, hagamos otro ejemplo: anule la función _start :

$ cat test2.c #include <stdio.h> #include <unistd.h> void _start() { puts("Hello"); _exit(0); } $ gcc test2.c -o a -nostartfiles $ strace ./a execve("./a", ["./a"], [/* 69 vars */]) = 0 brk(0) = 0x150c000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece55d000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=177964, ...}) = 0 mmap(NULL, 177964, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f3ece531000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "/177ELF/2/1/1/0/0/0/0/0/0/0/0/0/3/0>/0/1/0/0/0/320/37/2/0/0/0/0/0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1840928, ...}) = 0 mmap(NULL, 3949248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3ecdf78000 mprotect(0x7f3ece133000, 2093056, PROT_NONE) = 0 mmap(0x7f3ece332000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7f3ece332000 mmap(0x7f3ece338000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3ece338000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece530000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece52e000 arch_prctl(ARCH_SET_FS, 0x7f3ece52e740) = 0 mprotect(0x7f3ece332000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f3ece55f000, 4096, PROT_READ) = 0 munmap(0x7f3ece531000, 177964) = 0 fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 10), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece55c000 write(1, "Hello/n", 6Hello ) = 6 exit_group(0) = ? +++ exited with 0 +++ $

¡¿Qué era?! Simplemente anulamos la primera función en el binario, y seguimos viendo las llamadas al sistema, ¿por qué?

En realidad, se debe a que las llamadas no se ejecutan en su aplicación, sino en el núcleo antes de que la aplicación se cargue en la memoria y se permite su ejecución.

UPD: como vimos anteriormente, la aplicación no llama a las funciones. Honestamente, no pude encontrar lo que se está haciendo para los binarios estadísticos después de que un intérprete de comandos execve para su aplicación, pero de la lista parece que cada llamada que ve la hace el kernel en sí, sin ninguna aplicación secundaria, como el enlazador dinámico que aren No es necesario para binarios estáticos (y porque hay funciones como brk que funciona con segmentos de datos) .

Lo que sea, seguramente no puedes modificar este comportamiento tan fácil, necesitarás algo de piratería. Porque si pudieras anular fácilmente una función para el código que se ejecuta antes de tu ejecución binaria, es decir, desde el otro binario, sería un gran agujero negro en la seguridad, solo imagina: una vez que necesitas derechos de root, anulas una función con uno para ejecutar su código, y espere un poco mientras algún daemon con derechos de root ejecuta un script, y así activa su código.