Managed 程式碼中的字串是一類特殊的對象,它不可被改變的,每次使用 System.String 類中的方法之一或進行運算時(如賦值、拼接等)時,都要在記憶體中建立一個新的字串對象,也就是為該新對象分配新的空間。這就帶來兩個問題:
1:原來的字串是不是還在記憶體當中?
2:如果在記憶體當中,那麼機密資料(如密碼)該如何儲存才足夠安全?
先來看第一個問題:
代碼
publicclass Program
{
staticvoid Main(string[] args)
{
Method1(); //在此處打上斷點
Console.ReadKey();
}
staticvoid 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表示一個應保密的文本,在初始化時就已經被加密。
代碼
publicclass Program
{
static System.Security.SecureString secureString =new System.Security.SecureString();
staticvoid Main(string[] args)
{
Method2(); //在此處打上斷點
Console.ReadKey();
}
staticvoid Method2()
{
secureString.AppendChar('l');
secureString.AppendChar('u');
secureString.AppendChar('m');
secureString.AppendChar('i');
secureString.AppendChar('n');
secureString.AppendChar('j');
secureString.AppendChar('i');
}
}
相同的方法,可以發現在進入Method2後,已經找不到對應的字串了。但是,問題隨之而來,核心資料的儲存問題已經解決了,可是文本總是要取出來用的,只要取出來不是就會被發現嗎。沒錯,這個問題沒法避免,但是我們可以做到文本一使用完畢,就釋放掉。
見如下代碼:
代碼
staticvoid 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
///=======開始清理記憶體
//清理掉Unmanaged 程式碼中對應的記憶體的值
Marshal.ZeroFreeBSTR(addr);
//清理Managed 程式碼對應的記憶體的值(採用重寫的方法)
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。這就存在兩個問題,第一行實際調用的是Unmanaged 程式碼,它在記憶體中也會儲存一個“luminji”,第二行代碼是在託管記憶體中儲存一個“luminji”。這兩段文本的釋放方式是不一樣的。前者,可以通過使用:
Marshal.ZeroFreeBSTR(addr);
進行釋放。而託管記憶體中的文本,只能通過重寫來完成(如上文中,就是重寫成為無意義的“xxxxxx”)。
上段代碼涉及到的幾個方法如下:
代碼
publicstaticint GetProcessID()
{
Process p = Process.GetCurrentProcess();
return p.Id;
}
publicstatic IntPtr Open(int processId)
{
IntPtr hProcess = IntPtr.Zero;
hProcess = ProcessAPIHelper.OpenProcess(ProcessAccessFlags.All, false, processId);
if (hProcess == IntPtr.Zero)
thrownew Exception("OpenProcess失敗");
processInfo.hProcess = hProcess;
processInfo.dwProcessId = processId;
return hProcess;
}
publicstaticint 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)]
internalstruct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
publicint dwProcessId;
publicint 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
}
staticclass ProcessAPIHelper
{
[DllImport("kernel32.dll")]
publicstaticextern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll", SetLastError =true)]
publicstaticexternbool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, outint lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError =true)]
publicstaticexternbool ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[Out] byte[] lpBuffer,
int dwSize,
outuint lpNumberOfBytesRead
);
[DllImport("kernel32.dll", SetLastError =true)]
[return: MarshalAs(UnmanagedType.Bool)]
publicstaticexternbool CloseHandle(IntPtr hObject);
}
總結:
1:機密文本使用System.Security.SecureString儲存;
2:System.Security.SecureString被釋放後使用Marshal.ZeroFreeBSTR清除在記憶體中的痕迹;
3:託管字串只能使用重寫記憶體進行清除;
有關利用sos.dll調試Unmanaged 程式碼,查看http://www.cnblogs.com/luminji/archive/2011/01/27/1946217.html