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

Les rubriques (actu, forums, tutos) de Développez
Réseaux sociaux


 Discussion forum

Le , par ram-0000, Responsable Réseaux
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 :
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 :
‘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 :
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


Vous avez aimé cette actualité ? Alors partagez-la avec vos amis en cliquant sur les boutons ci-dessous :


 Poster une réponse

Avatar de JulienDuSud JulienDuSud
http://www.developpez.com
Membre éprouvé
le 18/09/2009 23:58
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.
Avatar de deadalnix deadalnix
http://www.developpez.com
Membre Expert
le 19/09/2009 5:09
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.
Avatar de smyley smyley
http://www.developpez.com
Expert Confirmé Sénior
le 19/09/2009 14:12
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 ...
Avatar de DomDA91 DomDA91
http://www.developpez.com
Membre habitué
le 16/03/2010 16:05
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.
Offres d'emploi IT
Développeur J2EE - Expert Technique
CDI
Agence Cortex - Belgique - Luxembourg
Parue le 08/09/2014
CORRESPONDANT SECURITE TECHNIQUE H/F
CDI
Société Générale France - Ile de France - Paris (75000)
Parue le 03/09/2014
Directeur Grands Projets Développements NTIC JAVA J2EE Confirmé ( (H/F))
CDI
PAC Recrutement - Ile de France - Saint-Ouen
Parue le 11/09/2014

Voir plus d'offres Voir la carte des offres IT
 
 
 
 
Partenaires

PlanetHoster
Ikoula