samedi 30 août 2008

Identity mapping, Demand paging et Copy on Write

Aller encore un petit tour avec des techniques qui utilise la mémoire virtuelle, aujourd'hui je vais vous présenter trois concepts vraiment très simple mais aussi très efficace.
Les deux derniers servent notamment à réduire la mémoire physique utilisé à un moment donnée ce qui n'est pas si mal puisque celle ci est beaucoup plus limité que la mémoire virtuelle.
Je ne vous l'avais peut être pas encore dit mais chaque processus à sa propre mémoire virtuelle d'ailleurs c'est pour ça qu'il ne peut pas accéder à la mémoire des autres processus or tout les processus partagent la même mémoire physique et la on voit bien que la mémoire physique est beaucoup plus utilisé que la mémoire virtuelle.


Bon alors commençons par l'identity mapping, concepts le plus simple qu'il existe, il s'agit d'un mapping à l'identique de la mémoire physique vers la mémoire virtuelle. Dans linux, l'identity mapping est utilisé pour l'espace noyau qui est le dernier giga de la mémoire virtuelle c'est à dire que 3G + x en mémoire virtuelle correspond à x en mémoire physique ceci facilite d'autant plus les manipulations de mémoire virtuelle. Cependant ont se heurte à un petit problème, comme l'espace noyau ne fait que 1G alors on ne pourra accèder qu'au premier giga de la ram. En fait ce qui ce passe dans linux c'est que les 128 derniers Mo sont réservés pour mapper le reste de la mémoire physique donc si le noyau à besoin d'un emplacement mémoire au dessus des 1G de la ram il va utilisait ces 128 derniers Mo pour mapper mais bon c'est top comme solution, il y a une autre solution c'est de donner 2G à l'espace noyau et 2G à l'espace user comme pour windows si je ne me trompe pas, bon le problème c'est qu'on diminue l'espace utilisateur et comme toute les librairies dynamiques sont mapper dans les 2G de l'espace utilisateur il vaut mieux ne pas en charger trop sinon il ne nous restera plus de mémoire mais bon il faut faire un choix soit on veut accéder à toute la mémoire physique soit on veut plus de mémoire virtuelle.


Bon maintenant passons à un premier concept qui va permettre d'économiser dans un premier temps la mémoire physique et donc on aura moins besoin de faire de manipulation de swap et les performances en seront améliorées.
Ce concept tout comme l'identity mapping est vraiment tout simple, en fait on va allouer l'espace mémoire que le programme à besoin en mémoire virtuelle mais pas en mémoire physique en fait il n'y aura pas d'association entre la mémoire virtuelle et la mémoire physique.
Voyons ça un peu plus en détail, la première chose que le programme va faire c'est qu'il va executer la première instruction de code ( logique non ? ) donc à ce moment là la première page virtuelle de code a été allouée ( ça a été fait par le loader quel qu'il soit -- elf, PE ... ) mais comme je l'ai dit aucune association avec la mémoire physique n'a été fait donc pagefault, a ce moment là l'OS va s'apercevoir qu'il n'y a pas d'association entre mémoire virtuelle et mémoire physique pour cette page donc normalement il devrait renvoyait un segfault ou accès violation sauf qu'il se rend compte que la mémoire physique a été alloué ( grâce à des structures internes ) et on arrive dans une situation de demand paging, il va alors copier les 4 premiers Ko de code grâce à fichier exécutable, les copier dans une page physique et faire une association entre mémoire physique et mémoire virtuelle pour cette page. Ensuite il réexecute la première instruction et là pas de problème puisqu'il y a association entre mémoire virtuelle et mémoire physique et ce jusqu'à ce qu'il sorte de cette page et rebellote copie du code dans une page physique et association entre mémoire virtuelle et mémoire physique sur la page en question, le même principe est appliqué pour les données.
Donc en fait on copie l'exécutable au fur et à mesure de son exécution et on retarde au plus possible les manipulations du swap.


Enfin voyons le Copy on Write souvent abrégé COW, qui est aussi une façon d'économiser l'utilisation de la mémoire physique.
Seulement ce concept ne s'applique seulement dans un cas précis celui du fork().
Bon déjà un fork c'est quoi ?
Un fork c'est tout simplement une duplication d'un processus, on dit que le créateur du second processus est le père et que le processus ainsi crée est le fils.
En fait ce qui se passe lors d'un fork c'est que le processus n'est pas dupliqué à partir de l'exécutable de base, sinon il y aurait deux fois le même code et bien entendu c'est absurde. Seul l'espace d'adressage ( la mémoire virtuelle avec ces associations avec la mémoire physique ) est dupliqué.
Seulement voila pour le code cela ne posera pas de problème puisque le code est en lecture seule seulement pour les données ça sera problématique puisque les deux processus n'auront pas forcément exactement les mêmes données au même moment, en général lors d'un fork on exécute un code différent pour le père et pour le fils mais même si le code exécuté était le même vu qu'on est dans un système multi-tâche, les deux processus s'exécute en même temps mais pas au même moment donc il y aura un conflit de données.
Il y a deux possibilités, soit la section ( une section est ensemble de pages ayant les même flags ) est en shared et dans ce cas là on laisse les deux processus partager cette section et après il se débrouille, soit la page est en private est la on utilisera le Copy on Write.
Le truc c'est que les sections private seront mis en lecture seule pour le fils donc lorsqu'il essayera d'écrire dans cette section ... pagefault, donc l'OS prend la main et se rend compte grâce à des structures internes que la page correspondante n'est pas réellement en lecture seule et qu'il s'agit d'un COW donc il va copier la page ( et non pas la section entière ) sur une nouvelle page physique changer l'association avec la mémoire virtuelle et relancer l'écriture.
Donc tant que le processus n'écrira pas sur une section private il n'y aura pas de nouvelle page physique d'allouer.


Voila pour ces trois concepts très intéressant que Linux implémente complètement ( je ne sais rien à propos de Windows si quelqu'un sait quelque chose merci de me le signaler ) et donc qui en améliore les performances et qui facilite la manipulation de l'espace kernel pour ce qui est de l'identity mapping.
Je pense qu'avec cet article j'ai fait le tour de ce que je pouvais dire sur la mémoire virtuelle en tout cas avec mes connaissances actuelles.
Dans tout les cas à bientôt dans un prochain épisode.

mercredi 13 août 2008

EmptyWorkingSet

Bon alors il y a pas longtemps j'ai découvert un soft qui s'appelle Firefox Ultimate Optimizer qui permet de libérer la mémoire non utilisé par Firefox car comme beaucoup le savent dans le version 2.0 il y a pas mal de fuite de mémoire. D'après les dev de Mozilla plus de 350 sources de fuites de mémoire ont été corrigées dans la version 3.0. Donc c'est un soft très intéressant car il n'est pas rare que mon Firefox atteigne les 200Mo d'utilisation de mémoire et dès que je lance le soft il redescend entre 5 et 10Mo ( impressionnant n'est ce pas ). D'après Korben, tout l'intérêt du soft réside dans la fonction EmptyWorkingSet() qui fait tout le boulot mais comme il l'a dit il fallait y penser. Donc sans plus attendre je m'empresse d'aller trouver le secret de cette fonction ( moi qui suit un fana de ce genre de fonction avec surement un peu de mémoire virtuelle ) dans les sources de reactos.

BOOL STDCALL EmptyWorkingSet(HANDLE hProcess)
{
NTSTATUS nErrCode;
QUOTA_LIMITS qlProcessQuota;

/* query the working set */
nErrCode = NtQueryInformationProcess
(
hProcess,
ProcessQuotaLimits,
&qlProcessQuota,
sizeof(qlProcessQuota),
NULL
);

/* failure */
if(!NT_SUCCESS(nErrCode))
goto fail;

/* empty the working set */
qlProcessQuota.MinimumWorkingSetSize = -1;
qlProcessQuota.MaximumWorkingSetSize = -1;

/* set the working set */
nErrCode = NtSetInformationProcess
(
hProcess,
ProcessQuotaLimits,
&qlProcessQuota,
sizeof(qlProcessQuota)
);

/* success */
if(NT_SUCCESS(nErrCode))
return (TRUE);

fail:
/* failure */
SetLastError(RtlNtStatusToDosError(nErrCode));
return (FALSE);
}


Hum apparemment tout ce passe dans NtSetInformatioProcess avec un ProcessInformationClass à ProcessQuotaLimits, donc allons voir ça de plus près :


NTSTATUS
NTAPI
NtSetInformationProcess(IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
IN PVOID ProcessInformation,
IN ULONG ProcessInformationLength)
{
PEPROCESS Process;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
ACCESS_MASK Access;
NTSTATUS Status;
HANDLE PortHandle = NULL;
HANDLE TokenHandle = NULL;
PROCESS_SESSION_INFORMATION SessionInfo = {0};
PVOID ExceptionPort;
PAGED_CODE();

/* Verify Information Class validity */
#if 0
Status = DefaultSetInfoBufferCheck(ProcessInformationClass,
PsProcessInfoClass,
RTL_NUMBER_OF(PsProcessInfoClass),
ProcessInformation,
ProcessInformationLength,
PreviousMode);
if (!NT_SUCCESS(Status)) return Status;
#endif

/* Check what class this is */
Access = PROCESS_SET_INFORMATION;
if (ProcessInformationClass == ProcessSessionInformation)
{
/* Setting the Session ID needs a special mask */
Access |= PROCESS_SET_SESSIONID;
}
else if (ProcessInformationClass == ProcessExceptionPort)
{
/* Setting the exception port needs a special mask */
Access |= PROCESS_SUSPEND_RESUME;
}

/* Reference the process */
Status = ObReferenceObjectByHandle(ProcessHandle,
Access,
PsProcessType,
PreviousMode,
(PVOID*)&Process,
NULL);
if (!NT_SUCCESS(Status)) return Status;

/* Check what kind of information class this is */
switch (ProcessInformationClass)
{
/* Quotas and priorities: not implemented */
case ProcessQuotaLimits:
case ProcessBasePriority:
case ProcessRaisePriority:
Status = STATUS_NOT_IMPLEMENTED;
break;
...
}
...
}


Rho zut alors reatos n'implémente pas encore cette fonctionnalité, en attendant si un reverser kernel windows dans l'âme avec quelques connaissances en mémoire virtuelle sous windows veut bien nous éclairer avec ce syscall ( perso j'y connais rien à ces syscall windows ni comment les tracer ), je sais que neitsabes avez commencé avec quelques ProcessInformationClass mais pas celui la donc voila.

Bon sinon ce que je pense de cette fonction, elle réside surement tout simplement dans un système de swap à la con qui vire les pages physiques les moins utilisées sans touchés aux pages virtuelles juste au cas où on en ait encore besoin sinon Access violation dans ta face et après tout 2Go en espace user de mémoire virtuelle ya de quoi faire alors que généralement la ram ne dépasse pas souvent les 1Go et il y a pas mal d'autre chose dans la mémoire physique alors que la mémoire virtuelle est spécifique au processus.

Dans tout les cas c'est une fonction sympa, on pourrait faire un petit tools qui fassent ça sur tout les process et pas seulement firefox car je suis sur qu'il doit bien y avoir dans d'autre process des fuites de mémoire.

http://msdn.microsoft.com/en-us/library/ms682606.aspx

Have fun with this function !!!

lundi 11 août 2008

Une autre façon de voir les listes chainées

Après une introduction aux listes chainées pour ceux qui ne savent pas ce que c'est, je vous présenterez une façon plus générique ( en tout cas selon moi ) et bien plus originale d'implementé cette structure de donnée qui est tirée du code de linux.

Avant tout, qu'est ce qu'une liste chainée et pourquoi on l'utilise ?

Et bien une liste chainée c'est une structure de donnée ( le mot clé struct en C ) qui permet de lier entre eux plusieurs données, un peu comme un tableau mais de façon beaucoup plus dynamique.
Un tableau de structure statique pourrait faire la même chose mais avec l'inconvénient que la longueur du tableau est fixé à la compilation et ne pourra pas être adapter en fonction des besoins.Prenons par exemple le cas d'une liste de processus, on pourrait fixer le tableau à 500 entrée ce qui a peut de chance d'arriver mais peut tout de même y arriver et puis on utiliserait de la mémoire pour rien ( environ 450 structures inutilisées la plupart du temps ).
Pour remédier à la taille du tableau, on pourrait utiliser un tableau dynamique avec le jeu de fonctions malloc()/realloc()/free() pour changer dynamiquement la taille du tableau. Mais la encore il se pose un autre problème c'est lorsqu'on a besoin de supprimer une structure, concrêtement on ne peut pas vraiment la supprimer, on peut la réinitialiser à 0 en jusqu'à ce que quelqu'un d'autre en ait besoin mais la encore on utilise de la mémoire pour rien puisqu'elle ne sera pas utilisée pendant un certain moment et puis imaginer que je ne sais pour qu'elle raison on ait besoin de 1000 structures et que soudain on ne s'en sert plus que d'une dizaine et bien là c'est vraiment ce qu'on apelle du gaspillage de mémoire. On aurait éventuellement la possibilité de faire un memmove() puis un realloc() mais le memmove est très coûteux si jamais on a beaucoup de données.
Donc une autre possibilité s'offre à nous c'est de faire une structure de données dans là qelle on ferait un ou deux champ(s) ( suivant que c'est une liste doublement ou simplement chainée ) qui sont de même type que la structure elle même.
Ces champs seront des pointeurs sur la structure précédente et la structure suivante pour le cas d'une liste doublement chainée et pour le cas d'une liste simplement chainée il n'y a que le champ vers la strucuture suivante ce qui nous permet bien de faire une liste de cette même structure.

La façon classique de déclarer une liste chainée c'est donc quelque chose qui ressemble à ça :

struct linked_list
{
// nawak
struct linked_list *prev, *next;
};

Voila donc une liste doublement chainée, pour une liste simplement chainée il n'y a que le champ next et bien évidement les données sont à la place de "nawak".

Voila en image à quoi ça ressemble des listes simplement et doublement chainées :



L'avantage des listes chainées c'est qu'on peut facilement ajouter ou supprimer un élément de la liste en agisant simplement sur les champs next et prev.
Par exemple, on a trois structures et on veut supprimer la seconde alors il suffit de faire pointer le champ next de la première vers la troisième et le champ prev de la troisième vers la première et on peut alors libérer la mémoire allouer pour la seconde structure.


Nous allons donc maintenant voir comment les listes chainées sont implémentées dans linux car c'est vraiment très intéressant à voir et ça ressemble beaucoup à stdarg.h qui utilise des macros avec des adresses qui n'ont l'air de correspondre à rien.
Enfin plein de truc marrant qui sont au final très pratique et comme je le disais en introduction l'implémentation de linux est bien plus générique que l'implémentation "classique".

Bon alors avant de partir plus loin voyons ensemble la définition d'une liste chainée dans include/linux/list.h :

struct list_head {
struct list_head *next, prev;
};


Wtf ? Euh elles sont où les données ?

C'est justement ça la particularité, au lieu que la structure de donnée ait des liens sur elle même, elle a un champ vers cette structure list_head. Et selon moi ceci fait que cette définition des listes chainées en font des listes chainées plus génériques que les autres.

Bon alors voyons maintenant la mise en place de ces listes chainées un peu particulière :

struct mastructure
{
// nawak

struct list_head list;
}


Bon c'est un peu particulier parce qu'on a pas l'habitude de l'apprendre comme ça et il y a certaines choses qui peuvent être un peu compliquer à comprendre.

Une petite image pour voir à quoi ça ressemble :



On dispose de plusieurs macros pour créer ou initialiser une liste chainée :

#define LIST_HEAD_INIT(name) { &(name), &(name) }

#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)


Bon rien de compliquer, on crée une variable avec le nom passé en paramètre de la macro et on initialise la liste pour que les champs prev et next pointent sur la liste elle même, on voit déjà que ce sont des listes circulaires.
Bon en général on n'aura pas besoin de créer de liste, on va juste l'initialiser.

Voila, je ne vais pas passer en revu toutes les macros et fonctions, si vous voulez savoir comment ça marche aller voir les sources directement ( c'est l'avantage du libre ^^ ).
Je vais vous expliquez maintenant le point le plus difficile qui est bien entendu : "Comment fait t'on pour accèder à notre structure avec ces listes chainées ?".

En fait c'est une petite astuce qui est vraiment toute simple mais encore faut t'il y penser. Récapitulons ce que nous avons, on a notre structure et dans celle ci ont à notre list_head, il faut donc qu'on se débrouille avec seulement ça. L'astuce est de soustraire à l'adresse de la list_head ( c'est bon on l'a ) son offset au sein la structure ( on l'a aussi mais de façon un peu plus détourné ) et comme ça on retombe sur nos pattes tels un félin avec l'adresse du début de la structure avec un petit transtypage et c'est réglé.
Bon maintenant le tout c'est de savoir comment on récupère l'offset d'un membre à l'intérieur d'une structure ( on ne va quand même pas hardcoder l'offset pour chaque structure et puis imaginer si on rajoute un membre ).
Heureusement pour nous il existe encore une autre macro très pratique qui fait ça très bien dans include/linux/stddef.h :

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


Et c'est la qu'on voit qu'on manipule des adresses qui ne correspondent à rien =p.
Bon en clair on chope l'adresse du membre et comme l'adresse de base de la structure est 0 on se retrouve bien avec notre offset.

Maintenant qu'on à l'offset, on peut faire notre soustraction pour trouver l'adresse de base de la structure, voyons voir comment ils ont fait ça dans include/linux/kernel.h :

/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})

Avec les explications pour chaque membre en anglais.

Bon ensuite dans /include/linux/list.h, on a un alias de cette fonction pour une utilisation spécifique aux listes :

#define list_entry(ptr, type, member) \
contaier_of(ptr, type, member)

No comment.


Et bien voila c'est la fin de cet article, je voulais vous présentez cette structure car je la trouve très intéressante et surtout originale ( j'aime bien l'originalité ).
Bien entendu il existe d'autres implémentation de listes chainées génériques, je pense notamment à la glib avec sa GList qui utilise un pointeur sur void comme donnée et donc on peut faire ce qu'on veut avec. Cependant l'implémentation de linux en plus d'être générique est très originale puisque avant de la connaître je pensais qu'il n'existait qu'une seule façon de faire des listes chainées ( comme tout le monde je pense ) mais je me tromper bien entendu.
De plus on voit qu'il y a plein de macro amusante et qu'on peut faire vraiment beaucoup de chose avec.

Et bien apparemment il existe la même chose avec windows ( merci Ivanlef0u ), voici un lien vers la msdn pour en savoir plus :
* http://msdn.microsoft.com/en-us/library/aa489548.aspx

Sur ce je vous dis à bientôt pour un prochain article.

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.

dimanche 2 mars 2008

L'interface homme-machine du futur ?

Johnny Chung Lee est un chercheur à la Carnegie Mellon University à Pittsburgh, il travaille sur les interactions homme-machine et grâce à lui on a peut être notre interface du futur.
Grâce à la dernière invention de Nitendo, la Wiimote, Johnny a brillamment su réutiliser cette technologie bon marché ( environ 40 € ). Il a grâce a un montage très simple et peu couteux réussi à piloter son ordinateur à distance en remplacement de sa souris. Cette technologie pourrait être utilisé lors de conférence pour plus de simplicité, plus besoin d'une personne devant l'ordinateur et une autre en train de parler. Avec cette technologie de nouvelle génération, la même personne peut contrôler l'ordinateur pour afficher le résultat des travaux comme une présentation.

Voici les vidéos montrant son œuvre très intéressantes :
Bien que le concept ne soit pas révolutionnaire puisqu'il existe déjà le crayon optique et l'écran tactile depuis très longtemps. La Wiimote reste a un prix abordable et avec un dispositif très simple on arrive à faire quelque chose de très imprésionnant.
De plus, ceci fonctionne sur tout les supports contrairement au crayon optique qui ne fonctionne qu'avec un écran cathodique.

Voici la page de son projet http://www.cs.cmu.edu/~johnny/projects/wii/ .

Il a aussi crée un projet sur sourceforge, il a déjà commencé à dev une version pour mac, une version pour windows est déjà dispo et il a fait un appel à la communauté pour coder un version linux.

Ce projet promet de bonne chose dans un futur ( proche ? ) pour un meilleur interfaçage homme-machine.

Voici trois autres vidéos de projets intéressants même si ils ne sont pas aussi prometteur que ceux de Johnny :

mardi 19 février 2008

Reverse Msnmsgr.exe

Bon aujourd'hui on va s'attaquer à ce cher petit msnmsgr.exe, ce petit outil utilisé par le monde entier et qui connait un succès grandissant.

On va voir comment arriver à lancer plusieurs fois MSN alors qu'à vu d'œil il refuse de se lancer plusieurs fois, à la place il donne le focus à la fenêtre déjà lancé.

Bien pour tout vous dire je me suis longtemps demander comment faisait beaucoup de programme pour détecter si ils sont déjà lancés ou pas.

Ils auraient pût lister la liste des processus et vérifier si le nom du prog ni figurait pas mais non ce n'est pas assez flexible, il suffit de renommer l'exe pour pouvoir le lancer plusieurs fois et la solution n'est donc pas très bonne au final.
Et puis je me suis demander si ce n'était pas une sorte de variable globale qui était déclaré lors du lancement de l'exe et qui été détruit en même tant que l'exe, comme ça lorsque l'exe été relancé il lui suffisait de vérifier si la variable existait et si tel était le cas bim on lui balance un MessageBox comme c'est le cas pour CodeBlocks ou alors on donne le focus au premier exe.

Et puis la ça fait bingo parce que justement Windows a une API qui nous permet de déclarer des variables globales enfin c'est pas tout à fait des variables globales c'est plutôt des events grâce à CreateEvent.

Reverse de MsnMsgr

Bon alors c'est parti pour le Reverse de MsnMsgr, maintenant qu'on sait ( on suppose en fait ) quelle fonction MsnMsgr va utilisé pour y parvenir ça va être très simple.

Alors on lance notre cher olly ( moi j'utilise olly parce que c'est le seul que j'utilise mais vous pouvez bien entendu utiliser votre debogueur préférer ) et on ouvre MsnMsgr ( chez moi il se trouve dans C:\Program Files\MSN Messenger\ ).

Et on laisse olly analyser le code parce que MsnMsgr c'est un sacré morceau ... 5 min plus tard olly à fini ^^

Donc comme on suppose que MsnMsgr va utiliser l'API CreateEvent() on va faire un clic droit -> Search For -> Name (label) in current module et on va rechercher CreateEvent(), il n'y a pas de symbole CreateEvent() mais il y a CreateEventA() et CreateEventW() d'après la msdn la seule différence entre ces deux fonctions est que CreateEventA() utilise un encodage ANSI alors que CreateEventW() utilise un encodage Unicode ( http://msdn2.microsoft.com/en-us/library/ms682396(VS.85).aspx Section Requirements -> Unicode).
On va donc poser des breakpoints sur les deux API à coup de clic droit -> Set breakpoint on every reference.

Maintenant un petit coup de F9 pour lancer le prog, on reconnait bien la lenteur de microsoft ... 5 min après boum break sur CreateEventW()

On regarde l'argument EventName, il est à NULL donc ça ne nous intéresse pas aller re F9, encore un appel qui ne nous concerne pas re F9 et puis la un CreateEventW qui pourrait nous servir sauf qu'après analyse aucune vérification n'est faite et donc ça ne nous intéresse pas non plus.
Après avoir relancer encore une fois à coup de F9, on tombe sur un CreateEventA avec comme EventName "MSNMSGR".
On va analyser un peu ce code pour voir de quoi il retourne.

...

00543CCB > 68 78D75500 PUSH msnmsgr.0055D778 ; /EventName = "MSNMSGR"
00543CD0 . 57 PUSH EDI ; |InitiallySignaled
00543CD1 . 6A 01 PUSH 1 ; |ManualReset = TRUE
00543CD3 . 57 PUSH EDI ; |pSecurity
00543CD4 . FF15 3C144000 CALL DWORD PTR DS:[<&KERNEL32.CreateEventA>] ; \CreateEventA
00543CDA . 3BC7 CMP EAX,EDI
00543CDC . 8B5D E8 MOV EBX,DWORD PTR SS:[EBP-18]
00543CDF . 8943 24 MOV DWORD PTR DS:[EBX+24],EAX
00543CE2 . 0F84 EA4B0000 JE msnmsgr.005488D2
00543CE8 . FF15 8C154000 CALL DWORD PTR DS:[<&KERNEL32.GetLastError>] ; [GetLastError
00543CEE . 3D B7000000 CMP EAX,0B7
00543CF3 . 0F84 2F4B0000 JE msnmsgr.00548828

...

Donc il appelle CreateEventA avec comme EventName MSNMSGR puis il compare EAX avec EDI or on peut remarquer que EDI est à 0 comme le témoigne le InitiallySignaled et pSecurity à FALSE donc il va tester si la fonction renvoie 0 donc hop un coup de msdn sur CreateEvent et on nous dis que CreateEvent renvoie un handle si la fonction est un succès sinon elle renvoie NULL donc il ne fait que tester si la fonction à bien marcher.
Si la fonction échoue on sort sinon on continue avec un appel à GetLastError().
Comme nous le dis la msdn GetLastError retourne ERROR_ALREADY_EXISTS si jamais l'event existe déjà, on peut donc supposer que le CMP EAX, 0B7 va vérifier si GetLAstError() renvoie ERROR_ALREADY_EXISTS.
Donc on fait un petit coup de msdn sur GetLastError pour trouver à quoi correspond ERROR_ALREADY_EXISTS, elle nous indique une page avec tout les codes d'erreur ( http://msdn2.microsoft.com/en-us/library/ms681381(VS.85).aspx )

On va donc chercher à quoi correspond 0B7h soit 183 et on tombe sur
ERROR_ALREADY_EXISTS         Cannot create a file when that file already exists.
183
0xB7
Tiens on ne s'en serait jamais douté ^^

Donc il vérifie si l'event existe déjà et si il existe il jmp en 00548828

On regarde ce qu'il fait en 00548828 et on s'aperçoit qu'il va chercher la fenêtre de MsnMsgr grâce à FindWindowA() et va lui envoyer un message pour qu'il prenne le focus avec PostMessageW().

Patch MsnMsgr

Donc pour finir cet article, il vous suffit de changer le 0B7 ( ERROR_ALREADY_EXISTS ) de "CMP EAX, 0B7" en 00543CEE par autre chose comme 0B6 ( ERROR_INVALID_ORDINAL ), ça fera très bien l'affaire.

Et bien j'espère que vous avez appris quelque chose aujourd'hui ( en tout cas moi oui ) d'intéressant et puis au moins ça m'aura permis de patcher mon msn pour ouvrir plusieurs compte en même temps ( plus besoin de aMsn )

Have fun :)

vendredi 15 février 2008

La fin du monde de Unix ?

J'imagine que beaucoup d'entre vous connaissent le timestamp, pour ceux qui ne savent pas ce que c'est, c'est un nombre qui représente le nombre de secondes écoulées depuis le 1er Janvier 1970.
Le timestamp est utilisé un peu partout dans les codes Unix notamment dans Linux et malheureusement il subsiste une petite imperfection dans ce mode de fonctionnement.
Certes de prendre une référence commune pour une meilleure synchronisation des informations est une très bonne chose mais le timestamp est un nombre codée sur 32 bits dont seuls les 31 premiers sont utilisables vu que le dernier est réservé le signe ( hum je ne comprend pas trop pourquoi d'ailleurs mais bon ... ).

On peut donc penser qu'il arrivera un moment où on aura attend ces 31 bits et qu'il n'y auras plus de place pour des nombres plus grand.

Voici un petit calcul d'estimation de la fin du monde de Unix :
Fin du monde de Unix le 19/01/2038 a 04:14:07

Donc voila si rien ne change d'ici 2038 Unix a du soucis à se faire pour son futur.
Il se produira comme ce qu'il c'est produit pour le bug de l'an 2000, les programmeurs du monde entier se demander ce qu'il se passerait quand on passerait à l'an 2000 car beaucoup de programme représentaient la date avec seulement 2 chiffres et ils allaient passer de 99 à 00 ce qui aurait pu dérégler les ordinateurs du monde entier mais en fait rien de cela ne c'est produit avec quelque petit bug mineur mais rien de grave.


Référence :

Code de l'estimation de la fin du monde de Unix :
http://akhenathon2.free.fr/coding/FinUnix.c

C'est dommage quand même ^^

lundi 11 février 2008

L'Export Table et GetProcAddress()

J'imagines que tout le monde connait le format PE, le format des executables Windows. Si ce n'est pas le cas je vous conseille d'aller lire ceci avant de commencer cet article : http://en.wikibooks.org/wiki/Reverse_Engineering/PE

Bon alors je vais aujourd'hui parlais d'un point peu soulever dans le format PE car tout le monde parle de l'Import Table dans les introductions au format PE mais j'ai trouvé très peu d'articles sur l'Export Table donc c'est parti pour l'avanture.


Donc on va d'abord voir comment accéder à l'export table et nous verrons ensuite comment récupérer les informations utiles de l'export table et enfin une petite implémentation de GetProcAddress() avec ce que nous viendrons de voir.


Je vous laisse regarder une petite représentation du format PE pour voir ce qui nous interessent : http://ivanlef0u.free.fr/repo/windoz/pe/_pe-tuts/GlobalPE.htm


Donc le format PE commence par une strutcture IMAGE_DOS_HEADER qui contient entre autre un champ e_magic qui représente la signature et le champ e_lflanew qui nous permet de savoir où se trouve la strucuture IMAGE_NT_HEADER.
Donc dans cette structure il y a toujours une signature et si on descend un peu dans l'OptionalHeader, on se retrouve avec des structures IMAGE_DATA_DIRECTORY dont la première est ... l'Export Table.

Bon alors voila je vais finir cette première partie en vous donnant toutes les structures du format PE pour que vous compreniez un peu mieux de quoi on parle.
http://ivanlef0u.free.fr/repo/windoz/pe/pe.txt


Bon après cette très courte introduction au format PE, on va approfondir un petit l'export table pour voir comment elle est composée.
Donc voici un petit résumer de l'organisation de l'export table en mémoire :

Flags ( Characteristics )TimestampMajor VersionMinor VersionNameBaseNombre de FonctionsNombre de NomAdresses des fonctionsAdresses des noms de fonctionsAdresses des noms ordinaux


Donc ici la seule chose que va nous interesser ce sont les 5 derniers champs, normalement Nombre de fonctions == Nombre de Noms donc on peut prendre soit l'un soit l'autre.

Ensuite il faut savoir que les champs d'adresses sont des pointeurs sur un tableau de pointeurs et que ce sont des tableaux en parallèles ce qui veut dire que le premier pointeur du tableau des adresses des fonctions et le premier pointeur du tableau des adresses des noms de fonctions correspondent aux même objet et donc à la même fonction.

En clair soit TabAdressFunc le tableau des adresses des fonctions et TabNameFunc le tableau des adresses des noms de fonctions, TabAdressFunc[0] et TabNameFunc[0] sont deux pointeurs qui font référence à la même fonction.
Et bien entendu la même chose pour le dernier tableau qui sert à déterminer l'adresse de l'adresse de l'API.

Pour récupérer le PE file d'une dll, il suffit juste d'appeler GetModuleHandle() avec comme param le nom de la dll.
Et donc voici un code qui dump l'export table d'une dll en d'occurrence kernel32 et puis vous verez qu'il y a vraiment beaucoup de fonctions exportés.
http://akhenathon2.free.fr/coding/DumpExportTable.c

Et donc voici ce que tout le monde attend, une implémentation de GetProcAddress() :
http://akhenathon2.free.fr/coding/MyGetProcAddress.c

Bon je vous accordes que les codes sont très moches mais en ce moment je n'ai pas trop le temps ( oué c'est les cours mais bon ça en vaut la peine 19.25 en math c'est la classe surtout en 1S ), je les retoucherais si j'ai un peu de temps.

En espérant que cet article vous auras permit d'en apprendre un peu plus sur l'Export Table et éventuellement découvert une alternative à GetProcAddress().

Doc : http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
Have fun :)

vendredi 1 février 2008

Récupérer le PID d'un process

Bon alors aujourd'hui on va voir une alternative aux fameux Process32First()/Process32Next() que je vois très souvent dans les codes pour faire une liste des process ou alors pour rechercher un process dans les process en execution et que je commenterais pas ici car il y a des articles très bien partout sur le net.

Pour un exemple de code regarder ici : http://akhenathon2.free.fr/coding/ProcessNameToPid.c

Bon je vois toujours cette méthode qui consiste à faire une boucle sur Process32Next() pour avoir une liste des process et ensuite vérifier si le szExeFile du process correspond au nom du process qu'on cherche.

Bon les code se ressembleront un peu cependant on va utiliser une fonction qui nous renvoie une liste de PID ( les PID des process en execution ).

Allez c'est parti pour la chasse au PID. On va donc utiliser la fonction EnumProcesses() qui nous renvoie comme promis une liste de PID ainsi que le nombre de process en execution ( il faudra faire une petit opération pour l'avoir ).

Bon comme d'hab on va voir la doc msdn : http://msdn2.microsoft.com/en-us/library/ms682629(VS.85).aspx

Voici le prototype de la fonction EnumProcesses() :
BOOL WINAPI EnumProcesses(
__out DWORD* pProcessIds,
__in DWORD cb,
__out DWORD* pBytesReturned
);

Il nous faut donc un tableau de DWORD qui recevra la liste des PID ainsi qu'un DWORD qui recevra le nombre de bytes retournés, il suffira de le divisé par la taille d'un DWORD pour obtenir le nombre de process en execution.
Alors attention, le tableau doit être assez grand pour recevoir tout les PID j'ai choisi de faire un tableau à 128 entrée mais après a vous de juger le nombre de PID qu'il est sensé y avoir ( avec 128 on a quand même une bonne marge ).
#include <windows.h>
#include <psapi.h>

int main(void)
{
DWORD ListProcess[128], nProcess;

EnumProcesses(ListProcess, sizeof(ListProcess),
&nProcess);
nProcess /= sizeof(DWORD);

/* ... */

return 0;
}


Bon alors maintenant qu'on a la liste des process, il faut récupérer le nom des process pour pouvoir les comparer avec le process qu'on recherche.

Pour ce faire on va utiliser une combinaison des fonctions OpenProcess() ( pour récupérer un handle sur le process ) puis EnumProcessModules() qui nous renverra la liste des module du process et on a des la chance parce que le premier module est le process lui même ensuite suffira de faire un GetModuleBaseName() pour récupérer son nom.

Ici je vais juste faire une liste des process ( je mettrais à la fin quelques codes ).
#include <windows.h>
#include <psapi.h>
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
DWORD ListProcess[128], nProcess;
size_t i;

EnumProcesses(ListProcess, sizeof(ListProcess),
&nProcess);
nProcess /= sizeof(DWORD);

for(i=0;i<nProcess;i++)
{
char szProcessName[MAX_PATH];
HANDLE hProcess=OpenProcess(PROCESS_VM_READ
|PROCESS_QUERY_INFORMATION,
FALSE, ListProcess[i]);

if(hProcess)
{
HMODULE hMod;
DWORD unused;

if(EnumProcessModules(hProcess, &hMod,
sizeof(hMod), &unused))
{
GetModuleBaseName(hProcess, hMod,
szProcessName,
sizeof(szProcessName)/sizeof(char));
printf("[+]ProcessName: %s\tPID: %lu\n",
szProcessName,
ListProcess[i]);
}
}
CloseHandle(hProcess);
}

return 0;
}


Ensuite en modifiant juste le HMODULE en un tableau assez grand vous pourrez récupérer tous les modules du process.

Et donc voici comme promis quelques petits code :
-> ProcessNameToPidWithEnumProcesses.c
-> ListModuleInProcess.c

Have fun :)

PS : N'oubliez pas de linker la lib psapi

Références :

EnumProcesses --> http://msdn2.microsoft.com/en-us/library/ms682629(VS.85).aspx
OpenProcess --> http://msdn2.microsoft.com/en-us/library/ms684320(VS.85).aspx
EnumProcessModules --> http://msdn2.microsoft.com/en-us/library/ms682631(VS.85).aspx
GetModuleBaseName --> http://msdn2.microsoft.com/en-us/library/ms683196(VS.85).aspx

lundi 28 janvier 2008

OutputDebugString

Bonjour a tous, dans cet article je vais essayer de vous expliquer comment récupérer les OutputDebugString d'un process en particulier.

Je pense que bon nombre d'entre vous connaissent déjà la fonction OutputDebugString() qui permet d'envoyer des strings au debugger, elle est très utilisés pour debugger une application pour remplacer les printf() qui pollue un peu la console.

Un outil très pratique pour récupérer les OutputDebugStrings est DebugView qui permet non seulement de récupérer les OutputDebugStrings des applis mais aussi ceux du kernel et peut aussi se connecter à un autre PC pour recevoir ses OutputDebugStrings.


Donc c'est parti, on va les récupérer ces OutputDebugString.
Bon on va voir un peu du côté de la page de man de msdn sur les debugging fonctions : http://msdn2.microsoft.com/en-us/library/ms679303(VS.85).aspx

Bon c'est cool, on voit la fameuse OutputDebugString() mais on voit aussi une fonction qui peut être très intéressante WaitForDebugEvent()

BOOL WINAPI WaitForDebugEvent(
__out LPDEBUG_EVENT lpDebugEvent,
__in DWORD dwMilliseconds
);


Mouai bon jusque là ça va rien de bien compliquer donc on lit la page de description jusqu'à la fin ( on fait pas comme moi et on se fait pas chier parce qu'on avait loupé un truc pourri =p )

Bon aller on fait un petit test alors

#include <windows.h>
#include <stdio.h>

int main(void)
{
LPDEBUG_EVENT lpDebugEvent;

if(!WaitForDebugEvent(lpDebugEvent, INFINITE))
printf("GetLastError() : %lu\n", GetLastError());

return 0;
}


Le résultat : GetLastError() : 6
Hum what the fuck ?

Bon on va voir ce que ça veut dire http://msdn2.microsoft.com/en-us/library/ms681381(VS.85).aspx

ERROR_INVALID_HANDLE The handle is invalid.

Bon au moins c'est clair.

Bon on regarde un peu mieux les fonctions de debugging, tiens c'est quoi DebugActiveProcess() ?

Super ça nous permet de spécifier un process.

Sauf qu'avant il faut choper le PID du process ( bon je passe j'en parlerais surement dans un prochain article ).

Donc on a le PID du process et on reteste en modifiant un peu le code.

#include <windows.h>
#include <stdio.h>

int main(void)
{
STARTUPINFO StartupInfo = {0};
PROCESS_INFORMATION ProcessInfo = {0};
char path[] = "C:\\test.exe";

if(!CreateProcess(path, NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &StartupInfo, &ProcessInfo))
{
printf("Error with CreateProcess() to open %s : %lu\n", path, GetLastError());
exit(1);
}

DebugActiveProcess(ProcessInfo.dwProcessId);

while(1)
{
DEBUG_EVENT DebugEvent;

if(!WaitForDebugEvent(&DebugEvent, INFINITE))
{
printf("GetLastError() : %lu\n", GetLastError());
exit(1);
}

printf("Debug event code : %lu\n", DebugEvent.dwDebugEventCode);

if(DebugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
{
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
break;
}

ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_CONTINUE);
}

return 0;
}


Il y a plus ou moins de DebugEvent selon le process, on n'oublie pas d'appeler ContinueDebugEvent() pour qu'on puisse recevoir les autres DebugEvent et on n'oublie pas non plus de fermer les handles lorsqu'on reçoit EXIT_PROCESS_DEBUG_EVENT comme le dis la doc msdn.


Maintenant suffit de ce concentrer sur le OUTPUT_DEBUG_STRING_EVENT pour recevoir les OutputDebugString() et d'utiliser la structure OUTPUT_DEBUG_STRING_INFO de DEBUG_EVENT.

Comme on le lis dans la doc, il faut utiliser ReadProcessMemory pour récupérer la string.

#include <windows.h>
#include <stdio.h>

int main(void)
{
STARTUPINFO StartupInfo = {0};
PROCESS_INFORMATION ProcessInfo = {0};
char path[] = "C:\\test.exe";

if(!CreateProcess(path, NULL, NULL, NULL, FALSE, DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &StartupInfo, &ProcessInfo))
{
printf("Error with CreateProcess() to open %s : %lu\n", path, GetLastError());
exit(1);
}

DebugActiveProcess(ProcessInfo.dwProcessId);

while(1)
{
DEBUG_EVENT DebugEvent;

if(!WaitForDebugEvent(&DebugEvent, INFINITE))
{
printf("GetLastError() : %lu\n", GetLastError());
exit(1);
}

if(DebugEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
{
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
break;
}
else if(DebugEvent.dwDebugEventCode == OUTPUT_DEBUG_STRING_EVENT)
{
char *str = malloc(sizeof(char) * DebugEvent.u.DebugString.nDebugStringLength);

ReadProcessMemory(ProcessInfo.hProcess, DebugEvent.u.DebugString.lpDebugStringData, str, DebugEvent.u.DebugString.nDebugStringLength, NULL);

printf("OutputDebugString sent %s\n", str);

free(str);
}
ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_CONTINUE);
}

return 0;
}


Et hop c'est magique, on récupère les OutputDebugString du Process.

Maintenant je vais essayer de trouver un moyen d'intercepter tout les OuputDebugString des tous les Process ( si quelqu'un connait un moyen d'y arriver qu'il me fasse signe ).

En espérant que cet article vous aura appris quelque chose d'utile et en espérant aussi que l'article soit assez compréhensible.

Si vous avez des questions ou des commentaires n'hésitez surtout pas.

Akhenathon

lundi 14 janvier 2008

Création d'un kernel

Mon projet du moment est la création d'un kernel en partant de zéro.
Donc bien entendu, il faut recoder entièrement la libc, ce qui n'est pas une mauvaise chose en soit je me suis bien éclater à écrire quelque fonction comme vsnprintf que gère le formatage des chaines à la façon de toutes les fonctions *f().

Pour le moment le kernel démarre et affiche un message de bienvenue à l'écran, ce qui est déjà bien.
J'ai commencé à implémenter quelques fonctions comme vsnprintf, printk qui est l'équivalent de printf mais pour le kernel ainsi que quelques structures de base comme size_t que j'utilise beaucoup.

Ouverture du blog

Bonjour à tous cher blogger,
j'ai décidé de créer ce blog pour traiter tout ce qui concerne le Reverse Engineering, la programmation et pour être plus général toutes mes découvertes intéressantes en sécurité informatique; my geek life quoi.

Je tiens à remercier très particulièrement lilxam qui sans lui ce blog n'existerait pas.

Have fun :)