Institut de technologie du Massachusetts. Cours magistral # 6.858. "Sécurité des systèmes informatiques." Nikolai Zeldovich, James Mickens. 2014 année
Computer Systems Security est un cours sur le développement et la mise en œuvre de systèmes informatiques sécurisés. Les conférences couvrent les modèles de menace, les attaques qui compromettent la sécurité et les techniques de sécurité basées sur des travaux scientifiques récents. Les sujets incluent la sécurité du système d'exploitation (OS), les fonctionnalités, la gestion du flux d'informations, la sécurité des langues, les protocoles réseau, la sécurité matérielle et la sécurité des applications Web.
Cours 1: «Introduction: modèles de menace»
Partie 1 /
Partie 2 /
Partie 3Conférence 2: «Contrôle des attaques de pirates»
Partie 1 /
Partie 2 /
Partie 3Conférence 3: «Débordements de tampon: exploits et protection»
Partie 1 /
Partie 2 /
Partie 3 Fait intéressant, un attaquant ne peut pas accéder à une adresse spécifique, malgré le fait que nous utilisons principalement des adresses codées en dur. Ce qu'il fait s'appelle une «attaque en tas», et si vous êtes une mauvaise personne, ce sera assez amusant pour vous. Avec une telle attaque, un pirate commence à allouer dynamiquement des tonnes de code shell et à le saisir simplement au hasard dans la mémoire. Cela est particulièrement efficace si vous utilisez des langages dynamiques de haut niveau tels que JavaScript. Ainsi, le lecteur de balises est dans une boucle étroite et génère simplement un grand nombre de lignes de code shell, puis en remplit un tas.
L'attaquant ne peut pas déterminer l'emplacement exact des lignes, il sélectionne simplement 10 Mo de lignes de code shell et effectue un saut arbitraire. Et s'il peut en quelque sorte contrôler l'un des pointeurs
ret , il y a une chance qu'il "atterrisse" dans le code shell.

Vous pouvez utiliser une astuce appelée
diapositive NOP, traîneau NOP ou
rampe NOP , où
NOP correspond
à des instructions de non-opération ou à des commandes vides et inactives. Cela signifie que le flux d'exécution des commandes du processeur «glisse» vers sa destination finale souhaitée chaque fois que le programme se rend à l'adresse mémoire n'importe où sur la diapositive.
Imaginez que si vous avez une ligne de code shell et que vous allez à un endroit aléatoire sur cette ligne, cela peut ne pas fonctionner, car cela ne vous permet pas de déployer l'attaque de la bonne manière.
Mais peut-être que les choses que vous mettez dans le tas ne sont fondamentalement qu'une tonne de
NOP , et à la toute fin, vous avez du code shell. C'est en fait assez intelligent, car cela signifie que vous pouvez maintenant vous rendre au bon endroit où vous sautez. Parce que si vous sautez dans l'un de ces
NOP , cela se produit simplement «boum, boum, boum, boum, boum, boum, boum», puis vous entrez dans le code shell.
Il semble que les gens viennent avec ça, ce que vous voyez probablement dans notre équipe. Ils inventent quelque chose comme ça, et c'est le problème. C'est donc une autre façon de contourner certaines choses aléatoires en rendant simplement robuste la randomisation de vos codes, si cela a du sens.
Nous avons donc discuté de certains types d'aléatoire que vous pouvez utiliser. Il y a des idées stupides qui ont également surgi chez les gens. Alors maintenant, vous savez que lorsque vous souhaitez effectuer un appel système, par exemple, en utilisant la fonction
syscall libc , vous passez essentiellement un numéro unique qui représente l'appel système que vous souhaitez effectuer. Alors peut-être que la fonction
fork est 7,
sleep est 8, ou quelque chose comme ça.
Cela signifie que si un attaquant peut trouver l'adresse de cette instruction
syscall et y accéder d'une manière ou d'une autre, il ou elle peut en fait simplement remplacer le numéro d'appel système qu'il souhaite utiliser directement. Vous pouvez imaginer que chaque fois que le programme s'exécute, vous créez en fait une affectation dynamique des numéros d'
appel système à des appels système valides, afin de compliquer la capture de l'attaquant.

Il y a même des suggestions avant-gardistes pour changer le matériel afin que l'équipement contienne la clé de cryptage
xor , qui est utilisée pour les fonctions dynamiques
xor . Imaginez que chaque fois que vous compilez un programme, tous les codes d'instructions obtiennent une certaine clé
xor . Cette clé est stockée dans le registre de l'équipement lorsque vous téléchargez initialement le programme, et ensuite, chaque fois que vous exécutez l'instruction, l'équipement effectue automatiquement une ou plusieurs opérations avec elle avant de poursuivre avec cette instruction. La bonne chose à propos de cette approche est que maintenant, même si un attaquant peut générer du code shell, il ne reconnaîtra pas cette clé. Il lui sera donc très difficile de comprendre ce qui doit être exactement mis en mémoire.
Public: mais s'il peut obtenir le code, il peut également utiliser
xor pour
reconvertir le code en instruction.
Professeur: oui, c'est le problème canonique, non. Ceci est quelque peu similaire à ce qui se passe lors des attaques
BROP , lorsque nous semblons randomiser l'emplacement du code, mais l'attaquant peut le "sentir" et découvrir ce qui se passe. On peut imaginer que, par exemple, si un attaquant connaît une sous-séquence de code qu'il s'attend à trouver dans un fichier binaire, il tentera d'utiliser l'opération
xor pour ce fichier afin d'extraire la clé.
Essentiellement, nous avons discuté de toutes sortes d'attaques de randomisation dont je voulais vous parler aujourd'hui. Avant de passer à la programmation, il convient de discuter des méthodes de protection utilisées dans la pratique. Il s'avère que
GCC et Visual Studio incluent l'approche par
défaut des canaris de pile . Il s'agit d'une communauté très populaire et très célèbre. Si vous regardez Linux et Windows, ils profitent également de choses comme la mémoire non exécutable et la randomisation de l'espace d'adressage. Certes, le système
baggy bounds n'est pas si populaire auprès d'eux, probablement en raison du coût de la mémoire, du processeur, des fausses alarmes, etc., dont nous avons déjà parlé. Donc, fondamentalement, nous avons examiné comment les choses vont empêcher le problème de dépassement de tampon.
Nous allons maintenant parler de
ROP , programmation orientée inverse. Aujourd'hui, je vous ai déjà dit ce que cela représente en termes de randomisation de l'espace d'adressage et d'empêcher l'exécution des données - c'est la lecture, l'écriture et l'exécution. Ce sont en fait des choses très puissantes. Parce que la randomisation empêche la possibilité pour un attaquant de comprendre où se trouvent nos adresses codées en dur. Et la possibilité d'empêcher l'exécution de données garantit que même si vous placez le code shell sur la pile, un attaquant ne peut pas simplement y accéder et l'exécuter.
Tout cela semble assez progressif, mais les pirates développent constamment des méthodes d'attaque contre de telles solutions de défense progressives.
Alors, quelle est l'essence de la programmation inversée?
Et si, au lieu de simplement créer du nouveau code lors d'une attaque, un attaquant pouvait combiner les morceaux de code existants puis les combiner de manière anormale? Après tout, nous savons que le programme contient des tonnes de ce code.

Donc, heureusement ou malheureusement, tout dépend de quel côté vous êtes. Si vous pouvez trouver des extraits de code intéressants et les combiner ensemble, vous pouvez obtenir quelque chose comme le langage
Turing , où l'attaquant peut essentiellement faire ce qu'il veut.
Regardons un exemple très simple qui vous semblera familier au début, mais qui se transformera rapidement en quelque chose de fou.
Disons que nous avons le programme suivant. Donc, ayons une sorte de fonction et, ce qui est pratique pour l'attaquant, voici cette belle
fonction shell run . Donc, ce n'est qu'un appel au système, il exécutera la commande
bin / bash et cela se terminera. Ensuite, nous avons un processus de débordement de tampon canonique ou, désolé, une fonction qui annoncera la création d'un tampon, puis utilisera l'une de ces fonctions dangereuses pour remplir le tampon d'octets.

Donc, nous savons que le débordement de tampon se produit ici sans problème. Mais ce qui est intéressant, c'est que nous avons cette
fonction run shell , mais il est difficile d'y accéder de manière basée sur les débordements de tampon. Comment un attaquant peut-il invoquer cette commande
run shell ?
Tout d'abord, l'attaquant peut démonter le programme, démarrer
GDB , et trouver l'adresse de cette chose dans le fichier exécutable. Vous connaissez probablement ces méthodes issues des travaux de laboratoire. Ensuite, lors d'un débordement de tampon, un attaquant peut prendre cette adresse, la mettre dans le débordement de tampon généré, et vérifier que la fonction retourne au
shell d'exécution .
Pour que ce soit clair, je vais le dessiner. Donc, vous avez une pile qui ressemble à ceci: en bas il y a un tampon débordé, au-dessus c'est un indicateur d'écart enregistré, au-dessus c'est l'adresse de retour pour
prosess_msg . En bas à gauche, nous avons un nouveau pointeur de pile qui lance la fonction, au-dessus un nouveau pointeur de rupture, puis le pointeur de pile qui sera utilisé, et encore plus haut est le pointeur de rupture de l'image précédente. Tout semble assez familier.

Comme je l'ai dit, pendant l'attaque,
GDB a été utilisé pour savoir quelle est l'adresse du
shell d'exécution . Ainsi, lorsque le tampon déborde, nous pouvons simplement mettre l'adresse du
shell d'exécution ici à droite. Il s'agit en fait d'une extension assez simple de ce que nous savons déjà faire. Essentiellement, cela signifie que si nous avons une commande qui lance le shell et si nous pouvons démonter le fichier binaire pour savoir où se trouve cette adresse, nous pouvons simplement la placer dans ce tableau de débordement situé au bas de la pile. C'est assez simple.
Donc, c'était un exemple extrêmement frivole, car le programmeur, pour une raison folle, a mis cette fonction ici, présentant ainsi à l'attaquant un véritable cadeau.
Supposons maintenant qu'au lieu d'appeler cette chose
run_shell , nous l'appellerons
run_boring , puis il exécutera simplement la commande
/ bin / ls . Cependant, nous n'avons rien perdu, car nous aurons la chaîne
char * bash_path en haut , qui nous indiquera le chemin vers ce
bin / bash .

Donc, la chose la plus intéressante à ce sujet est qu'un attaquant qui veut exécuter
ls peut «analyser» le programme et trouver l'emplacement de
run_boring , et ce n'est pas du tout amusant. Mais en fait, nous avons une ligne en mémoire qui pointe vers le chemin du shell, en plus, nous savons quelque chose d'autre d'intéressant. C'est que même si le programme n'appelle pas le système avec l'argument
/ bin / ls , il fait quand même une sorte d'appel.
Donc, nous savons que le système doit être en quelque sorte connecté à ce programme -
système («/ bin / ls») . Par conséquent, nous pouvons utiliser ces deux opérations
void pour réellement associer le système à cet
argument char * bash_path . La première chose que nous faisons est d'aller dans
GDB et de découvrir où se trouve ce
système («/ bin / ls») dans l'image du processus binaire. Donc, vous allez simplement dans
GDB , tapez simplement
print_system et obtenez des informations sur son décalage. C'est assez simple, et vous pouvez faire de même pour
bash_path . Autrement dit, vous utilisez simplement
GDB pour savoir où cette chose vit.
Une fois que vous avez terminé, vous devez faire autre chose. Parce que maintenant, nous devons vraiment comprendre comment invoquer le système en utilisant l'argument que nous avons choisi. Et notre façon de procéder consiste essentiellement à falsifier la trame d'appel du système. Si vous vous souvenez, une trame est ce que le compilateur et le matériel utilisent pour implémenter l'appel de pile.
Nous voulons organiser sur la pile quelque chose comme ce que j'ai représenté sur cette figure. En fait, nous allons simuler un système qui aurait dû être sur la pile, mais juste avant qu'il n'exécute réellement son code.
Donc, nous avons ici l'argument du système, c'est la ligne que nous voulons exécuter. En bas, nous avons une ligne où le système doit retourner lorsque la ligne avec l'argument est terminée. Le système s'attend à ce que la pile ait cet aspect juste avant le début de l'exécution.

Nous avions l'habitude de supposer qu'il n'y a pas d'arguments lorsque vous passez la fonction, mais maintenant, cela semble un peu différent. Nous devons juste nous assurer que cet
argument est dans le code de débordement que nous créons. Nous devons juste nous assurer que cette fausse
trame d'appel est dans ce tableau. Ainsi, notre travail sera le suivant. Rappelez-vous que le débordement de pile va de bas en haut.

Tout d'abord, nous allons mettre l'adresse système ici. Et en plus, nous
placerons une adresse de retour indésirable . C'est l'endroit où le système reviendra une fois terminé. Cette adresse sera un ensemble aléatoire d'octets. Au-dessus, nous mettrons l'adresse
bash_path . Que se passe-t-il lorsque le tampon déborde maintenant?
Après que
prosess_msg ait atteint la ligne d'arrivée, il dira: "OK, c'est l'endroit où je devrais revenir"! Le code système continue de fonctionner, il monte plus haut et voit le faux cadre d'appel que nous avons créé. Pour le système, rien d'étourdissant ne se produira, il dira: "ouais, ça y est, l'argument que je veux exécuter est
bin / bash ", il l'exécute, et c'est fait - l'attaquant a capturé le shell!
Qu'avons-nous fait maintenant? Nous avons profité de la connaissance de la
convention d'appel, de la convention d'appel , comme plate-forme pour créer de faux cadres de pile ou de faux noms de cadre, je dirais. En utilisant ces faux
cadres d'appel , nous pouvons effectuer n'importe quelle fonction référencée et déjà définie par l'application.
La prochaine question que nous devrions nous poser est: que faire si le programme n'a pas du tout cette ligne
char * bash_path ? Je note que cette ligne est presque toujours présente dans le programme. Supposons cependant que nous vivions dans un monde inversé et qu'il ne soit toujours pas là. Alors, que pourrions-nous faire pour mettre cette ligne dans un programme?
La première chose que vous pouvez faire pour cela est de spécifier l'adresse correcte pour
bash_path , en la plaçant plus haut, ici dans ce compartiment de notre pile, en y insérant trois éléments, chacun de 4 octets:
/ 0
/ pat
/ bin
Mais en tout cas, notre pointeur vient ici et - boum! - La chose est faite. De cette façon, vous pouvez maintenant appeler des arguments en les plaçant simplement dans votre code shell. Terrifiant, non? Et tout cela est construit avant une attaque
BROP complète. Mais avant de signaler une attaque
BROP complète, vous devez comprendre comment vous enchaînez simplement les éléments déjà
contenus dans le code. Lorsque j'ai cette adresse de retour sous-évaluée ici, nous voulons simplement accéder au shell. Mais si vous êtes un attaquant, vous pouvez diriger cette adresse de retour, ou cette adresse de retour, vers quelque chose qui pourrait vraiment être utilisé. Et si vous faisiez cela, alors vous pourriez enchaîner plusieurs fonctions dans une rangée dans une rangée, plusieurs signes d'une fonction dans une rangée. Il s'agit en effet d'une option très puissante.
Parce que si nous définissons simplement l'adresse de retour pour le saut, le programme se bloque généralement, ce que nous ne voulons peut-être pas. Par conséquent, il vaut la peine de lier certaines de ces choses ensemble afin de faire des choses plus intéressantes avec le programme.
Supposons que notre objectif soit de vouloir appeler le système un nombre arbitraire de fois. Nous ne voulons pas simplement le faire une fois, nous le ferons un nombre arbitraire de fois. Alors, comment cela peut-il être fait?
Pour ce faire, nous utilisons deux informations que nous savons déjà obtenir. Nous savons comment obtenir l'adresse système - il vous suffit de regarder dans
GDB et de la trouver là-bas. Nous savons également comment trouver l'adresse de cette ligne,
bin / bash . Maintenant, pour lancer cette attaque en utilisant plusieurs appels au système, nous devons utiliser des gadgets. Cela nous rapproche de ce qui se passe dans
BROP .
Il nous faut donc maintenant trouver l'adresse de ces deux opérations de code:
pop% eax et
ret . Le premier supprime le haut de la pile et le place dans le registre
eax , et le second le place dans le
pointeur d' instruction
eip . C'est ce que nous appelons le gadget. Il ressemble à un petit ensemble d'instructions d'assemblage qu'un attaquant peut utiliser pour construire des attaques plus ambitieuses.

Ces gadgets sont des outils standard que les pirates utilisent pour trouver des choses comme des fichiers binaires. Il est également facile de trouver l'un de ces gadgets, en supposant que vous ayez une copie du fichier binaire et que nous ne nous sommes pas souciés de la randomisation. Ces choses sont très faciles à trouver, ainsi que très facile à trouver l'adresse du système et ainsi de suite.
Donc, si nous avons l'un de ces gadgets, pourquoi pouvons-nous l'utiliser? Bien sûr, faire le mal! Pour ce faire, vous pouvez procéder comme suit.
Supposons que nous modifions notre pile pour qu'elle ressemble à ceci, l'exploit, comme précédemment, est dirigé de bas en haut. La première chose que nous faisons est de placer l'adresse système ici, et au-dessus, nous mettons l'adresse du gadget
pop / ret . Encore plus haut, nous mettons l'adresse de
bash_path , puis répétons tout: par le haut, nous plaçons à nouveau l'adresse du système, l'adresse du gadget
pop / ret et l'adresse de
bash_path .

Que va-t-il se passer ici maintenant? Ce sera un peu compliqué, donc les notes de cette conférence sont disponibles sur Internet, et pour l'instant vous pouvez simplement écouter ce qui se passe ici, mais quand j'ai compris cela pour la première fois, c'était comme comprendre que le Père Noël n'existait pas!
Nous allons commencer à l'endroit où se trouve l'entrée d'
entrée , revenir au système où l'instruction
ret va supprimer l'élément de la pile à l'aide de la commande
pop , donc maintenant le haut du pointeur de pile est ici. Ainsi, nous supprimons l'élément à l'aide de
pop , puis retournons la procédure
ret , qui transfère le contrôle à l'adresse de retour sélectionnée dans la pile, et cette adresse de retour y est placée avec la commande
call . Donc, nous appelons à nouveau le système, et ce processus peut être répété encore et encore.

Il est clair que nous pouvons relier cette séquence pour effectuer un nombre arbitraire de choses. Essentiellement, le noyau obtient ce qu'on appelle une programmation orientée inverse. Veuillez noter que nous n'avons rien effectué sur cette pile. Nous avons fait ce qui nous a permis d'empêcher l'exécution des données sans rien détruire. Nous venons de faire un saut inattendu pour faire ce que nous voulons. En fait, c'est très, très, très intelligent.
Et ce qui est intéressant, c'est qu'à un haut niveau, nous avons identifié ce nouveau modèle informatique. , , , . , , . , - . , , . , . . , . , ,
stack canaries., «» , . , , «»
ret address saved %ebp , - , «». ,
ret , , «», , - .
stack canaries .
, «». , . , «»?
, , , .
, , , «» , «» «».
, , , «» , , .
, - , «» , . , ? ?
,
fork . ,
fork . , , ,
fork , , , , «» . ,
stack canaries .
«»? . , , , «». «» . .

, , – , «». , , 0. , «», . , :
«, «»! , 0. «»! 1 – «», 2 – . , 2- . , , «».

, , , .
«», , , . , , «».
57:10
:
Cours MIT "Sécurité des systèmes informatiques". Conférence 3: Buffer Overflows: Exploits and Protection, Part 3La version complète du cours est disponible ici .Merci de rester avec nous. Aimez-vous nos articles? Vous voulez voir des matériaux plus intéressants? Soutenez-nous en passant une commande ou en le recommandant à vos amis, une
réduction de 30% pour les utilisateurs Habr sur un analogue unique de serveurs d'entrée de gamme que nous avons inventés pour vous: Toute la vérité sur VPS (KVM) E5-2650 v4 (6 cœurs) 10 Go DDR4 240 Go SSD 1 Gbps à partir de 20 $ ou comment diviser le serveur? (les options sont disponibles avec RAID1 et RAID10, jusqu'à 24 cœurs et jusqu'à 40 Go de DDR4).
Dell R730xd 2 fois moins cher? Nous avons seulement
2 x Intel Dodeca-Core Xeon E5-2650v4 128 Go DDR4 6x480 Go SSD 1 Gbps 100 TV à partir de 249 $ aux Pays-Bas et aux États-Unis! Pour en savoir plus sur la
création d'un bâtiment d'infrastructure. classe utilisant des serveurs Dell R730xd E5-2650 v4 coûtant 9 000 euros pour un sou?