龙空技术网

你的隐私数据真的安全吗?之memset()使用分析

文而科技匡吉 29

前言:

此时各位老铁们对“c语言memset”大约比较珍视,咱们都需要剖析一些“c语言memset”的相关知识。那么小编同时在网络上收集了一些对于“c语言memset””的相关资讯,希望小伙伴们能喜欢,各位老铁们一起来学习一下吧!

我们在实际编程中,需要保存许多私有数据,例如:密码、密钥等等。所以,我们需要经常在使用完这些私有数据后,清除内存使用踪迹,以防止被潜在的入侵者或者这些数据。这篇文章中,我们讨论使用memset()函数来清除私有数据时,可能发生的一系列问题。

1.在stack上分配的隐私数据

首先,我们给出一个代码片段示例,关于如何处理栈上分配变量的处理:

#include <string>#include <functional>#include <oistream>// 隐私数据类型struct PrivateData{  size_t m_hash;  char m_pswd[100];};// 操作在password上的函数void doSmth(PrivateData& data){  std::string s(data.m_pswd);  std::hash<std::string> 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)编译一个调试版本的代码,那么这个代码可以很完善地运行良好,包括函数的操作,也就是隐私数据用完之后,会得到清除。

但是让我们直接编译成一个运行版本,并且反编译出来的结果如下:

......     doSmth(data);000000013f3072BF   lea            rcx, [data]000000013F3072C3   call           doSmth  (013F30153Ch)  memset(&data, 0, sizeof(PrivateData));000000013F3072C8   mov            r8d, 70h000000013F3072CE   xor            edx, edx000000013F3072D0   lea            rcx, [data]000000013F3072D4   call           memset  (013F301352h)  return 1;000000013F3072D9   mov            eax, 1......

上面反编译代码可见当我们调用函数时,这就在使用隐私数据后清除。

我们进一步编译一个优化版本的发布代码,并且反编译之后,如下:

......000000013F7A1035  call        std::operator>><><char> > (013F7A18B0h)  000000013F7A103A  lea         rcx,[rsp+20h]  000000013F7A103F  call        doSmth (013F7A1170h)      return 0;000000013F7A1044  xor         eax,eax  ......

可知,所有关系到函数的代码都被删除了。从编译器优化角度来看,不再使用的数据没必要被清除掉,这对于编译器来说是合法的。从语言角度来看,函数内使用后的隐私数据不会被其他函数调用,所以不用进行清除也不会影响程序的操作。但是,从安全角度来看,我们的隐私数据没有被清除是非常危险的。

2.在heap上分配的隐私数据

现在,让我们进一步研究,假设我们在堆上使用函数或者操作符分配隐私数据,下面是使用函数的代码:

#include <string>#include <functional>#include <iostream>// 隐私数据类型struct PrivateData{  size_t m_hash;  char m_pswd[100];};// 操作在password上的函数void doSmth(PrivateData& data){  std::string s(data.m_pswd);  std::hash<std::string> 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编译一个发行版本,然后反编译出结果如下:

......000000013FBB1021  mov         rcx,        qword ptr [__imp_std::cin (013FBB30D8h)]  000000013FBB1028  mov         rbx,rax  000000013FBB102B  lea         rdx,[rax+8]  000000013FBB102F  call        std::operator>><><char> > (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版本的代码中添加了一些额外的代码,也就是读取被清除后的隐私数据地址上,通过读取被清除的指针,虽然这样的操作在实际编程中是不合理的,但是我们这边为了方便展示,代码如下:

....#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), %rsimovl $.LC2, %edixorl %eax, %eaxcall printfmovq %r12, %rdicall free

可见,函数后面直接跟着函数,函数直接被优化掉。这时,如果我们运行恶意代码,读取隐私数据地址上的信息,依然可以读取到相关数据。

现在让我们查看clang编译器:

movq (%r14), %rsimovl $.L.str.1, %edixorl %eax, %eaxcallq printfmovq %r14, %rdicallq free

同样,函数直接被优化掉,这样也会导致隐私数据泄露。

通过上述的一系列实验可知,函数直接被优化掉,不论是栈上数据还是堆上数据。最后,我们进一步探讨使用操作的情况,调整代码如下:

#include <string>#include <functional>#include <iostream>#include "string.h"struct PrivateData{  size_t m_hash;  char m_pswd[100];};void doSmth(PrivateData& data){  std::string s(data.m_pswd);  std::hash<std::string> hash_fn;  data.m_hash = hash_fn(s);}int funcPswd(){  PrivateData* data = new PrivateData();  std::cin >> data->m_pswd;  doSmth(*data);  memset(data, 0, sizeof(PrivateData));  delete data;  return 1;}int main(){  funcPswd();  return 0;}

使用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 printfmovq %r13, %rdimovq %rbp, %rcxxorl %eax, %eaxandq $-8, %rdimovq $0, 0(%rbp)movq $0, 104(%rbp)subq %rdi, %rcxaddl $112, %ecxshrl $3, %ecxrep stosqmovq %rbp, %rdicall _ZdlPv

上面,Visual Studio和gcc编译后的代码显示,之前的隐私数据都得到了清除,最后使用clang编译如下:

movq (%r14), %rsimovl $.L.str.1, %edixorl %eax, %eaxcallq printfmovq %r14, %rdicallq _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;}

标签: #c语言memset