从NX到SELinux:Linux PWN防护机制与绕过思路全解析

发布时间:2026/10/17 8:15:05

从NX到SELinux:Linux PWN防护机制与绕过思路全解析 1. PWN的起点先搞懂Linux在防什么入坑PWN的朋友第一课几乎都是同一个打靶场拿shell结果发现拿不到。不是你的exp写得不对是Linux内核和编译器在中间拦了一道又一道。本地调试能打通的exploit拖到远程就连不上多半不是网络问题而是你没有理解和尊重这堆安全机制。Linux安全防护机制说白了就是系统为了不让攻击者利用内存破坏类漏洞缓冲区溢出、use-after-free、格式化字符串等控制程序流程、读写任意内存而设置的一整套规则。这些机制不是某个单一模块也不是默认全开而是由内核特性、编译器选项、链接器参数、运行时加载器行为共同构成的一整套攻防体系。PWN题之所以越来越难本质上不是漏洞变复杂了而是这些防御机制在不断抬高利用的门槛。很多新手最容易犯的错是把这些机制当成可有可无的加固选项只盯着漏洞本身去构造exp。但实际流程正好相反拿到一个二进制文件第一步永远是查保护查完保护再决定漏洞怎么打。因为不同的防护组合直接决定了攻击路径——是栈溢出还是堆溢出能不能直接跳shellcode地址能不能泄漏GOT能不能改只有把这些问题先想清楚写的exploit才能真正落地。这篇文章我把Linux在PWN视角下最重要的几道防线逐一拆开讲清楚NX、ASLR、PIE、Stack Canary、RELRO、FORTIFY以及SELinux/AppArmor这类内核级访问控制。每一节都会说清楚三个问题它防什么、它怎么防、它对利用路径造成了什么影响。最后会附上实操时的检查方法与绕过思路边界帮你在做题目和实际项目评估时建立一套快速判断攻防代价的思维框架。2. 从编译器到内核几道防线的作用位置2.1 NX让数据段不再拥有执行权限NXNo-eXecute机制在Windows下叫DEP数据执行保护在Linux下对应的内核特性是NX或XNARM上叫XN意为Execute-Never。它做的事情非常直接把栈、堆、数据段等内存区域标记为不可执行。程序可以被执行的地方只有代码段.text和显式标记为可执行的映射区域。这意味着经典的往栈里塞shellcode然后跳过去执行这条路被彻底堵死了。很多初学者第一反应是那我用mmap申请一块内存在里面写shellcode不行吗——行但前提是这块内存映射时带了PROT_EXEC权限而且在真实环境里能让你随手映射一块可执行内存还往里面写内容的场景几乎不存在。PWN题目里即使在解析shellcode的这类场景也通常会有更强的沙箱或校验机制兜底。在绕过视角下NX被开启后通常的思路是转向代码复用ROPReturn-Oriented Programming或JOPJump-Oriented Programming。核心是利用程序中已有的gadget——那些以ret结尾的短指令序列——将它们串成一条攻击链。这里要注意一个常见误区即使开了NX如果程序自身把栈段标记为可执行比如某些老旧的JIT解释器NX就失效了。所以拿到题目第一件事不是看有没有开NX而是实际看readelf -l输出的GNU_STACK段标志RWE才是真可执行RW就是NX已生效。2.2 ASLR地址随机化的攻防量级ASLRAddress Space Layout Randomization地址空间布局随机化由内核在加载程序时生效。它会把栈地址、堆地址、共享库加载基址、mmap映射基址、可执行文件自身的加载基址等在进程启动时做随机化处理。攻击者如果不知道关键地址ROP链里的ret地址、栈上的buffer地址、GOT表项地址全都无从写起。但ASLR有一个粒度问题。32位系统上地址空间有限随机熵不够高暴力尝试有一定成功率64位系统熵明显提高盲目爆破就不现实了。更关键的是ASLR不同的随机化层级对应不同目标0完全关闭。1随机化共享库、栈、mmap基址但可执行文件基址不随机也就是PIE关闭情况下的默认形态。2在1的基础上再加上可执行文件基址的随机化PIE开启时生效的最高级别。实操中还有一个容易被忽略的点ASLR只负责基址随机化而函数在共享库内部的偏移是固定的。比如libc.so里system函数相对于libc基址的偏移是写死的。所以攻击者只要能泄露一次libc中某个函数比如puts或printf的实际加载地址反推libc基址就能算出所有目标函数和gadget的真实地址。这也是PWN里信息泄露为什么优先级那么高——没有泄漏ASLR几乎是一座不可逾越的大山有了泄漏ASLR的随机化就从不可预测降级为已知偏移的加减法。2.3 PIE连程序自身基址都随机化PIEPosition Independent Executable位置无关可执行文件就是把ASLR扩展到程序自身。没有PIE的可执行文件传统ET_EXEC格式加载基址固定通常就是0x400000开了PIE的文件是ET_DYN格式基址每次运行都会变。这意味着你没法在exp里写死程序自身某个函数的地址必须先泄漏一个程序内的指针再用偏移算出目标地址。这里有个非常典型的判断技巧checksec输出里PIE显示为Enabled时你要注意程序实际运行时基址是随机化的但如果显示RELRO: Partial且没有PIE通常GOT地址是固定的攻击者可以把GOT表项直接改成目标函数的地址。两者的攻防成本差别非常大——固定地址下改写GOT是最快的路径PIE开启后你得先做一次信息泄漏整个exp的复杂度会明显上升一个台阶。很多CTF题目为了梯度会把无PIE Partial RELRO设计成新手题把PIE Full RELRO Canary设计成进阶题。理解这个设计逻辑你就知道为什么不同题目的攻击路径差异如此之大。2.4 Stack Canary栈上的哨兵值Stack Canary栈保护也叫栈金丝雀是编译器在函数序言里往栈上插入的一个随机值在函数返回前由编译器生成的代码检查这个值是否被改动。如果发生栈溢出覆写了canary检查就会失败程序直接触发__stack_chk_fail终止运行。它专门打击栈溢出覆盖返回地址这类最经典的攻击。canary在64位系统上是8字节低字节是\x00这个设计很巧妙——因为字符串函数strcpy、read配合\x00截断在复制时遇到\x00会停止所以很难通过一次溢出完整覆盖掉canary。而canary的值随机且与进程相关每次启动都会变不能提前预置。PWN里面对canary常用思路有两个信息泄露如果存在越界读或格式化字符串漏洞可以先读出canary的值再在溢出时原样填回去这叫保留canary。逐字节爆破对于Fork出来的子进程由于fork会继承父进程的canary值而fork的子进程往往允许你反复测试可以逐字节爆破canary。每爆破出一个字节用掉256次尝试8字节就是2048次听起来多但对fork型题目这种爆破完全是可行路径。这里要提醒一个实操细节checksec看到Canary found只代表编译时开启了-fstack-protector但并不是所有函数都会被插canary。那些没有局部数组、没有栈上缓冲区的函数编译器可能不会插入canary。所以不要默认每个函数都有canary实际要用反汇编逐函数确认。2.5 RELROGOT表只读化的分级控制RELRORELocation Read-Only控制的是GOT全局偏移表的写入权限。GOT表用于存放外部函数如puts、system的实际地址是动态链接环境下程序调用库函数的中转站。攻击者如果能改写GOT表项就能把某个函数的调用劫持到任意地址。Partial RELROGOT表前面部分通常是.got段只读但后面存放动态解析结果的部分仍可写。这是Linux默认的链接行为也给GOT覆写攻击留下了空间。Full RELRO整个GOT表在程序启动后变为只读链接器在启动时就完成所有动态符号的解析相当于关掉了懒绑定。这种情况下GOT覆写路径被堵死攻击者通常只能转向覆写__malloc_hook、__free_hook这类glibc留出的hook指针或者覆写栈上返回地址。从利用成本来看Partial RELRO配合无PIE是最理想的GOT覆写场景地址固定、目标可写直接把printfgot改成system就行。Full RELRO之后你要费心找另一条路来改控制流。从防护角度理解Full RELRO的意义不仅是多了一处只读段而是强制攻击者把改数据改成构造ROP链这会让利用的稳定性和复杂度都显著增加。2.6 FORTIFY编译器帮你拦截危险函数FORTIFY-D_FORTIFY_SOURCE是glibc和GCC一起提供的一套编译期运行期检查机制。开启后像strcpy、sprintf、memcpy、read这类不检查边界的函数会被替换为带检查的版本比如__strcpy_chk、__sprintf_chk。在编译阶段如果编译器能确定目标缓冲区大小会直接报错运行阶段如果检测到写入长度超过目标缓冲区会调用abort终止程序。它对PWN的实际影响是栈溢出触发点变少而且很多原本想用strcpy、sprintf构造的溢出会直接失败。但FORTIFY不是万能的它只能针对缓冲区大小已知的场景做检查如果溢出发生在动态分配的堆内存中或者通过memcpy但长度参数不可静态判断检查效果就有限。所以判断题目有没有开FORTIFY建议反汇编看有没有__memcpy_chk这类内联检查调用而不是只看checksec的输出。3. 机制之间的配合为什么它们组合起来才是完整的防护3.1 单点机制与纵深防御很多新手会问既然NX能阻止shellcode执行为什么还需要ASLR既然canary能检测栈溢出为什么还需要RELRO答案很简单任何单一机制都有漏洞组合起来才能造成攻防成本的指数级上升。举个例子NX关闭时攻击者可以直接往栈里塞shellcodeASLR开不开对他影响不大NX开启了攻击者转向ROP这时候ASLR的价值就体现出来了——如果不知道libc基址连system的地址都猜不到如果PIE也开了那连程序内的gadget都定位不了如果canary也有栈溢出连返回地址都碰不到就算构造了一个没有canary的溢出点Full RELRO还会让GOT覆写失效。这几道防线一层层叠上去一个本来几行就能打穿的漏洞会硬生生变成需要三次信息泄露加一次精心构造的ROP链的高难度挑战。纵深防御的意义就在于此不是某一层特别强而是你突破每一层都需要特定的前置条件。攻击者要同时满足绕过canary、泄漏程序基址、泄漏libc基址、找到合适gadget这几个条件才会最终完成利用。每多一层保护可利用性就断崖式下降。3.2 编译选项与链接选项的对应关系写到这里建议把所有机制和编译/链接参数做一个完整的对应表方便你在实际审计时对照机制启用方式作用对象常见状态NX内核特性 编译时默认栈、堆、数据段几乎默认开启ASLRsysctl kernel.randomize_va_space栈、堆、libc、mmap默认2级开启PIEGCC-pie -fPIE可执行文件自身基址发行版默认开启Stack CanaryGCC-fstack-protector-all栈帧中的返回地址部分函数开启RELRO链接器-z relro -z nowGOT表Partial / FullFORTIFYGCC-D_FORTIFY_SOURCE2glibc高危函数发行版常开这里要特别提醒ASLR和PIE是两层东西。ASLR是内核加载时做的随机化PIE是程序自身被编译成位置无关的代码。一个没有PIE的程序即使ASLR开着它自身的基址也是固定的有了PIEASLR才能把程序自身也纳入随机化范围。所以判断时不要让checksec的输出替代对内核参数的确认——kernel.randomize_va_space设成0的话PIE照样形同虚设。4. 实操篇拿到二进制先做什么4.1 用checksec快速摸底如果你已经安装了pwntools直接用checksec命令是最快的checksec --file./pwn_test输出会包含Arch、RELRO、Stack Canary、NX、PIE、RPATH、RUNPATH、Symbols、FORTIFY等字段。大部分CTF平台和真实发行版默认都会开启NX、ASLR、PIE对守护进程而言canary则取决于编译选项。光看checksec还不够我建议你再补两条命令交叉验证readelf -l ./pwn_test | grep -E GNU_STACK|GNU_RELRO readelf -h ./pwn_test | grep Type第一条看GNU_STACK段的Flags如果同时有RWE说明栈可执行NX实际未生效第二条看文件类型EXEC表示无PIEDYN表示PIE已编译。4.2 用gdb确认运行时状态静态检查确认后再用gdb跑一遍确认ASLR是否真的影响到了进程行为gdb ./pwn_test start info proc mappings每次start后对比libc的加载地址如果每次都不同说明ASLR生效如果一直不变很可能randomize_va_space被关掉了。同理栈地址在第二次运行时也会变化这样就能直观看出ASLR带来的不确定性。4.3 判断攻击路径的优先级做完静态动态检查后我会按这个优先级来定攻击方案如果能找到信息泄露漏洞优先泄露canary和libc地址这是各类保护下最通用的打法。如果No PIE且Partial RELRO直接考虑GOT覆写成本最低。如果Full RELRO考虑hook指针__malloc_hook、__free_hook或者ROP链。如果canary存在且无法泄露寻找不经过canary检查的覆盖点比如覆盖函数指针、覆盖栈上的局部变量或者利用fork爆破canary。这里有个经验之谈保护机制开得越全信息泄露的权重就越高。一个没有输出函数的程序配合全量保护基本等于死路但只要有任意一次puts或printf的越界读攻防天秤就会立刻向攻击者倾斜。5. 常见问题与排查技巧实录5.1 为什么本地能打通远程打不通这是最典型的问题99%出在ASLR和PIE上。本地如果你用gdb调试gdb默认会关闭ASLRset disable-randomization on所以你本地固定地址可以打的exp拖到远程一跑libc基址和程序基址全变了自然打不通。解决办法是两个第一gdb里改成set disable-randomization off模拟真实环境第二exp里增加信息泄露步骤动态计算地址不要在本地把地址写死。5.2 canary爆破为什么有时会失败爆破canary适用于fork型服务每fork一个子进程会继承父进程的canary。但要注意如果父进程在fork之间重新执行了execve新进程会重新生成canary爆破就失败了。另外爆破要保证程序在canary校验失败时会返回不同的结果通常是stack smashing detected崩溃信息否则你无法区分哪一次猜对了。5.3 FORTIFY拦截了strcpy但程序还是被打了FORTIFY的检查在以下场景会失效使用动态长度且无法静态推断、通过自定义函数封装了危险操作、堆上缓冲区被错误使用。很多真实漏洞恰恰发生在这些检查盲区中所以不要把FORTIFY当成安全银弹。审计时遇到_chk函数先看检查长度是以什么为基准如果能被攻击者控制检查形同虚设。5.4 全开保护下还有没有突破口有而且不少。Full RELRO PIE NX Canary FORTIFY全开并不意味着程序不可攻击只是要求攻击者必须具备至少一个可靠的信息泄露点。常见的突破口包括格式化字符串漏洞任意读任意写可以在一次调用里完成地址泄漏和内存改写。堆上的UAF通过伪造堆块指针或改写函数指针绕过程序原有的控制流。逻辑漏洞比如整数溢出导致大小检查被绕过、数组越界写但不触发canary。动态加载的插件或解释器如果程序用了dlopen加载模块或内嵌了某种JIT这些组件的权限管理往往比主程序松散。就我的经验而言保护机制防得住打固定地址的脚本小子防不住愿意花时间逆向构造信息泄露链的攻击者。但后者需要投入的时间成本已经高到让大多数漏洞在经济上不值得利用了——这正是安全机制作为威慑的价值所在。6. 内核级访问控制SELinux与AppArmor6.1 它们和前面的机制有什么不同前面说的NX、ASLR、PIE、canary、RELRO解决的是内存漏洞能不能被利用的问题而SELinux和AppArmor解决的是即使进程被攻破还能不能祸及整个系统的问题。它们属于内核的强制访问控制MAC体系给进程、文件、端口、资源打上安全标签或配置策略限制进程能访问的对象范围。SELinux在主流发行版上默认状态是Enforcing常见于服务器场景AppArmor在另外一些发行版上作为默认选择两者实现思路不同但目标一致。对PWN题来说偶尔会遇到装了一套SELinux策略的靶机环境即使你成功getshell也可能因为策略限制读不了flag文件。这时候就得先看策略允许你访问哪些路径或者想办法让当前进程获得相应权限否则白忙一场。6.2 对利用链的影响真实环境攻防中一个拿到shell却没有权限读取敏感文件的攻击者并不能算完全打到RCE完成闭环。SELinux策略会把系统服务约束在最小权限集内比如Web服务进程只能读网页目录、连特定端口、执行特定二进制。一旦服务被攻破攻击者面对的是一个被阉割的shell。绕过这些策略的办法通常不在PWN范畴内而是内核提权漏洞或策略配置缺陷。所以从防御角度看纵深防御的最后一层就是这类访问控制——即使前面所有防线被突破攻击者仍然被困在沙箱里。这也是为什么近年来越来越多的CTF题目开始引入seccomp沙箱即使你拿到了shell系统调用被限制连execve都不让用你就得改用ORWopen-read-write这种只调用少量系统调用的方式来读取flag。理解seccomp其实就是理解内核如何以最小权限原则约束被攻破的进程这和SELinux的思路一脉相承。7. 这些机制对我做安全研究意味着什么我见过不少初学者对着全开保护的题目一头雾水第一反应是这题没法做。但换个角度想这些机制的存在恰恰是安全研究者发挥价值的地方。你在本地复现CVE的时候如果保护全开就利用不了那不是漏洞不存在而是你的利用姿势需要升级。真实世界的攻击不会因为系统开了ASLR和NX就放弃而是去寻找信息泄露点、去挖掘逻辑漏洞、去组合多条攻击路径。把这些机制当成题目给的基础条件而不是敌人心态会好很多。另外我也想说一句做PWN题目和做真实漏洞研究确实是两回事但理解防护机制这件事两者的根基完全相同。你在CTF里学到的先查防护、再定方案、再构造利用的思路放到真实漏洞分析里一样是核心流程。只是真实场景还要额外考虑稳定性、隐蔽性、业务逻辑复杂度高得多。我自己最初接触这些机制时也曾经觉得既然有这么多保护漏洞利用是不是已经没有意义了。后来跟了不少实操项目才明白防护机制抬高的是利用成本但并没有也不可能消灭漏洞本身。一个逻辑漏洞、一个错误配置、一段被信任的历史代码仍然可能成为突破口。安全机制的作用不是把攻击者拒之门外而是把每一次成功攻击的代价都抬到攻击者不愿意承受的高度。这大概也是我在这个领域这么久觉得最有价值的一点认知。如果你刚开始学PWN建议不要一上来就研究怎么绕过所有保护这种宏大的问题。先逐个机制上手编译一个带canary的程序反汇编看canary怎么插入的开启NX后用ROP链打一个最简单的溢出关掉ASLR写一个固定地址的exp再打开ASLR感受一下地址变化对利用的影响。一个个机制亲手摸一遍比看十篇理论文章都有效。
延伸阅读

更多相关文章

2026/10/17 8:15:05

知识图谱+Neo4j+Flask实现课程智能问答系统完整教程

我做数据库课程问答系统这个课题的时候,翻遍了GitHub和CSDN,发现开源的课程问答项目大都是基于Elasticsearch的关键词搜索,或者干脆就是写死的一个FAQ列表。但在实际使用中你会发现,学生问“什么是事务的ACID特性”和问“ACID都有…

2026/10/17 8:15:05

多特征多因变量时序预测:CNN-LSTM-Attention实践

多特征、多因变量的回归预测,在设备剩余寿命预测、电力负荷预测这类场景里几乎每天都要遇到。我最初直接怼一个LSTM,特征一多、序列一长,训练又慢精度又拉胯。后来把CNN和Attention加进去,组合成CNN-LSTM-Attention混合结构&#…

2026/10/17 10:50:16

MATLAB BSM1污水处理仿真系统:开箱即用的活性污泥模型

简介:本资源是基于Black-Scholes-Merton期权定价理论实现的MATLAB教学与研究型模型包,面向金融工程初学者、量化分析入门者及本科毕业设计学生,用于理解欧式期权定价机制、参数敏感性(希腊字母)计算及波动率建模等核心…

2026/10/17 10:50:16

手写C++实现AGNES层次聚类:核心原理与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 10:50:16

基于Spring MVC与Hibernate的校园失物招领系统设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 10:50:16

Spring Boot logback异常邮件告警实战配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 10:45:15

子母钟系统采购——应以全生命周期总拥有成本为核心标尺

关键词:数字子母钟、gps子母钟系统、ntp子母钟、网络子母钟系统医院、轨道交通、学校、工厂、智慧楼宇等项目建设子母钟系统时,采购常陷入只对比硬件报价、优先低价方案的误区。前期投入看似低廉,但运行3-5年后易出现备件停产、维…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑