On peut imaginer que stocker un mot de passe dans une chaîne de caractères pendant l’exécution d’un processus, offre une solution sécurisée et qu’il est difficile de lire le contenu de cette chaîne. En réalité, ce type de stockage n’a rien de sécurisé.
On peut trouver dans des applications .NET des mots de passe stockés en clair dans un objet de type System.String
. Toutefois avec très peu de moyen, on peut rapidement lire le contenu de n’importe quelle chaîne de caractères stockée dans un processus .NET. Ainsi, si ces mots de passe apparaissent en clair, il sera facile d’en lire le contenu.
Le but de cet article est d’abord de montrer comment on peut facilement lire une chaîne de caractères dans la mémoire d’un processus .NET. Ensuite, on proposera quelques solutions simples pour rendre une chaîne de caractères plus difficilement lisible par un outil externe.
Détecter le contenu d’une chaîne de caractères dans un processus
Récupérer le mot de passe avec un outil externe
Avec ProcessHacker
Avec WinDbg
Quelques précisions sur “System.String” en .NET
Quelques solutions pour stocker des mots de passe
Ne pas utiliser de “System.String”
Forcer l’exécution du garbage collector
Utiliser une “SecureString”
Utiliser le contenu d’une SecureString
Convertir une chaîne de caractères en SecureString
Convertir une “SecureString” en chaîne de caractères
Utiliser “System.String” et écraser son contenu après utilisation
Détecter le contenu d’une chaîne de caractères dans un processus
Dans un premier temps, on va montrer comment on peut facilement lire une chaîne de caractères stockée en clair dans un objet System.String
dans un processus .NET.
La première étape consiste à simplement coder un exécutable contenant une chaîne de caractères non statique. Pour illustrer, on considère une application qui vérifie le mot de passe de l’utilisateur à 3 reprises. Si le couple user/password est correct, il répond OK
sinon il réponds KO
.
Le code correspondant à ce processus se trouve dans le repository suivant: github.com/msoft/secureString.
Les mots de passe sont stockés par un objet satisfaisant IPasswordFinder
:
public interface IPasswordFinder
{
string GetPassword(string login);
}
L’objet satisfaisant IPasswordChecker
est de type Mock<IPasswordFinder>
. Il ne fait que renvoyer un mot de passe en fonction du nom d’un utilisateur. On l’instancie en utilisant PasswordFinderFactory
dont voici l’implémentation:
public class PasswordFinderFactory
{
private readonly Dictionary<string, string> passwordPerUser = new Dictionary<string, string>();
public void SetPassword(string login, string password)
{
this.passwordPerUser[login] = password;
}
public IPasswordFinder GetNewPasswordFinder()
{
var passwordFinderMock = new Mock<IPasswordFinder>();
foreach (var user in this.passwordPerUser)
passwordFinderMock.Setup(f => f.GetPassword(user.Key)).Returns(user.Value);
return passwordFinderMock.Object;
}
public static PasswordFinderFactory GetFactory()
{
var factory = new PasswordFinderFactory();
factory.SetPassword(
"User1",
$"{(char)109}{(char)121}{(char)115}{(char)101}{(char)99}{(char)114}{(char)101}
{(char)116}{(char)112}{(char)97}{(char)115}{(char)115}{(char)119}{(char)111}
{(char)114}{(char)100}"
);
factory.SetPassword(
"User2",
$"{(char)111}{(char)116}{(char)104}{(char)101}{(char)114}{(char)112}{(char)97}
{(char)115}{(char)115}{(char)119}{(char)111}{(char)114}{(char)100}"
);
return factory;
}
}
Comme on peut le voir le mot de passe avec lequel on effectue la comparaison n’est pas écrit sous forme d’une chaîne de caractères unique pour éviter que le compilateur ne l’interprète comme un objet statique:
factory.SetPassword(
"User1",
$"{(char)109}{(char)121}{(char)115}{(char)101}{(char)99}{(char)114}{(char)101}
{(char)116}{(char)112}{(char)97}{(char)115}{(char)115}{(char)119}{(char)111}
{(char)114}{(char)100}"
);
factory.SetPassword(
"User2",
$"{(char)111}{(char)116}{(char)104}{(char)101}{(char)114}{(char)112}{(char)97}
{(char)115}{(char)115}{(char)119}{(char)111}{(char)114}{(char)100}"
);
Si les chaînes étaient stockées de façon statique, il n’y aurait pas de nécessité d’exécuter l’application. En décompilant les assemblies, on verrait directement les chaînes de caractères.
La classe qui effectue la vérification du mot de passe est PasswordChecker
:
public class PasswordChecker
{
private IPasswordFinder passwordFinder;
public PasswordChecker(PasswordFinderFactory passwordFinderFactory)
{
this.passwordFinder = passwordFinderFactory.GetNewPasswordFinder();
}
public bool CheckPassword(string login, string password)
{
var foundPassword = this.passwordFinder.GetPassword(login);
return string.IsNullOrEmpty(foundPassword) ? false : foundPassword.Equals(password);
}
}
L’exécution est sans surprise:
Essai 1 Quel est le login ? > User1 Quel est le mot de passe ? > bad password KO Essai 2 Quel est le login ? > User1 Quel est le mot de passe ? > mysecretpassword OK
Pour l’exemple avec des chaînes de caractères simples, il faut regarder le contenu du répertoire SecureString/WithSimpleString. Pour utiliser les classes de ce répertoire, la fonction main
doit instancier passwordChecker
de cette façon:
var passwordChecker = new WithSimpleString.PasswordChecker(
WithSimpleString.PasswordFinderFactory.GetFactory());
Récupérer le mot de passe avec un outil externe
On propose 2 méthodes pour lire les chaînes de caractères contenant les mots de passe en utilisant des outils externes au processus:
- Avec ProcessHacker et
- Avec WinDbg.
Avec ProcessHacker
Pour lire toutes les chaînes de l’assembly, on peut exécuter ProcessHacker (disponible gratuitement):
- Il faut chercher l’application dans la liste des processus
- Double-cliquer sur l’application “SecureString.exe”:
- Aller dans l’onglet Memory:
- Cliquer sur “Strings”
- Laisser les paramètres par défaut:
- Sauvegarder les chaînes dans un fichier texte en cliquant sur “Save”.
En cherchant dans le fichier texte, on peut voir que les chaînes de caractères correspondant à ce qu’a écrit l’utilisateur à chaque tentative, sont visibles même si la variable password
est écrasée à chaque itération de la boucle. D’autre part, on peut voir les chaînes correspondant aux mots de passe à trouver en clair.
Par exemple:
0x162266c (84): ...
0x16228e0 (108): NLS_CodePage_850_3_2_0_0ns.IMocked`1_get_MocktPassword
0x1622b58 (32): mysecretpassword
0x1622d88 (26): otherpassword
0x1623e20 (76): Invariant Language (Invariant Country)
0x1623e90 (36): ...
Avec WinDbg
Une autre méthode pour parcourir la mémoire d’un processus est d’utiliser WinDbg. WinDbg est un outil puissant qui permet de déboguer un processus et de lire un dump de mémoire (cf. Les “dumps” mémoire en 5 min). Même si WinDbg est difficile à utiliser, il permet d’extraire plus d’informations d’un dump mémoire que Visual Studio.
On peut l’installer à partir SDK Windows 10:
- docs.microsoft.com/fr-fr/windows-hardware/drivers/debugger/
- developer.microsoft.com/fr-fr/windows/downloads/windows-10-sdk
Dans notre cas, le gros intérêt de WinDbg est de pouvoir travailler sur un dump mémoire du processus. Ce dump correspond à une photo de la mémoire occupée par un processus. Même si l’exécution du processus s’interrompt, on peut continuer d’exploiter le dump pour en extraire des informations.
Pour générer un dump, il faut ouvrir le gestionnaire de tâches en faisant [Ctrl] + [Maj] + [Echap]:
- Sélectionner le processus
- Faire un clique droit sur le processus
- Cliquer sur “Generate a dump”
Le dump est généralement écrit dans un fichier dans le répertoire:
C:\Users\<Nom utilisateur>\AppData\Local\Temp\SecureString.DMP
Après génération du dump, il faut le charger avec WinDbg:
- Cliquer sur File
- Puis sur “Open Crash Dump”.
- Sélectionner le fichier de dump
- Cliquer sur Window puis “Dock All” pour agrandir la fenêtre.
Une autre méthode sans générer de dump de mémoire, peut consister à s’attacher directement au processus:
- Cliquer sur File
- Puis sur “Attach to a process…”
- Sélectionner le processus puis cliquer sur OK
- Cliquer sur Window puis “Dock All” pour agrandir la fenêtre.
Quand le fichier dump est chargé ou quand le processus est attaché, il suffit de chercher une chaîne de caractères correspondant aux mots de passe en tapant la commande suivante:
s –u 0 0FFFFFFF "<chaîne à chercher>"
Cette instruction permet de chercher une chaîne Unicode (les chaînes de caractères en .NET sont stockées en UTF-16) de l’adresse mémoire 0 à l’adresse 0FFFFFFF
(car on ne connait pas l’adresse de fin).
D’autres commandes permettent d’extraire d’autres informations:
- Pour lister toutes les chaînes de caractères Unicode (la liste peut être très longue):
s –su 0 0FFFFFFF
- Pour lister toutes les chaînes de caractères ASCII:
s –sa 0 0FFFFFFF
- Pour voir le contenu d’une adresse en mémoire:
dc <adresse mémoire>
Par exemple:
0:000> s -u 0 0FFFFFFF "mysecretpassword"
01622b58 006d 0079 0073 0065 0063 0072 0065 0074 m.y.s.e.c.r.e.t.
0:000> dc 01622b58
01622b58 0079006d 00650073 00720063 00740065 m.y.s.e.c.r.e.t.
01622b68 00610070 00730073 006f0077 00640072 p.a.s.s.w.o.r.d.
01622b78 00000000 00000000 68363bc8 00000003 .........;6h....
01622b88 00000001 ffffffff 00000000 00000000 ................
01622b98 6835ca98 00000003 016225b8 01622b50 ..5h.....%b.P+b.
01622ba8 2c1c7fa3 ffffffff 0162264c 01622d80 ...,....L&b..-b.
01622bb8 2c1c7fa4 ffffffff 00000000 00000000 ...,............
01622bc8 00000000 00000000 00000000 68362158 ............X!6h
On peut chercher toutes les chaînes de caractères contenus dans le processus en exécutant les commandes suivantes:
- Pour les chaînes ASCII:
s –sa 0 0FFFFFFF
- Pour les chaînes Unicode:
s –su 0 0FFFFFFF
Attention, l’exécution de ces commandes peut prendre du temps.
Comme pour ProcessHacker, on s’aperçoit qu’on peut voir le contenu des chaînes du processus en clair.
Quelques précisions sur “System.String” en .NET
Les chaînes de caractères sont le plus souvent stockées en .NET dans un objet de type System.String
. Les objets de ce type sont de type référence c’est-à-dire qu’ils sont alloués et stockés dans le tas managé et qu’ils sont libérés lors des passages du garbage collector.
Les objets System.String
ont aussi la particularité d’être immutables c’est-à-dire qu’ils sont passés par référence lors d’appels de fonction ou lors d’affectation de variables toutefois toute manipulation sur une chaîne entraîne la création d’une nouvelle instance de chaîne.
Ainsi si on écrit:
string finalString = "chaine1" + "chaine2" + "chaine3";
4 objets de type System.String
sont créés dans le tas (finalString
et les objets contenant "chaine1"
, "chaine2"
et "chaine3"
). Il faudra attendre le passage du garbage collector pour collecter les objets contenant "chaine1"
, "chaine2"
et "chaine3"
. Ainsi avant le passage du garbage collector, on peut non seulement voir le contenu de finalString
mais aussi "chaine1"
, "chaine2"
et "chaine3"
.
Ainsi, si on stocke des mots de passe ou des sections de mot de passe dans un objet de type System.String
, on ne pourra jamais vraiment savoir quand cet objet sera supprimé par le garbage collector, il peut rester dans la mémoire même longtemps après l’avoir utilisé. Avant sa suppression, on peut largement avoir le temps de générer un dump et de l’étudier pour éventuellement en extraire des chaînes avec des mots de passe. C’est la raison pour laquelle il faut éviter d’utiliser ce type d’objet pour stocker des chaînes dont le contenu est sensible.
Quelques solutions pour stocker des mots de passe
Ne pas utiliser de “System.String”
Cette solution peut paraître évidente mais c’est la plus simple pour ne pas dupliquer une chaîne de caractères au contenu sensible dans la mémoire: éviter d’utiliser trop d’instances d’objet de type System.String
.
Eviter de conserver des références vers une chaîne de caractères peut, dans certain cas, permettre au garbage collector de “collecter” l’objet correspondant à la chaîne de caractères de façon à libérer l’espace occupé par l’objet et surtout écraser son contenu pour y affecter d’autres données. En effet, même si le garbage collector libère l’espace correspond à l’objet, le contenu en mémoire n’est pas forcément écrasé et la chaîne de caractères peut rester présente en mémoire pour une durée indéfinie.
Etant donné le manque de maitrise quant à la libération d’un objet de type System.String
, il est conseillé d’utiliser des objets System.Security.SecureString
pour stocker des chaînes de caractères sensibles.
Forcer l’exécution du garbage collector
Si on ne possède plus de références vers un objet de type System.String
, le garbage collector est susceptible de s’exécuter et de collecter cet objet pour libérer l’espace mēmoire occupé. Au lieu d’attendre l’exécution du garbage collector, on peut la forcer quand on sait qu’on n’utilisera plus la chaîne de caractères. Pour forcer l’exécution du garbage collector, on peut exécuter l’instruction:
GC.Collect();
Utiliser une “SecureString”
L’objet de type System.Security.SecureString est spécialement conçu pour stocker des chaînes de caractères sensibles. Le contenu stocké par cet objet est crypté en utilisant la fonction RtlEncryptMemory
du système d’exploitation (code source de System.Security.SecureString
sur referencesource.microsoft.com). Ainsi il est beaucoup difficile de lire le contenu de ce type d’objet en mémoire.
La contrepartie des objets SecureString
est que leur utilisation n’est pas forcément direct, en particulier si un mot de passe est stocké de façon provisoire dans un objet System.String
.
Dans certains cas, on peut directement avoir une instance de SecureString
: par exemple en utilisant l’objet System.Windows.Control.PasswordBox
en WPF pour permettre à l’utilisateur d’entrer un mot de passe. Dans ce cas, pour récupérer la valeur du mot de passe, on peut utiliser directement la propriété PasswordBox.SecurePassword
qui est de type SecureString
. La récupération du mot de passe est donc sécurisée.
A l’opposé, dans d’autres cas, par exemple en WinForm, pour récupérer un mot de passe tapé par l’utilisateur, il faut utiliser une TextBox
classique et utiliser la propriété TextBox.Text
qui est de type System.String
. Dans le cas de la TextBox
, il existe des solutions pour ce type de control(1) (2) pour éviter de trop exposer ce qui est tapé par l’utilisateur.
Utiliser le contenu d’une SecureString
Utiliser les données contenus dans un objet SecureString
n’est pas tout-à-fait direct. Ainsi, pour utiliser le contenu de ce type d’objet en limitant les risques de copie dans des parties de la mémoire dont on ne maîtrise pas la libération, on peut s’inspirer du code suivant:
public static TReturn UseSecureStringContent<TReturn>(
System.Security.SecureString secureString,
Func<char[], TReturn> stringUse)
{
int stringLength = secureString.Length;
char[] bytes = new char[stringLength];
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.SecureStringToBSTR(secureString);
bytes = new char[stringLength];
Marshal.Copy(ptr, bytes, 0, stringLength);
}
finally
{
if (ptr != IntPtr.Zero)
Marshal.ZeroFreeBSTR(ptr);
}
// Utiliser le chaîne de caractères sous forme de tableau de caractères
TReturn returnValue = stringUse(bytes);
for (int i = 0; i < stringLength; i++)
bytes[i] = '*';
return returnValue;
}
Dans cet exemple, au lieu de générer une chaîne de caractères, on extrait un tableau de caractères. L’intérêt d’utiliser un tableau de caractères est qu’on peut facilement écraser le contenu du tableau après utilisation. Pendant l’exécution de la fonction, la chaîne de caractères sera écrite en clair dans le tableau toutefois si l’exécution est rapide, il est plus difficile d’en lire le contenu en utilisant un dump ou en essayant de lire la mémoire.
Convertir une chaîne de caractères en “SecureString”
On peut convertir une chaîne de caractères en tableau de char
de cette façon:
public static System.Security.SecureString GetNewSecureString(char[] clearString)
{
var secureString = new System.Security.SecureString();
foreach (char caracter in clearString)
{
secureString.AppendChar(caracter);
}
return secureString;
}
Même si un tableau est alloué sur le tas managé, l’intérêt de son utilisation par rapport à la chaîne stockée dans un objet de type System.String
est qu’on peut facilement en écraser le contenu.
On peut facilement utiliser une méthode similaire pour convertir un objet System.String
en SecureString
:
public static System.Security.SecureString GetNewSecureString(string clearString)
{
return GetNewSecureString(clearString.ToArray());
}
Convertir une “SecureString” en chaîne de caractères
On peut convertir une SecureString
en tableau de caractères de cette façon:
public static char[] ConvertToCharArray(SecureString secureString)
{
int stringLength = secureString.Length;
var charArray = new char[stringLength];
IntPtr valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(secureString);
for (int i = 0; i < stringLength; i++)
{
charArray[i] = (char)Marshal.ReadInt16(valuePtr, i * 2);
}
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
return charArray;
}
Une conversion de SecureString
vers System.String
se fait plus directement de cette façon:
public static string ConvertToString(SecureString secureString)
{
IntPtr valuePtr = IntPtr.Zero;
try
{
valuePtr = Marshal.SecureStringToGlobalAllocUnicode(secureString);
return Marshal.PtrToStringUni(valuePtr);
}
finally
{
Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
}
}
Dans le cadre de l’exemple utilisé plus haut dans le repository GitHub SecureString, il faut adapter le code pour utiliser des objets de type SecureString
plutôt que System.String
. L’adaptation du code se trouve dans le répertoire SecureString/WithSecureString. La fonction main
doit utiliser les classes de ce répertoire et instancier passwordChecker
de cette façon:
var passwordChecker = new WithSecureString.PasswordChecker(
WithSecureString.PasswordFinderFactory.GetFactory());
Utiliser “System.String” et écraser son contenu après utilisation
Comme indiqué plus haut, les objets de type System.String
sont des objets de type référence alloués dans le tas managé. Leur durée de vie et surtout leur temps de présence en mémoire dépend de l’exécution du garbage collector. Comme on ne maitrise pas complètement l’exécution du garbage collector, stocker des chaînes de caractères sensibles dans un objet System.String
présente un risque. Toutefois dans certains cas d’utilisation, on peut être contraint d’utiliser un objet de ce type, par exemple pour transmettre un mot de passe à une bibliothèque dont on ne maitrise pas l’implémentation et qui impose l’utilisation d’objets de type System.String
.
Dans ce cas, une solution est d’utiliser un objet de type System.String
et d’en effacer le contenu après utilisation. Après écrasement, un mot de passe stocké dans un objet de ce type est effacé de la mémoire et ne peut plus être lu.
Comme on l’a expliqué plus haut, si on écrit:
string password = "mysecretpassword";
Et si on écrit ensuite la ligne suivante:
password = "*********";
On n’écrase pas le contenu de la mémoire car un objet de type System.String
est immutable. Ce qui signifie que:
- A la déclaration de la variable
password
: on alloue à la variablepassword
une référence vers la chaîne de caractères"mysecretpassword"
stockée dans le tas managé. - A l’affectation de
"*********"
à la variablepassword
: on alloue une référence vers une nouvelle chaîne de caractères stockée dans le tas managé toutefois l’ancienne chaîne de caractères n’est pas modifiée. C’est juste la référence contenue dans la variablepassword
qui est modifiée.
Il faut donc, un code qui permet réellement d’écraser le contenu de la chaîne de caractères comme par exemple, le code suivant:
public static void EraseStringContent(string stringToErase)
{
unsafe
{
fixed (char* stringContent = stringToErase)
{
for (int i = 0; i < stringToErase.Length; i++)
stringContent[i] = '*';
}
}
}
Ce code permet d’écraser le contenu de la chaîne de caractères passée en paramètre:
- Le mot-clé
unsafe
: permet d’indiquer que le code suivant contient des instructions manipulant directement des pointeurs. - Le mot-clé
fixed
: permet d’indiquer au garbage collector de ne pas modifier les emplacements en mémoire des objets manipulés. Sans l’utilisation du mot-cléfixed
, le CLR pourrait changer l’emplacement de certains objets en fonction de l’exécution du garbage collector. Ces déplacements pourraient intervenir pendant la manipulation de pointeurs rendant, de fait, invalides les adresses pointées.
Le gros inconvénient de cette méthode est qu’elle nécessite que le code exécuté soit “unsafe” ce qui signifie qu’il faut rajouter une option de compilation permettant d’autoriser l’exécution de code “unsafe”.
Pour autoriser l’exécution de code “unsafe” dans une assembly, il faut:
- Effectuer un clique droit sur le projet concerné dans Visual Studio
- Cliquer sur Propriétés
- Dans l’onglet “Build”, il faut cocher “Allow unsafe code”.
En combinant les SecureString
et l’écrasement des chaînes de caractères System.String
, on peut proposer l’implémentation suivante pour utiliser le contenu d’une SecureString
:
public static TReturn UseSecureStringContent<TReturn>(
SecureString secureString,
Func<string, TReturn> useString)
{
string simpleString = ConvertToString(secureString);
var returnValue = useString(simpleString);
EraseStringContent(simpleString);
return returnValue;
}
De même que précédemment, l’utilisation d’un objet de type System.String
rend plus facile la lecture des données sensibles en mémoire. Toutefois étant donné qu’on en efface le contenu après utilisation, la chaîne n’apparait pas longtemps en clair dans la mémoire ce qui limite le risque.
Le code correspondant aux manipulations de SecureString
se trouve dans SecureStringHelper
.
En conclusion
On a vu quelques méthodes pour facilement lire le contenu de chaînes de caractères dans la mémoire d’un processus quand cette chaîne est stockée en clair. On a pu remarquer que les objets System.String
ne sont pas adaptés pour stocker des données sensibles comme des mots de passe. Il est préférable d’utiliser des objets comme les SecureString
.
On peut aller plus loin pour stocker ces données sensibles en passant par la Data Protection API (i.e. DPAPI) qui est spécialement conçue pour ce type d’usage. On peut facilement trouver de la documentation pour utiliser la DPAPI(3).
(1) – My SecurePasswordTextBox control is famous: https://weblogs.asp.net/pglavich/440052
(2) – SecurePasswordTextBox update: https://weblogs.asp.net/pglavich/440191
(3) – Documentation pour utiliser la Data Protection API:
- An easy and secure way to store a password using Data Protection API: https://www.thomaslevesque.com/2013/05/21/an-easy-and-secure-way-to-store-a-password-using-data-protection-api/
- Windows Data Protection: https://msdn.microsoft.com/en-us/library/ms995355.aspx
- Password security best practices (with examples in C#): https://www.mking.net/blog/password-security-best-practices-with-examples-in-csharp
- Secure way to store and load password in app config: https://codereview.stackexchange.com/questions/128150/secure-way-to-store-and-load-password-in-app-config
- Windows Data Protection API (DPAPI): https://www.softfluent.fr/blog/expertise/Windows-Data-Protection-API-DPAPI
- Comment sécuriser une application universelle Windows ?: https://blog.octo.com/comment-securiser-une-application-universelle-windows/
- Code source System.Security.SecureString: https://referencesource.microsoft.com/#mscorlib/system/security/securestring.cs
- RtlEncryptMemory function: https://msdn.microsoft.com/en-us/library/windows/desktop/aa387693(v=vs.85).aspx
- SecureString from TextBox.Text: https://social.msdn.microsoft.com/Forums/vstudio/en-US/617b4bdc-57d2-4fa6-b6a5-902bc45e1106/securestring-from-textboxtext?forum=csharpgeneral
- PasswordBox Stores Password as a SecureString: https://wpf.2000things.com/tag/securestring/
- PasswordBox Allows Entering a Password: https://wpf.2000things.com/2013/11/14/950-passwordbox-allows-entering-a-password/
- “Arrays, heap and stack and value types” on stackoverflow.com: https://stackoverflow.com/questions/1113819/arrays-heap-and-stack-and-value-types
- “How to convert a string to securestring explicitly” on stackoverflow.com: https://stackoverflow.com/questions/9887996/how-to-convert-a-string-to-securestring-explicitly
- “Safe use of SecureString for login form” on stackoverflow.com: https://stackoverflow.com/questions/14449579/safe-use-of-securestring-for-login-form
- “How to convert SecureString to System.String?” on stackoverflow.com: https://stackoverflow.com/questions/818704/how-to-convert-securestring-to-system-string
- How to: Modify string contents in C#: https://docs.microsoft.com/fr-fr/dotnet/csharp/how-to/modify-string-contents
- fixed statement: https://docs.microsoft.com/fr-fr/dotnet/csharp/language-reference/keywords/fixed-statement
- unsafe: https://docs.microsoft.com/fr-fr/dotnet/csharp/language-reference/keywords/unsafe
- http://www.windbg.org/
Bonjour, Je suis tombé un peu par hasard sur ton blog ..et je le trouve tres interessant.
Je passe juste te remercier pour toutes les infos que tu mets à disposition de la communautés.
Ce que j’apprecie le plus tout est tres bien expliqué et souvent avec des exemples concret que l’on peut facilement reproduire afin de bien comprendre le tout …
Et ca …j’achete 🙂
bonne continuation
christophe
Merci pour ton commentaire 😉