zoukankan      html  css  js  c++  java
  • 你的隐私数据真的安全吗之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)编译一个调试版本的代码,那么这个代码可以很完善的运行,包括(memset())函数的操作,也就是隐私数据用完之后,会得到清除

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

    ...... 
        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
    ......
    

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

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

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

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

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

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

    #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的编译器没有优化掉相关的(memset())函数代码,我们进一步使用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 
    ", 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 
    ", data->m_hash);
    .... 
    

    现在,这里给出使用gcc编译器反汇编出来的代码片段:

    movq (%r12), %rsi
    movl $.LC2, %edi
    xorl %eax, %eax
    call printf
    movq %r12, %rdi
    call free
    

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

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

    movq (%r14), %rsi
    movl $.L.str.1, %edi
    xorl %eax, %eax
    callq printf
    movq %r14, %rdi
    callq free
    

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

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

    #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 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中,可以使用(RtlSecureZeroMemory)函数。从C++11标准开始,我们可以使用(memset\_s)函数。此外,我们也可以实现我们自己需要的安全版本的内存清楚函数,示例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;
    }
    
  • 相关阅读:
    在Android中如何获取视频的第一帧图片并显示在一个ImageView中
    利用MsChart控件绘制多曲线图表 z
    国外成熟的程序交易系统的思路 z
    稳健获利
    用vmware安装gho文件
    数学之美 zt
    大型邮箱smtp服务器及端口 收集
    英语之路 zt
    C# Get Desktop Screenshot ZZ
    C#/PHP Compatible Encryption (AES256) ZZ
  • 原文地址:https://www.cnblogs.com/zjz-819823900/p/14338462.html
Copyright © 2011-2022 走看看