GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cómo puedo controlar lo que se coloca en el búfer de salida estándar y romperlo cuando se deposita una cadena específica en la tubería?

Esta pregunta podría ser un buen punto de partida:¿cómo puedo poner un punto de interrupción en "algo está impreso en la terminal" en gdb?

Entonces, al menos podría interrumpir cada vez que se escribe algo en stdout. El método consiste básicamente en establecer un punto de interrupción en el write syscall con la condición de que el primer argumento sea 1 (es decir, STDOUT). En los comentarios, también hay una pista sobre cómo podría inspeccionar el parámetro de cadena del write llamar también.

modo x86 de 32 bits

Se me ocurrió lo siguiente y lo probé con gdb 7.0.1-debian. Parece que funciona bastante bien. $esp + 8 contiene un puntero a la ubicación de memoria de la cadena pasada a write , así que primero lo conviertes en una integral, luego en un puntero a char . $esp + 4 contiene el descriptor de archivo para escribir (1 para STDOUT).

$ gdb break write if 1 == *(int*)($esp + 4) && strcmp((char*)*(int*)($esp + 8), "your string") == 0

modo x86 de 64 bits

Si su proceso se ejecuta en modo x86-64, los parámetros se pasan a través de registros temporales %rdi y %rsi

$ gdb break write if 1 == $rdi && strcmp((char*)($rsi), "your string") == 0

Tenga en cuenta que se elimina un nivel de direccionamiento indirecto ya que estamos usando registros temporales en lugar de variables en la pila.

Variantes

Funciones distintas a strcmp se puede utilizar en los fragmentos anteriores:

  • strncmp es útil si desea hacer coincidir el primer n número de caracteres de la cadena que se está escribiendo
  • strstr se puede usar para encontrar coincidencias dentro de una cadena, ya que no siempre puede estar seguro de que la cadena que está buscando está al comienzo de cadena que se escribe a través del write función.

Editar: Disfruté esta pregunta y encontrar su respuesta posterior. Decidí hacer una publicación de blog al respecto.


catch + strstr condición

Lo bueno de este método es que no depende de glibc write siendo utilizado:rastrea la llamada al sistema real.

Además, es más resistente a printf() almacenamiento en búfer, ya que incluso podría capturar cadenas que se imprimen en múltiples printf() llamadas.

versión x86_64:

define stdout
    catch syscall write
    commands
        printf "rsi = %s\n", $rsi
        bt
    end
    condition $bpnum $rdi == 1 && strstr((char *)$rsi, "$arg0") != NULL
end
stdout qwer

Programa de prueba:

#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    write(STDOUT_FILENO, "asdf1", 5);
    write(STDOUT_FILENO, "qwer1", 5);
    write(STDOUT_FILENO, "zxcv1", 5);
    write(STDOUT_FILENO, "qwer2", 5);
    printf("as");
    printf("df");
    printf("qw");
    printf("er");
    printf("zx");
    printf("cv");
    fflush(stdout);
    return EXIT_SUCCESS;
}

Resultado:descansos en:

  • qwer1
  • qwer2
  • fflush . El anterior printf en realidad no imprimieron nada, ¡fueron almacenados en búfer! El write syacall solo sucedió en el fflush .

Notas:

  • $bpnum gracias a Tromey en:https://sourceware.org/bugzilla/show_bug.cgi?id=18727
  • rdi :registro que contiene el número de la llamada del sistema Linux en x86_64, 1 es para write
  • rsi :primer argumento de la llamada al sistema, para write apunta al búfer
  • strstr :llamada de función C estándar, busca subcoincidencias, devuelve NULL si no se encuentra

Probado en Ubuntu 17.10, gdb 8.0.1.

straza

Otra opción si te sientes interactivo:

setarch "$(uname -m)" -R strace -i ./stdout.out |& grep '\] write'

Salida de muestra:

[00007ffff7b00870] write(1, "a\nb\n", 4a

Ahora copie esa dirección y péguela en:

setarch "$(uname -m)" -R strace -i ./stdout.out |& grep -E '\] write\(1, "a'

La ventaja de este método es que puede usar las herramientas habituales de UNIX para manipular strace salida, y no requiere GDB-fu profundo.

Explicación:

  • -i hace que la salida de strace RIP
  • setarch -R deshabilita ASLR para un proceso con un personality llamada al sistema:Cómo depurar con strace -i cuando cada vez que la dirección es diferente GDB ya lo hace de forma predeterminada, por lo que no es necesario volver a hacerlo.

La respuesta de Anthony es increíble. Siguiendo su respuesta, probé otra solución en Windows (Windows x86-64 bits). Sé que esta pregunta es para GDB en Linux, sin embargo, creo que esta solución es un complemento para este tipo de preguntas. Podría ser útil para otros.

Solución en Windows

En Linux una llamada a printf daría como resultado una llamada a la API write . Y debido a que Linux es un sistema operativo de código abierto, pudimos depurar dentro de la API. Sin embargo, la API es diferente en Windows, proporcionó su propia API WriteFile. Debido a que Windows es un sistema operativo comercial de código no abierto, no se pudieron agregar puntos de interrupción en las API.

Pero parte del código fuente de VC se publica junto con Visual Studio, por lo que pudimos averiguar en el código fuente dónde finalmente se llamó el WriteFile API y establezca un punto de interrupción allí. Después de depurar el código de muestra, encontré el printf El método podría resultar en una llamada a _write_nolock en el que WriteFile se llama. La función se encuentra en:

your_VS_folder\VC\crt\src\write.c

El prototipo es:

/* now define version that doesn't lock/unlock, validate fh */
int __cdecl _write_nolock (
        int fh,
        const void *buf,
        unsigned cnt
        )

Comparado con el write API en Linux:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count); 

Tienen totalmente los mismos parámetros. Así que podríamos establecer un condition breakpoint en _write_nolock solo consulte las soluciones anteriores, con solo algunas diferencias en los detalles.

Solución portátil para Win32 y x64

Es muy afortunado que podamos usar el nombre de los parámetros directamente en Visual Studio al establecer una condición para los puntos de interrupción en Win32 y x64. Entonces se vuelve muy fácil escribir la condición:

  1. Agregue puntos de interrupción en _write_nolock

    AVISO :Hay poca diferencia en Win32 y x64. Podríamos usar el nombre de la función para establecer la ubicación de los puntos de interrupción en Win32. Sin embargo, no funcionará en x64 porque en la entrada de la función, los parámetros no se inicializan. Por lo tanto, no pudimos usar el nombre del parámetro para establecer la condición de los puntos de interrupción.

    Pero, afortunadamente, tenemos algunas soluciones:use la ubicación en la función en lugar del nombre de la función para establecer los puntos de interrupción, por ejemplo, la primera línea de la función. Los parámetros ya están inicializados allí. (Me refiero a usar el filename+line number para establecer los puntos de interrupción, o abrir el archivo directamente y establecer un punto de interrupción en la función, no en la entrada sino en la primera línea. )

  2. Restringir la condición:

    fh == 1 && strstr((char *)buf, "Hello World") != 0
    

AVISO :todavía hay un problema aquí, probé dos formas diferentes de escribir algo en la salida estándar:printf y std::cout . printf escribiría todas las cadenas en el _write_nolock función a la vez. Sin embargo std::cout solo pasaría carácter por carácter a _write_nolock , lo que significa que la API se llamaría strlen("your string") veces. En este caso, la condición no podría activarse para siempre.

Solución Win32

Por supuesto, podríamos usar los mismos métodos que Anthony proporcionado:establece la condición de los puntos de interrupción por registros.

Para un programa Win32, la solución es casi la misma con GDB en Linux. Puede notar que hay una decoración __cdecl en el prototipo de _write_nolock . Esta convención de llamadas significa:

  • El orden de paso de argumentos es de derecha a izquierda.
  • La función de llamada extrae los argumentos de la pila.
  • Convención de decoración de nombres:el carácter de subrayado (_) se antepone a los nombres.
  • No se realizó traducción de casos.

Hay una descripción aquí. Y hay un ejemplo que se usa para mostrar los registros y las pilas en el sitio web de Microsoft. El resultado se puede encontrar aquí.

Entonces es muy fácil establecer la condición de los puntos de interrupción:

  1. Establecer un punto de interrupción en _write_nolock .
  2. Restringir la condición:

    *(int *)($esp + 4) == 1 && strstr(*(char **)($esp + 8), "Hello") != 0
    

Es el mismo método que en Linux. La primera condición es asegurarse de que la cadena se escriba en stdout . El segundo es hacer coincidir la cadena especificada.

Solución x64

Dos modificaciones importantes de x86 a x64 son la capacidad de direccionamiento de 64 bits y un conjunto plano de 16 registros de 64 bits para uso general. A medida que aumentan los registros, x64 solo usa __fastcall como la convención de llamada. Los primeros cuatro argumentos enteros se pasan en registros. Los argumentos cinco y superiores se pasan a la pila.

Puede consultar la página Paso de parámetros en el sitio web de Microsoft. Los cuatro registros (en orden de izquierda a derecha) son RCX , RDX , R8 y R9 . Entonces es muy fácil restringir la condición:

  1. Establecer un punto de interrupción en _write_nolock .

    AVISO :es diferente de la solución portátil anterior, simplemente podríamos establecer la ubicación del punto de interrupción en la función en lugar de la primera línea de la función. La razón es que todos los registros ya están inicializados en la entrada.

  2. Condición de restricción:

    $rcx == 1 && strstr((char *)$rdx, "Hello") != 0
    

La razón por la que necesitamos convertir y desreferenciar en esp es que $esp accede al ESP registrarse, y para todos los efectos es un void* . Mientras que los registros aquí almacenan directamente los valores de los parámetros. Así que ya no se necesita otro nivel de direccionamiento indirecto.

Publicar

También disfruto mucho esta pregunta, así que traduje la publicación de Anthony al chino y puse mi respuesta como complemento. La publicación se puede encontrar aquí. Gracias por el permiso de @anthony-arnold.


Linux
  1. Guía para principiantes de DNSSEC y cómo puede proteger Internet

  2. ¿Qué tan grande es el amortiguador de tubería?

  3. Linux:¿cómo averiguar qué discos duros hay en el sistema?

  4. ¿Cómo puedo anteponer una cadena al comienzo de cada línea en un archivo?

  5. ¿Cómo puedo controlar la longitud de la cola de aceptación?

Cómo encontrar una cadena o palabra específica en archivos y directorios

Cómo encontrar quién reinició el sistema Linux y cuándo

Cómo monitorear el progreso de los datos a través de una tubería usando el comando 'pv'

¿Qué significa tener * * * en el comando traceroute? y ¿Cómo se puede hacer frente a esa situación?

¿Cómo puedo cambiar las direcciones IP y de puerta de enlace de forma permanente?

¿Cómo puedo ocultar la salida de una aplicación de shell en Linux?