很高兴新生能够点进 PWN 的新生基础知识,有学习的想法便超出常人一大截了,先自我介绍我是 HFTTC,NYSEC 的 pwn 手,如果你对 pwn 感兴趣,可以多向我咨询,或者询问”假设的人”(qq 名),当然新生群内还有一些神秘的 pwn 老登,你也可以去询问他们。
既然来到这里,那就先讲一讲什么是 PWN:
PWN(读作 /poʊn/,发音类似于英文单词 “own”)在网络安全和黑客文化中,指的是成功攻击、控制或攻破一个系统/程序。在常规的 CTF 赛事当中,我们通常会写一份 exploit 去获取靶机的 shell,flag 就是我们取得的关键信息,一般来说 pwn 题目也以拿到 flag 为得分点。接下来会着重讲解 pwn 中的栈漏洞。
hint:
来到 pwn 的世界,就默认你已经学会了基础的 python 和 C 语法,如果不了解的可以先去学习 C 语言,至于 python,那就写 exp 的时候用到什么学什么即可,这里推荐翁凯 C语言的课程,可以在 b 站直接搜索到。
pwn 环境的搭建:
构建一个 pwn 环境,我们首先需要一个 VMware 虚拟机和一个 Ubuntu 镜像,当然 kali 也是可以的,只要是 linux 系统。pwn 环境大家可以自行去配置:
这里直接给出 vmware 的安装教程以及下载链接:https://blog.csdn.net/weixin_74195551/article/details/127288338
pwn 环境搭建教程:
https://bbs.kanxue.com/thread-269743-1.htm,其实网上也有很多教程,因此这里不再详细讲述

考虑到新生不了解电脑环境的配置,配置 Ubuntupwn 环境是比较难的,如果有问题也可以直接来问我。
直接学习程序的各类保护可能对并不了解 pwn 保护机制的同学并不友好,这里暂先不介绍 pwn 的保护机制,直接从第一道 pwn 的栈溢出开始学习,以写题促进对 pwn 的了解,个人认为是一种比较好的方式。
IDA9.3:
ida 是一款强大的反编译软件,是逆向工程、二进制安全分析和恶意软件研究领域最权威、最流行的静态分析工具,由 Hex-Rays 公司开发与维护。
它能够将编译好的二进制程序(如 .exe、.dll、.so、ELF 等)还原为机器指令(汇编代码),并通过强大的 Hex-Rays 反编译器将汇编进一步转译为高可读性的 C 语言伪代码。
通过网盘分享的文件:idapro93
链接: https://pan.baidu.com/s/1lwkKsgiLxZFVa9ioK-g7hg?pwd=97zw 提取码: 97zw

选择自己需要的版本即可,windows 系统的就用 x64win.exe
ret2text:
这里再次强调,一定要有 C 语言的基础。那么话不多说,直接开启我们对栈溢出的学习:
既然是栈溢出,我们首先需要了解什么是栈?
如果用生活中的例子来理解,栈就像一筒羽毛球、一叠盘子或者一个洗口杯:你最先放进去的东西会被压在最底下,而最后放进去的东西会在最上面。当你取东西时,只能从最上面开始拿。当你想往一个羽毛球筒中塞多于它本身最大容量的数量时,羽毛球就会塞不进去,那么多余的羽毛球就会溢出。
相应的,可以把栈当做一个容器,如果你往栈当中放了过多的数据,他也会溢出:
**栈是计算机系统的数据结构,**按照先进后出的原则存储数据,先进入的数据被压入栈底,后放入的数据在栈顶,就像叠盘子一样总是取最上面的,先放入的盘子总是被压在最下面。

程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致与其相邻的栈中的其他重要数据结构被破坏,从而影响程序的正常运行,当我们写入的字符串长度可以覆盖到程序返回地址时,我们就可以控制程序的运行流程了。
1 | 高地址 (High Memory Address) |
如我们能够溢出数据并覆盖 rbp,那么我们就可以控制返回地址,在返回地址填充后门函数去获取 shell
以题目为例,讲解解题中需要用到的工具和解题技巧:
[CTF2]BUUCTF-bjdctf_2020_babystack

在写题目之前,我们首先要查看二进制文件的文件保护以及其架构:

先把文件移入虚拟机当中,然后在虚拟机终端中找到存在二进制文件的目录:
利用 check 查看其保护以及架构:发现其是 64 位程序,开启了 NX 保护
1 | pwn checksec ./bjd |

上面这些防御措施我们在文末会简单描述,具体内容遇到了再去学习,对于这道题目影响不大。
IDA 静态分析:
直接用 IDA 打开程序:

找到 main 函数:

按 F5 进行反汇编:得到伪 C 代码:

1 | _BYTE buf[12]; // [rsp+0h] [rbp-10h] BYREF |
该题目的关键点在于这几行:
给出 buf 的缓冲区大小是 0x10,scanf 可以决定读入数据的大小,read 可以读入 nbytes 个字节数据到 buf 当中
我们可以去测试输入数据到 rbp 的距离:pwndbg 动态调试 bjd 程序:
1 | gdb ./bjd |

1 | cyclic 120 |
生成一段长度为 120 的字符串:然后 n 运行到 read 之前:

输入生成的那一部分字符串:之后会得到一个报错的返回地址:

这个就是我们当前修改过的返回地址:测一下距离:
1 | cyclic -l |

发现偏移是二十四,意思就是说我们只需要填充 24 个字节就可以填充到返回地址了
那我们修改地址填什么呢?

我们发现在这道题目当中给了 backdoor,也就是我们的后门函数,执行 system(“/bin/sh”)就可以获取 shell 了
因此我们把返回地址修改为 backdoor 的地址:0x4006E6

有了以上逻辑,我们就可以编写以下 exp:先启动环境
1 | from pwn import * |
远程环境打通之后直接 cat flag 即可

至于本地打法我们之后再去了解,先感受一下第一次 pwn 成功拿到 flag 的喜悦
二进制文件保护:
1. RELRO (Relocation Read-Only)
- 防御原理: 这主要用来保护程序的全局偏移表(GOT 表)。在动态链接的程序中,GOT 表用于存储外部函数(如 printf, system)的真实内存地址。
- 如果是 Partial RELRO(部分开启)或关闭,可以通过漏洞(比如你刚才问的格式化字符串 %n 或者任意地址写)将 GOT 表中某个常用函数的地址篡改成恶意代码的地址。这样程序下次调用该函数时,就会执行恶意代码(这被称为 GOT 表劫持)。
- Full RELRO 会在程序启动时,将所有外部函数的地址解析完毕,然后将 GOT 表所在的内存区域设置为只读(Read-Only)。这意味着你无法再篡改 GOT 表了,直接封死了 GOT 表劫持这条路。
2. Stack (栈保护 / Canary)
- 防御原理: Canary(金丝雀)机制用于防御栈溢出(Stack Overflow)漏洞。
- 开启后,程序在调用函数时,会在栈上的“返回地址”之前放置一个随机生成的“安全值”
- 当函数执行完毕准备返回时,会先检查这个值有没有改变。如果黑客试图通过输入超长字符串来覆盖返回地址,必定会先覆盖掉这个 Canary 值。程序一旦发现 Canary 被修改,就会立刻判定遭到攻击并强制崩溃,从而阻止黑客劫持执行流。
3. NX (No-eXecute / 数据执行不可执行)
- 防御原理: 在 Windows 下叫 DEP
- 过去,黑客喜欢把恶意的机器码(Shellcode)直接作为输入发送到程序的栈区或堆区,然后把程序的执行流跳转到那里去执行。
- NX 机制将存放数据的内存区域(如栈、堆、BSS 段)标记为不可执行(Non-Executable)。即使黑客成功把恶意代码注入到了栈上,并跳转过去,CPU 也会拒绝执行并报错。
- 开启 NX 后,黑客被迫放弃直接执行 Shellcode,转而使用更复杂的 **ROP **技术,利用程序原本自带的代码片段来拼凑攻击逻辑。
4. PIE (Position Independent Executable)
- 防御原理: 这是针对程序自身的地址随机化(ASLR 的一部分)。
- 如果关闭 PIE,程序每次运行的代码段、数据段加载的内存基础地址都是固定不变的。可以轻易地在本地算好各种代码的地址(如后门函数的地址),直接写死在攻击脚本里。
- PIE enabled 表示程序在每次启动时,其自身的代码段、数据段的基地址都是完全随机的。如果不知道本次运行的基地址,就无法准确跳转到程序的任何函数。
- 要攻破 PIE,通常必须先找到一个“信息泄露”漏洞(比如用 %p 打印指针),算出基地址,才能进行后续攻击。
5. SHSTK (Shadow Stack / 影子栈) & IBT (Indirect Branch Tracking)
- 防御原理: 这两个是 Intel 较新的硬件级控制流完整性保护机制(CET - Control-Flow Enforcement Technology),防御力度非常强:
- SHSTK(影子栈): 专门用来对抗刚才提到的 ROP 攻击。它在内存中开辟了一个额外的、受到硬件严格保护的隐藏栈,专门只保存函数的返回地址。当函数返回时,CPU 会对比常规栈和影子栈里的返回地址。如果黑客篡改了常规栈上的返回地址,比对就会失败,程序直接终止。
- IBT: 专门用来对抗 JOP/COP(面向跳转/调用编程)。它要求所有的非直接跳转(如 call rax、jmp rdx)目标位置,必须是一条特殊的机器指令(ENDBR64)。否则 CPU 就会抛出异常。
6. Stripped (符号表剥离)
- 这不是防御机制,而是给逆向工程师的“福利”。
- 未剥离意味着程序里保留了开发者写的函数名、变量名等调试符号(比如 main, input_function 等名字都还在)。这会让反编译和逆向分析的过程变得非常轻松。如果显示 Yes,所有的名字都会变成无意义的地址或代号。
说些什么吧!