Fuzzer list that I and my team VirtualBoBs wrote during BoB project.

The levels of these fuzzers are not that high, but I think it is worth opening them.

Haven't found any bugs from these, but it is worth trying to run them, hunting bugs.

 

1. Network Protocol Fuzzer working in VMs (possibly targeting SLiRP)

https://github.com/VirtualBoBs/QEMUSLNetFuzz

 

2. VirtualBox VMSVGA 3d Fuzzer (possibly for VMware as well)

https://github.com/VirtualBoBs/VBox-SVGA3D-fuzzing 

'보안 > Bug Hunting' 카테고리의 다른 글

fetch --nohooks chromium 고치기  (1) 2021.06.19
QEMU USB Analysis and Fuzzing  (0) 2021.01.19
CCID Protocol  (0) 2021.01.19
VirtualBox SVGA  (0) 2021.01.19

소개

VirtualBox의 디스플레이 세팅을 보게 되면 Graphicss Controller와 Acceleration 세팅이 있다. 해당 컨트롤러에서 VMSVGA를 선택하면 SVGA를 사용하게 된다. Linux를 OS로 선택하면 3D acceleration 옵션까지 켤 수 있다.

이 문서에서는 SVGA의 내부 구조와 작동 방식에 대해 설명한다.

Contents

Graphics Device Architecture

SVGA는 해당 옵션이 켜져 있을 때 게스트에서 해당 디바이스의 커널 드라이버를 통해 접근할 수 있다. 크게 두 방법으로 접근 가능한데, (1) MMIO를 통한 FIFO 메모리 접근, (2) Port I/O를 통한 SVGA Register 접근이 가능하다. 해당 두 상태를 설정해놓으면 VM 구현 코드에서 데이터를 가져가 상태에 맞는 기능을 수행하게 된다. 위 그림의 경우 VMware 기준으로 설명이 되어 있지만 VirtualBox도 동일한 방식으로 작동한다.

SVGA Structure and Port I/O

SVGA의 경우 게스트 입장에서는 PCI 디바이스를 통해 접근할 수 있다. 위 도식에서 BAR 0, 1, 2는 각각 I/O 포트 번호, 글로벌 프레임 버퍼의 물리 주소, FIFO의 물리 주소를 나타낸다. IRQ의 경우 Interrupt Request의 약자로 인터럽트가 들어오는 것을 처리하는데 사용된다. I/O를 처리하는 것을 아래 코드로 살펴보자.

// VBox/Graphics/DevVGA.cpp:6661
pThis->hIoPortVmSvga    = NIL_IOMIOPORTHANDLE;
pThis->hMmio2VmSvgaFifo = NIL_PGMMMIO2HANDLE;
if (pThis->fVMSVGAEnabled)
{
    /* Register the io command ports. */
    rc = PDMDevHlpPCIIORegionCreateIo(pDevIns, pThis->pciRegions.iIO, 0x10, vmsvgaIOWrite, vmsvgaIORead, NULL /*pvUser*/,
                                      "VMSVGA", NULL /*paExtDescs*/, &pThis->hIoPortVmSvga);
    AssertRCReturn(rc, rc);

    rc = PDMDevHlpPCIIORegionCreateMmio2Ex(pDevIns, pThis->pciRegions.iFIFO, pThis->svga.cbFIFO,
                                           PCI_ADDRESS_SPACE_MEM, 0 /*fFlags*/, vmsvgaR3PciIORegionFifoMapUnmap,
                                           "VMSVGA-FIFO", (void **)&pThisCC->svga.pau32FIFO, &pThis->hMmio2VmSvgaFifo);
    AssertRCReturn(rc, PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
                                           N_("Failed to create VMSVGA FIFO (%u bytes)"), pThis->svga.cbFIFO));

    pPciDev->pfnRegionLoadChangeHookR3 = vgaR3PciRegionLoadChangeHook;
}

위 코드에서 I/O 핸들링을 어떻게 하는지, FIFO MMIO를 어떻게 처리할지에 대한 설정을 하고 있음을 확인할 수 있다. VRAM의 경우에는 VMSVGA 세팅에 상관없이 VGA에서 처리한다.

// VBox/Devices/Graphics/DevVGA.cpp:6680
/*
 * Allocate VRAM and create a PCI region for it.
 */
rc = PDMDevHlpPCIIORegionCreateMmio2Ex(pDevIns, pThis->pciRegions.iVRAM, pThis->vram_size,
                                       PCI_ADDRESS_SPACE_MEM_PREFETCH, 0 /*fFlags*/, vgaR3PciIORegionVRamMapUnmap,
                                       "VRam", (void **)&pThisCC->pbVRam, &pThis->hMmio2VRam);
AssertLogRelRCReturn(rc, PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
                                                 N_("Failed to allocate %u bytes of VRAM"), pThis->vram_size));

SVGA Handling

SVGA I/O나 MMIO로 접근하게 되면 vmsvgaIORead, vmsvgaIOWrite 콜백 함수들에서 해당 데이터들을 처리하게 된다. 들어오는 포트에 따라 이를 처리하는 루틴이 달라진다.

// VBox/Devices/Graphics/DevVGA-SVGA.cpp:2037
/**
 * @callback_method_impl{FNIOMIOPORTNEWIN}
 */
DECLCALLBACK(VBOXSTRICTRC) vmsvgaIORead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
{
    PVGASTATE   pThis = PDMDEVINS_2_DATA(pDevIns, PVGASTATE);
    RT_NOREF_PV(pvUser);

    /* Only dword accesses. */
    if (cb == 4)
    {
        switch (offPort)
        {
            case SVGA_INDEX_PORT:
                *pu32 = pThis->svga.u32IndexReg;
                break;

            case SVGA_VALUE_PORT:
                return vmsvgaReadPort(pDevIns, pThis, pu32);

            case SVGA_BIOS_PORT:
                Log(("Ignoring BIOS port read\n"));
                *pu32 = 0;
                break;

            case SVGA_IRQSTATUS_PORT:
                LogFlow(("vmsvgaIORead: SVGA_IRQSTATUS_PORT %x\n", pThis->svga.u32IrqStatus));
                *pu32 = pThis->svga.u32IrqStatus;
                break;

            default:
                ASSERT_GUEST_MSG_FAILED(("vmsvgaIORead: Unknown register %u was read from.\n", offPort));
                *pu32 = UINT32_MAX;
                break;
        }
    }
    else
    {
        Log(("Ignoring non-dword I/O port read at %x cb=%d\n", offPort, cb));
        *pu32 = UINT32_MAX;
    }
    return VINF_SUCCESS;
}

vmsvgaReadPortvmsvgaWritePort에서는 SVGA Register 값들을 세팅하게 된다. 예를들어, SVGA_REG_ID로 값을 읽으려 시도하면 SVGA id가 리턴된다.

반면에 FIFO 메모리와 VRAM의 경우 직접 해당 메모리 영역에 값을 쓰게 된다. (각각 pThisCC->svga.pau32FIFO, pThisCC->pbVRam) FIFO의 경우 이후 FIFO loop에서 커맨드를 하나씩 잘라서 처리하게 된다.

SVGA PCI Device

게스트 입장에서 SVGA 디바이스에 접근할 때에는 PCI 디바이스를 통해 접근할 수 있다. 게스트 입장에서 볼 수 있는 PCI 디바이스는 다음과 같다.

vm@vm:~$ lspci
(...)
00:02.0 VGA compatible controller: VMware SVGA II Adapter
(...)

코드상으로 해당 PCI 디바이스를 찾아 연결하기 위해서 vendor와 device id를 맞춰주어야 한다. 해당 id들은 svga_reg.h에 정의되어 있다.

// VBox/Devices/Graphics/vmsvga/svga_reg.h:41
/*
 * PCI device IDs.
 */
#define PCI_VENDOR_ID_VMWARE            0x15AD
#define PCI_DEVICE_ID_VMWARE_SVGA2      0x0405

코드상으로 연결하는 방법은 다양한 방법이 있지만 libpciaccess를 이용하여 연결하는 예시 코드는 아래와 같다.

// https://github.com/renorobert/virtualbox-vmsvga-bugs/blob/master/CVE-2017-10210/svga.c
int conf_svga_device(void)
{
    struct pci_device *dev;
    struct pci_device_iterator *iter;
    struct pci_id_match match;
    uint16_t command;

    if (getuid() != 0 || geteuid() != 0) 
        errx(EXIT_FAILURE, "[!] Run program as root");

    iopl(3);

    if (pci_system_init())
        return -1;

    match.vendor_id = PCI_VENDOR_ID_VMWARE;
    match.device_id = PCI_DEVICE_ID_VMWARE_SVGA2;
    match.subvendor_id = PCI_MATCH_ANY;
    match.subdevice_id = PCI_MATCH_ANY;
    match.device_class = 0;
    match.device_class_mask = 0;

    iter = pci_id_match_iterator_create(&match);
    dev = pci_device_next(iter);

    if (dev == NULL) {
        pci_cleanup(iter);
        return -1;
    }

    pci_device_probe(dev);

    gSVGA.ioBase = dev->regions[0].base_addr;
    gSVGA.fbMem = (void *)dev->regions[1].base_addr;
    gSVGA.fifoMem = (void *)dev->regions[2].base_addr;

    command = pci_device_cfg_read_u16(dev, 0, 4);
    pci_device_cfg_write_u16(dev, command | 7, 4);

    SVGA_WriteReg(SVGA_REG_ID, SVGA_ID_2);
    SVGA_WriteReg(SVGA_REG_ENABLE, true);

    gSVGA.vramSize = SVGA_ReadReg(SVGA_REG_VRAM_SIZE);
    gSVGA.fbSize = SVGA_ReadReg(SVGA_REG_FB_SIZE);
    gSVGA.fifoSize = SVGA_ReadReg(SVGA_REG_MEM_SIZE);

    pci_device_map_range(dev, (pciaddr_t)gSVGA.fbMem, (pciaddr_t)gSVGA.fbSize,
            PCI_DEV_MAP_FLAG_WRITABLE, (void *)&gSVGA.fbMem);
    pci_device_map_range(dev, (pciaddr_t)gSVGA.fifoMem, (pciaddr_t)gSVGA.fifoSize,
            PCI_DEV_MAP_FLAG_WRITABLE, (void *)&gSVGA.fifoMem);

    pci_cleanup(iter);

    return 0;
}

FIFO Command Handling

FIFO buffer로 MMIO를 수행하게 되면 FIFO loop (vmsvgaR3FifoLoop)에서 커맨드로 잘라서 해당하는 커맨드를 수행하게 된다. 기본적으로 커맨드 구조는 아래를 따른다.

  1. 커맨드 넘버

    // VBox/Devices/Graphics/vmsvga/svga_reg.h:1010
    typedef enum {
    SVGA_CMD_INVALID_CMD           = 0,
    SVGA_CMD_UPDATE                = 1,
    SVGA_CMD_RECT_COPY             = 3,
    SVGA_CMD_DEFINE_CURSOR         = 19,
    SVGA_CMD_DEFINE_ALPHA_CURSOR   = 22,
    SVGA_CMD_UPDATE_VERBOSE        = 25,
    SVGA_CMD_FRONT_ROP_FILL        = 29,
    SVGA_CMD_FENCE                 = 30,
    SVGA_CMD_ESCAPE                = 33,
    SVGA_CMD_DEFINE_SCREEN         = 34,
    SVGA_CMD_DESTROY_SCREEN        = 35,
    SVGA_CMD_DEFINE_GMRFB          = 36,
    SVGA_CMD_BLIT_GMRFB_TO_SCREEN  = 37,
    SVGA_CMD_BLIT_SCREEN_TO_GMRFB  = 38,
    SVGA_CMD_ANNOTATION_FILL       = 39,
    SVGA_CMD_ANNOTATION_COPY       = 40,
    SVGA_CMD_DEFINE_GMR2           = 41,
    SVGA_CMD_REMAP_GMR2            = 42,
    SVGA_CMD_MAX
    } SVGAFifoCmdId;
    
    // VBox/Devices/Graphics/vmsvga/svga3d\_reg.h:1031  
    #define SVGA\_3D\_CMD\_LEGACY\_BASE 1000  
    #define SVGA\_3D\_CMD\_BASE 1040
    
    #define SVGA\_3D\_CMD\_SURFACE\_DEFINE SVGA\_3D\_CMD\_BASE + 0 // Deprecated  
    #define SVGA\_3D\_CMD\_SURFACE\_DESTROY SVGA\_3D\_CMD\_BASE + 1  
    #define SVGA\_3D\_CMD\_SURFACE\_COPY SVGA\_3D\_CMD\_BASE + 2  
    #define SVGA\_3D\_CMD\_SURFACE\_STRETCHBLT SVGA\_3D\_CMD\_BASE + 3  
    #define SVGA\_3D\_CMD\_SURFACE\_DMA SVGA\_3D\_CMD\_BASE + 4  
    #define SVGA\_3D\_CMD\_CONTEXT\_DEFINE SVGA\_3D\_CMD\_BASE + 5  
    #define SVGA\_3D\_CMD\_CONTEXT\_DESTROY SVGA\_3D\_CMD\_BASE + 6  
    #define SVGA\_3D\_CMD\_SETTRANSFORM SVGA\_3D\_CMD\_BASE + 7  
    #define SVGA\_3D\_CMD\_SETZRANGE SVGA\_3D\_CMD\_BASE + 8  
    #define SVGA\_3D\_CMD\_SETRENDERSTATE SVGA\_3D\_CMD\_BASE + 9  
    #define SVGA\_3D\_CMD\_SETRENDERTARGET SVGA\_3D\_CMD\_BASE + 10  
    #define SVGA\_3D\_CMD\_SETTEXTURESTATE SVGA\_3D\_CMD\_BASE + 11  
    #define SVGA\_3D\_CMD\_SETMATERIAL SVGA\_3D\_CMD\_BASE + 12  
    #define SVGA\_3D\_CMD\_SETLIGHTDATA SVGA\_3D\_CMD\_BASE + 13  
    #define SVGA\_3D\_CMD\_SETLIGHTENABLED SVGA\_3D\_CMD\_BASE + 14  
    #define SVGA\_3D\_CMD\_SETVIEWPORT SVGA\_3D\_CMD\_BASE + 15  
    #define SVGA\_3D\_CMD\_SETCLIPPLANE SVGA\_3D\_CMD\_BASE + 16  
    #define SVGA\_3D\_CMD\_CLEAR SVGA\_3D\_CMD\_BASE + 17  
    #define SVGA\_3D\_CMD\_PRESENT SVGA\_3D\_CMD\_BASE + 18 // Deprecated  
    #define SVGA\_3D\_CMD\_SHADER\_DEFINE SVGA\_3D\_CMD\_BASE + 19  
    #define SVGA\_3D\_CMD\_SHADER\_DESTROY SVGA\_3D\_CMD\_BASE + 20  
    #define SVGA\_3D\_CMD\_SET\_SHADER SVGA\_3D\_CMD\_BASE + 21  
    #define SVGA\_3D\_CMD\_SET\_SHADER\_CONST SVGA\_3D\_CMD\_BASE + 22  
    #define SVGA\_3D\_CMD\_DRAW\_PRIMITIVES SVGA\_3D\_CMD\_BASE + 23  
    #define SVGA\_3D\_CMD\_SETSCISSORRECT SVGA\_3D\_CMD\_BASE + 24  
    #define SVGA\_3D\_CMD\_BEGIN\_QUERY SVGA\_3D\_CMD\_BASE + 25  
    #define SVGA\_3D\_CMD\_END\_QUERY SVGA\_3D\_CMD\_BASE + 26  
    #define SVGA\_3D\_CMD\_WAIT\_FOR\_QUERY SVGA\_3D\_CMD\_BASE + 27  
    #define SVGA\_3D\_CMD\_PRESENT\_READBACK SVGA\_3D\_CMD\_BASE + 28 // Deprecated  
    #define SVGA\_3D\_CMD\_BLIT\_SURFACE\_TO\_SCREEN SVGA\_3D\_CMD\_BASE + 29  
    #define SVGA\_3D\_CMD\_SURFACE\_DEFINE\_V2 SVGA\_3D\_CMD\_BASE + 30  
    #define SVGA\_3D\_CMD\_GENERATE\_MIPMAPS SVGA\_3D\_CMD\_BASE + 31  
    #define SVGA\_3D\_CMD\_ACTIVATE\_SURFACE SVGA\_3D\_CMD\_BASE + 40  
    #define SVGA\_3D\_CMD\_DEACTIVATE\_SURFACE SVGA\_3D\_CMD\_BASE + 41  
    #define SVGA\_3D\_CMD\_MAX SVGA\_3D\_CMD\_BASE + 42
  2. 커맨드 헤더 (id (지워짐), header size)

    // VBox/Devices/Graphics/vmsvga/svga3d_reg.h:1126
    /*
    * The data size header following cmdNum for every 3d command
    */
    typedef
    struct {
    /* uint32_t               id; duplicate*/
    uint32_t               size;
    } SVGA3dCmdHeader;
  3. 커맨드 body

    // example of command body
    
    STRUCTURE <svga3d\_reg.struct\_c\_\_SA\_SVGA3dCmdSetRenderState object at 0x7fbbcbe0a7b8>  
    FIELD cid (type=<class 'ctypes.c\_uint'>, bitlen=32)  
    VALUE = 103 (type=<class 'int'>)  
    STRUCTURE <svga3d\_reg.struct\_c\_\_SA\_SVGA3dRenderState object at 0x7fbbcbe0a7b8>  
    FIELD state (type=<class 'ctypes.c\_int'>, bitlen=32)  
    VALUE = 700967841 (type=<class 'int'>)  
    FIELD \_1 (type=<class 'svga3d\_reg.union\_c\_\_SA\_SVGA3dRenderState\_0'>, bitlen=32)  
    STRUCTURE <svga3d\_reg.union\_c\_\_SA\_SVGA3dRenderState\_0 object at 0x7fbbcbe0a8c8>  
    FIELD uintValue (type=<class 'ctypes.c\_uint'>, bitlen=32)  
    VALUE = 1482925787 (type=<class 'int'>)  
    FIELD floatValue (type=<class 'ctypes.c\_float'>, bitlen=32)  
    VALUE = 1001223113146368.0 (type=<class 'float'>)

4바이트씩 잘라서 MMIO로 데이터를 넘기면 FIFO buffer에 차례대로 들어간다. 이후 FIFO loop에서 커맨드 번호를 읽고, 헤더 사이즈를 읽은 뒤 헤더사이즈에 맞게 FIFO buffer에서 dequeue를 해서 커맨드를 읽는다. 읽은 커맨드는 커맨드 종류에 맞춰 기능을 수행하게 된다.

SVGA state에 따라 external command를 수행하는 경우도 있다. External command에는 reset, power off, save / load state 등이 있는데, SVGA 커맨드에 상관 없이 외부 요인에 의해 실행된다. (PCI command로 실행시킬 수도 있다. ex - /sys/bus/pci/devices/<device no.>/power)

// VBox/Devices/Graphics/DevVGA-SVGA.cpp:3529 (inside vmsvgaR3FifoLoop)
/*
 * Special mode where we only execute an external command and the go back
 * to being suspended.  Currently, all ext cmds ends up here, with the reset
 * one also being eligble for runtime execution further down as well.
 */
if (pThis->svga.fFifoExtCommandWakeup)
{
    vmsvgaR3FifoHandleExtCmd(pDevIns, pThis, pThisCC);
    while (pThread->enmState == PDMTHREADSTATE_RUNNING)
        if (pThis->svga.u8FIFOExtCommand == VMSVGA_FIFO_EXTCMD_NONE)
            PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pThis->svga.hFIFORequestSem, RT_MS_1MIN);
        else
            vmsvgaR3FifoHandleExtCmd(pDevIns, pThis, pThisCC);
    return VINF_SUCCESS;
}

References

  1. VMware SVGA Device Developer Kit: https://sourceforge.net/projects/vmware-svga/ (Alternative: https://github.com/prepare/vmware-svga)
  2. Straight outta VMware: Modern exploitation of the SVGA device for guest-to-host escape exploits (Blackhat Europe 2018): https://i.blackhat.com/eu-18/Thu-Dec-6/eu-18-Sialveras-Straight-Outta-VMware-Modern-Exploitation-Of-The-SVGA-Device-For-Guest-To-Host-Escapes.pdf
  3. https://github.com/renorobert/virtualbox-vmsvga-bugs

'보안 > Bug Hunting' 카테고리의 다른 글

fetch --nohooks chromium 고치기  (1) 2021.06.19
My Fuzzers  (2) 2021.02.21
QEMU USB Analysis and Fuzzing  (0) 2021.01.19
CCID Protocol  (0) 2021.01.19

+ Recent posts