¿Cómo cambiará el código, por ejemplo, llamadas a funciones?
¿Cómo cambiará el código, por ejemplo, llamadas a funciones?
PIE es compatible con la aleatorización del diseño del espacio de direcciones (ASLR) en archivos ejecutables.
Antes de que se creara el modo PIE, el ejecutable del programa no se podía colocar en una dirección aleatoria en la memoria, solo las bibliotecas dinámicas de código independiente de la posición (PIC) podían reubicarse en un desplazamiento aleatorio. Funciona de manera muy similar a lo que hace PIC para las bibliotecas dinámicas, la diferencia es que no se crea una tabla de vinculación de procedimiento (PLT), sino que se usa la reubicación relativa a la PC.
Después de habilitar el soporte de PIE en gcc / linkers, el cuerpo del programa se compila y se vincula como código independiente de la posición. Un enlazador dinámico realiza un procesamiento de reubicación completo en el módulo del programa, al igual que las bibliotecas dinámicas. Cualquier uso de datos globales se convierte en acceso a través de la Tabla de compensaciones globales (GOT) y se agregan reubicaciones GOT.
PIE está bien descrito en esta presentación de OpenBSD PIE .
Los cambios en las funciones se muestran en esta diapositiva (PIE vs PIC).
foto x86 vs pastel
Las variables y funciones globales locales se optimizan en forma circular
Las variables y funciones globales externas son las mismas que en la imagen
y en esta diapositiva (PIE vs enlace antiguo)
Pie x86 vs sin banderas (arreglado)
Las variables y funciones globales locales son similares a las fijas.
Las variables y funciones globales externas son las mismas que en la imagen
Tenga en cuenta que PIE puede ser incompatible con -static
Ejemplo de ejecución mínima: GDB el ejecutable dos veces
Para aquellos que quieran ver alguna acción, veamos cómo ASLR trabaja en el ejecutable PIE y cambia las direcciones entre ejecuciones:
C Principal
#include <stdio.h> int main(void) { puts("hello"); }main.sh
#!/usr/bin/env bash echo 2 | sudo tee /proc/sys/kernel/randomize_va_space for pie in no-pie pie; do exe="${pie}.out" gcc -O0 -std=c99 "-${pie}" "-f${pie}" -ggdb3 -o "$exe" main.c gdb -batch -nh \ -ex 'set disable-randomization off' \ -ex 'break main' \ -ex 'run' \ -ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \ -ex 'run' \ -ex 'printf "pc = 0x%llx\n", (long long unsigned)$pc' \ "./$exe" \ ; echo echo donePara el que tiene
-no-pie, todo es aburrido:Breakpoint 1 at 0x401126: file main.c, line 4. Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x401126 Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x401126Antes de comenzar la ejecución,
break mainestablece un punto de interrupción en0x401126.Luego, durante ambas ejecuciones, se
rundetiene en la dirección0x401126.El que tiene
-piesin embargo es mucho más interesante:Breakpoint 1 at 0x1139: file main.c, line 4. Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x5630df2d6139 Breakpoint 1, main () at main.c:4 4 puts("hello"); pc = 0x55763ab2e139Antes de iniciar la ejecución, el BGF sólo se necesita una dirección "ficticia" que está presente en el archivo ejecutable:
0x1139.Sin embargo, después de que se inicia, GDB advierte inteligentemente que el cargador dinámico colocó el programa en una ubicación diferente y la primera pausa se detuvo en
0x5630df2d6139.Luego, la segunda ejecución también notó inteligentemente que el ejecutable se movió nuevamente y terminó rompiendo en
0x55763ab2e139.echo 2 | sudo tee /proc/sys/kernel/randomize_va_spacegarantiza que ASLR esté activado (el valor predeterminado en Ubuntu 17.10): ¿Cómo puedo deshabilitar temporalmente ASLR (distribución aleatoria del diseño del espacio de direcciones)? | Pregúntale a Ubuntu .set disable-randomization offde lo contrario, GDB, como su nombre indica, desactiva ASLR para el proceso de forma predeterminada para proporcionar direcciones fijas entre ejecuciones para mejorar la experiencia de depuración: ¿ Diferencia entre direcciones gdb y direcciones "reales"? | Desbordamiento de pila .readelfanálisisAdemás, también podemos observar que:
da la dirección de carga de tiempo de ejecución real (la PC señaló la siguiente instrucción 4 bytes después):
64: 0000000000401122 21 FUNC GLOBAL DEFAULT 13 mainmientras:
da solo un desplazamiento:
65: 0000000000001135 23 FUNC GLOBAL DEFAULT 14 mainAl desactivar ASLR (con
randomize_va_spaceoset disable-randomization off), GDB siempre damainla dirección:,0x5555555547a9por lo que deducimos que la-piedirección se compone de:0x555555554000 + random offset + symbol offset (79a)TODO, ¿dónde está codificado 0x555555554000 en el kernel de Linux / cargador glibc / donde sea? ¿Cómo se determina la dirección de la sección de texto de un ejecutable PIE en Linux?
Ejemplo de montaje mínimo
Otra cosa interesante que podemos hacer es jugar con algún código ensamblador para entender más concretamente lo que significa PIE.
Podemos hacer eso con un ensamblaje independiente de Linux x86_64 hello world:
red eléctrica
.text .global _start _start: asm_main_after_prologue: /* write */ mov $1, %rax /* syscall number */ mov $1, %rdi /* stdout */ mov $msg, %rsi /* buffer */ mov $len, %rdx /* len */ syscall /* exit */ mov $60, %rax /* syscall number */ mov $0, %rdi /* exit status */ syscall msg: .ascii "hello\n" len = . - msgGitHub aguas arriba
y se ensambla y funciona bien con:
Sin embargo, si intentamos vincularlo como PIE con (
--no-dynamic-linkerse requiere como se explica en: ¿Cómo crear un ELF ejecutable independiente de posición vinculada estáticamente en Linux? ):entonces el enlace fallará con:
ld: main.o: relocation R_X86_64_32S against `.text' can not be used when making a PIE object; recompile with -fPIC ld: final link failed: nonrepresentable section on outputPorque la línea:
mov $msg, %rsi /* buffer */codifica la dirección del mensaje en el
movoperando y, por lo tanto, no es independiente de la posición.Si, en cambio, lo escribimos de forma independiente de la posición:
lea msg(%rip), %rsientonces el enlace PIE funciona bien, y GDB nos muestra que el ejecutable se carga en una ubicación diferente en la memoria cada vez.
La diferencia aquí es que
leacodificó la direcciónmsgrelativa a la dirección actual de la PC debido a laripsintaxis, consulte también: ¿Cómo usar el direccionamiento relativo RIP en un programa ensamblador de 64 bits?También podemos averiguarlo desmontando ambas versiones con:
que dan respectivamente:
e: 48 c7 c6 00 00 00 00 mov $0x0,%rsi e: 48 8d 35 19 00 00 00 lea 0x19(%rip),%rsi # 2e <msg> 000000000000002e <msg>: 2e: 68 65 6c 6c 6f pushq $0x6f6c6c65Entonces vemos claramente que
leaya tiene la dirección correcta completamsgcodificada como dirección actual + 0x19.Sin
movembargo, la versión ha establecido la dirección en00 00 00 00, lo que significa que se realizará una reubicación allí: ¿Qué hacen los enlazadores? El crípticoR_X86_64_32Sen elldmensaje de error es el tipo real de reubicación que se requirió y que no puede ocurrir en los ejecutables PIE.Otra cosa divertida que podemos hacer es poner el
msgen la sección de datos en lugar de.textcon:.data msg: .ascii "hello\n" len = . - msgAhora el
.oensamblaje para:e: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 15 <_start+0x15>por lo que el desplazamiento RIP es ahora
0, y suponemos que el ensamblador ha solicitado una reubicación. Lo confirmamos con:lo que da:
Relocation section '.rela.text' at offset 0x160 contains 1 entry: Offset Info Type Sym. Value Sym. Name + Addend 000000000011 000200000002 R_X86_64_PC32 0000000000000000 .data - 4tan claramente
R_X86_64_PC32es una reubicación relativa de PC queldpuede manejar los ejecutables PIE.Este experimento nos enseñó que el propio enlazador comprueba que el programa puede ser PIE y lo marca como tal.
Luego, al compilar con GCC,
-piele dice a GCC que genere un ensamblado independiente de la posición.Pero si escribimos ensamblaje nosotros mismos, debemos asegurarnos manualmente de haber logrado la independencia de posición.
En ARMv8 aarch64, la posición hello world independiente se puede lograr con la instrucción ADR .
¿Cómo determinar si un ELF es independiente de la posición?
Además de ejecutarlo a través de GDB, se mencionan algunos métodos estáticos en:
Probado en Ubuntu 18.10.
fuente