获取虚拟机的shell

badmonkey 2024年01月10日 407次浏览

获取虚拟机的shell

前言

很多读者可能会问,为什么要获取虚拟机shell?虚拟机不是自带root权限的shell嘛?实际上很多商业产品,商业防火墙,商业waf,商业邮件服务都是通过软件虚拟机进行部署的。而这类具有商业性质的产品都不会直接给一个root shell。但是作为安全研究人员,白盒审计要比黑盒测试的效率高的多,因此获取此类虚拟机的root shell,进行代码审计和漏洞挖掘是非常有必要的。

背景知识

大多数产品会使用定制化的linux虚拟机部署产品,因为基于linux的开发环境和组件已经十分成熟了。虚拟机的磁盘中保存了所有的后端逻辑代码,但是不同的厂家的安全措施做的不同,有的厂家会对虚拟机磁盘进行加密,而有的则不会。

磁盘挂载

一般来说可以通过下面几种方式进行磁盘挂载

  • qemu-nbd 挂载
sudo modprobe nbd max_part=8    # 加载 nbd 模块
sudo qemu-nbd --connect=/dev/nbd0 ./sonicwall.qcow2    # 挂载镜像
sudo fdisk /dev/nbd0 -l    # 查看分区信息
  • guestmount挂载
mkdir rootfs
sudo virt-filesystems -a img.qcow2 # 分区信息
sudo guestmount -a img.qcow2 -m /dev/sda4  rootfs # 挂载/dev/sda4 到rootfs
  • diskgenius 打开

Untitled

通常可以结合diskgenius查看分区信息,然后决定挂载那个分区

  • luks磁盘挂载
sudo cryptsetup --key-file key luksOpen /dev/xxx root
sudo mount /dev/mapper/root myrootfs1

Patch文件系统获得shell

所谓patch文件系统获得shell,其实是通过对磁盘进行挂载,修改系统的启动逻辑,增加一个telnet或者ssh启动进程,在系统启动后,研究人员可以通过ssh或者telnet拿到一个调试shell,而不影响系统的正常运行。但是patch文件系统需要对磁盘进行挂载,下面针对两种情况进行样例分析。

磁盘未被加密

如果磁盘本身没有进行加密,那么可通过直接挂载磁盘然后修改rc.local 等启动脚本,更改启动逻辑或者添加用户。

Untitled

SonicWall sma

本来想直接更改文件系统中的/etc/passwd 和 /etc/shadow 然后开启sshd获取root shell,但是修改后没有正常启动,经过分析发现固件启动时会更换内核及文件系统

https://img.badmonkey.site/202311141701849.png

因为需要对INITRD.gz 文件系统进行修改。可以挂载INITRD修改后对应的文件系统,然后使用guestmount挂载虚拟机磁盘并更新INITRD.gz。

sudo virt-filesystems -a old_sma.qcow2 ## find /dev/sda4
sudo guestmount -a old_sma.qcow2 -m /dev/sda4 rootfs
sudo su
cd rootfs
cp cf/firmware/current/INITRD.GZ ../ # patch INITRD.GZ
cp ../INITRD.gz cf/firmware/current/INITRD.GZ
cd ..
guestunmount rootfs

修改INITRD.GZ

mkdir ramfs
sudo su
mount -t ext2 INITRD ramfs/
cd ramfs
echo '#!/bin/bash' > ./usr/src/EasyAccess/bin/graphd # delete graphd
vim etc/passwd
vim etc/EasyAccess/etc/shadow
cp ../busybox bin/
chmod 777 bin/busybox
vim etc/rc.d/rc.local # open telent
cd ..
umount ramfs
gzip -9 -f INITRD

磁盘被加密

如果磁盘经过了加密,那么就需要通过调试的手段,获取加密密钥,然后对磁盘进行解密,然后在进行挂载和修改。

SonicWall NSV

首先挂载磁盘,发现磁盘被加密

Untitled

使用7zip解压后发现多个镜像使用了luks加密

Untitled

且使用了grub引导,根据grub配置文件发现kernel为/xen/pvboot-x86_64.elf,其中有一些新的grub配置信息

Untitled

因此可以确定是使用了luks对磁盘进行加密,然后在启动时对磁盘进行cryptomount,所以如果能够恢复密钥,那么就可以对文件系统进行patch,开启root shell。通过对luks的加解密关键函数进行定位可以找到boot分区下的luks.mod 对应了insmod luks

Untitled

根据grub_crypto_pbkdf2的定义可知,a4为密钥,因此调试时,dump出a4的值即可。这里根据链接1使用kvm进行调试,流程如下,新建虚拟机使用/dev/nbd0作为磁盘导入,然后使用virsh edit vm1

将第一行替换为

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>

devices标签后加入

<qemu:commandline>
   <qemu:arg value='-S'/>
   <qemu:arg value='-gdb'/>
   <qemu:arg value='tcp::1234'/>
 </qemu:commandline>

需要让虚拟机启动时找不到文件,从而进入救援模式,然后恢复文件,进行正常的grub引导和调试

Untitled

通过检索字符串,只发现了一处显然不是正确的函数地址,但是查看此地址的后面的值,发现是连续的字符串

Untitled

然后根据汇编代码进行find

Untitled

find /b 0x3ff56000,0x3ffff000,0x51,0x52,0x6a,0x20,0xff,0xb5 

Untitled

因此可以在0x3ff56890处下断点

Untitled

然后再断点处,dump $rdx 处的值即可

Untitled

实际上通过对luks.mod的逆向,可以发现luks的密钥生成逻辑是对luks的header进行了异或操作,然后得到luks的密钥。然后使用密钥进行挂载。详见链接2

Another

在实际漏洞挖掘的过程中遇到了一个产品,也是使用了加密的文件系统

其grub的配置为

Untitled

可以看到使用了decrypt_initrd的参数,加密格式也是luks

Untitled

通过对decrypt_initrd进行定位,首先将kernel.img进行解压提取其中发现了decryt_initrd等参数

Untitled

其中有一个debug字段以及quiet字段,其中quiet是一个内核启动参数,大胆的猜测debug参数也是一个内核启动时的参数,使用qemu启动内核

qemu-system-x86_64 -m 1024 -s -no-kvm -initrd initramfs.img -kernel kernel.img -append "decrypt_initrd decrypt debug no_timer_check elevator=loop product=spam"

开启debug后可以看到一系列的日志信息,其中可以看到decrypt的日志信息,根据关键字定位逻辑

Untitled

定位到解密逻辑

Untitled

继续跟进decrypt函数,发现其接受两个参数,很容易联想到其中一个参数为密文,一个为密钥

Untitled

因此可以在0xFFFFFFFF827B540D处下断点

Untitled

首先查看rdi寄存器的值

Untitled

而实际的initramfs.img的值

Untitled

结合两者可以知道第一个参数为密文地址,第二个参数为密钥,因此可以确定rdi会指向被加密的img,然后对其解密,所以可以在在0xFFFFFFFF827B5412下断点获取解密后的结果,解密后的明文为

Untitled

在此处dump出83923818字节就是被解密后的ramfs,最后可以在ramfs中发现luks密钥。

以上两个例子都是通过调试,直接或者间接的获得密钥,然后使用密钥进行luks挂载,patch文件系统,增加一个root shell。

Patch内存获得shell

相较于patch文件系统而言,patch内存来的更加直接。因为如果厂商使用的是各种奇奇怪怪的文件系统,加密方式,操作系统,那么通过调试获取密码更加困难。比如在实际分析的过程中发现过一个使用freebsd和geli加密的磁盘文件,

Untitled

patch内存的方式是通过对虚拟机打快照,然后劫持某个程序实现的,例如劫持cli为/bin/bash,当执行cli时,会执行一个sh,这样子就拿到了一个虚拟机的shell

Pulse Secure

早在blackhat 2019的时候orange就提出了使用内存patch的方式获得虚拟机shell的方式,但是没有具体操作的细节,首先时分析出了pulse secure在进行cli操作时,需要先按回车,然后进入一个config的界面,对应着按下回车后会执行一个/home/bin/dsconfig.pl

Untitled

因此考虑,在按下回车之前打一个快照,快照格式为vmem,使用01editor将所有的/home/bin/dsconfig.pl字符换成///////////////bin/sh

Untitled

可以劫持得到一个rootshell

Untitled

Another

实际分析到上文提到过的使用freebsd和geli加密的磁盘时,也是有一个类似的config界面

Untitled

这里通过对benchmarks关键字进行检索定位到了

Untitled

因此可以将此字段,进行劫持为////////////////////////////////////////bin/sh

然后执行run benchmarks,就可以得到一个交互式的shell

Untitled

实际上通过内存patch不仅可以实现shell的获取,还可以做更多的事情,比如关闭白名单限制等

Untitled

更多的可能性,还需要遇到实际问题做更多的探索。

参考链接

https://www.anquanke.com/post/id/266078

https://www.praetorian.com/blog/sonicwall-custom-grub-luks-encryption/

https://mambrui.github.io/2016/11/rooting-vm

https://sunichi.github.io/2019/02/13/Debug-Linux-Kernel-With-QEMU-KVM/

https://i.blackhat.com/USA-19/Wednesday/us-19-Tsai-Infiltrating-Corporate-Intranet-Like-NSA.pdf

https://hitcon.org/2023/CMT/agenda/93acdcc2-d9fd-493b-8b5a-3d8127e847d4/