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

发布于 2024-07-13 07:46:29 字数 493 浏览 7 评论 0原文

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

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

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

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

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

Is it possible to "wipe" strings in Delphi? Let me explain:

I am writing an application that will include a DLL to authorise users. It will read an encrypted file into an XML DOM, use the information there, and then release the DOM.

It is obvious that the unencrypted XML is still sitting in the memory of the DLL, and therefore vulnerable to examination. Now, I'm not going to go overboard in protecting this - the user could create another DLL - but I'd like to take a basic step to preventing user names from sitting in memory for ages. However, I don't think I can easily wipe the memory anyway because of references. If I traverse my DOM (which is a TNativeXML class) and find every string instance and then make it into something like "aaaaa", then will it not actually assign the new string pointer to the DOM reference, and then leave the old string sitting there in memory awaiting re-allocation? Is there a way to be sure I am killing the only and original copy?

Or is there in D2007 a means to tell it to wipe all unused memory from the heap? So I could release the DOM, and then tell it to wipe.

Or should I just get on with my next task and forget this because it is really not worth bothering.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

夢归不見 2024-07-20 07:46:30

我认为这不值得打扰,因为如果用户可以使用 DLL 读取进程的内存,那么同一用户也可以在任何给定时间点停止执行。 在擦除内存之前停止执行仍将允许用户完全访问未加密的数据。

IMO 任何足够感兴趣并且能够执行您所描述的操作的用户都不会因您的 DLL 擦除内存而造成严重不便。

I don't think it is worth bothering with, because if a user can read the memory of the process using the DLL, the same user can also halt the execution at any given point in time. Halting the execution before the memory is wiped will still give the user full access to the unencrypted data.

IMO any user sufficiently interested and able to do what you describe will not be seriously inconvenienced by your DLL wiping the memory.

煮酒 2024-07-20 07:46:30

关于此问题有两个一般要点:

首先,这是“如果您必须询问,您可能不应该这样做”的领域之一。 请不要误解这一点; 我的意思并不是对你的编程技能不尊重。 只是编写安全、加密功能强大的软件要么是专家,要么不是。 就像懂得“一点点空手道”比完全不懂空手道要危险得多。 有许多第三方工具可用于在 Delphi 中编写安全软件,并提供专家支持; 我强烈鼓励任何对 Windows 中的加密服务、加密的数学基础以及击败侧通道攻击经验不深的人使用它们,而不是尝试“自己动手”。

要回答您的具体问题:Windows API 有许多有用的函数,例如 CryptProtectMemory。 然而,如果您加密了内存,但系统其他地方有漏洞,或者暴露了侧通道,这会带来一种错误的安全感。 这就像在门上锁上锁但让窗户开着一样。

Two general points about this:

First, this is one of those areas where "if you have to ask, you probably shouldn't be doing this." And please don't take that the wrong way; I mean no disrespect to your programming skills. It's just that writing secure, cryptographically strong software is something that either you're an expert at or you aren't. Very much in the same way that knowing "a little bit of karate" is much more dangerous than knowing no karate at all. There are a number of third-party tools for writing secure software in Delphi which have expert support available; I would strongly encourage anyone without a deep knowledge of cryptographic services in Windows, the mathematical foundations of cryptography, and experience in defeating side channel attacks to use them instead of attempting to "roll their own."

To answer your specific question: The Windows API has a number of functions which are helpful, such as CryptProtectMemory. However, this will bring a false sense of security if you encrypt your memory, but have a hole elsewhere in the system, or expose a side channel. It can be like putting a lock on your door but leaving the window open.

幼儿园老大 2024-07-20 07:46:30

像这样的事情怎么样?

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;

How about something like this?

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;
云醉月微眠 2024-07-20 07:46:30

DLL 不拥有分配的内存,但进程拥有。 一旦进程终止,无论 DLL 是否挂起(因为它正在被另一个进程使用),由您的特定进程分配的内存都将被丢弃。

DLLs don't own allocated memory, processes do. The memory allocated by your specific process will be discarded once the process terminates, whether the DLL hangs around (because it is in use by another process) or not.

压抑⊿情绪 2024-07-20 07:46:30

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

How about decrypting the file to a stream, using a SAX processor instead of an XML DOM to do your verification and then overwriting the decrypted stream before freeing it?

背叛残局 2024-07-20 07:46:30

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

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

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

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

问候,

杰罗恩·普鲁默斯

If you use the FastMM memory manager in Full Debug mode, then you can force it to overwrite memory when it is being freed.

Normally that behaviour is used to detect wild pointers, but it can also be used for what your want.

On the other hand, make sure you understand what Craig Stuntz writes: do not write this authentication and authorization stuff yourself, use the underlying operating system whenever possible.

BTW: Hallvard Vassbotn wrote a nice blog about FastMM:
http://hallvards.blogspot.com/2007/05/use-full-fastmm-consider-donating.html

Regards,

Jeroen Pluimers

淡紫姑娘! 2024-07-20 07:46:30

请小心尝试将字符串视为指针的函数,并尝试使用 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;

Be careful of functions that try to treat a string as a pointer, and try to use FillChar or ZeroMemory to wipe the string contents.

  • this is both wrong (strings are shared; you're screwing someone else who's currently using the string)
  • and can cause an access violation (if the string happens to have been a constant, it is sitting on a read-only data page in the process address space; and trying to write to it is an access violation)

 

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;

Once you have the UnicodeString version, you can create the AnsiString and WideString versions:

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;
陌伤ぢ 2024-07-20 07:46:30

很混乱,但你可以记下你在堆中填充敏感数据时使用的堆大小,然后在释放该堆时执行 GetMem 为你分配一个跨越(例如)200% 的大块。 对该块进行填充,并假设任何碎片对于检查者来说都没有多大用处。
布里

Messy but you could make a note of the heap size that you've used while you've got the heap filled with sensitive data then when that is released do a GetMem to allocate you a large chunk spanning (say) 200% of that. do a Fill on that chunk and make the assumption that any fragmentation is unlinkely to be of much use to an examiner.
Bri

半城柳色半声笛 2024-07-20 07:46:30

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

编辑:您可以仅在最后一刻对所有敏感数据进行加密和解密。

How about keeping the password as a hash value in the XML and verify by comparing the hash of the input password with the hashed password in your XML.

Edit: You can keep all the sensitive data encrypted and decrypt only at the last possible moment.

伴我心暖 2024-07-20 07:46:30

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

将 char 数组分配给字符串时要小心,因为 Delphi 在这里有一些智能处理,以与传统的打包 char 数组 [1..x] 兼容。

另外,你可以使用 ShortString 吗?

Would it be possible to load the decrypted XML into an array of char or byte rather than a string? Then there would be no copy-on-write handling, so you would be able to backfill the memory with #0's before freeing?

Be careful if assigning array of char to string, as Delphi has some smart handling here for compatibility with traditional packed array[1..x] of char.

Also, could you use ShortString?

夜血缘 2024-07-20 07:46:30

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

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

编辑

但是知道哈希值(如果是一种方式)有什么帮助呢? 如果您真的很偏执,您可以通过只有您知道的可预测的内容来修改哈希值......例如,使用特定种子值加上日期的随机数。 然后,您可以在 xml 中仅存储足够的哈希值,以便可以将其用作比较的起点。 伪随机数生成器的好处是,它们总是在给定相同种子的情况下生成相同系列的“随机”数。

If your using XML, even encrypted, to store passwords your putting your users at risk. A better approach would be to store the hash values of the passwords instead, and then compare the hash against the entered password. The advantage of this approach is that even in knowing the hash value, you won't know the password which makes the hash. Adding a brute force identifier (count invalid password attempts, and lock the account after a certain number) will increase security even further.

There are several methods you can use to create a hash of a string. A good starting point would be to look at the turbo power open source project "LockBox", I believe it has several examples of creating one way hash keys.

EDIT

But how does knowing the hash value if its one way help? If your really paranoid, you can modify the hash value by something prediticable that only you would know... say, a random number using a specific seed value plus the date. You could then store only enough of the hash in your xml so you can use it as a starting point for comparison. The nice thing about psuedo random number generators is that they always generate the same series of "random" numbers given the same seed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文