虚拟机逃逸--QEMU案例学习
1. 介绍
当前虚拟机被大量部署用于个人使用和企业部门。网络安全供应商使用不同的虚拟机来分析受控和受限环境中的恶意软件。这样就产生了一个问题恶意软件能够从虚拟机中逃脱出去并且执行远程代码吗?
去年,来自CrowdStrike的Jason Geffner报告了一个严重的漏洞,QEMU影响虚拟软盘驱动器代码,允许攻击者从VM逃到主机。即使这个漏洞在netsec社区中受到了相当大的关注——可能是因为它有一个专门的名字(VENOM)——它也不是第一个。
在本文中,我们对CVE-2015-5165(内存泄漏漏洞)和CVE-2015-7504(基于堆的溢出漏洞)以及工作漏洞进行了深入分析。这两种利用的组合允许从VM中跳出来,在目标主机上执行代码。我们讨论了利用QEMU网卡设备模拟上的漏洞的技术细节,并提供了可重用的通用技术,以利用QEMU中未来的漏洞。例如,利用共享内存区域和共享代码的交互式bindshell。
2.什么是QEMU/KVM?
KVM(基于内核的虚拟机)是一个内核模块,它为用户空间程序提供了完整的虚拟化基础设施。它允许运行多个未修改过的Linux或Windows映像的虚拟机。
KVM的用户空间组件包含在主线QEMU(QuickEmulator)中,它专门处理设备仿真。
2.1 Workspace Environment
为了使那些想要使用本文中给出的示例代码的人更加容易,我们在这里提供了重现我们的开发环境的主要步骤。
犹豫漏洞已经修复, 我们需要切换到修复之前的版本。
git clone git://git.qemu-project.org/qemu.git
cd qemu
git checkout bd80b59
mkdir -p bin/debug/native
cd bin/debug/native
../../../configure --target-list=x86_64-softmmu --enable-debug --disable-werror
make
在我们的测试环境中,我们使用Gcc的4.9.2版本构建QEMU。
对于其余部分,我们假设阅读器已经有一个Linux x86_64映像,可以使用以下命令:
./qemu-system-x86_64 -enable-kvm -m 2048 -display vnc=:89
-netdev user,id=t0, -device rtl8139,netdev=t0,id=nic0
-netdev user,id=t1, -device pcnet,netdev=t1,id=nic1
-drive file=<path_to_image>,format=qcow2,if=ide,cache=writeback
2.2 QEMU Memory Layout
为来宾分配的物理内存实际上是QEMU虚拟地址空间中的一个mmapp'ed私有区域。重要的是要注意,在分配客户机的物理内存时没有启用PROT_EXEC标志。
下图说明了客户机的内存和主机的内存如何共存。
Virtual addr space | |
+--------------------+
| |
__ Page Table __
| | Guest kernel
+----+--------------------+----------------+
Guest's phy. memory | | | |
+----+--------------------+----------------+
| |
__ __
| QEMU process |
+----+------------------------------------------+
Virtual addr space | | |
+----+------------------------------------------+
| |
__ Page Table __
| |
+----+-----------------------------------------------++
Physical memory | | ||
+----+-----------------------------------------------++
另外,QEMU为BIOS和ROM保留了一个内存区域。
7f1824ecf000-7f1828000000 rw-p 00000000 00:00 0
7f1828000000-7f18a8000000 rw-p 00000000 00:00 0 [2 GB of RAM]
7f18a8000000-7f18a8992000 rw-p 00000000 00:00 0
7f18a8992000-7f18ac000000 ---p 00000000 00:00 0
7f18b5016000-7f18b501d000 r-xp 00000000 fd:00 262489 [first shared lib]
7f18b501d000-7f18b521c000 ---p 00007000 fd:00 262489 ...
7f18b521c000-7f18b521d000 r--p 00006000 fd:00 262489 ...
7f18b521d000-7f18b521e000 rw-p 00007000 fd:00 262489 ...
... [more shared libs]
7f18bc01c000-7f18bc5f4000 r-xp 00000000 fd:01 30022647 [qemu-system-x86_64]
7f18bc7f3000-7f18bc8c1000 r--p 005d7000 fd:01 30022647 ...
7f18bc8c1000-7f18bc943000 rw-p 006a5000 fd:01 30022647 ...
7f18bd328000-7f18becdd000 rw-p 00000000 00:00 0 [heap]
7ffded947000-7ffded968000 rw-p 00000000 00:00 0 [stack]
7ffded968000-7ffded96a000 r-xp 00000000 00:00 0 [vdso]
7ffded96a000-7ffded96c000 r--p 00000000 00:00 0 [vvar]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
2.3 地址转换
在QEMU中存在两个转换层:
--- 从客户虚拟地址到客户物理地址。在我们的exploit中,我们需要配置需要DMA访问的网卡设备。例如,我们需要提供Tx/Rx缓冲区的物理地址来正确配置网卡设备.
--- 从客户物理地址到QEMU的虚拟地址空间。在我们的利用中,我们需要注入假结构并在QEMU的虚拟地址空间中获得它们的精确地址。
在x64系统中,虚拟地址由页面偏移量(0-11位)和页码组成。在linux系统上,pagemap文件允许具有CAP_SYS_ADMIN权限的用户空间进程查找每个虚拟页面映射到哪个物理帧。pagemap文件为每个虚拟页面包含一个在kernel.org中有详细说明的64位值
- Bits 0-54 : physical frame number if present.
- Bit 55 : page table entry is soft-dirty.
- Bit 56 : page exclusively mapped.
- Bits 57-60 : zero
- Bit 61 : page is file-page or shared-anon.
- Bit 62 : page is swapped.
- Bit 63 : page is present.
为了将虚拟地址转换为物理地址,我们依赖于Nelson Elhage的代码3。下面的程序分配一个缓冲区,用字符串"Where are I?"填充它,并打印它的物理地址:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <inttypes.h>
#define PAGE_SHIFT 12
#define PAGE_SIZE (1 << PAGE_SHIFT)
#define PFN_PRESENT (1ull << 63)
#define PFN_PFN ((1ull << 55) - 1)
int fd;
uint32_t page_offset(uint32_t addr)
{
return addr & ((1 << PAGE_SHIFT) - 1);
}
uint64_t gva_to_gfn(void *addr)
{
uint64_t pme, gfn;
size_t offset;
offset = ((uintptr_t)addr >> 9) & ~7;
lseek(fd, offset, SEEK_SET);
read(fd, &pme, 8);
if (!(pme & PFN_PRESENT))
return -1;
gfn = pme & PFN_PFN;
return gfn;
}
uint64_t gva_to_gpa(void *addr)
{
uint64_t gfn = gva_to_gfn(addr);
assert(gfn != -1);
return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr);
}
int main()
{
uint8_t *ptr;
uint64_t ptr_mem;
fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
ptr = malloc(256);
strcpy(ptr, "Where am I?");
printf("%sn", ptr);
ptr_mem = gva_to_gpa(ptr);
printf("Your physical address is at 0x%"PRIx64"n", ptr_mem);
getchar();
return 0;
}
- MyBatis Generator自动创建代码
- Maven(六)之依赖管理
- 细说log4j
- SEVERE: Error configuring application listener of class org.springframework.web.context.ContextLoade
- TCP/IP(一)之开启计算机网络之路
- JSON入门指南--客户端处理JSON
- mysql5.7 ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
- TCP/IP中你不得不知的十大秘密
- Java Web开发学习之路2012版
- TortoiseSVN客户端使用的2个配置问题
- JavaWeb(二)会话管理之细说cookie与session
- 概率论09 期望
- Javascript中数组的sort()和reverse()方法
- CentOS6.5开放端口,配置防火墙
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 行为型设计模式:访问者模式
- redis实战第十篇 集群收缩
- redis实战第九篇 集群扩容自动迁移槽(redis-cli)
- 自动化运维| Ansible playbook的逻辑控制语句
- Windows通用应用平台UWP持久化
- redis实战第八篇 集群扩容 手动迁移槽
- Xinetd服务的安装与配置详解
- redis实战第七篇 使用redis工具(redis-cli)搭建redis cluster
- 使用ROS2机器人操作系统进行多机器人编程技术实践(Multi-Robot Programming Via ROS2 )
- 自动化运维实践 | Ansible playbook重用
- 手把手教你打造高效的 Kubernetes 命令行终端
- docker save load export import的区别
- 使用ABAC控制数据访问
- linux 压力测试工具之ab
- 什么是Python Wheels?为什么要关心它?