阿里云网站建设 部署与发布答案,上海的软件外包公司,免费合同模板网站,wordpress jp theme目录
C语言内存管理及栈攻击
内存管理
Linux虚拟内存空间分布#xff08;重要#xff09;
栈溢出#xff08;栈攻击#xff09;
堆栈的特点
栈攻击
栈攻击的实现
原理
编译器选项
实现案例
linux修改栈空间大小方式
内存泄漏
如何避免野指针#xff1f;
如何…目录
C语言内存管理及栈攻击
内存管理
Linux虚拟内存空间分布重要
栈溢出栈攻击
堆栈的特点
栈攻击
栈攻击的实现
原理
编译器选项
实现案例
linux修改栈空间大小方式
内存泄漏
如何避免野指针
如何杜绝忘记释放内存(分析诊断工具valgrind)
内存泄漏总结 C语言内存管理及栈攻击
内存管理
Linux虚拟内存空间分布重要
1bss段存放初始化的变量
1.未初始化的全局变量和静态局部变量
初始值为0的全局变量和静态局部变量(依赖于编译器实现)
2.data数据段存放初始化的变量
数据段通常用于存放程序中已初始化且初值不为0的全局变量、静态全局变量和静态局部变量。数据段属于静态内存分配(静态存储区)可读可写。其中有一个.rodata段一般用于存放常量字符串和只读变量。
3.text(代码段)
可执行文件加载到内存中的只有数据和指令之分而指令被存放在.text段中一般是共享的编译时确定,只读不允许修改
4.heap(堆)
用于存放进程运行时动态分配的内存可动态扩张或缩减这块内存由程序员自己管理通过malloc/new可以申请内存free/delete用来释放内存heap的地址从低向高扩展是不连续的空间
5.stack(栈)
记录函数调用过程相关的维护性信息栈的地址一般从高地址向低地址扩展是连续的内存区域
6共享库(libc.so)
静态链接库和动态链接库的区别
不同操作系统下后缀不一样 windows linux 静态库 .lib .a 动态/共享库 .dll .so
加载方法的时间点不同
*.a 在程序生成链接的时候已经包含(拷贝)进来了
*.so 程序在运行的时候才加载使用
静态库把包含调用函数的库是一次性全部加载进去的动态库是在运行的时候把用到的函数的定义加载进去所以包含静态库的程序所以用静态库编译的文件比较大如果静态库改变了程序得重新编译相反的动态库编译的可执行文件较小但.so改变了不影响程序动态库的开发很方便程序对静态库没有依赖性对动态库有依赖性。 栈溢出栈攻击
堆栈的特点
栈增长方向现在的内核高地址-低地址函数调用栈的示意图 栈攻击
在旧版本的内核栈空间是向下增长的由于栈空间后面紧挨着内核空间所有一旦栈溢出就会对内核空间进行非法访问这样就是所谓的栈攻击黑客常用攻击手段 因此Linux内核在2.0升级维护的时候在系统内核和栈之间增加了一个保护区大概2M。作用是一旦栈溢出到了保护区系统就自动报错栈溢出错误
还要一种做法就是改变栈的增长方向改为向下增长Linux内核5.0的做法。 栈攻击的实现
原理
如果忘了压栈的具体过程可参考
C/C函数调用的压栈模型 当函数从入口函数main函数开始执行时编译器会将我们操作系统的运行状态main函数的返回地址、main的参数、main函数中的变量、进行依次压栈;当main函数开始调用func1()函数时编译器此时会将main函数的运行状态进行压栈再将func1()函数的返回地址、func1函数的参数、func1定义变量依次压栈;当func1调用func2的时候编译器此时会将func2函数的运行状态进行压栈再将func2函数的返回地址、func2函数的参数、func2定义变量依次压栈。
以向上增长的栈为例
我们可以很容易的想到一种攻击方式比如我们想要获取func2的使用权我们可以在func1中使用一个数组通过增加数组的大小让它能够触及的地址一直增加最终就能够越界获取到func2的入口地址这样就能够获取到func2的使用权限
编译器选项
gcc C文件名 -z execstack -fno-stack-protector
-z execstack开启堆栈可执行机制
-fno-stack-protector关闭堆栈保护机制
实现案例
这个实例的实现过程其实还是不太明白已解决见后面
如图所示我们定义了两个函数func1中定义了一个数组通过赋值传入func2的函数名即函数的入口地址给数组成员注意这里传入的不是func2()不是调用func2函数在func2中打印了一句话。最终我们在main函数中调用func1函数 通过逐渐增加数组a[3]func2中数组成员的下标最终在a[6]的时候成功获取使用权 但通过在func1最后加了个while(1)发现func1退出不了func2就不会被调用所以最后得出结论应该是func1的函数返回地址刚好是func2的入口地址 该问题已被我发布在CSDN可以参考如下没有被调用的函数其代码为什么会被执行_xyz-x的博客-CSDN博客
2023年9月3日该问题已解决 结合GPT的回答可以总结出实现的原理是
函数的定义通常存放在代码段中而不是栈中。在程序运行时代码段是用来存储程序的指令的内存区域它通常是只读的。函数的定义在编译时就确定了并且存放在代码段中以便在程序执行过程中被调用和执行。栈则是用于存放局部变量、函数参数和临时数据等的内存区域它在函数调用时动态分配和释放具有先进后出的特性。
所以func1和func2的入口地址和返回地址在编译时已经确定好了在代码段中。
func1函数中的数组越界操作a[6] func2。因为在func1函数的栈帧上局部变量a的下标为 6 的位置处超过了a的实际长度大小为 4所以会覆盖到func1函数的返回地址。
因此func2函数的入口地址被写入到了func1函数的返回地址位置上。当func1函数执行完毕并返回时CPU会根据返回地址跳转到该地址对应的代码从而执行了func2函数。
linux修改栈空间大小方式 由于线程使用的线程函数因此栈空间越大就能支持越多的线程常用于网络编程中一个服务类连接更多的客户端
对于堆空间的大小一般是在创建系统时候决定堆空间一旦被分配完如通过malloc或者new的方式再次申请空间就会一直失败。
内存泄漏
内存泄漏有三种情况越界访问、野指针和内存忘记释放
如何避免野指针
明确指针的指向
如何杜绝忘记释放内存(分析诊断工具valgrind)
人无完人我们人是不可能完全记得释放自己开辟的内存的尤其在进行大型项目开发时往往会产生疏忽因此可以通过内存检测分析工具在写完程序之后进行诊断分析
推荐使用内存分析诊断工具valgrind
安装和使用可参考linux代码检测工具valgrind之内存检测memcheck_linux代码检查工具_夜雨听萧瑟的博客-CSDN博客
使用时在编译完程序使用命令valgrind --toolmemcheck --leak-checkfull ./test即可
内存泄漏总结
C语言没有更好的方法杜绝malloc的导致的内存泄漏