在实际编程中,我们需要保存大量的隐私数据,比如密码、密钥等。因此,我们需要在使用这些私有数据后经常清除内存使用痕迹,以防止潜在的入侵者或这些数据。在本文中,我们将讨论使用 memset() 函数清除私有数据时可能出现的一系列问题。
1.栈上分配的隐私数据
首先,我们给出一个代码片段示例,说明如何处理分配在堆栈上的变量:
#include
#include
#include
// 隐私数据类型
struct PrivateData
{
size_t m_hash;
char m_pswd[100];
};
// 操作在password上的函数
void doSmth(PrivateData& data)
{
std::string s(data.m_pswd);
std::hash hash_fn;
data.m_hash = hash_fn(s);
}
// 输入和处理password的函数
int funcPswd()
{
PrivateData data;
std::cin >> data.m_pswd;
doSmth(data);
memset(&data, 0, sizeof(PrivateData));
return 1;
}
int main()
{
funcPswd();
return 0;
}
上面的例子是一个完全错误的例子。如果我们使用编译器(这里Visual Studio 2015)编译一个debug版本的代码,那么代码可以完美运行,包括函数的操作,也就是私有数据用完后,它将被清除。
但是我们直接编译成运行版本,反编译结果如下:
......
doSmth(data);
000000013f3072BF lea rcx, [data]
000000013F3072C3 call doSmth (013F30153Ch)
memset(&data, 0, sizeof(PrivateData));
000000013F3072C8 mov r8d, 70h
000000013F3072CE xor edx, edx
000000013F3072D0 lea rcx, [data]
000000013F3072D4 call memset (013F301352h)
return 1;
000000013F3072D9 mov eax, 1
......
上面的反编译代码显示,当我们调用该函数时,使用私有数据后这个被清除了。
我们进一步编译一个优化版的发布代码,反编译如下:
......
000000013F7A1035 call
std::operator>><> > (013F7A18B0h)
000000013F7A103A lea rcx,[rsp+20h]
000000013F7A103F call doSmth (013F7A1170h)
return 0;
000000013F7A1044 xor eax,eax
......
如您所见,所有与函数相关的代码都已被删除。从编译器优化的角度来看,编译器不需要清除不再使用的数据,这对编译器来说是合法的。从语言的角度来看,函数中使用的私有数据不会被其他函数调用,所以不清除就不会影响程序的运行。但是,从安全的角度来看,我们的私人数据不被擦除是非常危险的。
2.堆上分配的隐私数据
现在,让我们更进一步,假设我们使用函数或运算符在堆上分配私有数据,下面是使用该函数的代码:
#include
#include
#include
// 隐私数据类型
struct PrivateData
{
size_t m_hash;
char m_pswd[100];
};
// 操作在password上的函数
void doSmth(PrivateData& data)
{
std::string s(data.m_pswd);
std::hash hash_fn;
data.m_hash = hash_fn(s);
}
// 输入和处理password的函数
int funcPswd()
{
PrivateData data = (PrivateData*)malloc(size0f(PrivateData));
std::cin >> data.m_pswd;
doSmth(data);
memset(&data, 0, sizeof(PrivateData));
free(data);
return 1;
}
int main()
{
funcPswd();
return 0;
}
对于上面的代码,我们使用Visual Studio 2015编译一个release版本,然后反编译结果如下:
......
000000013FBB1021 mov rcx,
qword ptr [__imp_std::cin (013FBB30D8h)]
000000013FBB1028 mov rbx,rax
000000013FBB102B lea rdx,[rax+8]
000000013FBB102F call
std::operator>><> > (013FBB18B0h)
000000013FBB1034 mov rcx,rbx
000000013FBB1037 call doSmth (013FBB1170h)
000000013FBB103C xor edx,edx
000000013FBB103E mov rcx,rbx
000000013FBB1041 lea r8d,[rdx+70h]
000000013FBB1045 call memset (013FBB2A2Eh)
000000013FBB104A mov rcx,rbx
000000013FBB104D call qword ptr [__imp_free (013FBB3170h)]
return 0;
000000013FBB1053 xor eax,eax
......
可以看出Visual Studio编译器并没有优化相关的功能代码。我们进一步使用5.2.1 版本的gcc 和3.7.0 版本的clang 编译并查看结果。
这里需要提一下,我们在gcc和clang版本的代码中增加了一些额外的代码,即通过读取cleared指针来读取被清除的私有数据地址,虽然这个操作是在实际编程中。不合理,不过这里为了展示方便,代码如下:
....
#include "string.h"
....
size_t len = strlen(data->m_pswd);
for (int i = 0; i < len;="" ++i)="" printf("%c",="" data-="">m_pswd[i]);
printf("| %zu \n", data->m_hash);
memset(data, 0, sizeof(PrivateData));
free(data);
for (int i = 0; i < len;="" ++i)="" printf("%c",="" data-="">m_pswd[i]);
printf("| %zu \n", data->m_hash);
....
现在,这里是使用 gcc 编译器反汇编的代码片段:
movq (%r12), %rsi
movl $.LC2, %edi
xorl %eax, %eax
call printf
movq %r12, %rdi
call free
可以看出,函数直接跟在函数后面,直接优化函数。这时,如果我们运行恶意代码,读取私有数据地址上的信息,我们仍然可以读取到相关数据。
现在让我们看一下clang编译器:
movq (%r14), %rsi
movl $.L.str.1, %edi
xorl %eax, %eax
callq printf
movq %r14, %rdi
callq free
同样,函数直接优化掉,也会导致隐私数据泄露。
通过以上一系列实验可以看出,无论是栈上的数据还是堆上的数据,函数都被直接优化了。最后,我们进一步探讨使用操作的情况,调整代码如下:
用Visual Studio编译后的反编译代码如下:
000000013FEB1044 call doSmth (013FEB1180h)
000000013FEB1049 xor edx,edx
000000013FEB104B mov rcx,rbx
000000013FEB104E lea r8d,[rdx+70h]
000000013FEB1052 call memset (013FEB2A3Eh)
000000013FEB1057 mov edx,70h
000000013FEB105C mov rcx,rbx
000000013FEB105F call operator delete (013FEB1BA8h)
return 0;
000000013FEB1064 xor eax,eax
用gcc编译的反编译代码如下:
call printf
movq %r13, %rdi
movq %rbp, %rcx
xorl %eax, %eax
andq $-8, %rdi
movq $0, 0(%rbp)
movq $0, 104(%rbp)
subq %rdi, %rcx
addl $112, %ecx
shrl $3, %ecx
rep stosq
movq %rbp, %rdi
call _ZdlPv
上面Visual Studio和gcc编译的代码显示之前的私有数据已经被清除,最后使用clang编译如下:
movq (%r14), %rsi
movl $.L.str.1, %edi
xorl %eax, %eax
callq printf
movq %r14, %rdi
callq _ZdlPv
可以看出clang对我们的代码进行了优化,私有数据依然存在。那么有密码的模块会被反编译吗,我们如何才能更好地清除我们的私人数据以确保我们的安全呢?
我们应该使用特殊的内存清理函数,指定编译器不会删除这些函数。例如在 Visual Studio 中有密码的模块会被反编译吗,可以使用函数。由于 C++11 标准,我们可以使用函数。另外,我们还可以实现我们需要的内存清除功能的安全版。示例1代码如下:
errno_t memset_s(void *v, rsize_t smax, int c, rsize_t n) {
if (v == NULL) return EINVAL;
if (smax > RSIZE_MAX) return EINVAL;
if (n > smax) return EINVAL;
volatile unsigned char *p = v;
while (smax-- && n--) {
*p++ = c;
}
return 0;
}
示例 2 代码:
void secure_zero(void *s, size_t n)
{
volatile char *p = s;
while (n--) *p++ = 0;
}
请登录后发表评论
注册
社交帐号登录