zoukankan      html  css  js  c++  java
  • C#中机密文本的保存方案

    托管代码中的字符串是一类特殊的对象,它不可被改变的,每次使用 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,如下:

    image

    打开调试中的内存查看窗口,定位到019db820(由!dso得到)。由于此时还没有进入到Method1,所以内存当中不存在字符串“luminji”。接着让程序运行到方法内部,我们看到内存当中已经存在了“luminji”了。

    image
    接着让程序继续运行,退出方法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:托管字符串只能使用重写内存进行清除;

    有关利用sos.dll调试非托管代码,查看http://www.cnblogs.com/luminji/archive/2011/01/27/1946217.html

  • 相关阅读:
    iOS 即时通讯 + 仿微信聊天框架 + 源码
    Accelerate Framework in Swift
    最新 iOS 框架整体梳理(三)
    最新 iOS 框架整体梳理(二)
    单元测试
    iOS
    画个Shape留意到的东西
    deleteSections & deleteRows 我踩的坑
    常用开发技巧系列(六)
    程序员该如何提高睡眠质量?—程序媛233酱的助攻~
  • 原文地址:https://www.cnblogs.com/luminji/p/1946635.html
Copyright © 2011-2022 走看看