checksec 完全指南:从安装到 PWN 利用策略推导
在PWN和二进制漏洞研究这个圈子里checksec 基本上属于“拿到文件第一件事”级别的工具。无论是CTF赛题、固件提取出来的可执行文件还是平时逆向分析碰到的程序先跑一遍 checksec心里就对它大概有几斤几两有数了。很多刚开始学PWN的朋友总是急着去看反汇编、找溢出点其实第一步应该先让 checksec 告诉你这个程序开了哪些防护后面所有利用思路都得围绕这些防护来展开。这篇东西我会把最新版 checksec 的安装、使用、输出解读一次讲透顺便分享一些我在实战里排查踩坑的经验。如果你刚接触 PWN或者已经在打 CTF 但每次对着一堆输出无从下手这篇文章应该能帮上忙。1. 先搞懂checksec在PWN里到底帮你解决什么问题1.1 现代程序的安全机制不是摆设早期软件漏洞利用之所以“简单粗暴”很大程度上是因为当时编译器默认不做任何防护你往栈里塞一段 shellcode然后把返回地址改成栈地址程序就乖乖跳过去执行了。后来防御方学聪明了编译器陆续引入了 NX、Stack Canary、PIE、RELRO、FORTIFY 这几类机制每一样都是冲着某一种经典利用手法去的。所以在2024年之后的今天你拿到一个稍微新点的程序如果还想用老套路一把梭大概率会被卡在第一步。checksec 干的事情就是帮你快速读取目标二进制文件的头信息和符号表告诉你这几类防护分别开没开、开到了什么程度。它在 PWN 流程里的位置相当于侦察兵不是直接提供攻击手段但决定了你后面选择什么打法。1.2 从对抗视角看checksec输出就是利用路线的地图我经常跟刚入门的学生说checksec 的输出不是给你看的“安全评分”而是给你看的“对抗条件”。同一个栈溢出漏洞放在不同防护组合下利用难度和路线完全不同。举个例子如果程序 NX 关闭你可以直接往栈上放 shellcode然后 ret 到栈地址这是最省事的办法。但 NX 一旦开启栈上没有执行权限同样的漏洞就得换成 ROP 链通过拼接 gadget 调 mprotect 或者调用 system 来完成利用。再比如 Canary 开启和关闭决定你能不能直接覆盖返回地址Partial RELRO 和 Full RELRO 的区别直接决定 GOT 表能不能改。这些信息全部写在 checksec 那几行输出里。所以把 checksec 装好、看懂、用到位是 PWN 入门阶段性价比最高的一件事。2. 最新版checksec的安装PWN入门第一课2.1 别再下老版本了选对维护分支网上很多教程还在让你从某个老仓库直接 wget 一个 checksec.sh 脚本那种做法在今天已经不太靠谱了。老版本主要是 Perl 写的功能相对简单而且对现代的 ELF 文件格式支持不够好比如处理被 strip 过的二进制、某些固件里的畸形 ELF 头时经常给出不准确的结论。现在社区里维护最活跃的是 GitHub 上的slimm609/checksec.sh仓库最新版本已经用 Python 重写解析 ELF 的能力强了很多输出格式也更丰富。这个版本虽然仓库名字还叫 checksec.sh但实际可执行文件就叫 checksec因为它是用 Python 写的脚本你不需要编译下载下来就能用。安装命令很简单git clone https://github.com/slimm609/checksec.sh.git cd checksec.sh仓库里会有一个叫checksec的可执行文件没有管理员权限的话把它放到自己的用户目录下的 bin 文件夹里就行。2.2 依赖准备新版本需要Python环境新版 checksec 的运行需要 Python 3 以及两个第三方库termcolor和pyelftools。termcolor用来给终端输出着色pyelftools是核心它负责解析 ELF 文件的各个段和符号。你可以先把依赖装上python3 -m pip install --user termcolor pyelftools如果你平时用的是系统自带的 Python 3这一步大概率能直接成功。要是遇到权限问题或者公司内网源的问题可以考虑用虚拟环境但通常--user参数就够了。接着把 checksec 放进可执行路径cp checksec ~/bin/ echo export PATH~/bin:$PATH ~/.bashrc source ~/.bashrc这里有个小坑如果你用的是 zsh记得改~/.zshrc而不是~/.bashrc。另外git clone下来之后如果发现文件没有执行权限需要手动加上chmod x ~/bin/checksec2.3 安装后快速验证是否正常装完先别急跑个版本号和帮助信息看看checksec --version checksec --help如果能看到版本信息并且参数列表正常显示说明依赖都齐了。然后你可以随便拿一个系统自带的二进制文件测试比如checksec --file/bin/ls看看能不能正常输出各防护项。要注意新版 checksec 依赖系统里的readelf和objdump如果没有装 binutils 包会报找不到命令的错误。Debian/Ubuntu 系的系统执行sudo apt install binutils就能解决CentOS/RHEL 系用sudo yum install binutils。3. checksec使用详解从单文件检测到批量扫描3.1 最常用的单文件检测日常用得最多的还是对单个 ELF 文件做检测。命令格式是checksec --file./pwn_test注意--file后面用的是等号连接也可以用空格checksec --file ./pwn_test两种写法都支持。输出大概长这样RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 77 Symbols No 0 3 ./pwn_test第一行是表头第二行是实际检测结果。这里每一项含义如下RELRO显示Partial RELRO或Full RELRO表示 GOT 表的重定位只读程度。STACK CANARYCanary found表示开了栈保护No canary found表示没开。NXNX enabled表示栈不可执行NX disabled表示栈可执行。PIEPIE enabled表示程序地址随机化已开No PIE表示固定基址。RPATH / RUNPATH动态链接库搜索路径相关PWN 实战里可以利用 RPATH 做劫持但平时检测得到No RPATH基本就是没事。Symbols显示二进制中符号的数量。FORTIFYFORTIFY 安全加固是否启用。你要是第一次看到这个输出可能觉得列很多其实重点关注前四列就足够了RELRO、STACK CANARY、NX、PIE。这四个直接决定了你的利用方案。3.2 批量目录扫描和进程检测有时候你手上不是一个文件而是一个目录下散落着几十个赛题一个一个跑太浪费时间。checksec 提供了目录扫描模式checksec --dir./challenges这个命令会扫描目录下所有文件逐个输出安全属性。遇到目录里有非 ELF 文件它也会给出提示但不会中断扫描。我平时复盘比赛的时候特别喜欢用这个功能把某场比赛的所有题目放在一个目录里一把梭全部扫完然后根据防护情况先把题目分个难度等级。还有一种场景是分析系统里正在运行的进程。比如你在排查某个服务程序时想知道它运行时实际的 RELRO、NX 状态可以用进程 ID 指定checksec --proc1234这里的 1234 是进程 ID可以通过ps -aux查到。对进程做检测和直接检测文件有所不同因为进程运行时可能已经经过了装载器处理某些属性会反映运行时状态这在分析漏洞利用条件时更贴近真实场景。3.3 机器可读输出批量收集信息的好帮手如果你要做批量统计比如把一批题目的防护情况整理成表格可能希望直接用脚本处理。checksec 很贴心地支持 JSON、XML、CSV 三种输出格式checksec --file./pwn_test --outputjson输出的大概形式是{ file: ./pwn_test, relro: partial, canary: true, nx: true, pie: true, rpath: false, runpath: false, symbols: 77, fortify: false }拿到 JSON 之后你可以用 Python 写个小脚本把一批文件的防护状态汇总成一个 CSV 表格复盘比赛时特别好用。我习惯在比赛结束之后做这样一件事把每道题的 checksec JSON 存下来然后写个小脚本统计出“哪些题开了 PIE”“哪些题没开 Canary”方便总结出题人的风格。这种信息收集工作长期积累下来对提升 PWN 实战能力非常有帮助。3.4 常用参数速查表为了方便查我把常用参数整理成一个表参数作用示例--file/-f检测指定 ELF 文件checksec --file./test--dir/-d扫描目录下所有文件checksec --dir./bin--proc/-p检测正在运行的进程checksec --proc1234--output/-o指定输出格式 json/xml/csvchecksec --file./test --outputjson--kernel/-k检测内核安全选项checksec --kernel--debug打开调试信息checksec --file./test --debug--version/-v显示版本号checksec --version--kernel这个参数可能有很多人不知道它会把当前系统内核的一些安全配置项拉出来比如 KASLR、SMEP、SMAP 这些是否开启。在打内核 PWN 题或者研究本地提权的时候会用到普通 PWN 入门阶段用不太上但知道有这么个东西总是好的。4. 从checksec输出反推PWN利用策略真正实战的一步4.1 每一项防护都封死了哪条路只看输出不理解含义等于白装。我把每一项防护和它在 PWN 利用中对应的“拦路虎”关系讲清楚。NXNo-Execute。NX 开启时栈和堆这些数据段没有执行权限传统的往栈上写 shellcode 然后跳过去执行的 ret2shellcode 打法直接失效。但注意NX 关闭不意味着随便打你还得确认程序是不是 PIE如果 PIE 也开了栈地址本身是随机的你连跳到哪都不知道。Stack Canary。Canary 实际上是一个放在栈上的随机数函数返回前会检查它是否被修改。如果程序开了 Canary你的栈溢出就不能直接覆盖返回地址因为中间还隔着一个四字节32位或八字节64位的 canary 值不知道它具体是多少就覆盖不进去。实战中要么用格式化字符串把它泄露出来要么找机会暴力猜解但64位下 canary 的随机空间太大暴力不现实。PIEPosition Independent Executable。PIE 开启意味着程序加载到内存的基址是随机的。如果你的漏洞利用需要固定跳到一个地址比如0x400xxx这样的代码段地址PIE 一开这些地址每次运行都变。对策是先泄露一个程序内的指针地址算出加载基址再计算出目标地址。这就是为什么很多 CTF 题目第一问都是让你先拿一个 leak。RELRO。RELRO 保护的是 GOT 表。Partial RELRO 下.got.plt段是可写的你可以通过溢出改写 GOT 表项把某个函数的地址换成 system 的地址这就是经典的 GOT 劫持。Full RELRO 下整个 GOT 都变成只读这条路彻底堵死你得改打__free_hook、__malloc_hook这类 libc 里的钩子指针或者找其他的可写目标。FORTIFY。FORTIFY 是编译器的安全加固它会在调用memcpy、strcpy、printf等函数时自动加上边界检查并且会把这些调用替换成带_chk后缀的检查版本。它对 PWN 利用的影响主要体现在格式化字符串漏洞某些情况下%n写操作会被拦截导致无法直接利用格式化字符串写任意地址。不过 FORTIFY 对栈溢出的利用影响相对小一些。4.2 一个典型样本的路线推演假设 checksec 输出如下RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 60 Symbols No 0 1 ./baby_overflow我来做一个实战推演没有 Canary栈溢出可以直接覆盖返回地址不需要泄露栈上数据。NX 开启不能执行栈上代码不能直接 ret2shellcode。没有 PIE代码段地址固定可以直接跳转到程序自带的某个函数或 gadget。Partial RELROGOT 可写可以考虑 GOT 劫持。综合下来最经典的路线是先找一个system函数地址然后通过栈溢出修改 GOT 表项或者更直接一点找程序里有没有现成的后门函数直接 ret 过去。如果程序里有system(/bin/sh)这种后门那连 GOT 劫持都不用直接返回后门即可。如果没有后门可以 ROP 调puts泄露 libc 地址再调system。这套思路完全是 checksec 输出直接推导出来的不看输出只能瞎猜。再换一个样本RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 120 Symbols No 0 4 ./hard_target这个组合就明显难多了Canary 要泄PIE 要泄GOT 还改不了。这时候你至少得找两个泄露点一个泄露 canary一个泄露 libc 或程序基址。Full RELRO 下还得考虑打__free_hook或setcontext这种高阶技巧。所以同样一个漏洞点在不同 checksec 环境下写出的 exploit 长度可以差好几倍这就是为什么老手看到 checksec 输出后心里基本就有底了。4.3 别忽略FORTIFY和其他冷门列的细节很多人看 checksec 只看前四列其实 FORTIFY 这列在特定场景下也很关键。如果你遇到的是格式化字符串漏洞程序又启用了 FORTIFYprintf会被替换为__printf_chk%n在某些条件下会被直接阻止。这时候格式化字符串的任意写就不能直接用了。但 FORTIFY 并不是所有情况都能防住。它依赖编译期的静态分析如果攻击者用一个非常规的长度参数绕过它的检查后续的写入仍然可能发生。而且 FORTIFY 对栈溢出类漏洞几乎没有任何防护作用它只是对某些库函数调用加检查。所以遇到 FORTIFY 开启的程序不要直接放弃要先判断漏洞类型再决定策略。还有一个容易忽略的 RPATH / RUNPATH 列。如果程序自身带了 RPATH指向一个用户可写的路径那你可以考虑在目标路径放一个恶意动态库利用动态链接器的搜索顺序劫持库加载。这种技巧在 CTF 里不算主流但在真实世界的供应链攻击中非常常见。checksec 能帮你快速筛选出这类有 RPATH 问题的二进制。5. 常见问题与排查技巧实录5.1 安装和运行过程中的高频报错我在给身边朋友和学员装机时遇到过好几类反复出现的报错这里整理成表方便对照排查。问题1checksec: command not found原因可执行文件没有放进 PATH或者没有执行权限。排查先确认文件位置然后ls -l ~/bin/checksec看权限。修复方法chmod x ~/bin/checksec echo export PATH~/bin:$PATH ~/.bashrc source ~/.bashrc如果还不行执行which checksec看看系统实际在哪个目录找命令。问题2ModuleNotFoundError: No module named termcolor原因Python 缺少依赖。新版 checksec 依赖 termcolor 和 pyelftools 两个库。修复执行python3 -m pip install --user termcolor pyelftools。注意如果你系统里有多个 Python 版本一定要用与运行 checksec 相同的那个 Python脚本第一行如果写的是#!/usr/bin/python3就用/usr/bin/python3 -m pip安装。问题3/usr/bin/readelf: No such file or directory或objdump: command not found原因没装 binutils 工具包。修复Debian/Ubuntu 用sudo apt install binutilsCentOS/RHEL 用sudo yum install binutils。问题4输出全是No但程序明明开了防护原因大概率是你的文件不是标准 ELF 格式或者被某种加壳工具处理过导致 ELF 头信息不全。用file命令看一下文件类型。遇到固件提取出来的裸二进制checksec 扫不出来是正常的这种情况得先手工恢复 ELF 头再检测。问题5git clone 下来的 checksec 无法执行提示 permission denied原因仓库里的脚本默认权限可能没有执行位。修复chmod x checksec。这个是新手最容易忽略的一步clone 和 cp 之后直接执行就会踩到这个坑。5.2 巧用debug模式定位问题如果 checksec 本身出了一些奇怪的错误比如某个二进制检测时会崩溃或者输出的某项防护和预期不符可以打开 debug 模式看它实际执行了什么命令checksec --file./test --debugdebug 模式下会显示底层调用的 readelf 或 objdump 具体命令以及返回值很多时候你一眼就能看出是文件格式的问题还是工具链缺失的问题。这个经验是我在分析一个 BIOS 固件中的某个模块时学到的当时直接跑 checksec 完全没输出打开 debug 才发现是 objdump 版本太老不支持某些新的 ELF 段。5.3 自己造样本验证理解学习效率翻倍最后分享一个我自己验证 checksec 输出判断的小技巧。想真正搞懂每一项防护对应的编译选项最直接的办法是自己编译几个不同防护组合的测试程序然后用 checksec 验证。# 全关防护 gcc -o test_none test.c -no-pie -fno-stack-protector -z execstack -Wl,-z,norelro # 只开NX gcc -o test_nx test.c -no-pie -fno-stack-protector -Wl,-z,norelro # 开Canary gcc -o test_canary test.c -no-pie -fstack-protector-all -Wl,-z,norelro # 全开 gcc -o test_full test.c -fstack-protector-all -pie -z now然后挨个跑 checksec你会发现输出和编译选项之间的对应关系一下就清晰了。这比自己死记硬背每一项的含义高效得多。我教学生的时候经常用这个办法十几分钟就能把 checksec 的常见输出组合全部过一遍。我现在拿到一个新二进制的标准动作从来不是直接打开 IDA 看反汇编而是先checksec --file看一眼输出心里默念一遍“NX开了要ROP、Canary要先泄、PIE要算基址、RELRO决定打不打GOT”然后再去分析逻辑。这套习惯帮我节省了大量试错时间希望也能帮你建立起同样的第一反应。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →