C++ 中的安全内存分配器
我想创建一个分配器,它提供具有以下属性的内存:
- 无法分页到磁盘。
- 通过附加的调试器很难访问。
这个想法是,这将包含用户无法访问的敏感信息(如许可证信息)。 我在网上做了常规的研究,并向其他一些人询问了这个问题,但我找不到解决这个问题的好地方。
I want to create an allocator which provides memory with the following attributes:
- cannot be paged to disk.
- is incredibly hard to access through an attached debugger
The idea is that this will contain sensitive information (like licence information) which should be inaccessible to the user. I have done the usual research online and asked a few other people about this, but I cannot find a good place start on this problem.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
您无法真正防止内存访问。 如果您以管理员或系统身份运行,您可能可以阻止分页,但无法阻止管理员或系统读取您的内存。 即使您可以以某种方式完全阻止其他进程读取您的内存(您不能),另一个进程实际上仍然可以将新线程注入您的进程并以这种方式读取内存。
即使您可以以某种方式完全锁定您的进程并保证操作系统永远不会允许任何其他人访问您的进程,您仍然没有得到充分的保护。 整个操作系统可以在虚拟机中运行,可以随时暂停和检查。
您无法保护内存内容免受系统所有者的侵害。 多年来,好莱坞和音乐产业一直在为此苦苦挣扎。 如果可能的话,他们早就这么做了。
You can't really protect against memory access. You can probably prevent paging if you are running as an admin or as the system, but you cannot prevent the admin or system from reading your memory. Even if you could somehow completely block other processes from reading your memory (which you can't), another process could still actually inject a new thread into your process and read the memory that way.
Even if you could somehow completely lock down your process and guarantee that the OS would never allow anyone else to access your process, you still don't have full protection. The entire OS could be running in a virtual machine, which could be paused and inspected at any time.
You cannot protect memory contents from the owner of the system. Hollywood and the music industry have been aching for this for years. If it were possible, they'd already be doing it.
安装 Libsodium,通过 #include
使用分配机制受保护的堆分配
比 malloc() 和朋友慢,它们需要 3 或 4 个额外的虚拟内存页。
使用
sodium_malloc()
和sodium_allocarray()
分配内存来存储敏感数据。 在使用这些堆防护之前,您需要首先调用sodium_init()
。sodium_allocarray()
函数返回一个指针,可以从该指针访问每个内存大小为 size 字节的 count 对象。 它提供与sodium_malloc()
相同的保证,但当count * size
超过SIZE_MAX
时,还可防止算术溢出。这些函数在受保护的数据周围添加了保护页,以使其在类似心脏出血的情况下不太可能被访问。
此外,可以使用锁定内存操作更改以这种方式分配的内存区域的保护:
sodium_mprotect_noaccess()
、sodium_mprotect_readonly()
和sodium_mprotect_readwrite().
在
sodium_malloc
之后,您可以使用sodium_free()
来解锁和释放内存。 此时,在您的实现中,请考虑在使用后将内存归零。使用后将内存归零
使用后,敏感数据应被覆盖,但 memset() 和手写代码可以通过优化编译器或链接器悄悄删除。
odium_memzero() 函数尝试有效地将从 pnt 开始的 len 字节归零,即使正在对代码应用优化也是如此。
锁定内存分配
sodium_mlock()
函数锁定从 addr 开始的至少 len 个字节的内存。 这有助于避免将敏感数据交换到磁盘。odium_mprotect_noaccess()函数使使用sodium_malloc()或sodium_allocarray()分配的区域不可访问。 无法读取或写入,但数据会被保留。 此功能可用于使机密数据无法访问,除非特定操作实际需要。
odium_mprotect_readonly()函数将使用sodium_malloc()或sodium_allocarray()分配的区域标记为只读。 尝试修改数据将导致进程终止。
在使用
进行保护后,
或sodium_mprotect_readwrite()
函数将使用sodium_malloc()
或sodium_allocarray()
分配的区域标记为可读可写>sodium_mprotect_readonly()sodium_mprotect_noaccess()
。install Libsodium, use allocation mechanisms by #including
<sodium.h>
Guarded heap allocations
Slower than malloc() and friends, they require 3 or 4 extra pages of virtual memory.
Allocate memory to store sensitive data using
sodium_malloc()
andsodium_allocarray()
. You'll need to first callsodium_init()
before using these heap guards.The
sodium_allocarray()
function returns a pointer from which count objects that are size bytes of memory each can be accessed. It provides the same guarantees assodium_malloc()
but also protects against arithmetic overflows whencount * size
exceedsSIZE_MAX
.These functions add guard pages around the protected data to make it less likely to be accessible in a heartbleed-like scenario.
In addition, the protection for memory regions allocated that way can be changed using the locking memory operations:
sodium_mprotect_noaccess()
,sodium_mprotect_readonly()
andsodium_mprotect_readwrite()
.After
sodium_malloc
you can usesodium_free()
to unlock and deallocate memory. At this point in your implementation consider zeroing the memory after use.zero the memory after use
After use, sensitive data should be overwritten, but memset() and hand-written code can be silently stripped out by an optimizing compiler or by the linker.
The sodium_memzero() function tries to effectively zero len bytes starting at pnt, even if optimizations are being applied to the code.
locking the memory allocation
The
sodium_mlock()
function locks at least len bytes of memory starting at addr. This can help avoid swapping sensitive data to disk.The sodium_mprotect_noaccess() function makes a region allocated using sodium_malloc() or sodium_allocarray() inaccessible. It cannot be read or written, but the data are preserved. This function can be used to make confidential data inaccessible except when actually needed for a specific operation.
The sodium_mprotect_readonly() function marks a region allocated using sodium_malloc() or sodium_allocarray() as read-only. Attempting to modify the data will cause the process to terminate.
The
sodium_mprotect_readwrite()
function marks a region allocated usingsodium_malloc()
orsodium_allocarray()
as readable and writable, after having been protected usingsodium_mprotect_readonly()
orsodium_mprotect_noaccess()
.让我们一次来看一下:
这很公平。
那会很难。 据我所知,您无法禁用虚拟分页,因为它是由操作系统处理的。 如果有办法,那么您将深入操作系统的内部。
您可以通过 PGP 运行它,并将其加密存储在内存中,并根据需要解密。 巨大的性能打击。
将所有敏感信息远离机器。 严重地。 不要在内存中存储敏感信息。 编写一个自定义删除例程,该例程将自动删除您执行的任何分配中的所有数据。 切勿允许一般人员接触装有敏感材料的机器。 如果您执行数据库访问,请确保在触发之前清理所有访问。 仅允许具有特定登录名的人员访问。 没有一般组访问权限。
转储内存。
Let's take this a bit at a time:
That's fair enough.
That's going to be hard. As far as I am aware, you cannot disable Virtual Paging as it is handled by the OS. If there is a way, then you'll be spelunking in the bowels of the OS.
You could run it through PGP and store it encrypted in memory and unencrypt it as needed. Massive performance hit.
Keep all sensitive information off the machine. Seriously. Don't store sensitive information in memory. Write a custom delete routine that will automatically remove all data from any allocations you perform. Never allow general access to a machine with sensitive material on it. If you perform db access, make sure all access is sanitized before firing. Only people with specific log-ins are allowed to access. No general group access.
Taking a dump of the memory.
最好的选择是实现类似于 .NET 的 SecureString 类的东西,并在完成后非常小心地将数据的任何纯文本副本清零(即使抛出异常也不要忘记进行清理)。 使用 std::string 执行此操作的一个好方法是使用 自定义分配器。
在 Windows 上,如果您使用 CryptProtectMemory(或旧系统的 RtlEncryptMemory),加密密码将存储在不可分页(内核?)内存中。 在我的测试中,这些功能非常快,尤其是。 考虑到他们为您提供的保护。
在其他系统上,我喜欢使用 Blowfish,因为它很好地结合了速度和力量。 在后一种情况下,您必须在程序启动时随机生成自己的密码(Blowfish 为 16+ 字节的熵)。 不幸的是,在没有操作系统支持的情况下,您无法采取很多措施来保护该密码,尽管您可以使用通用混淆技术将硬编码的盐值嵌入到您的可执行文件中,您可以将其与密码结合起来(每一点都有帮助)。
总的来说,这一策略只是更广泛的纵深防御方法的一部分。 另请记住,缓冲区溢出和不清理程序输入等简单错误仍然是迄今为止最常见的攻击媒介。
Your best bet is to implement something similar to .NET's SecureString class, and be very careful to zero out any plaintext copies of your data as soon as you are done (don't forget to cleanup even when exceptions are thrown). A good way to do this with std::string and such is to use a custom allocator.
On Windows, if you use CryptProtectMemory (or RtlEncryptMemory for older systems), the encryption password is stored in non-pageable (kernel?) memory. In my testing, these functions are pretty darn fast, esp. taking into account the protection they are giving you.
On other systems, I like to use Blowfish since it's a good mix between speed and strength. In the latter case, you will have to randomly generate your own password (16+ bytes of entropy for Blowfish) at program startup. Unfortunately, there's not a whole lot you can do to protect that password without OS support, although you might use general obfuscation techniques to embed a hard-coded salt value into your executable that you can combine with the password (every little bit helps).
Overall, this strategy is only one part of a broader defense-in-depth approach. Also keep in mind that simple bugs such as buffer overflows and not sanitizing program input remain by far the most common attack vectors.
您所要求的内容是在操作系统级别处理的。 一旦数据进入您的程序,它就可能被调出。
为了访问内存,有动力的人可以附加硬件调试器。
What you are asking for is handled at the OS level. Once the data is in your program, it is liable to be paged out.
For accessing the memory, a motivated individual can attach a hardware debugger.
在 Unix 系统上,您可以使用 mlock(2) 将内存页面锁定到 RAM 中,以防止它们正在被寻呼。
每个进程可以锁定的内存量是有限制的,可以使用 ulimit -l 显示,并以千字节为单位。 在我的系统上,每个进程的默认限制是 32 kiB。
On Unix systems you can use mlock(2) to lock memory pages into RAM, preventing them being paged.
There is a limit to how much memory each process can lock, it can be shown with
ulimit -l
and is measured in kilobytes. On my system, the default limit is 32 kiB per process.如果您正在为 Windows 进行开发,可以通过多种方法来限制对内存的访问,但完全阻止其他方法是不可行的。 如果您希望保守秘密,请阅读编写安全代码 - 它在一定程度上解决了这个问题,但请注意,您无法知道您的代码是在真实机器还是虚拟机上运行。 有很多 Win32 API 的东西可以处理加密货币,包括秘密的安全存储——这本书谈到了这一点。 您可以查看在线 Microsoft CyproAPI细节; 操作系统设计者认识到这个问题以及保持明文安全的必要性(再次阅读编写安全代码)。
Win32 API 函数
VirtualAlloc
< /a> 是操作系统级内存分配器。 它允许您设置访问保护; 您可以做的就是设置对PAGE_GUARD
或PAGE_NOACCESS
的访问权限,并在程序读取时将访问权限翻转为更友好的内容,然后重新设置它,但这只是一个减速带,如果有人正在非常努力地想窥探你的秘密。总之,看看你平台上的加密 API,它们会比你自己破解的东西更好地解决问题。
If you're developing for Windows, there are ways you can restrict access to memory, but absolutely blocking out others is not doable. If you're hoping to keep a secret secret, read Writing Secure Code - which addresses this problem at some length, but be aware that you have no way of knowing if your code is running on a real machine or a virtual machine. There's a bunch of Win32 API stuff to deal with crypto that handles this kind of thing, including safe storage of secrets - the book talks about that. You can look at the online Microsoft CyproAPI for details; the OS designers recognise this very problem and the need to keep the cleartext secure (again, read Writing Secure Code).
The Win32 API function
VirtualAlloc
is the OS level memory allocator. It allows you to set access protection; what you could do is set access toPAGE_GUARD
orPAGE_NOACCESS
, and flip the access to something friendlier while your program reads, and reset it afterward, but that's merely a speed hump if someone is trying really hard to peek at your secret.In summary, look at the crypto APIs on your platform, they'll address the problem better than something you hack up yourself.