¿Cómo podría interceptar las llamadas del sistema Linux?

10 minutos de lectura

avatar de usuario
Vhaerun

Además del truco LD_PRELOAD y los módulos del kernel de Linux que reemplazan una cierta llamada al sistema con una proporcionada por usted, ¿existe alguna posibilidad de interceptar una llamada al sistema (abierta, por ejemplo), para que primero pase por su función, antes de llegar a la apertura real?

  • La pregunta debe aclararse: es demasiado vaga. ¿Por qué LD_PRELOAD no es suficiente?

    – Arafangión

    17 mayo 2010 a las 14:12

  • @Arafangion: LD_PRELOAD le permite interceptar llamadas de biblioteca. Pero las llamadas al núcleo son algo diferente.

    – PP.

    8 de julio de 2010 a las 13:31

avatar de usuario
DJ Capelis

¿Por qué no puedes/no quieres usar el truco LD_PRELOAD?

Código de ejemplo aquí:

/*
 * File: soft_atimes.c
 * Author: D.J. Capelis
 *
 * Compile:
 * gcc -fPIC -c -o soft_atimes.o soft_atimes.c
 * gcc -shared -o soft_atimes.so soft_atimes.o -ldl
 *
 * Use:
 * LD_PRELOAD="./soft_atimes.so" command
 *
 * Copyright 2007 Regents of the University of California
 */

#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>

extern int errorno;

int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;

int open(const char * pathname, int flags, mode_t mode)
{
    if (NULL == _open) {
        _open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
    }
    if(flags & O_CREAT)
        return _open(pathname, flags | O_NOATIME, mode);
    else
        return _open(pathname, flags | O_NOATIME, 0);
}

int open64(const char * pathname, int flags, mode_t mode)
{
    if (NULL == _open64) {
        _open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
    }
    if(flags & O_CREAT)
        return _open64(pathname, flags | O_NOATIME, mode);
    else
        return _open64(pathname, flags | O_NOATIME, 0);
}

Por lo que entiendo… es más o menos el truco LD_PRELOAD o un módulo del kernel. No hay mucho término medio a menos que desee ejecutarlo con un emulador que pueda atrapar su función o reescribir el código en el binario real para atrapar su función.

Suponiendo que no puede modificar el programa y no puede (o no quiere) modificar el núcleo, el enfoque LD_PRELOAD es el mejor, suponiendo que su aplicación es bastante estándar y no es realmente una que intenta pasar maliciosamente tu interceptación. (En cuyo caso necesitará una de las otras técnicas).

  • Es totalmente opcional que un programa reconozca LD_PRELOAD. No todos los programas se vinculan con libc.

    – VIPW

    12 de diciembre de 2012 a las 20:33

  • @vipw ¿puedes dar más detalles? ¿Cómo es que un programa puede pasar por alto LD_PRELOAD? Todo programa que no se vincule con libc no tiene nada que ver con el hecho de que el enlazador cargará una biblioteca determinada antes que las demás al cargar un ejecutable, si se especifica con LD_PRELOAD. Si esa biblioteca tiene una función llamada por el ejecutable, el programa busca primero en la biblioteca cargada LD_PRELOAD. No importa que las bibliotecas posteriores hayan implementado la función también.

    – acib708

    11/03/2015 a las 19:29


  • @ acib708 Lo que quiero decir es que un programa puede hacer llamadas al sistema sin usar libc. Entonces, la biblioteca que se está cargando en realidad no importa, ya que no se llama a ningún símbolo. En cambio, una pequeña pieza de ensamblaje para configurar los registros y crear una interrupción puede realizar la llamada.

    – VIPW

    2 de abril de 2015 a las 9:36


  • @vipw Oh, está bien, sí, de acuerdo.

    – acib708

    17 abr 2015 a las 20:27


  • 100% de acuerdo: tengo exactamente ese problema con Golang: mi biblioteca de stubs se carga pero nada de eso se llama más allá del constructor… de hecho, golang ha decidido no usar la libc por… razones.

    –Eric

    18 de agosto de 2021 a las 6:47

Valgrind se puede utilizar para interceptar cualquier llamada de función. Si necesita interceptar una llamada al sistema en su producto terminado, esto no servirá de nada. Sin embargo, si intenta interceptar durante el desarrollo, puede ser muy útil. Con frecuencia he usado esta técnica para interceptar funciones hash para poder controlar el hash devuelto con fines de prueba.

En caso de que no lo sepa, Valgrind se usa principalmente para encontrar pérdidas de memoria y otros errores relacionados con la memoria. Pero la tecnología subyacente es básicamente un emulador x86. Emula su programa e intercepta llamadas a malloc/free, etc. Lo bueno es que no necesita recompilar para usarlo.

Valgrind tiene una característica que ellos llaman Envoltura de funciones, que se utiliza para controlar la interceptación de funciones. Ver apartado 3.2 de la Manual de Valgrind para detalles. Puede configurar el ajuste de funciones para cualquier función que desee. Una vez que se intercepta la llamada, se invoca la función alternativa que proporcione.

  • Valgrind es un simulador de CPU completo, por lo que no se trata tanto de interceptar llamadas al sistema, sino de proporcionar un gancho cuando ocurre una llamada al sistema en su CPU simulada, antes de pasar la llamada al sistema al kernel.

    – Timmmmm

    4 de noviembre de 2021 a las 23:25

  • Por lo tanto, es una opción para fines de depuración, pero no para uso en producción.

    – Timmmmm

    4 de noviembre de 2021 a las 23:25

Algunas aplicaciones pueden engañar a strace/ptrace para que no se ejecute, por lo que la única opción real que he tenido es usar systemtap

Systemtap puede interceptar un montón de llamadas al sistema si es necesario debido a su coincidencia de comodines. Systemtap no es C, sino un lenguaje separado. En el modo básico, systemtap debería evitar que hagas cosas estúpidas, pero también puede ejecutarse en “modo experto” que recurre a permitir que un desarrollador use C si es necesario.

No requiere que parchee su kernel (o al menos no debería), y una vez que se ha compilado un módulo, puede copiarlo desde un cuadro de prueba/desarrollo e insertarlo (a través de insmod) en un sistema de producción.

Todavía tengo que encontrar una aplicación de Linux que haya encontrado una manera de solucionar/evitar que systemtap me atrape.

  • ¿Cómo evitaría una aplicación ptrace?

    – Matheus Santana

    3 de abril de 2018 a las 9:49

avatar de usuario
timmmm

Primero, eliminemos algunas respuestas negativas que otras personas han dado:

  • Utilizar LD_PRELOAD. Sí, dijiste “Además LD_PRELOAD…” en la pregunta, pero aparentemente eso no es suficiente para algunas personas. Esta no es una buena opción porque solo funciona si el programa usa libc, lo cual no es necesariamente el caso.
  • Utilice Systemtap. Sí, dijiste “Además … Módulos del kernel de Linux” en la pregunta, pero aparentemente eso no es suficiente para algunas personas. Esta no es una buena opción porque tienes que cargar un módulo kernel personalizado que es un gran dolor en el culo y también requiere root.
  • Valgrind. Esto funciona, pero funciona simulando la CPU, por lo que es muy lento y muy complicado. Está bien si solo está haciendo esto para una depuración única. No es realmente una opción si estás haciendo algo digno de producción.
  • Varias cosas de auditoría de syscall. No creo que registrar llamadas al sistema cuente como “interceptarlas”. Claramente queremos modificar los parámetros de syscall/valores devueltos o redirigir el programa a través de algún otro código.

Sin embargo, hay otras posibilidades que aún no se mencionan aquí. Tenga en cuenta que soy nuevo en todo esto y aún no he probado nada de eso, por lo que puedo estar equivocado en algunas cosas.

Reescribir el código

En teoría, podría usar algún tipo de cargador personalizado que reescriba las instrucciones de llamada al sistema para saltar a un controlador personalizado. Pero creo que sería una auténtica pesadilla implementarlo.

ksondas

ksondas son una especie de sistema de instrumentación del kernel. Solo tienen acceso de solo lectura a cualquier cosa, por lo que no puede usarlos para interceptar llamadas del sistema, solo registrarlas.

rastrear

rastrear es la API que usan los depuradores como GDB para hacer su depuración. Hay un PTRACE_SYSCALL opción que pausará la ejecución justo antes/después de las llamadas al sistema. A partir de ahí, puedes hacer prácticamente lo que quieras de la misma manera que GDB. Aquí hay un artículo sobre cómo modificar los parámetros de syscall usando ptrace. Sin embargo, aparentemente tiene una gran sobrecarga.

Seccomp

Seccomp es un sistema que está diseñado para permitirle filtrar llamadas al sistema. No puede modificar los argumentos, pero lata bloquearlos o devolver errores personalizados. Los filtros Seccomp son programas BPF. Si no está familiarizado, son básicamente programas arbitrarios que los usuarios pueden ejecutar en una VM del espacio del kernel. Esto evita el cambio de contexto de usuario/núcleo que los hace más rápidos que ptrace.

Si bien no puede modificar los argumentos directamente desde su programa BPF, lata regreso SECCOMP_RET_TRACE que desencadenará un ptraceing padre para romper. Así que es básicamente lo mismo que PTRACE_SYSCALL excepto que puede ejecutar un programa en el espacio del kernel para decidir si realmente desea interceptar una llamada al sistema en función de sus argumentos. Por lo tanto, debería ser más rápido si solo desea interceptar algunas llamadas al sistema (por ejemplo, open() con caminos específicos).

Creo que esta es probablemente la mejor opción. Aquí hay un artículo al respecto del mismo autor que el anterior.. Tenga en cuenta que usan BPF clásico en lugar de eBPF, pero supongo que también puede usar eBPF.

Editar: en realidad solo puedes usar BPF clásico, no eBPF. hay un artículo de LWN al respecto.

Aquí hay algunas preguntas relacionadas. Definitivamente vale la pena leer el primero.

  • ¿Puede eBPF modificar el valor de retorno o los parámetros de una llamada al sistema?
  • Interceptar solo llamada del sistema con PTRACE_SINGLESTEP
  • ¿Es esta una buena manera de interceptar llamadas al sistema?
  • Mínima forma de sobrecarga de interceptar llamadas al sistema sin modificar el kernel

También hay un buen artículo sobre la manipulación de llamadas al sistema a través de ptrace aquí.

No tengo la sintaxis para hacer esto correctamente con un LKM, pero este artículo proporciona una buena descripción general de lo que debe hacer: http://www.linuxjournal.com/article/4378

También puede parchear la función sys_open. Comienza en la línea 1084 de file/open.c a partir de linux-2.6.26.

También puede ver si no puede usar inotify, systemtap o SELinux para hacer todo este registro por usted sin tener que construir un nuevo sistema.

  • ¿Cómo usaríamos SELinux para interceptar llamadas al sistema?

    – Matheus Santana

    3 de abril de 2018 a las 12:09

avatar de usuario
pjz

si solo quieres reloj lo que está abierto, desea ver la función ptrace() o el código fuente de la utilidad de línea de comandos strace. Si realmente quieres interceptar la llamada, para quizás hacer que haga otra cosa, creo que las opciones que enumeró, LD_PRELOAD o un módulo del kernel, son sus únicas opciones.

  • ¿Cómo usaríamos SELinux para interceptar llamadas al sistema?

    – Matheus Santana

    3 de abril de 2018 a las 12:09

avatar de usuario
johan dahlin

Si solo desea hacerlo con fines de depuración, busque en strace, que está integrado en la parte superior de la llamada al sistema ptrace(2) que le permite conectar el código cuando se realiza una llamada al sistema. Consulte la parte PTRACE_SYSCALL de la página del manual.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad