前言:
此时各位老铁们对“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