gpt4 book ai didi

c++ - 安全清除内存并重新分配

转载 作者:IT老高 更新时间:2023-10-28 21:34:10 24 4
gpt4 key购买 nike

在讨论后here ,如果你想有一个安全的类来在内存中存储敏感信息(例如密码),你必须:

  • memset/clear the memory before free it
  • 重新分配也必须遵循相同的规则 - 不要使用 realloc,而是使用 malloc 创建一个新的内存区域,将旧内存复制到新内存,然后 memset/clear 旧内存,然后最终释放它

所以这听起来不错,我创建了一个测试类来看看它是否有效。所以我做了一个简单的测试用例,我不断添加单词“LOL”和“WUT”,然后在这个安全缓冲区类中添加一个数字大约一千次,销毁该对象,然后最终执行导致核心转储的操作。

由于该类应该在销毁之前安全地清除内存,因此我不应该能够在 coredump 上找到“LOLWUT”。但是,我还是设法找到了它们,并想知道我的实现是否只是错误。但是,我使用 CryptoPP 库的 SecByteBlock 尝试了同样的事情:

#include <cryptopp/osrng.h>
#include <cryptopp/dh.h>
#include <cryptopp/sha.h>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <cryptopp/filters.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
using namespace std;

int main(){
{
CryptoPP::SecByteBlock moo;

int i;
for(i = 0; i < 234; i++){
moo += (CryptoPP::SecByteBlock((byte*)"LOL", 3));
moo += (CryptoPP::SecByteBlock((byte*)"WUT", 3));

char buffer[33];
sprintf(buffer, "%d", i);
string thenumber (buffer);

moo += (CryptoPP::SecByteBlock((byte*)thenumber.c_str(), thenumber.size()));
}

moo.CleanNew(0);

}

sleep(1);

*((int*)NULL) = 1;

return 0;
}

然后编译使用:

g++ clearer.cpp -lcryptopp -O0

然后启用核心转储

ulimit -c 99999999

然后,启用核心转储并运行它

./a.out ; grep LOLWUT core ; echo hello

给出以下输出

Segmentation fault (core dumped)
Binary file core matches
hello

这是什么原因造成的?由于 SecByteBlock 的 append 引起的重新分配,应用程序的整个内存区域是否重新分配?

另外,This is SecByteBlock's Documentation

edit:使用 vim 检查核心转储后,我得到了这个: http://imgur.com/owkaw

edit2:更新了代码,使其更易于编译,以及编译说明

final edit3:看起来 memcpy 是罪魁祸首。请参阅 Rasmus 在下面的答案中的 mymemcpy 实现。

最佳答案

尽管出现在核心转储中,但密码实际上并不在内存中清除缓冲区后不再。问题是 memcpying足够长的字符串会将密码泄漏到 SSE 寄存器中,那些是核心转储中显示的内容。

memcpysize 参数大于某个阈值— 80 bytes on the mac — 然后使用 SSE 指令执行内存复制。这些指令更快,因为它们可以复制 16一次并行处理字节,而不是逐个字符,一个字节一个字节,或者一个字一个字。这是源代码的关键部分 Libc on the mac :

LAlignedLoop:               // loop over 64-byte chunks
movdqa (%rsi,%rcx),%xmm0
movdqa 16(%rsi,%rcx),%xmm1
movdqa 32(%rsi,%rcx),%xmm2
movdqa 48(%rsi,%rcx),%xmm3

movdqa %xmm0,(%rdi,%rcx)
movdqa %xmm1,16(%rdi,%rcx)
movdqa %xmm2,32(%rdi,%rcx)
movdqa %xmm3,48(%rdi,%rcx)

addq $64,%rcx
jnz LAlignedLoop

jmp LShort // copy remaining 0..63 bytes and done

%rcx 是循环索引寄存器,%rsis源地址寄存器,%rdidestination 地址寄存器。每次绕圈跑,64 字节从源缓冲区复制到 4 个 16 字节 SSE 寄存器xmm{0,1,2,3};然后将这些寄存器中的值复制到目标缓冲区。

该源文件中还有很多东西可以确保出现拷贝仅在对齐的地址上,以填写剩余的拷贝部分在完成 64 字节 block 之后,并处理源和目的地重叠。

但是——SSE 寄存器在使用后不会被清除!这意味着 64 字节xmm{0,1,2,3} 寄存器中仍然存在被复制的缓冲区。

这是对 Rasmus 程序的修改,显示了这一点:

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <emmintrin.h>

inline void SecureWipeBuffer(char* buf, size_t n){
volatile char* p = buf;
asm volatile("rep stosb" : "+c"(n), "+D"(p) : "a"(0) : "memory");
}

int main(){
const size_t size1 = 200;
const size_t size2 = 400;

char* b = new char[size1];
for(int j=0;j<size1-10;j+=10){
memcpy(b+j, "LOL", 3);
memcpy(b+j+3, "WUT", 3);
sprintf((char*) (b+j+6), "%d", j);
}
char* nb = new char[size2];
memcpy(nb, b, size1);
SecureWipeBuffer(b,size1);
SecureWipeBuffer(nb,size2);

/* Password is now in SSE registers used by memcpy() */
union {
__m128i a[4];
char c;
};
asm ("MOVDQA %%xmm0, %0": "=x"(a[0]));
asm ("MOVDQA %%xmm1, %0": "=x"(a[1]));
asm ("MOVDQA %%xmm2, %0": "=x"(a[2]));
asm ("MOVDQA %%xmm3, %0": "=x"(a[3]));
for (int i = 0; i < 64; i++) {
char p = *(&c + i);
if (isprint(p)) {
putchar(p);
} else {
printf("\\%x", p);
}
}
putchar('\n');

return 0;
}

在我的 Mac 上,打印出来的是:

0\0LOLWUT130\0LOLWUT140\0LOLWUT150\0LOLWUT160\0LOLWUT170\0LOLWUT180\0\0\0

现在,检查核心转储,密码只出现一次,并作为确切的 0\0LOLWUT130\0...180\0\0\0 字符串。核心转储必须包含所有寄存器的拷贝,这就是该字符串存在的原因——它是xmm{0,1,2,4} 寄存器的值。

所以 调用后密码实际上不再在 RAM 中了SecureWipeBuffer,它只是看起来是,因为它实际上是在一些仅出现在核心转储中的寄存器。如果你担心memcpy 存在可被 RAM 卡住利用的漏洞,不用担心了。如果在寄存器中保存密码拷贝让您感到困扰,使用不使用 SSE2 寄存器的修改后的 memcpy,或清除它们完成后。如果您真的对此感到偏执,请继续测试您的coredumps 以确保编译器不会优化您的密码清除代码。

关于c++ - 安全清除内存并重新分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10683941/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com