on m'a posé une question piège, je n'ai pas de réponse absolue et je ne trouve pas sur Internet. Est-ce qu'une zone de code est modifiable ou protégée et va déclencher un "segmentation fault" en cas d'accès en lecture voir écriture.
* oui | non ?
* si oui, avec quelles restrictions ? (OS, droits user)
ce qui me semble intervenir dans la réponse :
* le microprocesseur,
* l'OS,
* le contexte d'exécution (mode noyau, mode privilégié, mode user)
* le langage (pas de pointeur, pas de peek-poke, pas de buffer-overflow : pas de risque)
merci pour tes réponses avisées, oh mon grand journal.
# Forum?
Posté par Nadine . Évalué à 1.
# du coté de elf32 ...
Posté par vincent LECOQ (site web personnel) . Évalué à 9.
Il y a deux zones dans un executable : la partie statique et la partie dynamique.
La partie statique est constituée du code et des chaines de caracteres fixes (definies a l'avance dans le code). Elle est en lecture seule, si tu tentes d'y ecrire, segfault.
La partie dynamique est composé des allocations mémoires faites durant l'execution, ca c'est en lecture/ecriture.
petit exemple :
ca ca segfaulte grave.
Il existe une option de gcc pour forcer la mise en lecture/ecriture de la zone statique, mais c'est fortement deconseillé.
Par contre il existe une méthode dite d'injection de code qui consiste a inserer un thread dans un processus. comme la liste des processus est dans la zone dynamique (il ne sont pas definis a l'avance en nombre) , on peut y inserer un nouveau dont le code est dans la zone de données ou ailleurs. la ca devient moins cool car c'est pas visible au premier coup d'oeil.
[^] # Re: du coté de elf32 ...
Posté par vincent LECOQ (site web personnel) . Évalué à 2.
[^] # Re: du coté de elf32 ...
Posté par Marc Quinton . Évalué à 1.
[^] # Re: du coté de elf32 ...
Posté par CoinKoin . Évalué à 1.
[^] # Re: du coté de elf32 ...
Posté par Nicolas Bernard (site web personnel) . Évalué à 3.
[^] # Re: du coté de elf32 ...
Posté par tene . Évalué à 1.
Comment ça se passe? la lib est chargée puis son espace mémoire est mis en lecture seule?
[^] # Re: du coté de elf32 ...
Posté par minico . Évalué à 2.
Sous Linux, le chargement des bibliothèques dynamiques est fait en espace utilisateur. Au momment du chargement d'un binaire, le noyau charge le code du programme en lecture seule, les differentes zones de données en lecture/ecriture et charge ensuite l'"interpreteur" à utiliser pour charger les bibliothèques dynamiques (en général /lib/ld-linux.so). le code du programme et l'interpreteur sont dans le meme espace d'adressage. En fonction de ces besoins, le programme peut appeller le code de l'intepreteur pour charger des bibliotheques (de maniere automatique pour les bibliothèques précisées en dependance du binaire executé, ou lors de l'appel aux fonctions dl{open,sym,close}.).
L'interpreteur utilise l'appel système mmap pour rendre accessible au programme les bibliothèques. Les zones de code des bibliothèques sont mappées en lecture seule. Les zones de données des bibliothèques sont mappées en lecture/ecriture.
Faire un strace sur un programme lié dynamiquement permet de mettre en evidence l'activité de l'interpréteur.
[^] # Re: du coté de elf32 ...
Posté par CoinKoin . Évalué à 1.
En effet, ces processeurs n'ont aucune protection en écriture vis-à-vis des instructions exécutées en mode privilégié (ce qui est d'ailleurs assez gênant...), du coup, la modification est parfaitement possible.
Ah, et au fait : même en espace utilisateur, cela dépend aussi de l'OS employé. Certains petits UNIX (Minix, notamment, je crois), ne mettent pas en place cette protection, du coup, la partie statique est quand même modifiable.
[^] # Re: du coté de elf32 ...
Posté par David Decotigny (site web personnel) . Évalué à 1.
Faux. c'est le bit WP du cr0 qui permet ça (a partir du 486).
[^] # Re: du coté de elf32 ...
Posté par CoinKoin . Évalué à 2.
[^] # Re: du coté de elf32 ...
Posté par Lucas . Évalué à 1.
Mais nécessaire si on fait du code auto-modifiable, comme GCC avec les trampolines. Voir http://www.lucas-nussbaum.net/blog.php/?2005/03/04/99-gcc-trampolin(...)
[^] # Re: du coté de elf32 ...
Posté par Nicolas Bernard (site web personnel) . Évalué à 2.
[^] # Re: du coté de elf32 ...
Posté par Lucas . Évalué à 1.
# sur un DSP
Posté par imalip . Évalué à 8.
-tiens, j'ai écrit dans le handler d'interruption, judicieusement placé en 0x0000000
-oups, erreur de mon pointeur, je viens d'écrire dans le registre d'adresse de retour qui est mappé en mémoire (coincidence totale, c'était mappé a une adresse qui était précisément la valeur que je copiais)
-vive le programme qui s'autopatch ! (oui, j'aime vivre dangereusement)
# zone de code ?
Posté par bleh . Évalué à 0.
Soit tu parles du registre d'instruction et du segment de code. Dans ce cas, le programmeur n'a pas accès à cette zone (et registre) qui est utilisé pour l'instruction courante et son décodage. On peut en revanche tenter de modifier l'adresse de retour d'une fonction (le compteur ordinal est stocké sur la pile) pour que la prochaine instruction chargée dans le registre d'instruction ne soit pas celle prévue normalement (technique du dépassement de buffer) mais celle d'un code malicieux. Dans le cas général (une simple erreur de programmation), si tu écrase l'adresse de retour par une adresse quelconque, tu récupère simplement un "Segmentation Fault".
Soit tu parles d'une zone mémoire standard (d'un processus et pas d'un thread), dans ce cas, il s'agit d'un espace utilisateur alloué pour le programme et toute tentative d'écriture en dehors de cette zone se traduit par un seg fault (pas toujours mais bon). Le mode privilégié (ou noyau) a le droit justement d'écrire où il le souhaite.
J'espère que ça réponds plus ou moins à ta question, c'est assez compliqué et je ne suis pas non plus un expert de la question.
[^] # Re: zone de code ?
Posté par David Decotigny (site web personnel) . Évalué à 1.
[^] # Re: zone de code ?
Posté par bleh . Évalué à 0.
Parce que j'avais oublié qu'on pouvait utiliser mprotect et que ça fait des années que je ne me suis pas posé ces questions (le C c'est un peu lointain et je ne bosse plus que sur mainframe), J'ai essayé de rassembler mes souvenirs mais c'est pas trop ça :+).
# archi IA32
Posté par Mouns (site web personnel) . Évalué à 1.
Par contre, si la gestion disque est mal faite et qu'il y a un gestionnaire de mémoire paginée ... alors il est possible d'injecter du code en modifiant la partie sur le disque.
Sinon, pour avoir un "segmentation fault", il te faut un systeme ayant des segments ... mais etrangement la mode au modele plat de mémoire lève cette bariere et permet de faire des merveille d'injection par buffer overflow ( qui ne font plus de segfault puisque pouvant acceder comme donnée au segment de code ).
[^] # Re: archi IA32
Posté par CoinKoin . Évalué à 1.
Définir "système bas niveau". Processus en espace utilisateur? Non, un tel processus ne peut pas écrire dans un tel segment. Ni le lire, d'ailleurs : il peut seulement y exécuter des instructions.
Par contre, si la gestion disque est mal faite et qu'il y a un gestionnaire de mémoire paginée ... alors il est possible d'injecter du code en modifiant la partie sur le disque.
Très très très mal faite, alors, mais c'est vrai. Cela dit, on appelle ça un bogue de l'OS, pas une fonctionnalité.
Sinon, pour avoir un "segmentation fault", il te faut un systeme ayant des segments ...
Faux. L'écriture dans une page marquée en lecture seule par un processus en espace utilisateur provoque (sur i386) la levée de l'interruption 14 du processeur (faute de page), avec un vecteur précisant que l'erreur est due à une écriture dans une page en lecture seule. Le système d'exploitation récupère alors cette erreur et la traduit par un SIGSEGV à l'égard du processus fautif.
mais etrangement la mode au modele plat de mémoire lève cette bariere et permet de faire des merveille d'injection par buffer overflow ( qui ne font plus de segfault puisque pouvant acceder comme donnée au segment de code ).
Adepte d'OpenBSD et de son modèle segmenté, hein? Ben non, ce n'est pas ça : les buffer overflows ont lieu sur la pile, pas dans la zone de code, et la protection supplémentaire qu'apporte le modèle segmenté tient non pas à ce qu'il interdit la modification des zones exécutables, mais à ce qu'il interdit à l'exécution de se poursuivre sur la pile.
Cela dit, cette protection est elle aussi contournable, mais il faut une connaissance très fine du binaire attaqué.
[^] # Re: archi IA32
Posté par Mouns (site web personnel) . Évalué à 1.
je parle des processus pouvant acceder aux segments ayant leur DPL à 0.
Dans ( j'ai pas envie de verifier si ca compile, donc c juste une idée ) :
je ne vois pas ou je touche a la pile, et pourtant, je fais un buffer overflow et avec un strncpy().
Au fait, explique moi comment dernierement certains ont presenté les raisons du flag NX au niveau de la gestion des pages ?
Si deux segments se superposent comme dans un modele plat, alors cela peut faire deux jeux de pages dont l'un est ecrivible l'autre non.
Puis sais tu que tu n'es pas obligé d'activer la pagination ?
Si le flag PG de CR0 est à 0, l'exception 14 ne se produira pas, par contre la 13 ( oué celle qui porte malheur ), elle pourra toujours se produire.
D'ailleurs, la securité des 80286 en mode protégé ne reposait pas sur l'exception 14 puisqu'elle n'existait pas pour eux.
[^] # Re: archi IA32
Posté par CoinKoin . Évalué à 1.
Au fait, explique moi comment dernierement certains ont presenté les raisons du flag NX au niveau de la gestion des pages ?
Comment? Avec des transparents, je suppose...
NX est un bit de pagination. Tu as écrit sur les archi IA32, un segment marqué code n'est accessible en théorie, qu'en écriture par le systeme bas niveau.
NX n'a rien a voir avec les segments.
Si deux segments se superposent comme dans un modele plat, alors cela peut faire deux jeux de pages dont l'un est ecrivible l'autre non.
Je n'ai jamais dit le contraire.
Puis sais tu que tu n'es pas obligé d'activer la pagination ?
Oui. Et je ne vois pas le rapport avec ce dont on parlait, à savoir ton affirmation selon laquelle pour avoir un "segmentation fault", il te faut un systeme ayant des segments, dont je maintiens le caractère erronné.
[^] # Re: archi IA32
Posté par Mouns (site web personnel) . Évalué à 2.
\cycliste{on m'aurait menti ?}
plus sérieusement, tu as des données dans ES/DS et ta pile dans SS.
explique moi comment tu peux sortir de SS mais pas de ES/DS par overflow ?
si je ne m'abuse [SS:SP] est incremental et donc le pointeur de retour n'est pas accessible si l'on copie trop de données dans la pile.
sinon, NX a à voir avec un modele plat de mémoire, ou ( de "ou bien" ) des segments données/code qui se superposent.
une page non executable qui se retrouvent dans CS ne sera pas executé ( fear ). NX palie juste au fait que le modele segmenté n'est plus mis en oeuvre dans les OS pour des soucis de portabilité et de facilité de developpement.
pour ce qui est de la pagination, tu proposais l'exception de gestion de faute de page et non de faute de segment ... je te disais juste que tu te trompais d'exception.
sinon pour obtenir une faute sur un segment ... il te faut des segments ( c'est d'une logique assez vile je l'admet ), que le segment soit un segment de mémoire alloué "physiquement" ( LDT / GDT ) ou "logiquement" ( table d'allocation noyau geré a coup de malloc/free ). dans le cas où l'allocation "logique" n'est pas contrainte par une allocation "physique" je vois mal comment le systeme peut gerer cela ( à moins d'utiliser des appels systemes pour acceder aux zones mémoire qui fait qu'il peut y avoir des controles de dépassement ).
puis utiliser les pages du systeme de pagination pour gerer les segments d'allocation ... c'est assimiler la page à un segment à taille fixe.
PS: il se peut que je me trompe, c'est probable. je vais me replonger dans mes bouquins ASM i80286/i80386/i80486 . entre temps, si tu as des pointeurs précis sur le sujet ( ma réponse ayant été sur les archi Intel IA32, je t'invite à rester autour de ces archis );
[^] # Re: archi IA32
Posté par CoinKoin . Évalué à 1.
plus sérieusement, tu as des données dans ES/DS et ta pile dans SS.
explique moi comment tu peux sortir de SS mais pas de ES/DS par overflow ?
si je ne m'abuse [SS:SP] est incremental et donc le pointeur de retour n'est pas accessible si l'on copie trop de données dans la pile.
Mais c'est pas ça, le principe d'une attaque par buffer overflow! Le principe, c'est justement de profiter de certaines données allouées sur la pile, donc dans le segment pointé par SS, pour écraser l'adresse de retour d'une fonction. La quantité de données qui y est copiée n'influe que sur la taille du buffer overflow nécessaire à l'attaque.
sinon, NX a à voir avec un modele plat de mémoire, ou ( de "ou bien" ) des segments données/code qui se superposent.
une page non executable qui se retrouvent dans CS ne sera pas executé ( fear ). NX palie juste au fait que le modele segmenté n'est plus mis en oeuvre dans les OS pour des soucis de portabilité et de facilité de developpement.
Ah, là, on est d'accord.
pour ce qui est de la pagination, tu proposais l'exception de gestion de faute de page et non de faute de segment ... je te disais juste que tu te trompais d'exception.
Oui, mais non, pas en modèle plat paginé...
Voilà le fichier de référence dont je me sers sur la question : ftp://download.intel.com/design/PentiumII/manuals/24319202.pdf(...)
(C'est le manuel du programmeur système sur i386).
Tu y verras (page 5-44) que, dans un modèle plat paginé, en cas d'écriture à une adresse non autorisée, c'est bien l'exception 14 qui est levée; et les noyaux d'UNIX récupèrent cette exception pour envoyer un SIGSEGV au processus fautif.
sinon pour obtenir une faute sur un segment ... il te faut des segments ( c'est d'une logique assez vile je l'admet ), que le segment soit un segment de mémoire alloué "physiquement" ( LDT / GDT ) ou "logiquement" ( table d'allocation noyau geré a coup de malloc/free ). dans le cas où l'allocation "logique" n'est pas contrainte par une allocation "physique" je vois mal comment le systeme peut gerer cela ( à moins d'utiliser des appels systemes pour acceder aux zones mémoire qui fait qu'il peut y avoir des controles de dépassement ).
Ah, ben voilà! Je n'avais pas compris que nous n'avions tout simplement pas la même définition du segment (je n'y incluais pas les segments logiques gérés par pagination). Effectivement, dans cette définition-là, je suis entièrement d'accord avec toi.
PS. : Pour ce qui est des bouquins, laisse tomber ceux liés au 286, d'abord, c'est un processeur 16 bits, ensuite, je crois bien qu'il ne comporte pas de pagination.
[^] # Re: archi IA32
Posté par Mouns (site web personnel) . Évalué à 2.
ma réponse concernait les archi IA32 avec segments ( oué bon, j'aurai pu peut etre précisé ) donc via l'execption "faute de segment" communément appelé SEGMENT_VIOLATION . je deteste fortement les modeles plats de mémoire selon le meme adage que l'on retrouve dans plein de domaine : si c facile pour les gentils c tout aussi facile pour les mechants.
tu proposais la gestion par le gestionnaire de pagination. il est logique de l'exception 14 soit levé dans ses cas. par contre, elle ne se leve que par page inaccessible ( donc soit 4ko ou 4Mo dans mon souvenir ). je vais lire le lien que tu as passé, il y a tjr des bonnes idées dans les docs constructeur.
merci du conseil :)
... le detail étant que j'ai commencé l'ASM, il y a plus de 15 ans avec des 286 et 386. ce qui fait que voulais me replonger dedans pour corroborer ma version. j'ai entre autres oeuvres sur le sujet, le Ziff-Davis sur les Opcode IA16 et IA32 , seule référence d'une époque où Intel avait du mal à avouer qu'il y avait des bugs dans ses processeurs.
je t'ai proposé un exemple simplet de buffer overflow, non ?
le buffer overflow est l'injection de données dans l'espace de données.
Après à toi pour te débrouiller pour qu'il execute autre chose.
mais je te rappelle que la pile fonctionnement par empilement incremental ce qui veut dire qu'il faut decrementer ton pointeur de pile pour injecter une mauvaise adresse de retour.
dans le cadre d'un buffer overflow sur la pile, quand tu copie, ton pointeur de pile est incrementé. si par hasard tu envisage de faire un tour complet de la pile, tu devrais avoir soit un segment_violation soit un page_fault soit un bound_violation au moment d'arriver au bord supérieur.
quand tu sors de ta procedure, tout ce qui se trouve sur la pile est considéré comme perdu ( état incohérent ).
par contre, quand tu fais un malloc, tu alloues dans l'espace de données, un segment logique de mémoire qui est permanent : il ne peut donc en aucun cas se trouver sur la pile !!!! par contre, l'adresse de ce segment peut etre sur la pile.
dans le cadre d'une programmation avec des segment "physique" ( GDT/LDT ), un depassement de l'espace alloué provoquera une exception. l'injection de code se retrouve donc bloqué.
dans un modele plat, le code et les données sont sur le meme segment. donc, un depassement d'allocation logique ne se retrouvant pas arreté, se retrouve à potentiellement écrire dans son code ou sa pile.
[^] # Re: archi IA32
Posté par CoinKoin . Évalué à 1.
Pas nécessairement, il suffit d'insérer une page invalide entre le code et les données. Ce n'est pas parce que Linux ne le fait pas que c'est impossible.
# Un peu tout ça...
Posté par David Decotigny (site web personnel) . Évalué à 4.
Ce n'est pas le CPU qui etablit tout seul a quel endroit qui a le droit d'ecrire/lire/executer. C'est l'OS. Donc a son tour l'OS peut decider d'exploiter ces notions ou pas. Par exemple DOS ne gere rien de tout ça ; le DOS de base ne sait meme pas qu'il y a une MMU. Mais Windows et les Unix exploitent ces notions.
C'est donc une combinaison hard/soft qui decide quoi faire au niveau des privileges et des droits d'acces autorises. C'est "politique" tout ça, l'OS peut faire ce qu'il veut.
En general (cas Linux/Windows), l'OS considere que les applis utilisateur sont executees avec le plus bas privilege. Ca interdit a ces applications l'utilisation de certaines instructions dont celles qui pourraient modifier leur config de la MMU ou leur niveau de privilege (mise a part les syscalls). Au niveau des privileges, donc, l'appli ne peut rien changer. Sauf si l'OS lui autorise a le faire (jamais le cas sous Linux).
Ensuite, les droits d'acces (r/w voire x) en VM sont definis aussi par l'OS. Mais pas n'importe comment. Dans le cas courant (Unix/Windows) ces droits sont definis dans le format de l'executable qui est charge (man objdump + comparer avec /proc/pid/maps). C'est le compilo qui genere ces fichiers, c'est lui qui dit quelle zone est r/w/x. Par defaut, les sections de code sont en rx, les sections data (et bss) en rw et les sections "rodata" (chaines de caracteres ecrites en dur dans le source) en read-only. Tout acces non autorise generera un SEGV.
Cependant, sous Unix (et j'imagine aussi sous windows), l'appli a toute lattitude pour modifier ces droits. Ca peut se faire en amont lors de la compilation (p.ex : demander a gcc de mettre les chaines en data plutot que rodata) et/ou de l'edition de liens (modif des scripts ld). Ca peut se faire aussi a l'execution (mprotect) car l'OS propose des syscalls pour que l'appli puisse modifier ses droits d'acces (syscalls nécessaires pour ld.so). Par exemple, le programme suivant passera une partie du code en r/w et ecrasera le debut du code de la fonction f (SEGV assure):
-----------------------------------------------------
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
void f()
{
printf("Hugh\n");
}
int main ()
{
int * ptr_f = (int*)f;
printf("Salut\n");
mprotect(((void*) (((long)ptr_f) & ~4095)), 4096,
PROT_READ | PROT_WRITE | PROT_EXEC);
*ptr_f = 0xdeadbeef;
f();
return 0;
}
-----------------------------------------------------
Evidemment, si on vire le mprotect on aura aussi un SEGV mais pas pour les memes raisons. Dans le premier cas (avec mprotect) c'est le code de f qui a ete ecrase et donc qui fait n'imorte quoi (=> SEGV), dans le 2eme cas (sans mprotect) c'est que main tente d'ecrire sur une zone en lecture seule.
Sinon, l'injection de code ne correspond pas obligatoirement a la creation d'un thread parasite. Il sagit tout betement d'ecrire du code la ou on peut sans grand effort (p.ex sur la pile par debordement de tableau local), puis a ruser la pile pour dire au CPU de sauter a l'endroit ou le code a ete injecte. Apres le code injecte peut faire ce qu'il veut : pourquoi pas creer un thread si ca lui chante.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.