谈CTF pwn方向,绕不开一个场景:打开攻防世界的pwn分类,点开新手区,第一道题大概率就是 get_shell。很多人第一次看到“pwn”会懵,觉得要懂汇编、懂内存布局、懂各种漏洞利用,还没开始就打了退堂鼓。其实 get_shell 这道题的设计非常友好,它不要求你立刻掌握复杂的二进制利用,而是把 pwn 题最核心的“最终目标”直接摆在你面前——连上远程靶机,拿到一个 shell,读取 flag 文件并提交。换句话说,它是整个 pwn 方向的“第一口饭”,也是你建立信心、熟悉工具链最好的起点。
这篇就专门聊聊怎么拿下攻防世界的 get_shell,包括题目背后的原理、完整实操步骤、环境配置,以及我实际做题时踩过的坑。不管你用的是 Kali、Ubuntu 虚拟机还是 Windows 下的 WSL,都能照着做出来。
1. 项目概述:get_shell 到底在考什么
1.1 先从题目场景说起
攻防世界是目前国内很常见的 CTF 练习平台,题目分类包含 pwn、reverse、web、crypto、misc 等。其中 pwn 类题目的核心是“通过程序漏洞拿到目标机器的权限”,而 get_shell 作为入门题,平台一般会提供一个远程服务地址,形式通常是nc 111.200.241.244 37153这样的字符串。实际地址以做题页面显示的为准,因为平台换题、换端口都是常有的事。
这道题的目标非常直接:你不需要逆向复杂的程序逻辑,也不用去构造溢出 payload,只需要想办法连上远程服务。连上之后,如果程序本身已经调用了/bin/sh,你就能直接得到一个交互式 shell。接着在 shell 里执行命令,找到 flag 文件,读出内容,再回到攻防世界页面提交 flag,就能得分。
对新手来说,这像一个“热身运动”:先让你体验一遍“拿到 shell、找到 flag、提交 flag”的完整闭环。这个过程虽然简单,却是后面所有 pwn 题都会重复的核心动作。
1.2 pwn 和 get_shell 的关系
先解释一下 pwn。在 CTF 圈子里,pwn 通常指二进制漏洞利用,英文原意是“own”的谐音,引申为“控制住目标程序”。拿到 shell 意味着你有了与程序交互的命令行权限,可以在目标机器上执行各种命令,比如列出目录、查看文件、运行命令等等。
很多 pwn 题会把 flag 放在服务端磁盘上,你必须通过漏洞拿到 shell 才能读取它。所以“get_shell”这个说法本身就是 pwn 方向最常用的一句话。get_shell 这道题则是把这个目标单独抽出来,去掉漏洞利用的步骤,让你先熟悉“连上服务之后该做什么”。
从这个角度理解,题目名称特别实在:它就是在告诉你,能 get 到一个 shell 就算成功。
1.3 这道题适合谁来做
我强烈建议下面几类人优先做 get_shell:
- 完全没接触过 pwn 的纯新手,想快速建立概念;
- 已经装了 Kali 或 Ubuntu,但不知道 pwn 题怎么下手的初学者;
- 做 web 方向、想横向了解一下二进制安全的同学;
- 即将参加校内 CTF 或入门级比赛,需要提前熟悉远程连接流程的人。
做这道题不需要太深的基础,但需要你至少知道一点 Linux 命令行常识,比如ls、cat、pwd这几个命令。如果连这些都不熟,正好通过这道题练一练,性价比很高。
2. 从零开始做 get_shell 的完整实操流程
2.1 准备工作:决定用哪台机器连接
做 pwn 题,第一步不是忙着敲命令,而是先选好“作战环境”。我见过太多新手在 Windows 的 CMD 里折腾,结果nc命令不存在,或者工具装不上,最后卡在最基础的一步上。这里我的建议很明确:优先用 Linux 环境,哪怕是虚拟机或者 WSL 都行。
如果你用的是 Kali Linux,那工具基本齐全,省去很多麻烦。如果用的是普通 Ubuntu,需要手动装一个 netcat。Windows 用户我建议直接装 WSL2,体验比 CMD 好太多。下面是一张简单的选型对比:
| 环境 | 优点 | 缺点 |
|---|---|---|
| Kali Linux | 预装大量逆向和 pwn 工具,社区资料多 | 虚拟机占用资源稍大 |
| Ubuntu 虚拟机 | 干净、可控,适合自己搭建环境 | 需要手动安装部分工具 |
| Windows + WSL2 | 不用单独开虚拟机,切换方便 | 部分工具在 WSL 下有小坑 |
| Windows 原生 CMD | 几乎不用 | 缺少标准工具,后面调试很痛苦 |
我个人更推荐 Ubuntu 虚拟机或 WSL2,因为后面学 gdb 调试、pwntools 脚本都要在 Linux 环境里跑,越早切换成本越低。
2.2 第一步:拿到题目网络入口
登录攻防世界平台,进入 pwn 分类的 get_shell 题目页面。一般情况下,题目区域会直接给出远程连接信息,比如:
nc 111.200.241.244 37153注意:这个主机和端口不是一成不变的,以你实际做题时页面显示的为准。有些版本还会提供附件下载,建议顺手下载下来,后面本地分析会用到。
拿到连接信息之后,先不要急着点“提交 flag”,因为你现在还不知道 flag 是什么。这一步要做的是把连接命令复制下来,准备到终端里执行。
2.3 第二步:先看本地附件(可选)
如果平台提供了附件,下载到本地之后,先尝试用file命令看看文件类型。比如:
file get_shell正常会看到类似这样的输出:
get_shell: ELF 64-bit LSB executable, x86-64, dynamically linked, not stripped这说明它是一个 64 位的 Linux 可执行文件。对这道题而言,本地运行大概率会直接进入 shell 交互界面。你可以先给它加上执行权限再运行:
chmod +x get_shell ./get_shell如果程序直接给你一个$或#提示符,说明它内部确实调用了/bin/sh。这时候你在本地也能敲命令,只是因为本地没有 flag 文件,所以看不出什么效果。这一步的意义是让你提前感受一下:这道题真的就是“白送”的。
2.4 第三步:用 nc 连接远程靶机
接下来就是重头戏。在终端里输入题目给出的 nc 命令:
nc 111.200.241.244 37153连接成功后,屏幕上通常会出现一个$提示符。如果没有立刻出现,先按一下回车看看。出现提示符后,先执行ls看看当前目录:
ls -la如果当前目录有flag文件,直接读取:
cat flag如果当前目录没有,再试试根目录下的/flag:
cat /flag一般来说,这道题的 flag 文件就在当前目录或根目录附近,多试两次就能找到。拿到flag{...}格式的内容之后,把它复制下来,回到攻防世界页面粘贴提交,分数就到手了。
整个过程就是这样。听起来很简单,但这正是这道题想让你掌握的:如何通过 nc 和远程二进制服务交互。
2.5 第四步:用 pwntools 脚本替代 nc
只靠 nc 已经能做完这道题,但既然要入门 pwn,还是建议尽早接触 pwntools。它是目前 pwn 方向最常用的 Python 工具库,能帮你完成远程连接、发送数据、接收数据、交互会话等一系列操作。
最简单的脚本是这样的:
from pwn import * io = remote('111.200.241.244', 37153) io.interactive()把remote里的主机和端口换成题目页面的实际信息,运行脚本后,同样会进入一个交互式 shell,你可以在里面执行cat flag。
如果想更自动化,也可以在脚本里直接发送cat flag并接收结果:
from pwn import * io = remote('111.200.241.244', 37153) io.sendline(b'cat flag') print(io.recvall().decode())这里sendline会发送一行命令并自动补上回车,recvall会接收程序返回的全部内容。这样的脚本会在后续题目里反复出现,越早熟悉越好。
3. 为什么连上就是 shell?题目背后的原理
3.1 服务端是如何把程序暴露出来的
很多新手做完 get_shell 之后会有个疑问:为什么我用 nc 连上去就是一个 shell?这背后其实是 CTF 平台常用的一种部署方式:服务端用 xinetd 或者 socat 把一个可执行程序绑定到某个端口上,每次有客户端连接,就执行一次这个程序。程序和客户端之间的输入输出都通过 TCP 端口传递。
你可以把服务端程序想象成一道“管道”:你通过 nc 发送的文本,会作为标准输入传给程序;程序的标准输出,又会通过端口回传给你。如果程序本身执行了system("/bin/sh"),那/bin/sh这个进程就会接管标准输入输出,你自然就获得了命令行的交互能力。
这和 web 题在浏览器里访问端口是完全不同的思路。pwn 题更接近“直接和某个程序对话”,只不过这个程序运行在远程服务器上。
3.2 一个简化版的 C 源码模型
虽然我们没有题目源码,但可以推测 get_shell 的 C 代码逻辑大概是这样的:
#include <stdio.h> #include <stdlib.h> int main() { setbuf(stdout, NULL); system("/bin/sh"); return 0; }setbuf(stdout, NULL)是为了关闭标准输出的缓冲,这样system("/bin/sh")产生的输出可以及时通过网络发送出来。system("/bin/sh")会启动一个新的 shell 进程,于是你和这个进程之间就有了完整的交互能力。
这个程序编译之后,运行就会直接进入 shell。题目提供的远程服务和这个模型基本一致,所以你连上去什么漏洞都不用触发,直接获得了 shell。
3.3 从 get_shell 到真正的漏洞利用
看到这里你可能觉得 pwn 太简单了。别急,get_shell 只是把最后一步提前给你。真正的 pwn 题不会直接调用system("/bin/sh"),而是需要你利用程序漏洞来人为构造这个调用。
常见的漏洞类型包括:
- 栈溢出:往缓冲区写入超长数据,覆盖返回地址,劫持程序流程;
- 格式化字符串:利用不安全的
printf读取或修改内存; - 堆溢出:在堆上修改指针或控制结构体;
- 整数溢出:绕过长度检查,造成异常内存访问。
但不管利用方式多复杂,最终目标往往都是让程序执行system("/bin/sh"),然后你通过 shell 读取 flag。所以 get_shell 就是提前让你感受这个终点,后面做漏洞利用时,你就知道自己在忙活什么了。
3.4 一个重要的边界提醒
我们在攻防世界这类合规的 CTF 平台上做题,是为了学习技术、提升能力,这是完全没有问题的。但同样要明确边界:不要拿这些方法去尝试未经授权的系统,也不要在真实服务器上做越权测试。合法授权和自主搭建的实验环境,才是练 pwn 的安全土壤。
4. pwn 环境配置:新手绕不开的硬骨头
4.1 为什么环境配置劝退这么多人
get_shell 这道题本身不难,但很多新手恰恰死在第一步:工具装不上、nc 打不开、Python 脚本报错。所以我想单独用一部分来聊聊 pwn 环境配置。这不是让你背诵一堆命令,而是把最容易踩坑的环节提前讲清楚。
pwn 方向常用的工具主要包括:netcat、gdb、pwntools、file、checksec,后面还会用到 IDA、ROPgadget、one_gadget 等等。对 get_shell 这道题,最核心的是 netcat 和 pwntools。
4.2 一步步配好基础环境
以 Ubuntu 为例,打开终端,先更新软件源:
sudo apt update sudo apt install -y netcat-openbsd gdb python3 python3-pip file binutils其中netcat-openbsd就是 nc 命令的常见实现。装完以后可以验证一下:
nc -h接着安装 pwntools:
pip3 install pwntools如果你用的是系统自带 Python,可能需要加--user参数或者用虚拟环境。在这里我推荐用venv建一个独立环境,避免污染系统 Python,尤其是后面装各种库的时候会省心很多:
python3 -m venv pwn_env source pwn_env/bin/activate pip install pwntools验证 pwntools 是否安装成功:
python3 -c "from pwn import *; print('ok')"如果打印出ok,说明环境已经就绪。
4.3 用 checksec 看看程序保护
拿到 get_shell 附件后,可以用 pwntools 自带的 checksec 功能查看程序的保护状态。在终端执行:
pwn checksec ./get_shell或者用 Python 方式查看:
from pwn import * e = ELF('./get_shell') print(e.checksec())输出里会出现RELRO、Stack Canary、NX、PIE等字样。这些是各种安全保护机制,后面做漏洞利用时会经常打交道。对 get_shell 这道题,即使你完全看不懂也没关系,先有一个概念就行。
4.4 本地先跑通,再连远程
我建议做 pwn 题时养成一个习惯:拿到附件先本地跑,本地没问题再连远程。这样可以区分“程序本身的问题”和“网络连接的问题”。
chmod +x ./get_shell ./get_shell本地能出$,说明程序正常。然后你再用 nc 连远程,发现问题就更容易定位。
5. 常见问题与排查技巧实录
5.1 nc 连不上怎么办
这是出现频率最高的问题。我把常见现象和解决办法整理成一张速查表:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
nc: command not found | 没安装 netcat | sudo apt install -y netcat-openbsd |
Connection refused | 端口不对或服务未启动 | 重新核对题目页面,换端口重试 |
Connection timed out | 网络不通或平台临时故障 | 换网络环境,或稍后再试 |
| 连接后立刻断开 | 服务端重启或 IP 限制 | 多试几次,必要时联系平台客服 |
另外,有些 Linux 发行版自带的是ncat,命令名字不同,你可以在终端输入ncat -h看看。如果只有ncat,也可以把题目里的nc替换成ncat来用。
5.2 连上了但没有提示符
有些程序可能不会打印任何提示字符,连接后屏幕就是一片空白。这时候别慌,直接输入命令按回车。比如:
ls如果还是没反应,检查一下是不是当前服务需要先发送某个数据才会响应。对 get_shell 来说,最可能的情况就是程序没有输出提示符,但 shell 已经启动。你敲完命令按回车,结果自然就出来了。
还有一个容易被忽略的点:远程 shell 可能没有回显你的输入,所以你看不到自己敲了什么,但命令是生效的。不要因为这个怀疑自己操作有问题。
5.3 找不到 flag 文件
如果连接成功,执行ls -la却看不到 flag,大概率是因为 flag 不在当前目录。可以按下面的顺序排查:
- 先执行
pwd看看当前目录; - 执行
ls -la查看当前目录所有文件,包括隐藏文件; - 尝试
cat /flag,很多服务端会把 flag 放在根目录; - 如果还找不到,用
find / -name "*flag*" 2>/dev/null搜索整个文件系统。
find命令可能比较慢,但能有效解决问题。注意输出的文件路径,然后cat对应路径即可。
5.4 提交 flag 不通过
题目平台提交 flag 时一般要求完整复制,包括开头的flag{和结尾的},中间内容一个字符都不能少。常见错误包括:
- 复制时多复制了空格或换行;
- 大小写不对;
- 把题目描述里的示例 flag 当成真实 flag 提交;
- 在远程 shell 里输出过多个内容,复制错了。
解决办法很简单:提交前先仔细核对,最好只复制flag{...}这个完整片段,不加任何多余字符。
5.5 我自己的踩坑记录
说个真实经历。我第一次做 get_shell 的时候,卡在 nc 上整整半小时。我当时用的是 Windows CMD,输入nc显示“不是内部或外部命令”,我以为题目有问题,疯狂换端口、换地址,结果发现就是本机没有 nc。后来装了 Git Bash,里面带了 nc,才终于连上。
还有一次,我连上远程后执行ls没有看到 flag,心里一慌,又开了一个新连接,反反复复试了十几次。最后才发现 flag 放在根目录,执行cat /flag就出来了。所以遇到问题先冷静,按顺序排查,多数情况下都不是什么大问题。
6. 做完 get_shell 之后怎么继续
6.1 下一阶段该练什么题
get_shell 只是一个开始。做通之后,接下来可以按这样的顺序往下练:
- 攻防世界的
hello_pwn:经典栈溢出入门,练返回地址覆盖; - 攻防世界的
CGfsb:格式化字符串漏洞入门; - 简单 ret2text:程序里已经有后门函数,让你劫持流程跳到它;
- ret2shellcode:在栈上写入 shellcode 并执行;
- 简单 ret2libc:通过泄露 libc 地址绕过防护。
这些名字在不同的 CTF 平台里可能会重复出现,核心知识点是一样的。每做完一类题,就总结一下漏洞特征和利用模板。
6.2 学习路线梳理
如果你真的想在 pwn 方向走下去,我建议补这几块知识:
- 二进制基础:ELF 文件格式、常见节区、程序入口;
- 汇编基础:x86 和 x64 常用指令,比如
push、pop、call、ret、leave; - 内存布局:栈、堆、BSS、数据段、代码段分别放什么;
- 调试工具:gdb 的使用、断点、内存查看;
- 逆向工具:IDA 或 Ghidra,用来分析程序逻辑。
不需要一次性全部学完,而是每做一道题,遇到不懂的概念再去查。这样记下来的知识是最牢固的。
6.3 学习习惯建议
我特别想说一点:做 pwn 题不要只看 writeup 然后照抄。那样做十道题,印象还是很浅。比较好的习惯是:
- 先自己尝试运行程序,观察输入输出;
- 用 checksec、file、strings 等工具收集信息;
- 根据信息猜测漏洞类型;
- 解不出来再看 writeup,看完了合上文章,自己重新做一遍。
另外,准备一个专门的笔记文件,把常用命令、工具参数、踩坑记录都写下来。学 pwn 很多时候比拼的是信息检索和熟练度,有自己的知识库会高效很多。
6.4 把 get_shell 封装成自动化脚本
作为收尾,分享一个小扩展:你可以把 get_shell 的拿 flag 过程写成一个更完整的 Python 脚本,方便下次遇到同类“白送 shell”的题目直接复用。大致思路是这样的:
from pwn import * import re def exploit(host, port): io = remote(host, port) io.sendline(b'cat flag') data = io.recvall() flag = re.findall(rb'flag\{[^}]+\}', data) if flag: print(flag[0].decode()) else: print(data.decode(errors='ignore')) if __name__ == '__main__': exploit('111.200.241.244', 37153)这个脚本做了三件事:连接远程、发送读取 flag 的命令、用正则提取 flag 内容。后续遇到更复杂的 pwn 题,你可以在exploit函数里加入漏洞利用 payload,思路是完全一样的。
说回我自己的体会,get_shell 这道题虽然简单,但它让我第一次真正理解了“远程程序交互”是怎么一回事。从那之后,我再看到 pwn 题,第一反应不再是害怕,而是先在脑子里过一遍“连接、分析、利用、读 flag”这套流程。希望这篇内容也能帮你跨过这个坎,顺利拿下自己第一个 pwn 题目的 flag。