我们在实际编程中,需要保存许多私有数据,例如:密码、密钥等等。所以,我们需要经常在使用完这些私有数据后,清除内存使用踪迹,以防止被潜在的入侵者获得这些数据。这篇文章中,我们讨论使用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 \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

可见,\(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;
}

你的隐私数据真的安全吗之memset()使用分析的更多相关文章

  1. 从ofo牵手理财平台看,用户隐私数据的使用有底线吗?

    智慧生活的到来既是社会变迁的拐点,又不可避免地带来一种挥之不去的焦虑.这种焦虑的由来,是因个人隐私数据在智慧生活下变成一种"黑暗财富".随着相关数据挖掘.收集.分析技术的成熟,人们 ...

  2. Springboot 配置文件、隐私数据脱敏的最佳实践(原理+源码)

    大家好!我是小富- 这几天公司在排查内部数据账号泄漏,原因是发现某些实习生小可爱居然连带着账号.密码将源码私传到GitHub上,导致核心数据外漏,孩子还是没挨过社会毒打,这种事的后果可大可小. 说起这 ...

  3. iOS10-配置获取隐私数据权限声明

    iOS10中,苹果加强了对用户隐私数据的保护,在访问以下数据的时候都需要在info.list重配置privacy,进行声明,否则程序无法正常运行. Contacts, Calendar, Remind ...

  4. Fabric v2.0中的隐私数据

    文章来源于https://hyperledger-fabric.readthedocs.io/en/release-2.0/ 私有数据集在v1.4中提出,一直使用的是隐私数据集方式,即建立一个隐私数据 ...

  5. Hadoop基础-HDFS数据清理过程之校验过程代码分析

    Hadoop基础-HDFS数据清理过程之校验过程代码分析 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 想称为一名高级大数据开发工程师,不但需要了解hadoop内部的运行机制,还需 ...

  6. Wireshark数据抓包教程之认识捕获分析数据包

    Wireshark数据抓包教程之认识捕获分析数据包 认识Wireshark捕获数据包 当我们对Wireshark主窗口各部分作用了解了,学会捕获数据了,接下来就该去认识这些捕获的数据包了.Wiresh ...

  7. 65、Spark Streaming:数据接收原理剖析与源码分析

    一.数据接收原理 二.源码分析 入口包org.apache.spark.streaming.receiver下ReceiverSupervisorImpl类的onStart()方法 ### overr ...

  8. 兼容iOS 10:配置获取隐私数据权限声明

    原文链接 iOS 10的一大变化是更强的隐私数据保护.在文档中是这么描述的: You must statically declare your app’s intended use of protec ...

  9. iOS 用户的隐私数据-privacy-sensitive data

    1  Xcode 报错:This app has crashed because it attempted to access privacy-sensitive data without a usa ...

随机推荐

  1. [NOIP2013 提高组] 货车运输

    前言 使用算法:堆优化 \(prim\) , \(LCA\) . 题意 共有 \(n\) 个点,有 \(m\) 条边来连接这些点,每条边有权值.有 \(q\) 条类似于 \(u\) \(v\) 询问, ...

  2. 手摸手带你用Hexo撸博客(二)之配置主题

    在上一篇博客手摸手带你用Hexo撸博客(一)中主要介绍了博客的初步搭建 今天我们继续讲如何在Hexo搭建的博客中应用主题 官网选择自己喜欢的主题 点击这里Hexo主题进入官网主题页面 然后选择自己喜欢 ...

  3. 2020年Python文章盘点,我选出了个人TOP10

    大家好,我是猫哥.2020年过得真快啊!总感觉这一年里还没有做成多少事,一眨眼就又到了写年度总结的时候了-- 去年1月1日的时候,我写了<我的 2019 年 Python 文章榜单>,简单 ...

  4. Docker本地镜像仓库搭建Nginx+BusyBox为例

    下载Busybox.Nginx镜像 docker pull busybox docker pull nginx 基于Busybox镜像创建容器,并在容器中做部分变更操作,生成新镜像 添加一些内容 正在 ...

  5. ASP.NET Core路由中间件[3]: 终结点(Endpoint)

    到目前为止,ASP.NET Core提供了两种不同的路由解决方案.传统的路由系统以IRouter对象为核心,我们姑且将其称为IRouter路由.本章介绍的是最早发布于ASP.NET Core 2.2中 ...

  6. mysql的binlog+maxwell+kakka

    1.业务库痛点及解决⽅案 初期出⾏业务的订单相关,是以mysql作为业务库为基准的,但是随着业务线增多,每⽇新增数据指 数上涨,⼏乎在每天的⾼峰期期间,都会出现业务库所在服务器的cpu.IO.内存等跑 ...

  7. (解决)easypoi图片导出只占用一个单元格

    @ 目录 前提 依赖环境 问题原因 解决方案 重写jar中的方法 原理 前提 本解决方案来源于网络,因解决自己需求,因此自行记录起来,如有侵权请联系我. 依赖环境 easypoi--依赖版本3.1.0 ...

  8. Vue.nextTick()的使用

    什么是Vue.nextTick()?? 定义:在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 所以就衍生出了这个获取更新后的DOM的Vue方法.所 ...

  9. LeetCode94 二叉树的中序遍历

    给定一个二叉树,返回它的中序 遍历. 示例: 输入: [1,null,2,3] 1 \ 2 / 3 输出: [1,3,2] 进阶: 递归算法很简单,你可以通过迭代算法完成吗?       /** * ...

  10. JVM 源码分析(四):深入理解 park / unpark

    前言 Parker 源码调试与分析 park/unpark 原理总结 补充:jstack 命令和 kill 命令 前言 熟悉 Java 并发包的人一定对 LockSupport 的 park/unpa ...