清除堆栈变量存储器

时间:2020-08-26 20:09:15

标签: c# .net memory-management

我有8个uint代表这样的安全密钥:

uint firstParam = ...;
uint secondParam = ...;
uint thirdParam = ...;
uint etcParam = ...;
uint etcParam = ...;

它们在UNSAFE方法内部分配为局部变量。 这些键非常敏感。 我想知道方法结束后堆栈上的那些本地对象是否会被删除? UNSAFE方法是否对此有影响? MSDN说不安全代码会自动固定在内存中。

如果没有将它们从内存中删除,即使分析人员说这没有效果,在方法结束时是否将它们全部分配给0帮助?

所以我测试了将变量清零的方法。但是,在x64 Release模式下,归零会从最终产品中删除(已使用ILSpy检查) 有什么办法可以阻止这种情况?

这是示例代码(在x64版本中)

private static void Main(string[] args)
{
    int num = new Random().Next(10, 100);
    Console.WriteLine(num);
    MethodThatDoesSomething(num);
    num = 0;                         // This line is removed!
    Console.ReadLine();
}

private static void MethodThatDoesSomething(int num)
{
    Console.WriteLine(num);
}

在x64版本中删除了num = 0语句。

我无法使用SecureString,因为我正在P /调用将UInt作为参数的本机方法。

我正在P /调用非托管方法AllocateAndInitializeSid,它使用8个uint作为参数。在这个场景中我该怎么办?

我尝试添加

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]

到示例代码(在Main方法之上),但是num = 0仍被删除!

2 个答案:

答案 0 :(得分:3)

编辑:经过一番推理,我来纠正这个答案。

请勿使用SecureString,就像@Servy和@Alejandro在评论中指出的那样,它不再被认为是真正的安全性,并且会给人一种误导的安全感,可能会导致更多未经考虑的暴露。

我已经击中了我不再适应的段落了,在它们的位置,建议如下。

要分配firstParam,请使用:

firstParam = value ^ OBFUSCATION_MASK;

要再次阅读firstParam,请使用:

firstParam ^ OBFUSCATION_MASK;

^(按位XOR)运算符是其本身的反函数,因此对其应用两次将返回原始值。通过减少时间,该值就不会混淆(因为CPU时间实际上是机器代码周期数),因此也减少了它的暴露时间。当该值长期存储(例如2-3微秒)时,应始终对其进行混淆。例如:

private static uint firstParam; // use static so that the compiler cannot remove apparently "useless" assignments

public void f()
{
    // somehow acquire the value (network? encrypted file? user input?)
    firstParam = externalSourceFunctionNotInMyCode() ^ OBFUSCATION_MASK; // obfuscate immediately
}

然后,几毫秒后:

public void g()
{
    // use the value
    externalUsageFunctionNotInMyCode(firstParam ^ OBFUSCATION_MASK);
}

两个external[Source|Usage]FunctionNotInMyCode()是该值的入口点和出口点。重要的是,只要将值存储在我的代码中,它就永远不会模糊,它总是被混淆。在我的代码之前和之后发生的事情不受我们的控制,我们必须忍受它。有时必须输入和/或退出值。否则它将是什么程序?

最后一点是关于OBFUSCATION_MASK的。我将在应用程序的每次启动时将其随机化,但要确保熵足够高,这意味着01的数量可能不是五十/五十,而是接近五十。我认为RNGCryptoServiceProvider就足够了。如果没有,总是可以对位数进行计数或计算熵:

private static readonly uint OBFUSCATION_MASK = cryptographicallyStrongRandomizer();

到那时,识别二进制汤中的敏感值相对困难,如果将数据分页到磁盘,甚至可能不相关。

和往常一样,安全性必须与成本和效率(在这种情况下,还要兼具可读性和可维护性)之间达到平衡。


原始答案:

即使使用固定的非托管内存,您也无法确定操作系统是否将物理内存分页到磁盘上。

实际上,在互联网酒吧非常普遍的国家/地区,客户可以在可公开访问的计算机上使用您的程序。攻击者可以尝试执行以下操作:

  1. 通过运行偶尔分配所有可用RAM的进程来破坏计算机;
  2. 等待其他客户端使用该计算机并运行包含敏感数据(例如用户名和密码)的程序;
  3. 流氓程序耗尽所有RAM后,操作系统会将虚拟内存页面分页到磁盘;
  4. 在其他客户端使用了几个小时后,攻击者又回到了计算机上,将未使用的扇区和空闲空间复制到外部设备上;
  5. 他希望pagefile.sys多次更改扇区(这是通过扇区轮换发生的,操作系统可能无法避免 ,这取决于硬件/固件/驱动程序) ;
  6. 他将外部设备带到地牢中,然后缓慢但耐心地分析收集到的数据,这些数据主要是乱码,但可能会有一些ASCII字符。

通过不间断地分析世界上所有时间的数据,他可能会发现pagefile.sys曾经被写入过几次“写入”的那些扇区。在那里,可以检查RAM的内容,从而可以检查程序的堆/堆栈。

如果程序在string中存储了敏感数据,则此过程将公开它。

现在,您使用的是uint,而不是string,但是相同的原理仍然适用。为了确保即使分页到磁盘也不会暴露任何敏感数据,您可以使用类型的安全版本,如<{strike>,例如SecureString

使用uint可以保护您免受ASCII扫描的侵扰,但是要确保不要将敏感数据存储在不安全的变量中,这意味着您应该以某种方式将uint转换为string表示形式,并将其专门存储在SecureString 中。

希望可以帮助实现安全应用程序的人。

答案 1 :(得分:2)

在.NET中,您永远无法确定实际上已从内存中清除了变量。

由于CLR管理内存,因此可以随意移动它们,自由地将旧副本留在后面,包括如果有意用零或其他随机值覆盖它们。如果内存分析器或调试器具有足够的特权,它们仍然可能会获得它们。

那你该怎么办?

仅终止该方法会将数据留在堆栈中,它们最终将被其他内容覆盖,无法确定何时(或是否)会发生这种情况。

如果编译器没有优化“无用”的分配,则手动覆盖它会有所帮助(有关详细信息,请参见this thread)。如果变量是短寿命的(在GC有机会移动它们之前),这将更有可能成功,但是您仍然没有保证在其他地方不会有其他副本。

您要做的第二件事是立即终止整个过程,最好也将它们覆盖。这样,内存将返回到操作系统,并在将其分配给另一个进程之前将其清除。虽然您仍然受内核模式分析器的支配,但是现在您已经大大提高了标准。

相关问题