web-dev-qa-db-fra.com

Comment la protection de pile est-elle appliquée dans un binaire?

Sur un boîtier Linux avec un processeur Intel, disons que j'ai compilé en binaire avec -fstack-protect-all.

  • Comment est-ce encodé dans le binaire? (Puis-je voir ces informations en utilisant readelf?). Est-il encodé dans chaque page/segment ou est-il ajouté au binaire en un seul endroit et le chargeur le ramasse?

  • Comment le noyau/chargeur obtient-il ces informations lors du chargement d'une page? Comment l'utilise-t-il?

  • Quelle est l'instruction x86 utilisée pour définir la page en lecture seule?

8
SFlow

Sur un boîtier Linux avec un processeur Intel, disons que j'ai compilé en binaire avec -fstack-protect-all.

Puisqu'il n'est pas explicitement indiqué, il sera supposé que cela fait référence aux binaires ELF compilés à l'aide de GCC avec l'argument -fstack-protect ou - all.

Mécanismes de protection de la pile GCC

-fstack-protector-all Est une extension de -fstack-protector:

-fstack-protector

Emettez du code supplémentaire pour vérifier les débordements de tampon, tels que les attaques par écrasement de pile. Cela se fait en ajoutant une variable de garde aux fonctions avec des objets vulnérables. Cela inclut les fonctions qui appellent alloca et les fonctions avec des tampons supérieurs à 8 octets. Les protections sont initialisées lorsqu'une fonction est entrée puis vérifiées à la sortie de la fonction. Si une vérification de garde échoue, un message d'erreur est imprimé et le programme se ferme.

-fstack-protector-all

Comme -fstack-protector Sauf que toutes les fonctions sont protégées.

La "variable de garde" susmentionnée est communément appelée canary :

L'idée de base derrière la protection de la pile est de pousser un "canari" (un entier choisi au hasard) sur la pile juste après avoir poussé le pointeur de retour de fonction. La valeur canari est ensuite vérifiée avant le retour de la fonction; s'il a changé, le programme s'interrompra. Généralement, les attaques de débordement de tampon de pile (aka "smashing de pile") devront changer la valeur du canari lors de l'écriture au-delà de la fin du tampon avant de pouvoir atteindre le pointeur de retour. Étant donné que la valeur du canari est inconnue de l'attaquant, il ne peut pas être remplacé par l'attaque. Ainsi, la protection de la pile permet au programme d'interrompre lorsque cela se produit plutôt que de retourner là où l'attaquant voulait qu'il aille.1


Passons maintenant à la première série de questions:

Comment est-ce encodé dans le binaire? (Puis-je voir ces informations en utilisant readelf?). Est-il encodé dans chaque page/segment ou est-il ajouté au binaire en un seul endroit et le chargeur le ramasse?

Le -fstack-protector-all Oblige le compilateur à générer du code dans lequel une variable de garde est envoyée à toutes les fonctions et vérifiée avant son retour. En d'autres termes, des instructions machine supplémentaires sont générées, liées à la poussée, à la vérification et à l'éclatement de la pile des canaris.

Cela peut être illustré en utilisant 2 exemples de binaires produits à partir de la même source, dans lesquels l'un a été compilé avec l'argument -fstack-protector-all Et l'autre ne l'a pas été.

Code source:

int test(int i) {
    return i;
}

int main(void) {
    int x;
    int i = 10;
    x = test(i);
    return x;
}

Fonction binaire compilée sans -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   

Fonction binaire compilée avec -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   

La protection de la pile étant implémentée via le code d'assemblage, les résultats de la famille d'indicateurs -fstack-protector Sont mieux étudiés à l'aide d'un désassembleur comme objdump. readelf affiche techniquement ces informations, car l'argument -x produit un vidage hexadécimal de la section sélectionnée.

readelf vidage hexadécimal de binaire compilé sans protection de pile:

$ 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 vidage hexadécimal du binaire compilé avec la protection de la pile:

$ 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                                ..

Évidemment, ce n'est pas très utile.

La discussion des pages et des segments n'est pas pertinente car toutes les sections marquées comme exécutables dans un binaire ELF sont mappées dans le même segment (le segment text).


Comment le noyau/chargeur obtient-il ces informations lors du chargement d'une page? Comment l'utilise-t-il?

L'utilisation de -fstack-protector-all N'a aucune incidence sur les autorisations de section ou de segment.

Pour plus d'informations sur le chargeur de programme du noyau, voir:


Quelle est l'instruction x86 utilisée pour définir la page en lecture seule?

Les autorisations de segment sont définies par l'éditeur de liens: Autorisations de segment


1. Protection de pile "forte" pour GCC
13
julian

La protection de pile de GCC est basée sur un logiciel et n'est pas liée à la protection matérielle de DEP. Lorsqu'un système d'exploitation active DEP, tous les programmes qui y sont exécutés (ou un sous-ensemble défini par l'utilisateur) sont automatiquement protégés par des indicateurs matériels, quels que soient les indicateurs de compilateur utilisés pour créer le binaire.

Lorsque les indicateurs de protection de pile sont activés dans GCC, il fournit une protection en plaçant des gardes et des vérifications supplémentaires lorsqu'une fonction est appelée ou revient pour s'assurer que le programme abandonne plutôt que de violer son intégrité. Cela augmente légèrement la taille de l'exécutable et ralentit l'exécution du code (prend plus de temps CPU).

Le chargeur ni le système d'exploitation ne sont conscients de la protection de la pile de GCC, pas plus que cela n'est nécessaire. En ce qui concerne le système d'exploitation, il s'agit simplement d'un code exécutable normal. Le système d'exploitation peut fournir DEP sans protection de pile activée, et la protection de pile ne nécessite pas DEP.

Avec DEP, si l'IP (pointeur d'instruction) atterrit en dehors d'un CS (segment de code), une exception est déclenchée et le système d'exploitation met fin au programme. Avec la protection de pile de GCC, la pile est mise en mémoire tampon avec un espace supplémentaire contenant certaines valeurs, et vérifiée pour s'assurer que ces zones mises en mémoire tampon ne sont pas modifiées. Si une fonction échoue au contrôle de garde, le programme est interrompu pour éviter d'autres dommages.

2
phyrfox