记录一下DIR-815栈溢出漏洞的复现过程。

从官方漏洞报告:国家信息安全漏洞共享平台可以知道在D-Link DIR-645型号的hedwig.cgi中存在缓冲区溢出漏洞,但是实际上多个型号都存在这个漏洞,这里就以D-Link DIR-815为例进行分析。

基础环境与工具配置

haoo@cxh:~/Desktop$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 24.04.4 LTS
Release: 24.04
Codename: noble

这是我Ubuntu的版本号,lsb_release -a是用来查看当前Linux系统的发行版信息。

基础的pwn环境搭建可以借鉴★pwn 24.04环境搭建保姆级教程★_ubuntu24 pwn-CSDN博客这个师傅的博客,跟着一步一步搭建就行了。

binwalk工具安装

sudo apt install binwalk

sasquatch安装配置

sudo apt-get install build-essential zlib1g-dev liblzma-dev liblzo2-dev 
git clone https://github.com/devttys0/sasquatch.git
cd sasquatch
./build.sh

如果出现了下面这个问题,可以借鉴binwalk缺少sasquatch报错_unsquashfs.c:1835:5: error: this ‘if’ clause does -CSDN博客这个博客解决

unsquashfs.c:1835:5: error: this ‘if’ clause does not guard... [-Werror=misleading-indentation]
1835 | if(swap)
| ^~
unsquashfs.c:1841:9: note: ...this statement, but the latter is misleadingly indented as if it were guarded by the ‘if’
1841 | read_fs_bytes(fd, SQUASHFS_START, sizeof(struct squashfs_super_block),
| ^~~~~~~~~~~~~
cc1: all warnings being treated as errors
make: *** [<builtin>: unsquashfs.o] Error 1

当然还有可能出现下面这种问题

LZMA/lzma465/C/LzmaEnc.c:2161:19: error: storing the address of local variable ‘outStream’ in ‘*(CLzmaEnc *)pp.rc.outStream’ [-Werror=dangling-pointer=]
2161 | p->rc.outStream = &outStream.funcTable;
| ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
LZMA/lzma465/C/LzmaEnc.c:2145:20: note: ‘outStream’ declared here
2145 | CSeqOutStreamBuf outStream;
| ^~~~~~~~~
LZMA/lzma465/C/LzmaEnc.c:2139:45: note: ‘pp’ declared here
2139 | SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit,
| ~~~~~~~~~~~~~~~^~
cc1: all warnings being treated as errors
make: *** [<builtin>: LZMA/lzma465/C/LzmaEnc.o] Error 1

一个有效的方法就是应用PR #56的补丁

git pull origin pull/56/head

现在就可以使用binwalk对文件进行分解。legacyfiles.us.dlink.com - /DIR-815/REVA/FIRMWARE/在这个网站上进行附件下载,一般下载v1.01版本固件。

haoo@cxh:~/Desktop$ binwalk -Me ./DIR-815.bin
Traceback (most recent call last):
File "/usr/bin/binwalk", line 2, in <module>
from binwalk.__main__ import main
File "/usr/lib/python3/dist-packages/binwalk/__main__.py", line 24, in <module>
import binwalk.modules
File "/usr/lib/python3/dist-packages/binwalk/modules/__init__.py", line 3, in <module>
from binwalk.modules.disasm import Disasm
File "/usr/lib/python3/dist-packages/binwalk/modules/disasm.py", line 21, in <module>
class Disasm(Module):
File "/usr/lib/python3/dist-packages/binwalk/modules/disasm.py", line 60, in Disasm
Architecture(type=capstone.CS_ARCH_ARM64,
^^^^^^^^^^^^^^^^^^^^^^
AttributeError: module 'capstone' has no attribute 'CS_ARCH_ARM64'. Did you mean: 'CS_ARCH_ARM'?

在binwalk使用过程中可能会出现上面这个问题,意思是binwalk尝试访问capstone库中一个不存在的架构定义,出现这个问题的原因一般有:版本兼容性问题、依赖关系变化(因为最新版binwalk已经用rust重写了,移除了对capstone的依赖)、环境冲突。这里我就直接下载binwalk最新版本了,下载完成后,记得全局替换旧版的,否则不在指定文件夹下使用binwalk还是会报这个错误。这里借鉴Binwalk V3 安装 - ffff5 - 博客园这篇文章进行下载

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
. $HOME/.cargo/env
sudo apt install git
git clone https://github.com/ReFirmLabs/binwalk
sudo apt install build-essential libfontconfig1-dev liblzma-dev
cd binwalk
cargo build --release

验证一下

haoo@cxh:~/Desktop/binwalk$ ./target/release/binwalk -V
binwalk 3.1.1

进行全局替换

确定位置与PATH优先级
which -a binwalk
echo $PATH
把新版复制到/usr/local/bin/
sudo cp ~/Desktop/binwalk/target/release/binwalk /usr/local/bin/binwalk
sudo chmod +x /usr/local/bin/binwalk
刷新命令缓存
hash -r
验证
which -a binwalk
binwalk -V

弄好之后,使用binwalk将固件进行分解,这下就不会报错了,解释一下这个命令参数:-M表示递归提取,意思是提取出来的文件如果里面还有可提取内容,就继续提取;-e表示自动提取,根据扫描到的文件签名,调用相应的工具把内容解包提取出来。

大致的工具与基础的pwn环境就配置好了,qemu配置复现环境等漏洞分析完毕后复现时再进一步介绍。

前置知识

因为复现对象的架构不是x86,而是MIPS,有一些基础知识可能不知道,在这里简单的介绍一下。

寄存器

在MIPS32架构下,32个寄存器均为32位宽,有两种命名方式:数字编号($0-$31)、助记符命名($zero、$at、$v0……),除了$zero外,硬件上寄存器无强制功能限制,但是在软件层面约定必须遵守。

  • 首先是$0($zero,零寄存器),硬件层面永久固定为0,是MIPS中唯一只读寄存器,用于生成0、忽略返回值、简化指令。无论向其写入任何值,读取结果永远是0。
  • 接着是$2-$3($v0-$v1,函数返回值寄存器),存放函数执行后的返回值,是函数调用的核心寄存器。$v0为主返回值寄存器,存放整数、布尔值、系统调用等核心返回值;$v1为辅助返回值寄存器,仅当函数有两个返回值时,存放第二个返回值。
  • $4-$7($a0-$a3,函数参数寄存器),向被调用函数传递参数,若传递参数个数超过4个,则剩余参数通过栈传递。
  • $29($sp,栈指针寄存器),指向程序栈的栈顶,MIPS的栈地址从高到低,向下增长,有压栈和弹栈两种操作。
  • $31($ra,返回地址寄存器),存放函数调用后的返回地址,执行jal(跳转并链接)指令时,硬件会自动将下一条指令的地址写入$ra中,函数结束时,执行jr $ra指令,跳回$ra存放的地址继续执行。
  • $8-$15($t0-$t7)、$24-$25($t8-$t9)为临时寄存器,存放函数内部的临时变量,若函数调用其他函数时,这些寄存器的值可能会被覆盖,若需要保留$t系列寄存器的值,调用者需自行将其保存到栈上。
  • $16-$23($s0-$s7)为保存寄存器,主要存放需要跨函数调用保留的变量(如全局变量、函数中需复用的值),若被调用函数需要使用$s系列寄存器,必须先将其值压入栈中保存,用完后弹出栈恢复。
  • $1($at,汇编器临时寄存器),用于汇编器内部专用,用于处理伪指令,在编写汇编程序时禁止直接使用
  • $30($fp,帧指针寄存器),用于指向当前函数栈帧的基址,方便访问栈中的局部变量和参数
  • $26-$27($k0-$k1,内核/异常处理寄存器),用于操作系统内核、异常/中断处理程序专用,用户态禁止使用
  • $28($gp,全局指针寄存器),用于指向全局数据区的中间位置,可通过短偏移量快速访问全局变量,减少地址计算开销

基础汇编

有了存储的东西,就得对其进行相应的操作,下面简单介绍一些汇编,方便逆向时认得到某一条汇编语言干了什么

  • 加:add $s1,$s2,$s3意思是$s1=$s2+$s3
  • 减:sub $s1,$s2,$s3意思是$s1=$s2-$s3
  • 加立即数:addi $s1,$s2,100意思是$s1=$s2+100
  • 无符号加:addu $s1,$s2,$s3意思是$s1=$s2+$s3
  • 无符号减:subu $s1,$s2,$s3意思是$s1=$s2-$s3
  • 无符号加立即数:addiu $s1,$s2,100意思是$s1=$s2+100
  • 从协处理器寄存器中获得:mfc0 $s1,$epc意思是$s1=$epc
  • 乘:mult $s2,$s3意思是Hi,Lo=$s2*$s3
  • 无符号乘:multu $s2,$s3意思是Hi,Lo=$s2*$s3
  • 除:div $s2,$s3意思是Lo=$s2/$s3,Hi=$s2 mod $s3
  • 无符号除:divu $s2,$s3意思是Lo=$s2/$s3,Hi=$s2 mod $s3
  • 从Hi中获得:mfhi $s1意思是$s1=Hi
  • 从Lo中获得:mflo $s1意思是$s1=Lo
  • 取字:lw $s1,20($s2)意思是$s1=Memory[$s2+20]
  • 存字:sw $s1,20($s2)意思是Memory[$s2+20]=$s1
  • 取无符号半字:lhu $s1,20($s2)意思是$s1=Memory[$s2+20]
  • 存半字:sh $s1,20($s2)意思是Memory[$s2+20]=$s1
  • 取无符号字节:lbu $s1,20($s2)意思是$s1=Memory[$s2+20]
  • 存字节:sb $s1,20($s2)意思是Memory[$s2+20]=$s1
  • 取链接字:ll $s1,20($s2)意思是$s1=Memory[$s2+20]
  • 存条件字:sc $s1,20($s2)意思是Memory[$s2+20] = $s1,$s1=0或1
  • 立即数读入高16位:lui $s1,100,$s1=100*2^16
  • 与:AND $s1,$s2,$s3意思是$s1=$s2&$s3
  • 或:OR $s1,$s2,$s3意思是$s1=$s2|$s3
  • 或非:NOR $s1,$s2,$s3意思是$s1=~($s2|$s3)
  • 与立即数:ANDi $s1,$s2,100意思是$s1=$s2&100
  • 或立即数:ORi $s1,$s2,100意思是$s1=$s2|100
  • 逻辑左移:sll $s1,$s2,10意思是$s1=$s2<<10
  • 逻辑右移:srl $s1,$s2,10意思是$s1=$s2>>10
  • 相等时跳转:beq $s1,$s2,25意思是if($s1==$s2)跳转至PC+4+100
  • 不相等时跳转:bne $s1,$s2,25意思是if($s1!=$s2)跳转至PC+4+100
  • 小于时置位:slt $s1,$s2,$s3意思是if($s2<$s3)$s1=1否则$s1=0
  • 小于立即数时置位:slti $s1,$s2,100意思是if($s2<100)$s1=1否则$s1=0
  • 无符号数比较小于时置位:sltu $s1,$s2,$s3意思时if($s2<$s3)$s1=1否则$s1=0
  • 无符号数比较小于立即数时置位:sltiu $s1,$s2,100意思是if($s2<100)$s1=1否则$s1=0
  • 跳转:j 2500意思是跳转至1000
  • 跳转至寄存器所指的位置:jr $ra意思是跳转至$ra
  • 跳转并链接:jal 2500意思是$ra = PC+4;跳转到10000

漏洞分析

根据官方给的说明,漏洞在hedwig.cgi这里面,.cgi(通用网关接口)是web服务器用来调用外部程序的一种接口规范。因此现在先找一下这个文件并进行查看

haoo@cxh:~/Desktop/extractions/DIR-815.bin.extracted/F008C/squashfs-root$ find ./ -name hedwig.cgi
./htdocs/web/hedwig.cgi

找到文件所在目录后,定位到这里,并使用ls -la查看一下。这个指令的意思是列出当前目录下的所有文件并以详细信息显示

lrwxrwxrwx  1 haoo haoo   14 Sep 15 14:29 hedwig.cgi -> /htdocs/cgibin

这表明hedwig.cgi是一个软连接,相当于快捷方式,指向的是cgibin,意思就是我们访问、执行或读取hedwig.cgi时,系统实际上是去操作cgibin。找到这个文件,并查看一下是什么架构以及开启了哪些保护

haoo@cxh:~/Desktop/extractions/DIR-815.bin.extracted/F008C/squashfs-root/htdocs$ checksec cgibin
Segmentation fault (core dumped)

发生了段错误,说明checksec这个程序在运行过程中崩溃了,解决方法就是更新一下pwntools版本,太旧了可能会有一些小bug,这里更新用虚拟环境进行更新,因为PEP 668规定,可能版本太高不允许用系统pip直接往系统python里装包。

python3 -m venv ~/pwn-venv
source ~/pwn-venv/bin/activate
pip install -U pwntools
checksec cgibin
[*] '/home/haoo/Desktop/extractions/DIR-815.bin.extracted/F008C/squashfs-root/htdocs/cgibin'
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x400000)
Stack: Executable
RWX: Has RWX segments

是一个misp32位程序,没有开启任何保护。放入ida中进行分析一下,首先在main函数中找到hedwig.cgi

接着再进入hedwigcgi_main函数进行分析