mardi 8 juillet 2008

[OS]Le mirroring

Suite à l'article précédent, je présente ici une technique de mise en œuvre de la pagination car il nous reste encore un petit problème à régler avant d'activer la pagination qui se fait pas le biais du bit 31 du registre cr0.

La pagination sert à transformer des adresses logiques ( j'appellerais dans la suite adresses logiques les adresses fictives dont j'ai parlé dans l'article précédent car c'est le nom qu'on leur attribut normalement ) en adresses physiques qui correspondent à l'emplacement en RAM. Jusque là il n'y a pas de problème, cependant une fois que la pagination sera activée si nous n'avons pas fait de référence aux PDE et PTE on en pourra pas les modifier et on ne pourra donc pas rajouter/supprimer d'espace mémoire virtuel ce qui est très embêtant car il nous faudra alors allouer toute la mémoire avant de lancer la pagination et bien entendu l'intérêt de la pagination dans ce cas là est bien moindre car on ne pourra pas utilisé tout les principes sous-jacents à la pagination comme le fait de pouvoir utiliser une partition swap ou tout autre espace de stockage en mémoire morte comme un stockage sur un autre pc par le réseau.

Il nous faut donc obligatoirement faire référence aux PDE et PTE, pour cela on pourrait décider qu'un PT qui serait charger de faire référence à tout les autres PTE et aux PDE. Étant donnée qu'il y a 1024 PTE dans un PT et en considérant qu'on n'a pas besoin de modifier le PT qui fait référence aux autres, il nous reste 1023 PT à référencer + le PD, ce qui nous rempli entièrement le PT. Nous pouvons mélanger les PDE avec les PTE car ceux-ci ont une structure similaire, seul deux bits ont une signification différente mais ils sont tout de même "compatible".

Cette solution pourrait être envisageable mais au niveau de l'implémentation, ça serait assez chiant et vous connaissez les programmeurs ils n'aiment pas perdre leur temps et vont toujours au plus rapide même si on perd en lisibilité.


Le mirroring consiste tout simplement à indiquer à un PDE qu'elle doit pointer sur l'adresse de base du PD ( qui est contenu dans cr3 ) et on comprend maintenant le nom de cette technique. Le PDE en question peut être choisi arbitrairement cependant un choix comme la première ou la dernière serait plus "logique" que de choisir n'importe qu'elle entrée au milieu du PD.

Donc voyons ce que ça donne en pratique, lorsque l'on veut modifier un PTE on utilisera comme offset du PDE l'index du mirroring, comme offset du PTE le numéro du PT auquel on veut accéder et on choisira le dernier offset en fonction du PTE qu'on veut modifier. Et lorsque l'on veut modifier un PDE on utilisera comme offset du PDE et du PTE celui de l'index du mirroring comme ça on est redirigé deux fois vers le PD lui même et on se sert de l'offset pour accéder au PDE correspondant. Attention cependant à ne pas modifier l'index du mirroring pour qu'il ne pointe plus vers l'adresse de base du PD sinon tout le système sera court-circuiter et on ne pourra plus modifier ni PDE ni PTE comme précédemment. Afin d'éviter ceci il faut effectuer une petite vérification dans le code.

Voila un petit schéma qui illustre ceci ( je l'ai fait à la main n'étant pas infographiste donc si quelqu'un qui sait utiliser un logiciel de dessin passe par la et qu'il pouvait me faire ceci je lui en serait très reconnaissant ) :

Voici un exemple avec un offset de PD de 1024, un offset de PT de 123 et un offset de page de 673.
Donc on peut voir que le dernier PDE pointe vers la base du PD ( en rouge ), que le PDE pointe vers le PT ( en bleu ) comme d'habitude sauf que ceci se passe à la deuxième indirection et non pas à la première et enfin on accède au PTE en vert avec le dernier offset.

Les deux techniques sont équivalentes en terme de mémoire virtuelle, elles utilisent toutes les deux 4Mo de mémoire virtuelle. Dans le premier cas on utilise 1024 PTE qui pointent chacun normalement sur une page de 4Ko ce qui nous fait bien 4Mo ( 1024*4Ko ) et le mirroring bloque un PDE et donc en même temps libère un PT qui reste inutilisé dans la mémoire physique or un PDE représente la même mémoire virtuelle qu'un PT donc encore 4Mo de mémoire virtuelle.
Cependant la technique du mirroring est bien plus propre, rapide, ingénieuse que la première méthode exposée et de plus elle libère 4Ko dans la mémoire physique.


Et bien voila une bonne chose de faite, en attendant moi je continue de coder mon petit kernel même si le temps me manque pour pouvoir m'investir comme je le voudrais.

dimanche 1 juin 2008

[OS]La pagination

Il y a déjà un petit moment que je voulais sorti cet article mais bon vous savez ce que c'est, les cours, les devoirs, la flaime et vla quoi.
Donc je me décide à prendre ma patience en main et à me décider à prendre un peu de temps pour écrire cet article qui sera surement le début d'une série d'article concernant les mécanismes des OS modernes.
Ces articles se baseront sur mes recherches, car comme je l'avais annoncé il y a un petit moment je me suis lancé dans la conception d'un kernel minimaliste.

Ce premier article va parler de la pagination qui est au cœur de tout les systèmes de swap et de partages de la mémoire, elle est donc omniprésente dans tout les OS modernes à savoir pour les plus connus Windows, Linux, Mac OS, Solaris, freebsd et bien d'autres encore.

La pagination est une technique basé sur une double indirection ( je reviendrais sur ce terme plus tard ) qui sert à étendre la mémoire virtuelle jusqu'à un maximum de 4Go quelque soit la RAM disponible, c'est donc un mécanisme extrêmement performant implanté directement dans la MMU ( Memory Management Unit ) qui est elle même généralement inclus dans les microprocesseurs récents, et c'est donc pour cela qu'elle est très largement utilisé dans tout les OS modernes.
Elle permet aussi selon sa configuration de séparer différentes parties de la mémoire notamment le cloisonnement des processus et pourra donc être utilisé pour détecter des tentatives de violation d'accès.

Dans les années 60 la mémoire physique ( RAM ) était très limitée de part son prix élevé, c'est pour cela que des mécanismes de mémoire virtuelle ont été inventé. Parmi ces mécanismes ont retrouve la segmentation ( que je présenterais surement pas car elle n'est pas très intéressante ) et la pagination ( qui est le sujet du présent article ).

Les adresses manipulés par le CPU sont entièrement fictives, c'est la MMU qui se charge de savoir si l'adresse fictive correspond à une adresse physique, si tel est le cas alors il donne l'accès au CPU à cette adresse sinon il demande à l'OS de trouver l'emplacement mémoire correspondant, si l'OS ne trouve pas de correspondance alors il le signale au CPU ( segfault, violation d'accès ) sinon il swape l'emplacement mémoire en RAM et donne l'accès à cet emplacement au CPU, si aucune place en RAM n'est disponible l'OS va alors swaper un morceau de la RAM peu utilisé vers une mémoire de masse généralement le disque dur.
Cette action est appelée traduction d'adresse car la MMU traduit des adresses fictives en adresses physiques.

Ceci laisse donc penser que la MMU dispose de tables de traductions faisant la correspondance adresses fictive --> adresses physique pour chaque adresse fictive de la mémoire virtuelle. Ceci demanderai beaucoup trop de mémoire, c'est donc pour cela que la mémoire est partagée en page de 4Ko ou 4Mo selon la configuration de la pagination, il suffit ensuite de connaitre l'offset pour retrouver l'adresse physique.

Revenons maintenant sur le terme de double indirection qui est au coeur de toute la puissance de la pagination avec un petit shéma ( tiré de la doc intel ).




On peut voir sur ce schéma que l'adresse fictive est découpé en trois parties égales :
* bit 31-22 : l'index de la PDE ( Page Directory Entry )
* bit 21-12 : l'index de la PTE ( Page Table Entry )
* bit 11-0 : l'offset de la page

Ces trois parties de l'adresse fictive représente chacune 11 bits soit exactement soit exactement 4Ko adressables pour chacune des parties.

La première indirection concerne la PD ( le registre cr3 conserve son adresse ), la première partie de l'adresse fictive sert donc à repérer le PDE correspondant à l'adresse fictive, ce PDE est en fait un pointeur sur la PT correspond à l'adresse avec en plus quelques données sur les privilèges requis et quelques autres petites choses, c'est à ce moment que survient la seconde indirection avec la deuxième partie de l'adresse fictive qui sert elle à repérer le PTE correspondant qui est elle même un pointeur sur une page physique avec les mêmes données que pour la PDE, enfin la traduction se finit grâce à l'offset qu'on ajoute à l'adresse de la page physique.

Comme le montre ce schéma la PD a exactement 1024 PDE, chaque PDE correspond à une PT, chaque PT a 1024 PTE et chaque PTE correspond a une page physique.
Ce qui fait 1024*1024 pages physiques adressables, comme chaque pages physiques correspond à 4Ko il y a 1024*1024*(1024*4) soit 4Go.

Certains processeurs implémente des mécanismes avec 3 indirections ce qui augmente encore la mémoire physique, il est aussi possible de travailler avec des pages de 4Mo et d'utiliser seulement un PD ce qui revient au même étant donner qu'il y aura 1024 pages de 4Mo ce qui fait bien 4Go et les 22 bits de l'offset corresponde bien au 4Mo adressables de la page.

Les informaticiens se sont rendu compte que certains espace mémoire étaient plus sollicité que d'autre, ils ont donc décidé de mettre en place un cache de traductions d'adresses ( le TLB pour Translation Lookaside Buffer ). Ce cache conserve un certains nombres de traductions d'adresses et il permet d'accélérer la phase de traduction d'adresse car si on regarde un peu mieux le schéma on se rend compte qu'il faut 3 accès à la RAM pour chaque accès à la mémoire virtuelle.


C'est la fin de cet article, je pense que mon prochain article portera sur une technique très particulière de pagination que j'ai moi même utilisé et que je trouve très intéressante car extrêmement simple mais aussi très ingénieuse.

* http://fr.wikipedia.org/wiki/Pagination_%28informatique%29
* Doc Intel Vol 3 --> Chap 3.6 Paging ( Virtual memory )
* http://sos.enix.org/wiki-fr/upload/SOSDownload/sos-texte-art4.pdf
( article très intéressant sur la pagination et sa mise en place )

lundi 7 avril 2008

Off-by-one overflow - Exemple de strncat

Comme nous l'avons vu dans un précédent article, il y a pas mal de confusions possibles avec les jeux de fonctions str*cat/str*cpy ceci provoque des buffer overflow.
Bon alors lançons nous maintenant dans un buffer overflow causer la plus part de temps par une mauvaise utilisation de strncat.

Commençons par le code d'exemple qui servira de POC ( proof of concept )
#include <stdio.h>
#include <string.h>

void disbonjour(char *str)
{
char buffer[16] = "Bonjour ";

strncat(buffer, str, sizeof(buffer)-strlen(buffer));

printf("%s", buffer);
}

int main(int argc, char *argv[])
{
disbonjour(argv[1]);

return 0;
}


Ici strncat n'est pas bien utilisé ce qui va entrainer un buffer overflow un peu spécial voyons ça d'un peu mieux.

Attention gcc rajoute des données dans la pile, je ne sais pas trop ce que c'est il y a un truc pour la gestion des exeptions enfin bref ... donc ce que je voulais dire c'est que ceci ne marche que pour les anciennes versions de gcc.

bonjour.exe azertyuiop


Et nous avons une belle exeption Access Violation à l'adresse 41414141.

En fait, ce qui ce passe ici c'est que nous avons un buffer overflow un peu particulier, le off-by-one overflow, nous ne pouvons écraser qu'un seul octet et dans le cas de strncat il ne peut s'agir que d'un '\0'.

Nous allons donc écraser le premier octet du ebp sauvegarder par un '\0' cependant comme nous sommes en litte endian nous allons en fait écraser l'octet de poids faible du ebp sauvé.

La redirection ne vient pas ici du eip sauvé ni même directement du ebp sauvé, en fait une fois de retour dans la fonction main ebp sera différent et donc toutes les données aussi y compris le eip sauvé pour retourné à la fonction qui a appelé main or ici nous aurons un eip différent et donc le prog sera rediriger.

Ici ce pose un problème car on ne peut pas vraiment savoir à l'avance sur quoi pointera le nouveau ebp, si nous avons de la chance notre nouveau ebp pointera sur un buffer et nous pourons ainsi contrôler la redirection.

Cependant si nous n'avons pas de chance, l'écart entre l'ancien et le nouveau ebp sera si petit que nous ne pourrons rien faire.

Je voulais juste vous présentez très simplement les off-by-one car c'est très sympa mais la plupart du temps nous n'arriverons pas à faire que le nouveau ebp pointe vers un buffer.

mercredi 26 mars 2008

Exploitation classique de Buffer overflow

Attention, j'estime que vous connaissez un minimum sur les bof, si ce n'est pas le cas je vous conseilles de lire l'article de wikipédia sur les bof : http://fr.wikipedia.org/wiki/D%C3%A9passement_de_tampon

Suite à mon article sur les fonctions str*cpy/str*cat, 0vercl0k m'a fait remarqué que ça serait sympa que je montre comment exploiter ces failles de sécurités.
Je vous expose donc mes découvertes sur les exploitations de ces failles classiques, on va commencer par quelque chose de simple comme ceci :


#include <stdio.h>
#include <string.h>


void foo(char *str)
{
char buffer[32];

strcpy(buffer, str);

printf("%s\n", buffer);
}

int main(int argc, char *argv[])
{
foo(argv[1]);

return 0;
}


Ce code est très simple mais il n'en ai pas moins vulnérable étant donné qu'on utilise la fonction strcpy et nous savons tous que cette fonction conduit à des bof.
Donc compiler ce prog et voyons ensemble comment nous y prendre.

vuln.exe azerty

azerty


Maintenant provoquons un bof !

vuln.exe AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


Et puis nous avons une belle fenêtre avec un beau plantage.

Si on regarde bien le rapport de bug, on observe que le prog plante à l'offset 41414141 or 41 correspond à la lettre A on peut donc supposer que eip avez la valeur AAAA et que c'est lui qui a causer le bug.

Il faut savoir qu'après notre buffer ce trouve deux choses importantes la sauvegarde de ebp et la sauvegarde de eip qui servira pour revenir à l'instruction juste après le call.

On peut donc facilement rediriger l'exécution du programme pour en faire ce qu'on veut et même exécuter un shellcode.


Bon ce article touche à ça fin mais j'en posterais plusieurs autres car je n'ai pas encore parler des autres failles avec str*cpy/str*cat mais je voulais le sortir parce que j'ai eu quelques problèmes avec gcc qui mettait des protections un peu partout.
Je me suis finalement rendu compte qu'il suffisait de bourriner pour arriver à faire des bof.

Have fun :)

mardi 11 mars 2008

Detect hidden process

Suite à l'article de lilxam ( Listing all processes ) qui fait lui même suite à l'article de Ivanlef0u ( NtSystemDebugControl Demystified ) j'ai voulu aller un peu plus loin que lilxam. Au lieu d'afficher tout les process en faisant un bf des PIDs, j'ai voulu détecter les process hidden et les afficher.

Il nous suffit donc de bf tout les PIDs comme l'a fait lilxam et puis ensuite de voir si les fonctions First32Process/Next32Process trouvent bien ce PID, si elles ne le trouvent pas alors c'est qu'il a utilisé la technique expliquer par Ivanlef0u pour se cacher.

Donc on commence par faire une liste de tout les process avec First32Process/Next32Process, que l'on gardera bien au chaud, ensuite on fait un bf PIDs comme l'a montré lilxam ( on s'arrête à 0x4E1C ) et on recherche dans la liste des process qu'on avait garder si ce PID existe ensuite deux cas s'offre à nous :
  • soit il n'existe pas donc on sort de la boucle et on l'affiche ( je passe les détails pour afficher le nom d'un process avec son handle, merci lilxam )
  • soit il existe au quel cas il ne faut pas oublier de comptabiliser ces threads ( windows ne sait pas trop compter apparement )
Et bien voila pas très dur mais windows m'a bien fais chier avec ces threads qui sont considérer comme des process mais qui n'apparaissent pas avec First32Process/Next32Process. Et je n'ai pas trop compris pourquoi tous les Threads n'étaient pas comptabilisés si quelqu'un a la réponse ?

On cache le process avec l'outil de Ivanlef0u ( xchat.exe par exemple ) :
KFist.exe calc.exe


Puis on regarde la liste des process avec le gestionnaire de tâches et on remarque que xchat à disparu.

Donc on lance notre petit programme et il nous affiche ceci :
[+]Process Hidden : xchat.exe
[+]Process Hidden : xchat.exe
[+]Process Hidden : xchat.exe
[+]Process Hidden : xchat.exe


Bon il nous affiche plusieurs fois xchat.exe parce qu'il nous affiche tout les threads mais en tout cas ça marche et l'avantage c'est qu'on a le résultat directement.

Un bon moyen de détecter un process qui s'est hidden.

Have fun ;)


Voici le code : DetectProcessHidden.c

PS : n'oublier pas de linker la lib psapi

dimanche 9 mars 2008

Les erreurs de codages avec str*cpy et str*cat

Dans cet article je vais vous parlez des erreurs de codage fréquemment rencontrés avec les fonctions str*cpy et str*cat, ces erreurs sont généralement dû à une mauvaise utilisation des fonctions en question et amènent à un stack overflow.

Tout le monde sait que la meilleure façon de faire un stack overflow, c'est d'utilisé strcpy() ou strcat() car elle ne vérifie pas la longueur des variables et écrivent sans aucun remord dans des zones ne leur appartenant pas généralement une sauvegarde de ebp et eip mais si ils continuent ils peuvent écraser encore plus de données. C'est généralement l'écrasement de eip qui conduit à une redirection des instructions vers un shellcode seulement un écrasement de ebp est tout aussi dangereux.

Les programmeurs pour éviter les stack overflow utilisent donc leur équivalent un peu plus sécurisé ( pas entièrement sinon c'est pas marrant ) strncpy() et strncat() en leur passant en troisième paramètre la taille du buffer.
Cependant les deux fonctions n'ont pas le même comportement, dans la page de man de strncpy() on nous dit que le \0 final n'est pas compter et il faut donc en tenir compte alors que dans la page de man de strncat() on nous dis que le \0 final est rajouter par la fonction elle même.
Cette différence entraine généralement des erreurs de codage.

strncpy(dest, src, sizeof(dest)-1);

au lieu de
strncpy(dest, src, sizeof(dest)-1);
dest[sizeof(dest)-1] = 0;


En général, le stack overflow ne vient pas de cette ligne mais d'un éventuel str*cat() qui suit, imaginez ce code:

strncpy(dest, src, sizeof(dest)-1);
strncat(dest, src2, sizeof(dest)-strlen(dest)-1);


Ici strncat est bien utilisé mais le strncpy() va provoquer deux choses d'abord un integer overflow et après un buffer overflow.

strncat à la prototype suivant char * strncat ( char * destination, char * source, size_t num );

Ici l'absence de \0 dans dest à la fin du strncpy va faire que strlen(dest) va être beaucoup plus grand que sizeof(dest) et donc que le résultat sera négatif or la fonction accepte un size_t qui est unsigned donc cela fera un nombre extrêmement grand d'où le possible overflow.

Ensuite il faut savoir que strncat met un \0 à la fin de la chaine de destination à tout les coups donc cet appel provoquera un overflow ( en supposant qu'il y ai bien un \0 dans dest pour ne pas revenir dans le même cas que précédemment ) :

strncat(dest, src, sizeof(dest)-strlen(dest));


Ici la fonction strncat va bien copier sizeof(dest)-strlen(dest) et le buffer sera rempli mais la fonction va rajouter un \0 et provoquer un overflow qui va écraser le premier octet de la sauvegarde de ebp.

C'est quoi un handle ?

On utilise des HANDLE un peu partout, pour manipuler des fichiers après un CreateFile() par exemple, pour le chargement des dlls et leur manipulation vu que HMODULE n'est qu'un simple typedef de HANDLE ( en passant par l'intermédiaire de HINSTANCE qui est aussi un typedef de HANDLE ) avec l'API LoadLibrary().

Mais je vais essayer de vous expliquer ce qu'est concrètement un HANDLE.
Bon alors on fait un petit tours du côté de winnt.h et on recherche le typedef HANDLE et on tombe sur ceci vers les lignes 150 chez moi :
#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE(n) typedef struct n##__{int i;}*n
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(n) typedef HANDLE n
#endif


Et c'est la qu'on ce rend compte qu'en fait un HANDLE ce n'est pas grand chose ( void qui signifie vide ).

Bon ça ne nous avance pas beaucoup mais on sait au moins que c'est un pointeur sur void. Dans la plus part des cas un pointeur sur void n'est rien d'autres qu'une adresse mémoire, on va donc essayer de l'afficher.

- DumpHMod.c

Hum tiens intéressant, tiens l'adresse nous dis quelque chose, elle correspond à l'adresse de kernel32 en mémoire. Quand au Dump pas très bavard il n'y a que trois octets ( il y a surement un \0 en quatrième octets ) cependant on reconnait bien le début d'un MZ header.

Pour conclure, je dirais juste qu'un HANDLE n'est en fait qu'un pointeur sur un fichier mapper en mémoire et c'est donc pour ça qu'on utilise autant de HANDLE et que ceci sont aussi utile.