www.zerodayinitiative.com/advisories/ZDI-21-451/

 

Home | Zero Day Initiative

 

www.zerodayinitiative.com

This vulnerability allows local attackers to disclose sensitive information on affected installations of Oracle VirtualBox. An attacker must first obtain the ability to execute high-privileged code on the target guest system in order to exploit this vulnerability.

The specific flaw exists within the VMSVGA virtual device. The issue results from an integer truncation before reading from memory. An attacker can leverage this in conjunction with other vulnerabilities to escalate privileges and execute arbitrary code in the context of the hypervisor.

 

*No more details will be disclosed.

*This post is not a disclosure

 

전에 ZDI에 제보한 버그가 CVE를 받음과 동시에 이번 버전에서 패치되었다. (6.1.20) 본인이 제보한 것 중 최초로 돈을 받은 건이어서 인상이 깊다.

 

www.oracle.com/security-alerts/cpuapr2021.html

 

공개는 ZDI에서 해당 취약점을 공개한 뒤에 이루어질 예정이다. 부연 설명을 붙이는 방식으로 이루어질 것 같다.

 

The details of CVE-2021-2266 will be released after ZDI releases this bug.

'보안 > 일지' 카테고리의 다른 글

추가 버그  (1) 2021.04.15
pwnable.xyz 올클  (1) 2021.02.21
커널 공부 근황 1  (0) 2021.02.18
CVE-2021-2127 VirtualBox  (1) 2021.01.20

최근에 버그를 하나 더 찾아서 ZDI에 리포트한 결과, 억셉되어 돈을 받을 수 있었다. 자세한 내용이나 얼마나 받았는지 등은 공개하지 못하겠으나 이렇게 혼자서 삽질하다가 이렇게 돈이 되는 취약점을 찾은 것은 처음이기에 자랑을 목적으로 글을 쓴다.

CVSS 스코어는 6.0을 받았다. 그리 낮지도, 높지도 않은 준수한 점수인 것 같다. 

 

자세한 내용은 ZDI에서 해당 내용을 공개하거나 명시된 데드라인이 지나서 공개할 것 같다.

'보안 > 일지' 카테고리의 다른 글

CVE-2021-2266 VirtualBox (ZDI-CAN-13464)  (2) 2021.04.22
pwnable.xyz 올클  (1) 2021.02.21
커널 공부 근황 1  (0) 2021.02.18
CVE-2021-2127 VirtualBox  (1) 2021.01.20

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

Before going on content...

I originally mentioned that this bug will be disclosed 3 months after the patch. However, I decided to release it today since 3 months seems to be too long... As long as there is an another researcher who disclosed his bugs at the patch date, it would be fine releasing this bug today.


High-level overview of the vulnerability and the possible effect of using it

A divide by zero vulnerability exists in vmsvga3dSurfaceDMA, in SVGA3D of Oracle VirtualBox 6.1.16 Linux distributions. To use this vulnerability, 3D acceleration option should be turned for a guest OS, and an attacker must get a root privilege in the guest OS.

This vulnerability allows local or remote attackers to crash VMs with 3D acceleration, leading to a denial-of-service condition. Flaws exist in three points: (1) Invalid surfaces can be defined in vmsvga3dSurfaceDefine, (2) Textures can be bound to invalid surfaces, (3) No check for divisor in vmsvga3dSurfaceDMA.

Exact product that was found to be vulnerable including complete version information

Version Information

Oracle VirtualBox 6.1.16 releases (which is the latest release in 2020/11/17) except for Windows are affected by this vulnerability. PoC code is tested with the release and Ubuntu 20.04.1 desktop for host and guest OSes, but it is also expected to work with other operating systems, except with Windows.

Precondition

An attacker must get a root privilege in a guest OS in order to use this vulnerability. Also, 3D acceleration option must be turned on for the guest OS to use this vulnerability. Finally, graphics controller option must be set to VMSVGA, which is the default option for Linux guest OSes.

Root Cause Analysis

Overview

A divide by zero vulnerability exists in vmsvga3dSurfaceDMA, which handles the SVGA_3D_CMD_SURFACE_DMA command in the SVGA FIFO loop. A VM crashes when the vulnerability is triggered, leading to the denial-of-service condition on the software. The vulnerability lies in the simple overflow check of the guest offset, before the offset gets verified by vmsvgaR3GmrTransfer. (See Code 1)

// Code 1

// Devices/Graphics/DevVGA-SVGA3d.cpp:610
/* srcx, srcy and srcz values are used to calculate the guest offset.
 * The offset will be verified by vmsvgaR3GmrTransfer, so just check for overflows here.
 */
AssertReturn(srcz < UINT32_MAX / pMipLevel->mipmapSize.height / cbGuestPitch, VERR_INVALID_PARAMETER);

cbGuestPitch can be zero with forged surfaces, leading to a divide by zero vulnerability. No check exists for pMipLevel->cbSurfacePitch, so cbGuestPitch can be zero. AssertReturn function remains as an if-else statement in a release version, so the vulnerability exists in a release version.

Three steps are needed to reach the vulnerable condition. These steps each uses a bug contained in its process.

  1. Two invalid surfaces, which have invalid mipmap sizes, are defined using a SVGA_3D_CMD_SURFACE_DEFINE command. One of two surfaces may be a valid surface. It doesn't affect the next step.
  2. Textures are bound to two surfaces using SVGA_3D_CMD_SURFACE_STRETCHBLT command. Two surfaces are each sent as source and destination surfaces.
  3. Divide by zero is triggered by using SVGA_3D_CMD_SURFACE_DMA command with guest.pitch = 0

Code flow from input to vmsvga* functions

Commands are asynchronously handled with the FIFO loop. (vmsvgaR3FifoLoop in Devices/Graphics/DevVGA-SVGA.cpp) Commands sent via VMware SVGA II PCI device are first enqueued to a SVGA command FIFO buffer, and they are dequeued and handled asynchronously in FIFO order by the loop.

Parsing and executing commands is done in vmsvgaR3FifoLoop function, which contains a simple switch-case statement to handle commands. With a given command id, the loop selects the method of parsing and handling the command. The loop eventually calls vmsvga~~ function to handle a command.

Step 1. Definition of invalid surfaces

Definition of an invalid surface is needed for to reach the vulnerable condition. Our goal is to make a surface that has the following conditions:

  1. Mipmap size is not zero so the clip box is not considered as empty in DMA command.
  2. cbSurfacePitch (derived from cBlocksX, cBlocksY, ...) is zero to trigger divide by zero.
  3. Texture is bound to the surface so that oglId is not zero.

In this step, a surface is defined with a non-zero mipmap size and a zero cbSurfacePitch value. A surface should not be defined in this condition, so this can be considered as a bug.

Making an invalid surface is simple: a surface can be defined with a negative size. (i.e. (-1, -1, -1)) Then, the definition fails in the condition described below. (See Code 2)

// Code 2

// Devices/Graphics/DevVGA-SVGA3d.cpp:278
if (RT_LIKELY(pSurface->cxBlock == 1 && pSurface->cyBlock == 1))
{
    cBlocksX = pMipmapLevel->mipmapSize.width;
    cBlocksY = pMipmapLevel->mipmapSize.height;
}
else
{
    (...)
}

(...)

const uint32_t cMaxBlocksX = cbMemRemaining / pSurface->cbBlock;
if (cBlocksX > cMaxBlocksX)
    return VERR_INVALID_PARAMETER;

cBlocksX becomes a negative value (i.e. -1), which is given as the argument of the command. cMaxBlocksX is 0x80000000 / n since cbMemRemaining remains 0x80000000 and pSurface->cbBlock becomes a small integer which is calculated from a given surface format. (i.e. 1, 2, 4, ..., see DevVGA-SVGA3d-shard.cpp:263 for details) Since the check is done with an unsigned comparison, cBlocksX is always bigger than cMaxBlocksX, so the check fails and the function returns.

A mipmap is already allocated before checking the validity of cBlocks, and the default value of a mipmap is zero for all other remaining values that are not initialized. Therefore, *cbSurfacePitch also becomes zero since it is not initialized. Mipmap size is already assigned before validation, so it is not zero. (See Code 3)

// Code 3

// Devices/Graphics/DevVGA-SVGA3d.cpp:223
for (uint32_t i=0; i < cMipLevels; i++)
    pSurface->paMipmapLevels[i].mipmapSize = paMipLevelSizes[i];

The outcome of step 1 is an object that looks like below. (Size is given as (-1, -1, -1)) Note that mipmapSize is not zero, and cbSurfacePitch is zero.

[*] surface
$2441 = 0x7fff6c7c49a0
$2442 = {
  id = 0x0,
  idWeakContextAssociation = 0xffffffff,
  surfaceFlags = 0x2,
  format = SVGA3D_BUFFER,
  internalFormatGL = 0xffffffff,
  formatGL = 0xffffffff,
  typeGL = 0xffffffff,
  enmOGLResType = VMSVGA3D_OGLRESTYPE_NONE,
  oglId = {
    texture = 0x0,
    buffer = 0x0,
    renderbuffer = 0x0
  },
  targetGL = 0x0,
  bindingGL = 0x0,
  fEmulated = 0x0,
  idEmulated = 0x0,
  faces = {{
      numMipLevels = 0x1
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }},
  cFaces = 0x1,
  cMipmapLevels = 0x1,
  paMipmapLevels = 0x7fff6fca48a0,
  multiSampleCount = 0x0,
  autogenFilter = SVGA3D_TEX_FILTER_NONE,
  cbBlock = 0x1,
  cxBlock = 0x1,
  cyBlock = 0x1,
  cbBlockGL = 0x1,
  fDirty = 0x0
}
[*] paMipmapLevel[0]
$2443 = {
  mipmapSize = {
    width = 0xffffffff,
    height = 0xffffffff,
    depth = 0xffffffff
  },
  cBlocksX = 0x0,
  cBlocksY = 0x0,
  cBlocks = 0x0,
  cbSurfacePitch = 0x0,
  cbSurfacePlane = 0x0,
  cbSurface = 0x0,
  pSurfaceData = 0x0,
  fDirty = 0x0
}

Step 2. Texture binding

Texture binding to the surface made in step 1 is needed to reach the vulnerable condition. In step 2, SVGA_3D_CMD_SURFACE_STRETCHBLT command is used to bind a texture. Draw primitive functions may be used to bind textures, but they are more complicated than vmsvga3dSurfaceStretchBlt function, so the function is used in step 2. Also, a buffer may be bound to a surface instead of binding a texture to it, but binding a texture is chosen because of the same reason.

vmsvga3dSurfaceStretchBlt needs two surfaces: one as a source surface and the other as a destination surface. One of two surfaces may be valid, but one of them must be the surface which is made in step 1 to exploit the vulnerability.

Codes described in Code 4 is used to create and bind the texture to the surfaces. Even if a surface is invalid, a texture will be created and bound to the surface. vmsvga3dBackCreateTexture function will create a texture and bind it to the surface.

// Code 4

// Devices/Graphics/DevVGA-SVGA3d.cpp:437
if (!VMSVGA3DSURFACE_HAS_HW_SURFACE(pSrcSurface))
{
    /* Unknown surface type; turn it into a texture, which can be used for other purposes too. */
    LogFunc(("unknown src sid=%u type=%d format=%d -> create texture\n", sidSrc, pSrcSurface->surfaceFlags, pSrcSurface->format));
    rc = vmsvga3dBackCreateTexture(pState, pContext, pContext->id, pSrcSurface);
    AssertRCReturn(rc, rc);
}

if (!VMSVGA3DSURFACE_HAS_HW_SURFACE(pDstSurface))
{
    /* Unknown surface type; turn it into a texture, which can be used for other purposes too. */
    LogFunc(("unknown dest sid=%u type=%d format=%d -> create texture\n", sidDst, pDstSurface->surfaceFlags, pDstSurface->format));
    rc = vmsvga3dBackCreateTexture(pState, pContext, pContext->id, pDstSurface);
    AssertRCReturn(rc, rc);
}

A texture should not be bound if a surface is invalid. Therefore, this can be also considered as a bug.

The outcome of step 2 is an object that looks like below. Note that oglId is not zero anymore.

[*] pSurface
$2453 = {
  id = 0x1,
  idWeakContextAssociation = 0xffffffff,
  surfaceFlags = 0x22,
  format = SVGA3D_BUFFER,
  internalFormatGL = 0xffffffff,
  formatGL = 0xffffffff,
  typeGL = 0xffffffff,
  enmOGLResType = VMSVGA3D_OGLRESTYPE_TEXTURE,
  oglId = {
    texture = 0x38,
    buffer = 0x38,
    renderbuffer = 0x38
  },
  targetGL = 0x806f,
  bindingGL = 0x806a,
  fEmulated = 0x0,
  idEmulated = 0x0,
  faces = {{
      numMipLevels = 0x1
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }, {
      numMipLevels = 0x0
    }},
  cFaces = 0x1,
  cMipmapLevels = 0x1,
  paMipmapLevels = 0x7fff6c7c17a0,
  multiSampleCount = 0x0,
  autogenFilter = SVGA3D_TEX_FILTER_NONE,
  cbBlock = 0x1,
  cxBlock = 0x1,
  cyBlock = 0x1,
  cbBlockGL = 0x1,
  fDirty = 0x0
}
[*] pMipLevel
$2454 = {
  mipmapSize = {
    width = 0xffffffff,
    height = 0xffffffff,
    depth = 0xffffffff
  },
  cBlocksX = 0x0,
  cBlocksY = 0x0,
  cBlocks = 0x0,
  cbSurfacePitch = 0x0,
  cbSurfacePlane = 0x0,
  cbSurface = 0x0,
  pSurfaceData = 0x0,
  fDirty = 0x0
}

Step 3. Triggering the bug

Divide by zero bug in vmsvga3dSurfaceDMA can now be triggered with the surface made in step 1 and step 2. The problematic code is described in Code 5.

// Code 5

// Devices/Graphics/DevVGA-SVGA3d.cpp:610
/* srcx, srcy and srcz values are used to calculate the guest offset.
 * The offset will be verified by vmsvgaR3GmrTransfer, so just check for overflows here.
 */
AssertReturn(srcz < UINT32_MAX / pMipLevel->mipmapSize.height / cbGuestPitch, VERR_INVALID_PARAMETER);

Here, divide by zero occurs when cbGuestPitch is zero. cbGuestPitch becomes zero if we give guest.pitch = 0. (guest.pitch is an argument of a DMA command given by the user) AssertReturn function remains as an if-else statement in a release version, so the vulnerability exists in a release version. See Code 6.

// Code 6

// Devices/Graphics/DevVGA-SVGA3d.cpp:597
uint32_t cbGuestPitch = guest.pitch;
if (cbGuestPitch == 0)
{
    /* Host must "assume image is tightly packed". Our surfaces are. */
    cbGuestPitch = pMipLevel->cbSurfacePitch;
}
else { (...) }

There are two checks that need to be bypassed to reach the vulnerable condition. One (Code 7) is bypassed by defining an invalid surface in step 1, and the other (Code 8) is bypassed by binding a texture to the invalid surface defined in step 2. The check in Code 7 requires a non-zero mipmap size, and the other check in Code 8 requires a non-zero oglId.

// Code 7

// Devices/Graphics/DevVGA-SVGA3d.cpp:536
/* The copybox's "dest" is coords in the host surface. Verify them against the surface's mipmap size. */
SVGA3dBox hostBox;
hostBox.x = paBoxes[i].x;
hostBox.y = paBoxes[i].y;
hostBox.z = paBoxes[i].z;
hostBox.w = paBoxes[i].w;
hostBox.h = paBoxes[i].h;
hostBox.d = paBoxes[i].d;
vmsvgaR3ClipBox(&pMipLevel->mipmapSize, &hostBox);

if (   !hostBox.w
    || !hostBox.h
    || !hostBox.d)
{
    Log(("Skip empty box\n"));
    continue;
}

// Devices/Graphics/DevVGA-SVGA.cpp:5274
/**
 * Unsigned coordinates in pBox. Clip to [0; pSize).
 *
 * @param   pSize   Source surface dimensions.
 * @param   pBox    Coordinates to be clipped.
 */
void vmsvgaR3ClipBox(const SVGA3dSize *pSize, SVGA3dBox *pBox)
{
    /* x, w */
    if (pBox->x > pSize->width)
        pBox->x = pSize->width;
    if (pBox->w > pSize->width - pBox->x)
        pBox->w = pSize->width - pBox->x;

    /* y, h */
    if (pBox->y > pSize->height)
        pBox->y = pSize->height;
    if (pBox->h > pSize->height - pBox->y)
        pBox->h = pSize->height - pBox->y;

    /* z, d */
    if (pBox->z > pSize->depth)
        pBox->z = pSize->depth;
    if (pBox->d > pSize->depth - pBox->z)
        pBox->d = pSize->depth - pBox->z;
}
// Code 8

// Devices/Graphics/DevVGA-SVGA3d.cpp:504
if (!VMSVGA3DSURFACE_HAS_HW_SURFACE(pSurface))
{
    /*
     * Not realized in host hardware/library yet, we have to work with
     * the copy of the data we've got in VMSVGA3DMIMAPLEVEL::pSurfaceData.
     */
    AssertReturn(pMipLevel->pSurfaceData, VERR_INTERNAL_ERROR);
}

// Devices/Graphics/DevVGA-SVGA3d-internal.h:683 (VMSVGA3D_DIRECT3D not defined in linux release)
/** @def VMSVGA3DSURFACE_HAS_HW_SURFACE
 * Checks whether the surface has a host hardware/library surface.
 * @returns true/false
 * @param   a_pSurface      The VMSVGA3d surface.
 */
#ifdef VMSVGA3D_DIRECT3D
# define VMSVGA3DSURFACE_HAS_HW_SURFACE(a_pSurface) ((a_pSurface)->u.pSurface != NULL)
#else
# define VMSVGA3DSURFACE_HAS_HW_SURFACE(a_pSurface) ((a_pSurface)->oglId.texture != OPENGL_INVALID_ID)
#endif

Vulnerable code can be reached by bypassing these two conditions. VM crashes with divide by zero, trying to divide with cbGuestPitch, which has zero value.

gdb-peda$ c
Continuing.

Thread 24 "VMSVGA FIFO" received signal SIGFPE, Arithmetic exception.
[Switching to Thread 0x7f3acc41f700 (LWP 73906)]
[----------------------------------registers-----------------------------------]
RAX: 0x1 
RBX: 0x0 
RCX: 0xffffffff 
RDX: 0x0 
RSI: 0x0 
RDI: 0x0 
RBP: 0x7f3acc41ed10 --> 0x7f3acc41ee50 --> 0x7f3acc41ee90 --> 0x7f3acc41eeb0 --> 0x7f3acc41eef0 --> 0x0 
RSP: 0x7f3acc41ec10 --> 0x400000001 
RIP: 0x7f3acd87b333 (div    ebx)
R8 : 0x0 
R9 : 0x0 
R10: 0x1 
R11: 0x2a ('*')
R12: 0x0 
R13: 0x7f3a94603ed0 --> 0xffffffff00000001 
R14: 0x7f3a9775f260 --> 0xffffffffffffffff 
R15: 0x7f3a94000b80 --> 0x0
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7f3acd87b32b:    mov    eax,ecx
   0x7f3acd87b32d:    div    DWORD PTR [r14+0x4]
   0x7f3acd87b331:    xor    edx,edx
=> 0x7f3acd87b333:    div    ebx
   0x7f3acd87b335:    cmp    eax,DWORD PTR [rbp-0xbc]
   0x7f3acd87b33b:    jbe    0x7f3acd87b568
   0x7f3acd87b341:    mov    eax,ecx
   0x7f3acd87b343:    xor    edx,edx
[------------------------------------stack-------------------------------------]
0000| 0x7f3acc41ec10 --> 0x400000001 
0008| 0x7f3acc41ec18 --> 0x7f3a94000b64 --> 0xfffffffe 
0016| 0x7f3acc41ec20 --> 0x7f3ad8134568 --> 0xffffeeee 
0024| 0x7f3acc41ec28 --> 0x22057251 
0032| 0x7f3acc41ec30 --> 0x0 
0040| 0x7f3acc41ec38 --> 0x7f3ad8134250 --> 0x5000000010 
0048| 0x7f3acc41ec40 --> 0x7f3acc6e4140 --> 0x7f3ab8000000 --> 0xff1d1d1dff1d1d1d 
0056| 0x7f3acc41ec48 --> 0x7f3acc6e4800 --> 0x8000000 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGFPE
0x00007f3acd87b333 in ?? () from /usr/lib/virtualbox/VBoxDD.so
gdb-peda$ bt
#0  0x00007f3acd87b333 in  () at /usr/lib/virtualbox/VBoxDD.so
#1  0x00007f3acd876bcb in  () at /usr/lib/virtualbox/VBoxDD.so
#2  0x00007f3b10724c8a in  () at /usr/lib/virtualbox/components/VBoxVMM.so
#3  0x00007f3b21f879e8 in  () at /usr/lib/virtualbox/VBoxRT.so
#4  0x00007f3b2204b452 in  () at /usr/lib/virtualbox/VBoxRT.so
#5  0x00007f3b2235f609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#6  0x00007f3b22280293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
gdb-peda$ vmmap
(...)
0x00007f3acd7c6000 0x00007f3acd7e8000 r--p    /usr/lib/virtualbox/VBoxDD.so
0x00007f3acd7e8000 0x00007f3acd94d000 r-xp    /usr/lib/virtualbox/VBoxDD.so
0x00007f3acd94d000 0x00007f3acdb7f000 r--p    /usr/lib/virtualbox/VBoxDD.so
0x00007f3acdb7f000 0x00007f3acdb80000 ---p    /usr/lib/virtualbox/VBoxDD.so
0x00007f3acdb80000 0x00007f3acdb93000 r--p    /usr/lib/virtualbox/VBoxDD.so
0x00007f3acdb93000 0x00007f3acdb9b000 rw-p    /usr/lib/virtualbox/VBoxDD.so
0x00007f3acdb9b000 0x00007f3ace201000 rw-p    mapped
(...)

Suggested Fixes

Three main causes exist in this vulnerability.

  1. An invalid surface, which has a non-zero mipmap size and zero cBlocks* and cbSurfacePitch, can be defined.
  2. A texture can be bound to an invalid surface.
  3. No check for the divisor, which is cbGuestPitch, in DMA command.

Fixing this vulnerability depends on the design choice that developers make. There are two options to fix this vulnerability.

  1. Deallocate the surface when defining a surface fails.
  2. Validate mipmaps of surfaces whether they have proper fields whenever surfaces are used in commands. For this vulnerability, there are problems in vmsvga3dSurfaceStretchBlt, vmsvga3dBackCreateTexture, and vmsvga3dSurfaceDMA. However, other commands may also contain problems.

Proof-of-Concept

Environment

Proof-of-Concept code is tested on VirtualBox 6.1.16 version (recent version in 2020-11-20), Linux distribution. Version for host and guest OSes are Ubuntu 20.04.1 desktop, but other OSes except Windows are supposed to work as well. 3D acceleration under display option should be turned on, and graphics controller option should remain VMSVGA, which is the default setting for display in Linux distribution.

Running PoC code

https://github.com/candymate/pwn/tree/master/CVE/CVE-2021-2127

PoC code should be run inside a guest. Thus, download the attached file inside a guest and extract the file.

libpciaccess-dev should be installed first to run the attached code. Run sudo apt install libpciaccess-dev first before running the code.

After installing the library, run make to compile the code. Next, run the compiled code with root privilege. (Do sudo ./poc)

Additional Information

Most of the codes are based on one of released PoC codes, which are in https://github.com/renorobert/virtualbox-vmsvga-bugs.

Software Download Link

VirtualBox release can be downloaded in https://www.virtualbox.org/wiki/Downloads. Linux distribution needs to be downloaded for this vulnerability. (PoC code is tested on Ubuntu 20.04.1 desktop for guest and host OSes)

Ubuntu 20.04.1 desktop image can be downloaded in https://releases.ubuntu.com/20.04/. This may be optional.


Patch

Patched at 6.1.18. Allocation (or Definition) of invalid surfaces is now prohibited. (See VBox/Devices/Graphics/DevVGA-SVGA3d.cpp)

 

VirtualBox SVGA에서 버그를 하나 발견하여 벤더에 제보했고, CVE 번호를 하나 받게 되었다. 버그를 제보하고 CVE까지 받는 것은 처음인데, 내 이름이 패치 내역에 있는 것이 상당히 기분이 좋았다. 프로젝트로 진행해서 열심히 삽질하고 한 것이 나름대로 보상이 된 것 같다.

 

www.oracle.com/security-alerts/cpujan2021.html

크레딧 내용에 있는 본인 CVE

 

저 자료를 보면 VirtualBoBs 팀으로 버그가 3개가 들어가 있는데, 팀에서 벤더에 직접 제보하여 이번에 패치된 3건, ZDI 제보해서 오퍼까지 받은게 3건으로 총 6건을 발견하여 제보하였다. 버그 헌팅 처음 하는 사람들끼리 모여서 단기간에 으쌰으쌰해서 열심히 삽질한 것이 어떻게 성과가 잘 나온 것 같다. 처음에 VM 관련해서 아무런 지식이 없어 원데이로 삽질하고, CVE 훑어보고, 심지어는 방법론 자체를 공부하는 등 여러 삽질을 했다. 그럼에도 이런 활동들을 나름 계획을 세우고, 서로 자료 공유도 열심히 하고, 코로나 때문에 얼굴 한번 보지 못했어도 간단하게 인력 관리 봇 하나 구현해서 운영까지 해가며 어케어케 하다보니 프로젝트가 잘 된 것 같다.

 

버그에 대한 자세한 내용은 3달 정도 더 기다린 후에 공개하려 한다. 어제 저녁쯤에 패치가 되어 현재 해결이 된 상태지만 패치 갭을 생각해서 3달 정도 있다가 하는게 맞는 것 같다.


The details of CVE-2021-2127 will be released after 3 months, considering patch gaps of softwares. Even though the bug was fixed yesterday, I think it is right to disclose the bug after 3 months, considering the gaps.

'보안 > 일지' 카테고리의 다른 글

CVE-2021-2266 VirtualBox (ZDI-CAN-13464)  (2) 2021.04.22
추가 버그  (1) 2021.04.15
pwnable.xyz 올클  (1) 2021.02.21
커널 공부 근황 1  (0) 2021.02.18

주의

지극히 개인적인 의견만 들어가 있습니다.

소개

이 문서에서는 QEMU USB에 대한 삽질 내용들을 다룬다. USB에 대한 퍼징 시도는 많지만 이를 VM 환경에서 수행한 사례가 많이 없기 때문에 USB 퍼저를 작성하려는 시도를 하려 한다. 또한, 그 시도에서 얻은 정보들을 이곳에 간략하게나마 정리하려 한다.

본문

VM 환경에서의 퍼징

VM 환경에서 퍼징하는데 많은 어려움이 있는데 이를 정리하면 아래와 같다.

  1. High Overhead : 현실적으로 VM을 매 테스트케이스마다 껐다가 킬 수 없다. 따라서 VM을 켜놓은 상태에서 지속적인 (Persistent) 방법으로 퍼징을 수행해야 한다.
    1. 지속적인 방법으로 퍼징을 하려 한다면 퍼징 루프를 잘 설계해서 상태를 매 루프마다 원래대로 복구시켜줄 필요가 있다. 이는 테스트를 진행하는 사람이 져야 하는 위험이다. (Risk)
    2. 지속적인 방법에서 피드백을 지속적으로 제공해줄 수 있는 프록시 시스템이 필요하다.
    3. 테스트 하고 싶은 함수를 따로 고립시켜서 테스트 하는 방법도 있을 수 있겠지만 이는 생각보다 어렵다. 타겟의 전체적인 구조를 파악한 뒤에 시도해볼 만한 방법이다.
  2. QEMU 모드 등을 사용 불가능하다. (Nested virtualization 환경에서 테스트를 제대로 할 수 있을까)
  3. Instrumentation을 하는데 어려움이 많다. 컴파일 오류 등 여러 문제가 발생한다.

Interaction (Kernel)

기본적으로 실행 옵션에 USB를 주면 /sys/devices 아래 pciXXX 라는 이름으로 폴더가 생성된다. USB와 인터랙션을 하려면 해당하는 pci 디바이스에 찾아가서 resourceN에 r/w를 수행하면 된다. 일반적인 경우에는 open :arrow_right: mmap :arrow_right: memory r/w를 수행하는 방식으로 USB를 사용한다. 추가로 DMA 버퍼를 두는 경우가 있는데, 이 경우에는 mmap으로 추가로 페이지를 할당받은 뒤 mlock을 이용하여 swap out이 되지 않도록 막는다. (DMA 버퍼로 사용한다)

참고 : https://www.kernel.org/doc/Documentation/filesystems/sysfs-pci.txt

어떤 PCI 디바이스가 원하는 USB 디바이스인지 확인하기 위해 lspci 명령어를 사용할 수 있다. 해당 명령어를 실행하면 아래처럼 결과가 나온다.

vm@vm:/sys/devices/pci0000:00/0000:00:01.0$ lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.2 USB controller: Intel Corporation 82371SB PIIX3 USB [Natoma/Triton II] (rev 01)
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 03)
00:02.0 VGA compatible controller: Device 1234:1111 (rev 02)
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)

우리가 원하는 디바이스는 USB 디바이스이므로 00:01.2 디바이스를 참조하면 된다. 위 경우에는 해당 디바이스가 /sys/devices/pci0000:00/0000:00:01.2에 위치하게 된다.

만약에 디바이스가 PCI가 아닌 캐릭터 디바이스 레벨로 접근을 하고 싶다면 /dev/bus/usb 폴더 안을 찾아보면 된다. 디바이스가 제대로 인식되는지 확인하려면 lsusb 명령어를 실행하여 리스트에 디바이스가 있는지 확인하면 된다. 캐릭터 디바이스도 PCI 디바이스와 마찬가지로 mmap과 메모리 연산을 통해 접근할 수 있다.

Analysis

Basic Structure

기본적으로 USB 에뮬레이터가 3가지 파트로 구성되어 있다.

  1. Raw data parsing
  2. State machine to track USB state
  3. Handler (read, can_read, event)

또한, 이들은 프런트엔드와 백엔드로 구성되어 있다. 아래 내용을 보자.

https://www.qemu.org/docs/master/qemu-doc.html#Character-device-options
A character device may be used in multiplexing mode by multiple front-ends. Specify mux=on to enable this mode. A multiplexer is a "1:N" device, and here the "1" end is your specified chardev backend, and the "N" end is the various parts of QEMU that can talk to a chardev. If you create a chardev with id=myid and mux=on, QEMU will create a multiplexer with your specified ID, and you can then configure multiple front ends to use that chardev ID for their input/output. Up to four different front ends can be connected to a single multiplexed chardev. (Without multiplexing enabled, a chardev can only be used by a single front end.) For instance you could use this to allow a single stdio chardev to be used by two serial ports and the QEMU monitor

프런트엔드와 백엔드 사이엔 MUX가 있어서 하나의 백엔드에 여러 프런트엔드를 연결할 수 있는 것으로 보인다. 즉, char device 하나로 두개의 디바이스를 제어할 수 있게 되는 것이다. 아래 링크의 단축키로 MUX를 제어할 수 있다.

https://www.qemu.org/docs/master/qemu-doc.html#mux_005fkeys

core

hw/usb/core.c에는 USB core 부분이 구현되어 있다. USB 탈부착이나 raw USB 패킷 파싱을 이 부분에서 진행한다.

dev-***, desc

각각의 디바이스에 해당하는 패킷을 처리하거나 특수한 에뮬레이션이 필요한 것들을 제공한다. 어떻게 보면 기본적인 드라이버 부분을 제공하는 것으로, 예를 들어 시리얼 통신의 경우 -serial 옵션을 주었을 경우 가상 디바이스를 생성해서 이를 에뮬레이션한다. 가상 디바이스에 들어오는 패킷들을 처리하거나 그에 맞는 동작들을 수행한다.

hcd-***

HCD (Host Controller Driver)에 해당하는 동작들을 수행한다. HCI 동작들을 수행하는 역할을 한다. USB 2.0, 3.0 등의 디바이스가 추가되었을 때 그에 알맞는 기능들을 수행한다.

Comment

전체적으로 state machine과 패킷 파싱 부분으로 깔끔하게 분리될 수 있고 각 부분이 워낙 명확하게 구현되어 있기 때문에 개발자가 실수할 부분이 많지가 않다. 그나마 취약점이 나올만한 부분이 HCD 쪽인데, 해당 부분을 테스트하기 힘들 뿐인 데다가 복잡도가 좀 있기 때문이다. 하지만 이 부분도 취약점이 나온 전적이 많지 않고 나왔더라도 임팩트가 낮은 취약점이어서 여기서 추가로 유의미한 취약점이 나올 수 있을지 의문이다.

결론

지금까지 USB에 대해 분석하였고, QEMU에서 어떤 부분들을, 또 어떻게 에뮬레이션해서 서비스를 제공하는지 알아보았다. USB는 기본적으로 좋은 벡터지만 QEMU의 경우 워낙 명확하게 패킷들을 처리하고 또 보안쪽으로 많이 신경썼기 때문에 (디바이스에 대한 퍼저가 따로 존재할 정도) 취약점이 나오기 힘들 것으로 사료된다.

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

fetch --nohooks chromium 고치기  (1) 2021.06.19
My Fuzzers  (2) 2021.02.21
CCID Protocol  (0) 2021.01.19
VirtualBox SVGA  (0) 2021.01.19

주의

여기에 남긴 내용은 지극히 개인적인 의견들입니다. 틀릴 가능성이 80% 이상입니다.

소개

CCID (Chip Card Interface Device) 프로토콜은 스마트카드 (USB) 에서 사용하는 프로토콜이다. 이는 스마트카드가 하나의 보안 토큰으로 사용될 수 있게 한다. (주로 Two-factor authentication에 사용된다) QEMU의 경우 스마트카드 사용 옵션을 붙여서 컴파일을 해주면 게스트에서 해당 디바이스를 사용할 수 있고, 이를 공략하기 위해 해당 프로토콜을 분석한다.

프로토콜에 대한 공식적인 문서는 링크에 포함되어 있다. 하지만, 프로토콜에 대한 설명이 너무 장황한 데에다가 하드웨어 부분들도 모두 설명되어 있기 때문에 공식 문서를 통해 프로토콜에 대한 정보를 얻는 것은 많이 어렵다. 따라서 본인은 QEMU에 구현된 에뮬레이터 코드를 보고 프로토콜에 대한 정보를 얻으려 한다.

본문

디바이스 특성

디바이스는 캐릭터 디바이스 형태로 구현되어 있으며 QEMU 게스트 안에서 /dev/bus/usb/001/002 형태로 나타난다. 디바이스 이름은 GemPC433-Swap으로 이를 검색해보면 스마트카드 디바이스임을 확인할 수 있다.

vm@vm:/dev/bus/usb/001$ lsusb
Bus 001 Device 002: ID 08e6:4433 Gemalto (was Gemplus) GemPC433-Swap
Bus 001 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

위에서 보이듯이 캐릭터 디바이스 형태이기 때문에 해당 디바이스를 접근하는 방법으로는 두가지가 있다. (1) 커널 모듈을 작성해서 캐릭터 디바이스와 통신하면 된다. 하지만 커널 모듈을 작성하고 올려보고 하는 과정이 많이 복잡하기 때문에 이보다는 다른 방법을 사용하는 것이 편하다. (2) 해당 디바이스 /dev/bus/usb/001/002를 통해 파일 읽기 및 쓰기를 하면서 통신할 수 있다. 자세한 내용은 아래 인용에 나와있다.

https://stackoverflow.com/questions/9276345/checking-simple-char-device-read-write-functions-in-linux

Documentation

자세한 내용을 설명하기 전에 참고할만한 자료들을 일부 소개해놓고 시작하려 한다.

  1. libcacard (Smartcard emulation library)
    1. Git page : https://gitlab.freedesktop.org/spice/libcacard
    2. Documentation : https://github.com/cedric-vincent/qemu/blob/master/docs/libcacard.txt
  2. CCID documentation in QEMU : https://github.com/qemu/qemu/blob/master/docs/ccid.txt
  3. APDU : https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit#:~:text=In%20the%20context%20of%20smart,security%20and%20commands%20for%20interchange.
  4. VSCard Protocol (QEMU) : https://wiki.qemu.org/Features/Smartcard
  5. CCID 발표자료 : https://pt.slideshare.net/ssuserf27290/what-is-smart-card-on-tam?smtNoRedir=1

디바이스 종류

QEMU에서 구현하는 디바이스는 두가지가 있다.

  1. passthru protocol을 사용하는 스마트카드 디바이스 (-device ccid-card-passthru)
  2. certificate 파일이나 실제 하드웨어를 기반으로 하는 NSS 벡엔드가 존재하는 에뮬레이터 (-device ccid-card-emulated)

위 두가지 경우 이외에도 실제 스마트카드 디바이스와 버스 역할만 해주는 경우도 있다. (-device usb-ccid)

APDU

APDU는 스마트카드와 단말기가 통신하는 메세지 단위다. 크게 Command APDU와 Response APDU로 나누어지며 Command APDU는 단말기가 카드에게 전달하는 메세지인 반면 Response APDU는 카드가 단말기에 전달하는 메세지다. 형식은 아래와 같다.

Mandatory Field는 CLA, INS, P1, P2, SW1, SW2이다. 나머지 바이트는 선택이다.

마지막으로, 통신 모델은 아래와 같다.

Emulated

Emulated의 경우 certificate 파일이나 실제 하드웨어를 기반으로 하는 NSS 백엔드가 존재한다. 실행 옵션에 -device ccid-card-emulated를 넣고 실행하면 게스트에서 접근할 수 있는 스마트카드 디바이스가 생성되고, 해당 디바이스를 통해 유저는 certificate 정보를 획득할 수 있다. NSS 백엔드의 상태에 따라 state machine에 transition이 발생하고 (ex - 카드 삽입, 제거, 읽기, ...) state에 따라 해당하는 액션이 취해진다.

전체적인 코드 구조는 USB와 동일하다. 간단한 state machine과 handler로 구현되어 있다. 복잡도가 그렇게 높지 않아 개발자가 실수할 포인트가 많지 않고 따라서 취약점이 나오기 힘들 것으로 생각된다. 또한, 게스트 입장에서 해당 디바이스를 공략할 수 있는 것은 백엔드와 상호작용하는 부분인데, 게스트 입장에서 가능한 것은 APDU로 요청을 보내서 APDU Response를 받아오는 것밖에 없다. 즉, APDU 패킷들이 하나의 벡터가 되는 것인데, 해당 패킷들을 처리하는 것이 복잡하지 않고 개발자가 실수할 수 있는 포인트가 적어 취약점이 나오기 힘들다.

Passthru

Passthrough의 경우 VSCard 프로토콜을 추가로 구현한다. 해당 프로토콜은 APDU 기반으로 동작하며, 시나리오는 아래와 같다.

https://github.com/qemu/qemu/blob/master/docs/ccid.txt

Passthrough도 마찬가지로 정해진 상태 머신을 따라가며 handler로 구성되어 차례대로 메세지를 처리하니 위와 마찬가지로 개발자가 실수할 수 있는 포인트가 적다. 따라서 마찬가지로 취약점이 나오기 힘들 것으로 사료된다.

결론

  1. CCID의 경우 공략할 부분이 passthru 밖에 없음. 왜냐면 certificate의 경우 호스트에서 파일을 제공하고 실행 옵션에 넣어야 하기 때문.
  2. 이유를 알지 못하지만 char device로 스마트 카드 디바이스가 접근이 불가능함. (Read는 되는데 Write가 안됨) 또한 Spice를 사용하면 스마트 카드에 대해 삽입/제거 시뮬레이션을 할 수 있다고 하는데 (shift-f8, shift-f9) 이를 사용하려면 별도의 컴파일 옵션과 실행 옵션이 필요해서 이게 게스트에서 사용할 수 있는 기능인가 의아함.
  3. CCID의 경우 사람들이 많이 사용하지 않는 기능으로 보임. Two-factor authentication으로 사용하는 이 기능은 개발자가 간략히 개발하고 넘어가는 것으로 생각됨. 하지만 생각보다 보안 관련 기능이다 보니 모듈 자체에 보안을 많이 신경썼다는 느낌이 강함.
  4. 프로토콜 정보의 경우 APDU에 대한 내용은 위키, CCID 자체에 대한 내용은 123쪽짜리 공식 문서가 있음.
  5. passthru에 관한 코드는 굉장히 짧음. State machine / handler 구조가 끝.
  6. libcacard라는 외부 라이브러리를 사용하는데 해당 라이브러리에는 libfuzzer로 구현된 자체 퍼저가 있음. 파싱 부분과 encoding / decoding 부분에 대한 퍼징을 수행함.
  7. 해당 부분이 터진 경우는 딱 한번 있는데, memory corruption 류나 logic bug 류가 아니라 단순하게 메모리가 계속 쌓여서 DoS가 발생하는 경우.

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

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

+ Recent posts