百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

原创技术干货 | 解读Linux安全机制之栈溢出保护

ahcoder 2025-04-27 11:56 9 浏览

0x00 概述

栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary(以下统一使用canary)。

gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all编译参数以支持栈保护功能,4.9新增了-fstack-protector-strong编译参数让保护的范围更广。以下是-fstack-protector和-fstack-protector-strong的区别:

Linux系统中存在着三种类型的栈:

  1. 应用程序栈:工作在Ring3,由应用程序来维护;

  2. 内核进程上下文栈:工作在Ring0,由内核在创建线程的时候创建;

  3. 内核中断上下文栈:工作在Ring0,在内核初始化的时候给每个CPU核心创建一个。

0x01 应用程序栈保护

1. 栈保护工作原理

下面是一个包含栈溢出的例子:

我们先禁用栈保护功能看看执行的结果

当返回地址被覆盖后产生了一个段错误,因为现在的返回地址已经无效了,所以现在执行的是CPU的异常处理流程。我们打开栈保护后再看看结果:

这时触发的就不是段错误了,而是栈保护的处理流程,我们反汇编看看做了哪些事情:

我们看到函数开头(地址:0x40061f)处gcc编译时在栈帧的返回地址和临时变量之间插入了一个canary值,该值是从%fs:0x28里取的,栈帧的布局如下:

在函数即将返回时(地址:0x400655)检查栈中的值是否和原来的相等,如果不相等就调用glibc的__stack_chk_fail函数,并终止进程。

2. canary值的产生

这里以x64平台为例,canary是从%fs:0x28偏移位置获取的,%fs寄存器被glibc定义为存放tls信息的,我们需要查看glibc的源代码:

结构体tcbhead_t就是用来描述tls的也就是%fs寄存器指向的位置,其中+0x28偏移位置的成员变量stack_guard就是canary值。另外通过strace ./test看到在进程加载的过程中会调用arch_prctl系统调用来设置%fs的值:

产生canary值的代码在glibc的_dl_main和__libc_start_main函数中:

_dl_random是一个随机数,它由_dl_sysdep_start函数从内核获取的。_dl_setup_stack_chk_guard函数负责生成canary值,THREAD_SET_STACK_GUARD宏将canary设置到%fs:0x28位置。

在应用程序栈保护中,进程的%fs寄存器是由glibc来管理的,并不涉及到内核提供的功能。

3. x32应用程序栈保护

解读完了x64的实现,我们来看看x32下面的情况,我们还是使用上面例子的代码在x32的机器上编译,得到下面的代码:

在x32下的实现和x64是一样的,只不过canary值保存在%gs:0x14中,glibc使用%gs寄存器来保存TLS信息。

0x02 内核态栈保护

Linux的CC_STACKPROTECTOR补丁提供了对内核栈溢出保护功能,该补丁是Tejun Heo在09年给主线kernel提交的。

  • 2.6.24:首次出现CONFIG_CC_STACKPROTECTOR编译选项并实现了x64平台的进程上下文栈保护支持;

  • 2.6.30:新增对内核中断上下文的栈保护和对x32平台进程上下文的栈保护支持;

  • 3.14:对该功能进行了一次升级以支持gcc的-fstack-protector-strong参数,提供更大范围的栈保护。

1. 栈保护工作原理

我们参照前面的代码写了一个可加载模块并反汇编来看看是怎么样的:

内核函数的栈保护工作原理和应用程序的栈保护是一样的,只不过canary是从内核%gs:0x28位置取的,并且检查失败时调用内核的__stack_chk_fail函数并且产生panic。

2. 中断上下文canary值的产生

x64平台

当内核刚进入64位模式的时候,startup_64函数为内核的初始化工作设置好了%gs寄存器和分配栈空间,代码在arch/x86/kernel/head_64.S中,下面是startup_64函数片段:

其中%gs被定义为percpu变量,可用irq_stack_union联合体表示:

startup_64的末尾会跳转到start_kernel函数,该函数也是canary产生的地方。start_kernel调用了boot_init_stack_canary,它的作用就是产生一个随机的canary并且应用到当前%gs:0x28位置:

start_kernel函数是由boot CPU执行的,在多核心的情况下还会在每个CPU核心初始化的时候分别调用boot_init_stack_canary来产生canary值。我们发现中断上下文的canary值保存在每个核心的idle进程task_struct->stack_canary中,这样当上下文切换的时候就不会丢失了。

x32平台

在x32平台上的percpu区域是保存在%fs中的,内核初始化的时候会为每个cpu核心产生canary值存放在percpu区域的stack_canary成员中备用。

但是gcc要求canary值必须从%gs:0x14偏移的位置获取,因此必须让%gs:0x14指向percpu变量stack_canary.canary才行。首先内核选用GDT第28项描述的数据段来存放canary信息:

接下来只要设置%gs寄存器来索引该段描述符就可以了:

到这里x32平台的%gs:0x14偏移的canary值已经初始化完成了。

3. 进程上下文canary值的产生

无论是内核线程还是用户线程,当一个线程创建的时候,内核给线程生成一个canary值存放在task_struct结构体的stack_canary成员变量中,见dup_task_struct函数:

接下来内核要做的事情就是当发生线程切换的时候让该canary值设置到%gs:0x28偏移处,这个是在switch_to宏中完成的:

上面是x64平台上的代码并且忽略了与canary值切换无关的部分,它把task_struct->stack_canary赋值到percpu变量
irq_stack_union.stack_canary,这样我们代码中找到的canary值就是当前上下文的了。在x32平台上也是类似的只不过percpu变量是stack_canary.canary。

总结:

在gcc、glibc和内核的共同支持下,Linux对所有的可能发生缓冲区溢出的栈返回地址都进行了保护:

  1. 在应用进程上下文,canary值由glibc产生并保存在tcbhead_t中,当canary检查失败时执行glibc的__stack_chk_fail,并终止进程;

  2. 在内核进程上下文,canary值在内核copy_process时产生并保存在task_struct中,当canary检查失败时执行内核的__stack_chk_fail,并产生panic;

  3. 在内核中断上下文,canary值在start_kernel以及每个CPU核心初始化的时候产生并保存在每个CPU核心的idle进程task_struct中,当canary检查失败时执行内核的__stack_chk_fail,并产生panic。

0x03 参考资料

  • http://git.kernel.org/cgit/linux/kernel/git/next/linux-next.git/commit/?id=60a5317ff0f42dd313094b88f809f63041568b08

  • https://lwn.net/Articles/584278/

  • https://lwn.net/Articles/318565/

  • http://blog.aliyun.com/1126

  • https://outflux.net/blog/archives/2014/01/27/fstack-protector-strong/?utm_source=tuicool&utm_medium=referral

  • http://www.ibm.com/developerworks/cn/linux/l-overflow/

  • https://github.com/wishstudio/flinux/wiki/Difference-between-Linux-and-Windows

  • https://xorl.wordpress.com/2010/10/14/linux-glibc-stack-canary-values/

本文由椒图科技原创,转载请注明出处,谢谢!

相关推荐

PC也能装MAX OS X

MACBOOK向来以其时尚的外观以及易用的OSX操作系统成为了时(zhuang)尚(bi)人士的最爱。但是其动不动就上万元的昂贵价格,也将一批立志时(zhuang)尚(bi)人士的拒之门外。但是最近...

一千多元的笔记本能买吗?英特尔11代+大屏幕,豆小谷值得选吗?

前言:有很多粉丝都问过本人,一千多元到底能买到什么样的笔记本?在此笔者只想说,这样的资金预算真的太低了!如果想买全新的,那大概率买的就是性能比较拉垮的上网本,比如搭载英特赛扬N系列、J系列处理器的轻薄...

首款配备骁龙X Elite处理器的Linux笔记本:采用KDE Plasma桌面环境

德国Linux硬件供应商TUXEDOComputers宣布正在开发一款配备高通骁龙XElite处理器(SnapdragonXEliteSoC)的ARM笔记本电脑,内部将该...

System76推出Gazelle Linux笔记本:配酷睿i9-13900H处理器

IT之家3月30日消息,主打Linux硬件的厂商System76于今天发布了新一代Gazelle笔记本电脑,共有15英寸和17英寸两个版本,将于3月30日接受预订,...

Kubuntu Focus Xe Gen 2笔记本发布,预装Linux系统

IT之家3月25日消息,KubuntuFocusXeGen2笔记本于近日发布,这是一款预装Kubuntu22.04LTSGNU/Linux发行版的轻薄本。上一代Kub...

这台Linux笔记本已用上英特尔12代酷睿,最高可选i7-1255U、卖1149美元起

Linux笔记本可能因为比较小众,一般都是拿Windows笔记本换个系统而来,硬件上也会落后同期Windows笔记本一两代,不过现在专门做Linux电脑的System76,推出了一款名为LemurP...

戴尔Inspiron 14 Plus骁龙笔记本迎新补丁,支持启动Linux

IT之家4月25日消息,科技媒体phoronix今天(4月25日)发布博文,报道称最新发布的Linux内核补丁,针对骁龙芯片的戴尔Inspiron14Plus笔记本,让其...

TUXEDO推出InfinityFlex 14二合一Linux笔记本,配i5-1335U

IT之家8月12日消息,Linux硬件企业TUXEDO当地时间本月2日推出了InfinityFlex14二合一Linux笔记本。该笔记本搭载2+8核的英特尔酷睿i5-...

登月探测器嫦娥使用什么操作系统,是Linux还是其它自主研发?

这是不是国家机密啊。事实什么样的不知道,但是从美国的探测器来看,就算不是也是相似的东西。下面我来说说我知道的。龙芯已经随北斗卫星上天了.就算登月探测器嫦娥是用"龙芯+Linux"也不出奇.没必要...

DNS分离解析实验

如果本文对你有帮助,欢迎关注、点赞、收藏、转发给朋友,让我有持续创作的动力目录一、分离解析概述二、实验需求三、实验步骤3.1双网卡服务器配置3.1.1添加两张网卡(内外网)3.1.2对两个网卡进...

一个小实验巩固下进程管理

先回顾下之前的三篇文章:Linux进程在内核眼中是什么样子的?Linux进程线程是如何创建的?Linux是如何调度进程的?通过这三篇文章的学习我们知道,无论内核进程还是用户进程,都是可以用task...

VMware Kali无线WIFI密码破解

WIFI破解前准备工作一张支持Kali系统监听的无线网卡VMware虚拟机安装好Kali系统(本实验用的是Kali2022版本)Kali系统下载、安装官方网站:https://www.kali.or...

python多进程编程

forkwindows中是没有fork函数的,一开始直接在Windows中测试,直接报错importosimporttimeret=os.fork()ifret==0:...

拔电源十台电脑藏后门!德国实验惊曝Windows致命漏洞

2025年4月15日,央视突然曝出一个超级大新闻!原来美国国家安全局通过黑龙江,往微软Windows系统里发送加密信息,激活了系统里藏着的后门程序,想破坏哈尔滨亚冬会!这消息一出来,大家才发现,竟然已...

深度探索RK3568嵌入式教学平台实战案例:设备驱动开发实验

一、产品简介TL3568-PlusTEB人工智能实验箱国产高性能处理器64位4核低功耗2.0GHz超高主频1T超高算力NPU兼容鸿蒙等国产操作系统二、实验目的1、熟悉基本字符设备的驱动程序...