IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Actualités Discussion :

Conséquences d'une incohérence subtile de spécification

  1. #1
    Rédacteur

    Avatar de ram-0000
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    Mai 2007
    Messages
    11 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Consultant en sécurité
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Mai 2007
    Messages : 11 517
    Points : 50 369
    Points
    50 369
    Par défaut Conséquences d'une incohérence subtile de spécification
    Conséquences d'une incohérence subtile de spécification

    Cet article analyse les conséquences des différentes méthodes de représentation des chaines de caractères dans l'espace de nommage Microsoft Windows. En particulier, sont présentées les différences entre l'API native et l'API standard pour l'accès à la base de registre et la possibilité de masquer des clés de registre à l'API standard. Plus généralement, il est possible de masquer n'importe quel objet nommé à l'API standard, mais il existe de nombreux outils utilisant l'API native permettant de les manipuler. Ce masquage n'est donc qu'un masquage de premier niveau.

    Représentation d’une chaine de caractères
    Dans les programmes, il existe plusieurs représentations des chaines de caractères. Les 2 représentations qui nous intéressent dans cet article sont les chaines de caractères dites "comptées" et les chaines de caractères dites "zero terminated".

    Les chaines "comptées"
    Une chaine de caractères "comptée" est représentée en mémoire par ses différents caractères précédés du nombre de caractères de cette chaine. C’est cette représentation qui est utilisée dans le langage de programmation "Pascal" ou "Java".

    Ainsi la chaine de caractères "Cert-IST" sera représentée comme suit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    8 ‘C’ ‘e’ ‘r’ ‘t’ ‘-’ ‘I’ ‘S’ ‘T’
    Dans cet exemple, le 8 représente le nombre de caractères de la chaine "Cert-IST". Au total, le stockage de la chaine de caractères nécessite 9 octets.

    Les chaines "zero terminated"
    Une chaine de caractères "zero terminated" est représentée en mémoire par ses différents caractères suivi d’un octet spécial marquant la fin de la chaine. Cet octet a pour valeur 0. C’est cette représentation qui est utilisée dans le langage de programmation "C" et "C++".

    Ainsi la chaine de caractères "Cert-IST" sera représentée comme suit :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ‘C’ ‘e’ ‘r’ ‘t’ ‘-’ ‘I’ ‘S’ ‘T’ 0
    Au total, le stockage de la chaine de caractères nécessite 9 octets.

    Espace de nommage
    A priori, ces différentes méthodes de représentation sont de la mécanique interne et ne devraient pas avoir d’influence sur le comportement des programmes.

    Toutefois, l’ensemble des noms (appelé aussi espace de nommage) représentable avec la méthode "comptée" n’est pas identique à l’ensemble des noms représentable avec la méthode "zero terminated".

    Ainsi, dans l’exemple suivant, la chaine représentée est valide en utilisant la méthode comptée, mais ne possède pas de représentation valide avec la méthode "zero terminated" (à cause de l’octet 0 en milieu de chaine de caractère).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    9 ‘C’ ‘e’ ‘r’ ‘t’ 0 ‘I’ ‘S’ ‘T’
    Conséquences
    S’il est possible de manipuler un objet nommé avec une API capable d’utiliser l’une ou l’autre des méthodes de représentation des chaines de caractères, certains noms seront visibles en utilisant une chaine "comptée" et non visibles en utilisant une chaine "zero terminated".

    Ceci est la conséquence directe du fait que les espaces de nommage des chaines "comptées" et "zero terminated" sont différents.

    Accès à la base de registre

    L’API standard

    Microsoft propose une API standard et parfaitement documentée pour accéder à la base de registre. Cette API contient par exemple la fonction "RegCreateKey()" permettant d’ouvrir ou de créer une clé de registre. Cette fonction est implémentée dans la DLL système "AdvApi32.dll" et attend comme paramètre (entre autres) le nom de la clé de registre à ouvrir ou à créer. Ce nom de clé est une chaine de caractères du type "zero terminated".

    L’API native
    Il existe une autre API non ou mal documentée par Microsoft. Cette API est appelée l’API native car, en fin de compte, tous les appels systèmes effectués en utilisant l’API standard finissent par être convertis en appels à l’API native qui est l’API privilégiée par Microsoft pour accéder à la base de registre.

    Ainsi, et pour rejoindre l’exemple précédent, il existe une fonction "NtCreateKey()" permettant de créer une clé de registre. Cette fonction est implémentée dans la DLL système "NTDLL.dll" et attend comme paramètre (entre autres) le nom de la clé de registre à créer. Par contre, ce nom de clé est une chaine de caractères du type "comptée".

    API native ou API standard ?
    Pratiquement, tout ce qu’il est possible de faire avec l’API native est faisable avec l’API standard à quelques exceptions mineures près. Ainsi, il est possible par exemple de spécifier si l’ouverture d’un fichier est sensible aux différences majuscules/minuscules avec l’API native, mais pas avec l’API standard.

    Par contre, il n’y a pas de privilèges supplémentaires ni de "super pouvoirs cachés" à utiliser l’API native, c’est pourquoi il faut préférer l’utilisation de l’API standard.

    Conséquence
    La conséquence de ceci est qu’il est possible de créer une clé de registre au moyen de l’API native et possédant un nom particulier qui ne pourra pas être ouverte en utilisant l’API standard. C’est ainsi qu’il est possible de "cacher" des clés de registres à l’API standard.

    En règle générale, tous les programmes utilisent l’API standard parce que c’est l’API qui est documentée et supportée par Microsoft. Ainsi le programme "regedit.exe" qui est le programme de manipulation de la base de registres utilise cette API standard et n’est donc pas capable de voir les clés de registre "cachées".

    Conclusions

    Généralisation de la méthode
    Le paragraphe précédent s’appuie sur les accès à la base de registre, mais il existe d’autres objets nommés qui possèdent les mêmes caractéristiques au niveau des chaines de caractères. Il s’agit par exemple des objets "process" et "thread", des fichiers et répertoires, des tubes nommés, des fichiers "mailslot", des pilotes, des clés de registres, des objets "atomes" et "événements", des variables d’environnement et de localisation et probablement d’autres encore.

    Utilisation de la méthode
    Une fois que l’on connait ce principe de noms cachés (clé de registres, fichier ou toute autre chose nommée), qu’est-il possible de faire ?

    En fait, cette méthode servira surtout à cacher des choses. Ainsi un programme pourra t’il écrire dans un fichier qui ne pourra pas être ouvert par le programme "notepad.exe" ou encore il sera possible de créer tout une branche dans la base de registres qui ne pourra pas être parcourue par le programme "regedit.exe".

    Toutefois, il ne faut pas se tromper, cette méthode permettant de cacher de l’information est une méthode facile à détecter et il est très facile de trouver sur Internet des outils et programmes permettant de manipuler ces objets aux noms "bizarres".

    Pourquoi ce problème ?
    L’existence de ces différences minimes au niveau de l’espace de nommage des noms montrent qu’il est parfaitement possible d’avoir des spécifications légèrement différentes et que l’on pense identiques mais dont les effets de bords sont non négligeables avec le temps et l’analyse. En effet, il faut avoir un esprit retord pour imaginer et trouver une différence entre les chaines "zero terminated" et "comptées".

    Il est important lors de la spécification des interfaces de bien faire en sorte de ne pas introduire ce genre de problème.

    Une explication possible à ceci pourrait être le fait que la personne qui a spécifié les interfaces de l’API native connaissait plus particulièrement le langage "Pascal" et la personne qui a spécifié l’interface de l’API standard connaissant plus particulièrement le langage C. Ces 2 personnes ont fait leurs spécifications de leur côté et quand il a fallut faire communiquer les 2 interfaces entre elles, une méthode qui satisfait les 2 API a été trouvée et le trou dans l’espace de nommage n’a pas été vu.

    Pour plus d'informations :
    Plus d’informations sur le site SysInternals concernant l’API native :
    http://www.sysinternals.com/Information/NativeApi.html

    Un projet "freeware" de manipulation de la base de registres similaire à "regedit.exe" et utilisant l’API native :
    http://www.codeproject.com/system/NtRegistry.asp

    Le livre de référence concernant l’API native :
    Amazon.com: Windows NT/2000 Native API Reference (0619472701997): Gary Nebbett: Books

    Source : Cert-IST

  2. #2
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Quid des chaines de caractères de plus de 255 caractères dans la spécification "comptée" ?

    On pourrait arbitrairement dire que la taille doit être codée sur 2 ou 4 bytes, pour permettre une très longue chaine de caractère, mais alors des problèmes d'endianess surviendront, et donc des traitements supplémentaires, sans même parler du fait que les chaines peuvent être plus longue que ça. Au mieux, on devrait définir 1 premier byte qui définit la taille dans lequel est encodée la taille de la chaine, bref, carrément un en-tête ! Franchement, non, merci.

    Bref, cette spécification est d'or et déjà, pour moi, flawed by design.

    À mon avis, le caractère null (0x00) n'étant pas un caractère (dans le sens caractère lisible et affichable, défini par une norme) à proprement parler, il est correct de dire que les chaines de caractères 'zero terminated' sont sûrs. Maintenant, si quelqu'un s'amuse à insérer ce caractère dans un mot ou une phrase, les bugs engendrés ne doivent pas être cherchés du côté de la spécification, mais entre l'écran et la chaise. Et si c'est intentionnel, ce n'est pas un bug mais une feature; je ne vois pas le problème.

  3. #3
    Membre émérite
    Profil pro
    Inscrit en
    Juillet 2006
    Messages
    1 537
    Détails du profil
    Informations personnelles :
    Localisation : Canada

    Informations forums :
    Inscription : Juillet 2006
    Messages : 1 537
    Points : 2 548
    Points
    2 548
    Par défaut
    Ces problèmes sont relativement vieux et apparaissent dans plein de domaines !

    Par exemple, ceci peut poser problèmes dans l'utilisation des accès disques en PHP.

  4. #4
    Expert éminent
    Avatar de smyley
    Profil pro
    Inscrit en
    Juin 2003
    Messages
    6 270
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2003
    Messages : 6 270
    Points : 8 344
    Points
    8 344
    Par défaut
    Citation Envoyé par JulienDuSud Voir le message
    Bref, cette spécification est d'or et déjà, pour moi, flawed by design.
    J'avais l'habitude d'utiliser un byte pour la longueur, et un deuxième byte si le premier vaut 255, etc etc. (dans un système perso).
    Ainsi, pour les chaines de 0..254 caractères aucun changement dans le format, de 255..65535 la longueur est sur 2 bytes, etc etc ...

    Sinon avec une structure fixe il faudrait avoir un int64 par exemple mais du coup on aurait quasi systématiquement 7 bytes ne servant à rien ...

  5. #5
    Membre éclairé

    Homme Profil pro
    Rédacteur technique (retraité)
    Inscrit en
    Octobre 2009
    Messages
    168
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 82
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Rédacteur technique (retraité)

    Informations forums :
    Inscription : Octobre 2009
    Messages : 168
    Points : 807
    Points
    807
    Par défaut
    Le problème ne se limite pas à la simple dualité "zero terminated" ou "comptées".
    Il faut aussi tenir compte d'une autre notion toute aussi importante, celle de l'encodage des caractères.
    A ma connaissance les routines de l'API de windows peuvent, selon les routines, supporter différents encodages: Unicode, ANSI, DBCS/MBCS, ....etc.

    Notamment avec DBCS DBCS , une chaîne de x caractères ne comporte pas nécessairement x octets. Dans ces conditions que représente le compte d'une chaîne comptée, le nombre d'octet ou le nombre de caractères ??.

    Même chose en Unicode/UTF16 (mais mieux normalisé). Le consortium Unicode a défini aujourd'hui largement plus de 65536 caractères différents et l'encodage simple sur 16 bits est insuffisant, un certain nombre de caractères doivent être encodés sur deux mots de 16 bits successifs.

Discussions similaires

  1. conséquence d'une transacation non achevée (dans un script php)?
    Par Vincent BONNAL dans le forum Requêtes
    Réponses: 1
    Dernier message: 14/02/2009, 04h02
  2. Réponses: 1
    Dernier message: 09/02/2007, 00h10
  3. Réponses: 4
    Dernier message: 13/09/2006, 15h17
  4. Réponses: 3
    Dernier message: 23/05/2006, 16h09
  5. Réponses: 3
    Dernier message: 04/05/2006, 17h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo