C#中机密文本的保存方案

简介: 托管代码中的字符串是一类特殊的对象,它不可被改变的,每次使用 System.String 类中的方法之一或进行运算时(如赋值、拼接等)时,都要在内存中创建一个新的字符串对象,也就是为该新对象分配新的空间。

托管代码中的字符串是一类特殊的对象,它不可被改变的,每次使用 System.String 类中的方法之一或进行运算时(如赋值、拼接等)时,都要在内存中创建一个新的字符串对象,也就是为该新对象分配新的空间。这就带来两个问题:

1:原来的字符串是不是还在内存当中?

2:如果在内存当中,那么机密数据(如密码)该如何保存才足够安全?

 

先来看第一个问题:

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 代码
 
  
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表示一个应保密的文本,在初始化时就已经被加密。

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 代码
 
  
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后,已经找不到对应的字符串了。但是,问题随之而来,核心数据的保存问题已经解决了,可是文本总是要取出来用的,只要取出来不是就会被发现吗。没错,这个问题没法避免,但是我们可以做到文本一使用完毕,就释放掉。

见如下代码:

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 代码
 
  
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”)。

上段代码涉及到的几个方法如下:

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 代码
 
  
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

Creative Commons License本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 http://www.cnblogs.com/luminji(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
目录
相关文章
|
监控 编译器 API
[笔记]Windows核心编程《二十二》注入DLL和拦截API(一)
[笔记]Windows核心编程《二十二》注入DLL和拦截API
502 0
|
7月前
|
供应链 安全 算法
签名不等于可信:详解PE数字签名校验的漏洞与主动规避方案
本文探讨了CVE-2013-3900漏洞的原理及其影响,该漏洞允许攻击者在不破坏数字签名有效性的情况下,向PE文件中添加恶意代码。漏洞源于Windows对签名数据后附加数据的校验缺失,导致恶意软件可伪装成合法软件。文章分析了WinVerifyTrust函数的工作机制及修复方法,包括通过注册表启用严格签名校验(EnableCertPaddingCheck)。同时,提出了通过hook注册表函数主动规避漏洞的方法,确保安全软件在未启用严格校验时仍能检测潜在威胁。此研究对提升PE文件签名安全性具有重要意义。
|
安全 Linux Shell
Kali渗透测试-远程控制:6200端口变成“后门”
Kali渗透测试-远程控制:6200端口变成“后门”
285 0
|
开发框架 JavaScript 前端开发
Electron技术深度解析:构建跨平台桌面应用的利器
【10月更文挑战第13天】Electron技术深度解析:构建跨平台桌面应用的利器
831 0
|
NoSQL MongoDB 数据库
Mongo 数据库备份和恢复命令
Mongo 数据库备份和恢复命令
701 4
|
搜索推荐 Java 开发者
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException 问题处理
【5月更文挑战第14天】org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException 问题处理
5055 1
|
安全 网络安全 数据库
Kali渗透测试:使用工具Metasploit攻击操作系统(一)
Kali渗透测试:使用工具Metasploit攻击操作系统(一)
801 0
|
关系型数据库 API C#
C#调用执行命令行窗口cmd,及需要交互执行的处理
C#执行外部程序用到的是Process进程类,打开一个进程,可以指定进程的启动信息StartInfo(启动的程序名、输入输出是否重定向、是否显示UI界面、一些必要参数等)...
4087 0
C#调用执行命令行窗口cmd,及需要交互执行的处理
|
安全
qt.qpa.xcb: could not connect to display 问题解决
【5月更文挑战第16天】qt.qpa.xcb: could not connect to display qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found. This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. 问题解决
7302 1
|
API Windows
[笔记]Windows核心编程《番外篇》常用的NT API及使用示例
[笔记]Windows核心编程《番外篇》常用的NT API及使用示例
482 0