15

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

  • 无法分页到磁盘。
  • 很难通过附加的调试器访问

这个想法是这将包含用户应该无法访问的敏感信息(如许可证信息)。我已经在网上进行了通常的研究,并就此询问了其他一些人,但我找不到一个很好的起点来解决这个问题。

更新

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,但我还没有实现。

我发现Neil Furguson 和 Bruce Schneier 的 Practical Cryptography 也很有帮助。

4

13 回答 13

19

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

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

无法保护系统所有者的内存内容。多年来,好莱坞和音乐产业一直在为此苦恼。如果可能的话,他们早就这样做了。

于 2008-08-12T05:17:09.473 回答
8

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

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

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

于 2008-09-02T02:25:23.350 回答
5

让我们一次看一下:

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

这很公平。

* cannot be paged to disk.

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

* is incredibly hard to access through an attached debugger

您可以通过 PGP 运行它并将其加密存储在内存中并根据需要对其进行解密。巨大的性能冲击。

这个想法是这将包含用户应该无法访问的敏感信息(如许可证信息)。我已经在网上进行了通常的研究,并就此询问了其他一些人,但我找不到一个很好的起点来解决这个问题。

将所有敏感信息远离机器。严重地。不要将敏感信息存储在内存中。编写一个自定义删除例程,该例程将自动从您执行的任何分配中删除所有数据。切勿允许对带有敏感材料的机器进行一般访问。如果您执行数据库访问,请确保在触发之前对所有访问进行清理。只有具有特定登录名的人才能访问。没有一般的组访问权限。

附带说明一下,除了附加调试器之外,还有哪些其他方法可以访问进程的内存?

转储内存。

于 2008-08-12T05:13:00.637 回答
5

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

Win32 API 函数VirtualAlloc是操作系统级别的内存分配器。它允许您设置访问保护;您可以做的是设置对PAGE_GUARDorPAGE_NOACCESS的访问权限,并在您的程序读取时将访问权限翻转为更友好的内容,然后再将其重置,但如果有人真的很努力地偷看您的秘密,这只是一个减速带。

总之,看看你平台上的加密 API,它们会比你自己破解的东西更好地解决问题。

于 2008-08-26T01:16:51.790 回答
5

安装 Libsodium,通过 #include 使用分配机制<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()函数返回一个指针,从该指针可以访问每个 size 字节的内存对象。它提供了与超过相同的保证,sodium_malloc()但也防止算术溢出。count * sizeSIZE_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);

在使用or保护之后,该函数将使用orsodium_mprotect_readwrite()分配的区域标记为可读和可写。sodium_malloc()sodium_allocarray()sodium_mprotect_readonly()sodium_mprotect_noaccess()

于 2015-11-06T13:45:38.610 回答
2

您要求的内容是在操作系统级别处理的。一旦数据在您的程序中,它很可能被调出。

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

于 2008-08-12T05:07:08.837 回答
1

@格雷厄姆

您可以通过 PGP 运行它并将其加密存储在内存中并根据需要对其进行解密。巨大的性能冲击。

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

于 2008-08-12T05:21:33.053 回答
0

您无法保护系统所有者的内存内容。多年来,好莱坞和音乐产业一直在为此苦恼。如果可能的话,他们早就这样做了。

您是否看过 Vista(及更高版本)受保护的进程(直接.doc 下载)。我相信操作系统强制保护是娱乐业的礼貌。

于 2010-08-22T16:38:41.963 回答
0

最好的办法是实现类似于 .NET 的 SecureString 类的东西,并在完成后非常小心地将数据的任何纯文本副本清零(即使抛出异常也不要忘记清理)。使用 std::string 执行此操作的一个好方法是使用自定义分配器

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

在其他系统上,我喜欢使用 Blowfish,因为它是速度和力量的完美结合。在后一种情况下,您必须在程序启动时随机生成自己的密码(对于 Blowfish,熵为 16+ 字节)。不幸的是,在没有操作系统支持的情况下,您无法保护该密码,尽管您可以使用一般的混淆技术将硬编码的盐值嵌入到您可以与密码结合的可执行文件中(每一点都有帮助)。

总体而言,该策略只是更广泛的纵深防御方法的一部分。还要记住,缓冲区溢出和未清理程序输入等简单错误仍然是迄今为止最常见的攻击媒介。

于 2011-10-31T22:05:01.513 回答
-1

@Derek:哦,但是通过可信计算,您可以使用内存窗帘!:-P</devils-advocate>

于 2008-08-12T06:03:16.773 回答
-1

@roo

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

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

这绝对是问题所在。只要您从不授予访问权限,您就可以安全地存储某些内容,但是一旦您授予访问权限,您的控制权就消失了。你可以让它更难一点,但仅此而已。

于 2008-08-12T06:12:44.027 回答
-1

@克里斯

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

但是,您实际上必须愿意为别人拥有的计算机付费。:p

于 2008-08-12T06:15:27.640 回答
-1

@德里克公园

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

于 2008-08-12T08:10:27.393 回答