托管代码中的字符串是一类特殊的对象,它不可被改变的,每次使用 System.String 类中的方法之一或进行运算时(如赋值、拼接等)时,都要在内存中创建一个新的字符串对象,也就是为该新对象分配新的空间。这就带来两个问题:
1:原来的字符串是不是还在内存当中?
2:如果在内存当中,那么机密数据(如密码)该如何保存才足够安全?
先来看第一个问题:
代码 public class Program { static void Main( string [] args) { Method1(); // 在此处打上断点 Console.ReadKey(); } static void Method1() { string str = " luminji " ; Console.WriteLine(str); } }
在Method1处打上断点,让VS执行到此处,在即时窗口中运行命令:.load sos.dll 和 !dso,如下:
打开调试中的内存查看窗口,定位到019db820(由!dso得到)。由于此时还没有进入到Method1,所以内存当中不存在字符串“luminji”。接着让程序运行到方法内部,我们看到内存当中已经存在了“luminji”了。
接着让程序继续运行,退出方法Method1,发现“luminji”依然留在内存当中。
这就带来一个问题,如果有恶意人员扫描你的内存,你的程序所保存的机密信息将无处可逃。幸好FCL中提供了System.Security.SecureString,SecureString表示一个应保密的文本,在初始化时就已经被加密。
代码 public class Program { static System.Security.SecureString secureString = new System.Security.SecureString(); static void Main( string [] args) { Method2(); // 在此处打上断点 Console.ReadKey(); } static void Method2() { secureString.AppendChar( ' l ' ); secureString.AppendChar( ' u ' ); secureString.AppendChar( ' m ' ); secureString.AppendChar( ' i ' ); secureString.AppendChar( ' n ' ); secureString.AppendChar( ' j ' ); secureString.AppendChar( ' i ' ); } }
相同的方法,可以发现在进入Method2后,已经找不到对应的字符串了。但是,问题随之而来,核心数据的保存问题已经解决了,可是文本总是要取出来用的,只要取出来不是就会被发现吗。没错,这个问题没法避免,但是我们可以做到文本一使用完毕,就释放掉。
见如下代码:
代码 static void Method3() { secureString.AppendChar( ' l ' ); secureString.AppendChar( ' u ' ); secureString.AppendChar( ' m ' ); secureString.AppendChar( ' i ' ); secureString.AppendChar( ' n ' ); secureString.AppendChar( ' j ' ); secureString.AppendChar( ' i ' ); IntPtr addr = Marshal.SecureStringToBSTR(secureString); string temp = Marshal.PtrToStringBSTR(addr); // 使用该机密文本do something /// =======开始清理内存 // 清理掉非托管代码中对应的内存的值 Marshal.ZeroFreeBSTR(addr); // 清理托管代码对应的内存的值(采用重写的方法) int id = GetProcessID(); byte [] writeBytes = Encoding.Unicode.GetBytes( " xxxxxx " ); IntPtr intPtr = Open(id); unsafe { fixed ( char * c = temp) { WriteMemory((IntPtr)c, writeBytes, writeBytes.Length); } } /// =======清理完毕 }
注意查看上文代码:
IntPtr addr = Marshal.SecureStringToBSTR(secureString);
string temp = Marshal.PtrToStringBSTR(addr);
这两行代码表示的就是将机密文本从secureString取出来,临时赋值给字符串temp。这就存在两个问题,第一行实际调用的是非托管代码,它在内存中也会存储一个“luminji”,第二行代码是在托管内存中存储一个“luminji”。这两段文本的释放方式是不一样的。前者,可以通过使用:
Marshal.ZeroFreeBSTR(addr);
进行释放。而托管内存中的文本,只能通过重写来完成(如上文中,就是重写成为无意义的“xxxxxx”)。
上段代码涉及到的几个方法如下:
代码 public static int GetProcessID() { Process p = Process.GetCurrentProcess(); return p.Id; } public static IntPtr Open( int processId) { IntPtr hProcess = IntPtr.Zero; hProcess = ProcessAPIHelper.OpenProcess(ProcessAccessFlags.All, false , processId); if (hProcess == IntPtr.Zero) throw new Exception( " OpenProcess失败 " ); processInfo.hProcess = hProcess; processInfo.dwProcessId = processId; return hProcess; } public static int WriteMemory(IntPtr addressBase, byte [] writeBytes, int writeLength) { int reallyWriteLength = 0 ; if ( ! ProcessAPIHelper.WriteProcessMemory(processInfo.hProcess, addressBase, writeBytes, writeLength, out reallyWriteLength)) { // throw new Exception(); } return reallyWriteLength; } [StructLayout(LayoutKind.Sequential)] internal struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } [Flags] enum ProcessAccessFlags : uint { All = 0x001F0FFF , Terminate = 0x00000001 , CreateThread = 0x00000002 , VMOperation = 0x00000008 , VMRead = 0x00000010 , VMWrite = 0x00000020 , DupHandle = 0x00000040 , SetInformation = 0x00000200 , QueryInformation = 0x00000400 , Synchronize = 0x00100000 } static class ProcessAPIHelper { [DllImport( " kernel32.dll " )] public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); [DllImport( " kernel32.dll " , SetLastError = true )] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte [] lpBuffer, int nSize, out int lpNumberOfBytesWritten); [DllImport( " kernel32.dll " , SetLastError = true )] public static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte [] lpBuffer, int dwSize, out uint lpNumberOfBytesRead ); [DllImport( " kernel32.dll " , SetLastError = true )] [ return : MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); }
总结:
1:机密文本使用System.Security.SecureString保存;
2:System.Security.SecureString被释放后使用Marshal.ZeroFreeBSTR清除在内存中的痕迹;
3:托管字符串只能使用重写内存进行清除;
本文转自最课程陆敏技博客园博客,原文链接:http://www.cnblogs.com/luminji/archive/2011/01/28/1946635.html,如需转载请自行联系原作者