C ++中的安全内存分配器

时间:2008-08-12 04:18:10

标签: c++ security memory ram-scraping

我想创建一个分配器,它为内存提供以下属性:

  • 无法分页到磁盘。
  • 难以置信地通过附加的调试器进行访问

这个想法是,它将包含敏感信息(如许可证信息),这些信息应该是用户无法访问的。我在网上做了一般的研究,并向其他几个人询问了这个问题,但我找不到一个好的地方开始解决这个问题。

更新

Josh提到使用VirtualAlloc来设置内存空间的保护。我创建了一个自定义分配器(如下所示)我发现使用VirtualLock函数限制了我可以分配的内存量。这似乎是设计的。由于我将它用于小物件,这不是问题。

//
template<class _Ty>
class LockedVirtualMemAllocator : public std::allocator<_Ty>
{
public:
    template<class _Other>
    LockedVirtualMemAllocator<_Ty>& operator=(const LockedVirtualMemAllocator<_Other>&)
    {   // assign from a related LockedVirtualMemAllocator (do nothing)
        return (*this);
    }

    template<class Other>
    struct rebind {
        typedef LockedVirtualMemAllocator<Other> other;
    };

    pointer allocate( size_type _n )
    {
        SIZE_T  allocLen = (_n * sizeof(_Ty));
        DWORD   allocType = MEM_COMMIT;
        DWORD   allocProtect = PAGE_READWRITE;
        LPVOID pMem = ::VirtualAlloc( NULL, allocLen, allocType, allocProtect );
        if ( pMem != NULL ) {
            ::VirtualLock( pMem, allocLen );
        }
        return reinterpret_cast<pointer>( pMem );
    }
    pointer allocate( size_type _n, const void* )
    {
        return allocate( _n );
    }

    void deallocate(void* _pPtr, size_type _n )
    {
        if ( _pPtr != NULL ) {
            SIZE_T  allocLen = (_n * sizeof(_Ty));
            ::SecureZeroMemory( _pPtr, allocLen );
            ::VirtualUnlock( _pPtr, allocLen );
            ::VirtualFree( _pPtr, 0, MEM_RELEASE );
        }
    }
};

并使用

 //a memory safe std::string
 typedef std::basic_string<char, std::char_traits<char>, 
                           LockedVirtualMemAllocato<char> > modulestring_t;

Ted Percival提到了mlock,但我还没有实现。

我发现Practical Cryptography by Neil Furguson and Bruce Schneier也很有帮助。

13 个答案:

答案 0 :(得分:19)

您无法真正防止内存访问。如果您以管理员或系统身份运行,则可能会阻止分页,但您无法阻止管理员或系统读取内存。即使你能以某种方式完全阻止其他进程读取你的内存(你不能),另一个进程仍然可以实际注入一个新线程进入你的进程并以这种方式读取内存。

即使您可以某种方式完全锁定您的流程并保证操作系统永远不会允许其他人访问您的流程,您仍然无法获得全面保护。整个操作系统可以在虚拟机中运行,可以随时暂停和检查。

无法保护系统所有者的内存内容。多年来,好莱坞和音乐界一直在为此做好准备。如果可能的话,他们已经在做了。

答案 1 :(得分:6)

在Unix系统上,您可以使用mlock(2)将内存页锁定到RAM中,防止它们被分页。

  

mlock()和mlockall()分别锁定部分或全部调用   进程的虚拟地址空间进入RAM,防止内存   被分页到交换区域。

每个进程可以锁定多少内存是有限制的,它可以用ulimit -l显示,并以千字节为单位进行测量。在我的系统上,默认限制为每个进程32 kiB。

答案 2 :(得分:5)

让我们一点一点地看一下:

  

我想创建一个分配器   提供以下内存   属性:

这很公平。

* cannot be paged to disk.

那将是艰难的。据我所知,您无法禁用虚拟分页,因为它由操作系统处理。如果有办法,那么你将在操作系统的内容中进行探索。

* is incredibly hard to access through an attached debugger

您可以通过PGP运行它并将其加密存储在内存中并根据需要对其进行解密。大量的性能受到了影响。

  

这个想法是包含的   敏感信息(如许可证   信息)应该是   用户无法访问。我已经做好了   通常的在线研究并问了一个   很少有其他人这个,但我   找不到一个好的地方开始这个   问题

将所有敏感信息保留在机器上。认真。不要将敏感信息存储在内存中。编写一个自定义删除例程,它将自动删除您执行的任何分配中的所有数据。绝不允许一般访问带有敏感材料的机器。如果执行数据库访问,请确保在触发之前清除所有访问权限。只允许具有特定登录的人员访问。没有一般的群组访问权。

  

另一方面,还有其他方法   在那里访问的记忆   附加过程以外的过程   调试器?

转储内存。

答案 3 :(得分:5)

如果您正在为Windows开发,有一些方法可以限制对内存的访问,但绝对阻止其他内容是不可行的。如果您希望保守秘密,请阅读Writing Secure Code - 它会解决此问题,但要注意您无法知道您的代码是在真机还是在虚拟机上运行。有一堆Win32 API来处理加密处理这类事情,包括安全存储秘密 - 本书谈到了这一点。您可以查看在线Microsoft CyproAPI了解详情;操作系统设计人员认识到这个问题以及保持明文安全的必要性(再次,阅读编写安全代码)。

Win32 API函数VirtualAlloc是操作系统级别的内存分配器。它允许您设置访问保护;您可以做的是设置对PAGE_GUARDPAGE_NOACCESS的访问权限,并在您的程序读取时将访问权限转换为更友好的内容,然后重置它,但如果有人真的努力尝试,那只是速度突变看看你的秘密。

总之,看看你平台上的加密API,他们会比你自己解决的问题更好地解决这个问题。

答案 4 :(得分:4)

安装Libsodium,使用#including <sodium.h>

的分配机制

受保护的堆分配

比malloc()和朋友慢,他们需要3或4个额外的虚拟内存页面。

void *sodium_malloc(size_t size);

使用sodium_malloc()sodium_allocarray()分配内存以存储敏感数据。在使用这些堆保护之前,您需要先调用sodium_init()

void *sodium_allocarray(size_t count, size_t size);

sodium_allocarray()函数返回一个指针,从中可以访问每个大小为内存字节数的计数对象。它提供与sodium_malloc()相同的保证,但在count * size超过SIZE_MAX时也可防止算术溢出。

这些功能在受保护数据周围添加保护页面,使其不太可能在类似心脏的场景中访问。

此外,可以使用锁定内存操作更改对以这种方式分配的内存区域的保护:sodium_mprotect_noaccess()sodium_mprotect_readonly()sodium_mprotect_readwrite()

sodium_malloc之后,您可以使用sodium_free()来解锁和释放内存。在您的实现中,此时请考虑在使用后将内存归零。

使用后将内存归零

void sodium_memzero(void * const pnt, const size_t len);

使用后,应覆盖敏感数据,但优化编译器或链接器可以无提示地删除memset()和手写代码。

即使优化应用于代码,sodium_memzero()函数也会尝试从pnt开始有效地清零len个字节。

锁定内存分配

int sodium_mlock(void * const addr, const size_t len);

sodium_mlock()函数从addr开始锁定至少len个字节的内存。这有助于避免将敏感数据交换到磁盘。

int sodium_mprotect_noaccess(void *ptr);

sodium_mprotect_noaccess()函数使用sodium_malloc()或sodium_allocarray()分配的区域不可访问。它不能被读取或写入,但数据被保留。除非特定操作实际需要,否则此功能可用于使机密数据无法访问。

int sodium_mprotect_readonly(void *ptr);

sodium_mprotect_readonly()函数将使用sodium_malloc()或sodium_allocarray()分配的区域标记为只读。尝试修改数据将导致进程终止。

int sodium_mprotect_readwrite(void *ptr);

sodium_mprotect_readwrite()函数使用sodium_malloc()sodium_allocarray()保护后,使用sodium_mprotect_readonly()sodium_mprotect_noaccess()分配的区域标记为可读写。

答案 5 :(得分:2)

您要求的是在操作系统级别处理。一旦数据出现在你的程序中,它就有可能被分页。

为了访问内存,有动力的人可以附加硬件调试器。

答案 6 :(得分:1)

@graham

  

您可以通过PGP运行它并将其加密存储在内存中并根据需要对其进行解密。大量的性能受到了影响。

然后你必须把钥匙拿在记忆中。这会让它变得更难,但绝对不是不可能的。任何有动力的人仍然会设法从记忆中获取数据。

答案 7 :(得分:0)

  

您无法保护系统所有者的内存内容。   多年来,好莱坞和音乐界一直在为此做好准备。   如果可能的话,他们已经在做了。

您是否看过Vista(及以上)Protected Processes(直接.doc download)。我认为操作系统强制保护是由娱乐业提供的。

答案 8 :(得分:0)

最好的办法是实现类似于.NET的SecureString类,并且在完成后要非常小心地将数据的任何明文副本清零(即使抛出异常也不要忘记清理)。使用std :: string这样做的好方法是使用custom allocator

在Windows上,如果使用CryptProtectMemory(或旧系统的RtlEncryptMemory),加密密码将存储在不可分页(内核?)内存中。在我的测试中,这些功能非常快,特别是。考虑到他们给你的保护。

在其他系统上,我喜欢使用Blowfish,因为它是速度和力量之间的良好组合。在后一种情况下,您必须在程序启动时随机生成自己的密码(Blowfish的16个字节以上的熵)。遗憾的是,在没有操作系统支持的情况下,您可以做很多事情来保护密码,尽管您可能会使用通用的混淆技术将硬编码的salt值嵌入到您可以与密码结合的可执行文件中(每一点都有帮助)。

总体而言,这一战略只是更广泛的纵深防御方法的一部分。还要记住,缓冲区溢出等简单错误以及不清理程序输入仍然是最常见的攻击媒介。

答案 9 :(得分:-1)

@Derek:哦,但是对于可信计算,你可以使用memory curtaining! :-P&lt; / devil's-advocate&gt;

答案 10 :(得分:-1)

@roo

  

我真的希望那是可能的,而我还没有找到它。你的例子让我意识到这正是我们想要做的 - 只允许在我们程序的上下文中访问文件,从而保留IP。

     

我想我必须接受没有真正安全的方法将某人的文件存储在另一台计算机上,尤其是在某些时候,所有者允许访问该文件。

这绝对是个问题。只要您从未授予访问权限,就可以安全地存储内容,但只要您授予访问权限,您的控制就会消失。你可以让它变得更难一点,但就是这样。

答案 11 :(得分:-1)

@克里斯

  

哦,但是使用可信计算,你可以使用内存窗帘! :-P

但是你必须真的愿意为别人拥有的电脑买单。 :P

答案 12 :(得分:-1)

@Derek Park

他只说更难,不是不可能。 PGP会让它变得更难,而不是不可能。