如何在Delphi中提高内存安全性?

时间:2009-02-11 11:04:28

标签: security delphi memory-management ram-scraping

是否可以在Delphi中“擦除”字符串?让我解释一下:

我正在编写一个包含DLL来授权用户的应用程序。它会将加密文件读入XML DOM,使用那里的信息,然后释放DOM。

很明显,未加密的XML仍然存在于DLL的内存中,因此容易受到检查。现在,我不会过分保护 - 用户可以创建另一个DLL - 但我想采取一个基本步骤来防止用户名在内存中存放多年。但是,由于引用,我认为我无论如何都不能轻易擦除内存。如果我遍历我的DOM(这是一个TNativeXML类)并找到每个字符串实例,然后将它变成类似“aaaaa”的东西,那么它实际上不会将新的字符串指针分配给DOM引用,然后保留旧的字符串在内存中等待重新分配?有没有办法确定我要杀死唯一的原始副本?

或者在D2007中有一种方法可以告诉它从堆中擦除所有未使用的内存吗?所以我可以释放DOM,然后告诉它擦除。

或者我应该继续我的下一个任务而忘记这一点因为它真的不值得打扰。

11 个答案:

答案 0 :(得分:7)

我认为不值得烦恼,因为如果用户可以使用DLL读取进程的内存,同一个用户也可以在任何给定的时间点停止执行。在擦除内存之前暂停执行仍然可以让用户完全访问未加密的数据。

IMO任何对您所描述的内容充满兴趣和能力的用户都不会因您的DLL擦拭内存而感到非常不便。

答案 1 :(得分:4)

关于这一点的两个一般观点:

首先,这是其中一个“如果你不得不问,你可能不应该这样做。”请不要采取错误的方式;我的意思是不要不尊重你的编程技巧。只是编写安全,加密强大的软件无论是你是专家还是你都不是。知道“一点空手道”比完全不知道空手道更危险的方式非常相似。在Delphi中有许多用于编写安全软件的第三方工具,可以提供专家支持;我强烈鼓励任何人不熟悉Windows中的加密服务,加密的数学基础,以及击败侧通道攻击的经验,而不是试图“自己动手”。

回答您的具体问题:Windows API有许多有用的功能,例如CryptProtectMemory。但是,如果加密内存,但在系统的其他地方有一个漏洞,或者暴露一个侧通道,这将带来虚假的安全感。这可能就像把门锁在门上,但是打开窗户。

答案 2 :(得分:3)

这样的事情怎么样?

procedure WipeString(const str: String);
var
  i:Integer;
  iSize:Integer;
  pData:PChar;

begin
    iSize := Length(str);
    pData := PChar(str);

    for i := 0 to 7 do
    begin
      ZeroMemory(pData, iSize);
      FillMemory(pData, iSize, $FF); // 1111 1111
      FillMemory(pData, iSize, $AA); // 1010 1010
      FillMemory(pData, iSize, $55); // 0101 0101
      ZeroMemory(pData, iSize);
    end;
end;

答案 3 :(得分:2)

DLL没有已分配的内存,进程可以。一旦进程终止,您的特定进程分配的内存将被丢弃,无论DLL是否挂起(因为它正被另一个进程使用)。

答案 4 :(得分:1)

如何将文件解密为流,使用SAX处理器而不是XML DOM进行验证,然后在释放之前覆盖解密的流?

答案 5 :(得分:1)

如果在完全调试模式下使用FastMM内存管理器,则可以强制它在释放时覆盖内存。

通常,该行为用于检测野生指针,但它也可用于您想要的内容。

另一方面,请确保您了解Craig Stuntz写的内容:不要自己编写此身份验证和授权内容,尽可能使用底层操作系统。

BTW:Hallvard Vassbotn撰写了一篇关于FastMM的精彩博客: http://hallvards.blogspot.com/2007/05/use-full-fastmm-consider-donating.html

此致

Jeroen Pluimers

答案 6 :(得分:0)

凌乱但你可以记下你在堆中填充敏感数据时使用的堆大小然后在发布时使用GetMem为你分配大块(例如)200%那个。填写该块并假设任何碎片对于审查员来说都是非常有用的。 BRI

答案 7 :(得分:0)

如何将密码保存为XML中的哈希值,并通过将输入密码的哈希值与XML中的哈希密码进行比较来验证。

编辑:您可以保留所有敏感数据,只在最后一刻加密和解密。

答案 8 :(得分:0)

是否可以将解密的XML加载到char或byte而不是字符串数组中?那么就没有写时复制处理,所以你可以在释放之前用#0回填内存吗?

如果将char数组赋给string,请小心,因为Delphi在这里有一些智能处理,以便与传统的char数组[1..x]兼容。

另外,你能使用ShortString吗?

答案 9 :(得分:0)

如果您使用XML(甚至是加密的)来存储密码,那么您的用户将面临风险。更好的方法是存储密码的哈希值,然后将哈希值与输入的密码进行比较。这种方法的优点是,即使知道哈希值,您也不会知道产生哈希的密码。添加强力标识符(计算无效密码尝试次数,并在一定数量后锁定帐户)将进一步提高安全性。

您可以使用几种方法来创建字符串的哈希值。一个很好的起点是看涡轮增压开源项目“LockBox”,我相信它有几个创建单向散列键的例子。

修改

但是如果知道哈希值的单向帮助怎么办?如果你真的很偏执,你可以通过可预知的东西来修改哈希值,只有你知道...比如,使用特定种子值加上日期的随机数。然后,您可以在xml中仅存储足够的哈希值,以便将其用作比较的起点。关于伪随机数生成器的好处是它们总是在给定相同种子的情况下生成相同系列的“随机”数字。

答案 10 :(得分:0)

小心尝试将字符串视为指针的函数,并尝试使用FillCharZeroMemory来擦除字符串内容。

  • 这两个都是错的(字符串是共享的;你正在搞其他正在使用该字符串的人)
  • 并且可能导致访问冲突(如果字符串恰好是常量,它位于进程地址空间中的只读数据页;并且尝试写入它是一种访问冲突)

procedure BurnString(var s: UnicodeString);
begin
    {
        If the string is actually constant (reference count of -1), then any attempt to burn it will be
        an access violation; as the memory is sitting in a read-only data page.

        But Delphi provides no supported way to get the reference count of a string.

        It's also an issue if someone else is currently using the string (i.e. Reference Count > 1).
        If the string were only referenced by the caller (with a reference count of 1), then
        our function here, which received the string through a var reference would also have the string with
        a reference count of one.

        Either way, we can only burn the string if there's no other reference.

        The use of UniqueString, while counter-intuitiave, is the best approach.
        If you pass an unencrypted password to BurnString as a var parameter, and there were another reference,
        the string would still contain the password on exit. You can argue that what's the point of making a *copy*
        of a string only to burn the copy. Two things:

            - if you're debugging it, the string you passed will now be burned (i.e. your local variable will be empty)
            - most of the time the RefCount will be 1. When RefCount is one, UniqueString does nothing, so we *are* burning
                the only string
    }
    if Length(s) > 0 then
    begin
        System.UniqueString(s); //ensure the passed in string has a reference count of one
        ZeroMemory(Pointer(s), System.Length(s)*SizeOf(WideChar));

        {
            By not calling UniqueString, we only save on a memory allocation and wipe if RefCnt <> 1
            It's an unsafe micro-optimization because we're using undocumented offsets to reference counts.

            And i'm really uncomfortable using it because it really is undocumented.
            It is absolutely a given that it won't change. And we'd have stopping using Delphi long before
            it changes. But i just can't do it.
        }
        //if PLongInt(PByte(S) - 8)^ = 1 then //RefCnt=1
        //  ZeroMemory(Pointer(s), System.Length(s)*SizeOf(WideChar));

        s := ''; //We want the callee to see their passed string come back as empty (even if it was shared with other variables)
    end;
end;

拥有UnicodeString版本后,您可以创建AnsiStringWideString版本:

procedure BurnString(var s: AnsiString); overload;
begin
    if Length(s) > 0 then
    begin
        System.UniqueString(s);
        ZeroMemory(Pointer(s), System.Length(s)*SizeOf(AnsiChar));

        //if PLongInt(PByte(S) - 8)^ = 1 then //RefCount=1
        //  ZeroMemory(Pointer(s), System.Length(s)*SizeOf(AnsiChar));

        s := '';
    end;
end;

procedure BurnString(var s: WideString);
begin
    //WideStrings (i.e. COM BSTRs) are not reference counted, but they are modifiable
    if Length(s) > 0 then
    begin
        ZeroMemory(Pointer(s), System.Length(s)*SizeOf(WideChar));

        //if PLongInt(PByte(S) - 8)^ = 1 then //RefCount=1
        //  ZeroMemory(Pointer(s), System.Length(s)*SizeOf(AnsiChar));

        s := '';
    end;
end;