Estoy golpeando mi cabeza contra la pared con esto.
En mi proyecto, cuando estoy asignando memoria con mmap , el mapeo ( /proc/self/maps ) muestra que es una región legible y ejecutable a pesar de que solicité solo memoria legible.
Después de analizar strace (que se veía bien) y otras depuraciones, pude identificar lo único que parece evitar este extraño problema: eliminar los archivos de ensamblaje del proyecto y dejar solo C puro (¿qué?)
Así que aquí está mi extraño ejemplo, estoy trabajando en Ubunbtu 19.04 y gcc predeterminado.
Si compila el ejecutable de destino con el archivo ASM (que está vacío), mmap devuelve una región legible y ejecutable, si compila sin entonces se comporta correctamente. Vea la salida de /proc/self/maps que incrusté en mi ejemplo.
ejemplo.c
#include <stdio.h> #include <string.h> #include <sys/mman.h> int main() { void* p; p = mmap(NULL, 8192,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,-1,0); { FILE *f; char line[512], s_search[17]; snprintf(s_search,16,"%lx",(long)p); f = fopen("/proc/self/maps","r"); while (fgets(line,512,f)) { if (strstr(line,s_search)) fputs(line,stderr); } fclose(f); } return 0; }ejemplo.s : ¡Es un archivo vacío!
Salidas
Con la versión ASM incluida
VirtualBox:~/mechanics/build$ gcc example.c example.s -o example && ./example 7f78d6e08000-7f78d6e0a000 r-xp 00000000 00:00 0Sin la versión ASM incluida
VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example 7f1569296000-7f1569298000 r--p 00000000 00:00 0Linux tiene un dominio de ejecución llamado READ_IMPLIES_EXEC , que hace que todas las páginas asignadas con PROT_READ también reciban PROT_EXEC . Los núcleos de Linux más antiguos solían usar esto para ejecutables que usaban el equivalente de gcc -z execstack . Este programa le mostrará si eso está habilitado por sí mismo:
#include <stdio.h> #include <sys/personality.h> int main(void) { printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false"); return 0; } Si compila eso junto con un archivo .s vacío, verá que está habilitado, pero sin uno, estará deshabilitado. El valor inicial de esto proviene de la metainformación de ELF en su archivo binario . Haga readelf -Wl example . Verá esta línea cuando compile sin el archivo .s vacío:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10Pero este cuando compilaste con él:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10 Tenga en cuenta RWE en lugar de solo RW . La razón de esto es que el enlazador asume que sus archivos de ensamblaje requieren read-imlies-exec a menos que se le indique explícitamente que no lo hacen, y si alguna parte de su programa requiere read-imlies-exec, entonces está habilitado para todo su programa. . Los archivos de ensamblaje que compila GCC le dicen que no necesita esto, con esta línea (lo verá si compila con -S ):
.section .note.GNU-stack,"",@progbits Los permisos de sección predeterminados no incluyen e x ec. Consulte la parte ELF de la documentación de .section para conocer el significado de las "banderas" y @atributos.
(Y no olvide cambiar a otra sección como .text o .data después de esa directiva .section , si su .s dependía de .text porque la sección predeterminada en la parte superior del archivo).
Ponga esa línea en example.s (y cualquier otro archivo .s en su proyecto). La presencia de esa sección .note.GNU-stack servirá para decirle al enlazador que este archivo de objeto no depende de una pila ejecutable, por lo que el enlazador usará RW en lugar de RWE en los metadatos GNU_STACK , y su programa funcionará. como se esperaba.
De manera similar para NASM , una directiva de section con las banderas correctas especifica pilas no ejecutables.
Los kernels de Linux modernos entre 5.4 y 5.8 cambiaron el comportamiento del cargador de programas ELF. Para x86-64, ya nada activa READ_IMPLIES_EXEC . A lo sumo (con un RWE GNU_STACK agregado por ld ), obtendrá que la pila en sí sea ejecutable, no todas las páginas legibles. ( Esta respuesta cubre el último cambio, en 5.8, pero debe haber habido otros cambios antes de eso, ya que esa pregunta muestra la ejecución exitosa del código en .data en x86-64 Linux 5.4)
exec-all ( READ_IMPLIES_EXEC ) solo ocurre con ejecutables heredados de 32 bits en los que el enlazador no agregó ninguna entrada de encabezado GNU_STACK . Pero como se muestra aquí, el ld moderno siempre agrega eso con una configuración u otra, incluso cuando a un archivo de entrada .o le falta una nota.
Aún debe usar esta sección .note para señalar pilas no ejecutables en programas normales. Pero si esperaba probar el código automodificable en .data o seguir algún tutorial antiguo paraprobar el shellcode , esa no es una opción en los núcleos modernos.
Como alternativa a la modificación de sus archivos de ensamblaje con variantes de directivas de sección específicas de GNU, puede agregar -Wa,--noexecstack a su línea de comando para crear archivos de ensamblaje. Por ejemplo, vea cómo lo hago en la configure de musl:
https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a
Creo que al menos algunas versiones de clang con ensamblador integrado pueden requerir que se pase como --noexecstack (sin -Wa ), por lo que su script de configuración probablemente debería verificar ambos y ver cuál es aceptado.
También puede usar -Wl,-z,noexecstack en el momento del enlace (en LDFLAGS ) para obtener el mismo resultado. La desventaja de esto es que no ayuda si su proyecto produce archivos de biblioteca estáticos ( .a ) para que los use otro software, ya que entonces no controla las opciones de tiempo de enlace cuando otros programas lo usan.