En una caja de Linux con una CPU Intel, digamos que compilé por binario con -fstack-protect-all.
Dado que no se indica explícitamente, se asumirá que esto se refiere a los binarios ELF compilados usando GCC con el argumento -fstack-protect o -all.
Mecanismos de protección de pila GCC
-fstack-protector-all
es una extensión de -fstack-protector
:
-fstack-protector
Emitir código adicional para comprobar si hay desbordamientos de búfer, como ataques de aplastamiento de pila. Esto se hace agregando una variable de guarda a las funciones con objetos vulnerables. Esto incluye funciones que llaman a alloca, y funciones con buffers de más de 8 bytes. Las guardas se inicializan cuando se ingresa a una función y luego se verifican cuando la función sale. Si falla una comprobación de seguridad, se imprime un mensaje de error y el programa sale.
-fstack-protector-all
Me gusta -fstack-protector
excepto que todas las funciones están protegidas.
La "variable de guarda" mencionada anteriormente se conoce comúnmente como canary :
La idea básica detrás de la protección de pila es empujar un "canario" (un entero elegido al azar) en la pila justo después de que se haya empujado el puntero de retorno de función. El valor canario se comprueba antes de que la función regrese; Si ha cambiado, el programa abortará. En general, los ataques de desbordamiento de búfer de pila (también conocido como "aplastamiento de pila") deberán cambiar el valor del canario a medida que escriben más allá del final del búfer antes de que puedan llegar al puntero de retorno. Como el atacante desconoce el valor del canario, no puede ser reemplazado por el ataque. Por lo tanto, la protección de la pila permite que el programa aborte cuando eso suceda, en lugar de volver a donde el atacante quería que fuera. 1
Ahora al primer conjunto de preguntas:
¿Cómo se codifica esto en el binario? (¿Puedo ver esta información usando readelf?). ¿Se codifica en cada página / segmento o se agrega al binario en un lugar y el cargador lo recoge?
El -fstack-protector-all
hace que el compilador genere un código en el que una variable de guarda se inserta en todas las funciones y se verifica antes de regresar. En otras palabras, se generan instrucciones adicionales de la máquina que tienen que ver con empujar, revisar y abrir el canario de pila.
Esto se puede ilustrar utilizando 2 binarios de ejemplo producidos desde la misma fuente, en los que uno se compiló con el argumento -fstack-protector-all
y el otro no.
Código fuente:
int test(int i) {
return i;
}
int main(void) {
int x;
int i = 10;
x = test(i);
return x;
}
Función de binario compilado sin -fstack-protector-all
:
$ objdump -dj .text test | grep -A7 "<test>:"
00000000004004ed <test>:
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
4004f1: 89 7d fc mov %edi,-0x4(%rbp)
4004f4: 8b 45 fc mov -0x4(%rbp),%eax
4004f7: 5d pop %rbp
4004f8: c3 retq
Función de binario compilado con -fstack-protector-all
:
$ objdump -dj .text protected_test | grep -A20 "<test>:"
000000000040055d <test>:
40055d: 55 push %rbp
40055e: 48 89 e5 mov %rsp,%rbp
400561: 48 83 ec 20 sub $0x20,%rsp
400565: 89 7d ec mov %edi,-0x14(%rbp)
400568: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax <- get guard variable value
40056f: 00 00
400571: 48 89 45 f8 mov %rax,-0x8(%rbp) <- save guard variable on stack
400575: 31 c0 xor %eax,%eax
400577: 8b 45 ec mov -0x14(%rbp),%eax
40057a: 48 8b 55 f8 mov -0x8(%rbp),%rdx <- move it to register
40057e: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx <- check it against original
400585: 00 00
400587: 74 05 je 40058e <test+0x31>
400589: e8 b2 fe ff ff callq 400440 <__stack_chk_fail@plt>
40058e: c9 leaveq
40058f: c3 retq
Dado que la protección de la pila se implementa a través del código de ensamblaje, los resultados de la familia de indicadores -fstack-protector
se investigan mejor utilizando un desensamblador como objdump
. readelf
técnicamente muestra esta información, ya que el argumento -x
produce un volcado hexadecimal de la sección seleccionada.
readelf
hex dump de binario compilado sin protección de pila:
$ readelf -x .text test
Hex dump of section '.text':
0x00400400 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
0x00400410 c7c09005 400048c7 c1200540 0048c7c7 [email protected].. [email protected]..
0x00400420 f9044000 e8b7ffff fff4660f 1f440000 [email protected]..
0x00400430 b83f1060 0055482d 38106000 4883f80e .?.'.UH-8.'.H...
0x00400440 4889e577 025dc3b8 00000000 4885c074 H..w.]......H..t
0x00400450 f45dbf38 106000ff e00f1f80 00000000 .].8.'..........
0x00400460 b8381060 0055482d 38106000 48c1f803 .8.'.UH-8.'.H...
0x00400470 4889e548 89c248c1 ea3f4801 d048d1f8 H..H..H..?H..H..
0x00400480 75025dc3 ba000000 004885d2 74f45d48 u.]......H..t.]H
0x00400490 89c6bf38 106000ff e20f1f80 00000000 ...8.'..........
0x004004a0 803d910b 20000075 11554889 e5e87eff .=.. ..u.UH...~.
0x004004b0 ffff5dc6 057e0b20 0001f3c3 0f1f4000 ..]..~. ......@.
0x004004c0 48833d58 09200000 741eb800 00000048 H.=X. ..t......H
0x004004d0 85c07414 55bf200e 60004889 e5ffd05d ..t.U. .'.H....]
0x004004e0 e97bffff ff0f1f00 e973ffff ff554889 .{.......s...UH.
0x004004f0 e5897dfc 8b45fc5d c3554889 e54883ec ..}..E.].UH..H..
0x00400500 10c745f8 0a000000 8b45f889 c7e8dbff ..E......E......
0x00400510 ffff8945 fc8b45fc c9c3660f 1f440000 ...E..E...f..D..
0x00400520 41574189 ff415649 89f64155 4989d541 AWA..AVI..AUI..A
0x00400530 544c8d25 d8082000 55488d2d d8082000 TL.%.. .UH.-.. .
0x00400540 534c29e5 31db48c1 fd034883 ec08e855 SL).1.H...H....U
0x00400550 feffff48 85ed741e 0f1f8400 00000000 ...H..t.........
0x00400560 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
0x00400570 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
0x00400580 415e415f c366662e 0f1f8400 00000000 A^A_.ff.........
0x00400590 f3c3 ..
readelf
hex dump de binario compilado con protección de pila:
$ readelf -x .text protected_test
Hex dump of section '.text':
0x00400470 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
0x00400480 c7c05006 400048c7 c1e00540 0048c7c7 [email protected][email protected]..
0x00400490 90054000 e8b7ffff fff4660f 1f440000 [email protected]..
0x004004a0 b8471060 0055482d 40106000 4883f80e .G.'.UH-@.'.H...
0x004004b0 4889e577 025dc3b8 00000000 4885c074 H..w.]......H..t
0x004004c0 f45dbf40 106000ff e00f1f80 00000000 .].@.'..........
0x004004d0 b8401060 0055482d 40106000 48c1f803 .@.'.UH-@.'.H...
0x004004e0 4889e548 89c248c1 ea3f4801 d048d1f8 H..H..H..?H..H..
0x004004f0 75025dc3 ba000000 004885d2 74f45d48 u.]......H..t.]H
0x00400500 89c6bf40 106000ff e20f1f80 00000000 ...@.'..........
0x00400510 803d290b 20000075 11554889 e5e87eff .=). ..u.UH...~.
0x00400520 ffff5dc6 05160b20 0001f3c3 0f1f4000 ..].... ......@.
0x00400530 48833de8 08200000 741eb800 00000048 H.=.. ..t......H
0x00400540 85c07414 55bf200e 60004889 e5ffd05d ..t.U. .'.H....]
0x00400550 e97bffff ff0f1f00 e973ffff ff554889 .{.......s...UH.
0x00400560 e54883ec 20897dec 64488b04 25280000 .H.. .}.dH..%(..
0x00400570 00488945 f831c08b 45ec488b 55f86448 .H.E.1..E.H.U.dH
0x00400580 33142528 00000074 05e8b2fe ffffc9c3 3.%(...t........
0x00400590 554889e5 4883ec10 64488b04 25280000 UH..H...dH..%(..
0x004005a0 00488945 f831c0c7 45f00a00 00008b45 .H.E.1..E......E
0x004005b0 f089c7e8 a5ffffff 8945f48b 45f4488b .........E..E.H.
0x004005c0 55f86448 33142528 00000074 05e86efe U.dH3.%(...t..n.
0x004005d0 ffffc9c3 662e0f1f 84000000 00006690 ....f.........f.
0x004005e0 41574189 ff415649 89f64155 4989d541 AWA..AVI..AUI..A
0x004005f0 544c8d25 18082000 55488d2d 18082000 TL.%.. .UH.-.. .
0x00400600 534c29e5 31db48c1 fd034883 ec08e8f5 SL).1.H...H.....
0x00400610 fdffff48 85ed741e 0f1f8400 00000000 ...H..t.........
0x00400620 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
0x00400630 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
0x00400640 415e415f c366662e 0f1f8400 00000000 A^A_.ff.........
0x00400650 f3c3 ..
Obviamente esto no es muy útil.
La discusión de páginas y segmentos no es relevante ya que todas las secciones marcadas como ejecutables en un binario ELF se asignan en el mismo segmento (el segmento text
).
¿Cómo obtiene el kernel / cargador esta información al cargar una página? ¿Cómo lo usa?
El uso de -fstack-protector-all
no tiene relación con los permisos de sección o segmento.
Para obtener información sobre el cargador del programa del kernel, consulte:
¿Cuál es la instrucción x86 utilizada para configurar la página en solo lectura?
Los permisos de segmento se establecen mediante el editor de enlaces: Permisos de segmento
1. Protección de pila "fuerte" para GCC