0%

VM Escape using CVE-2019-2525/2548 (1)

  • CVE-2019-2525과 CVE-2019-2548을 이용한 VM exploit.

Background

3D Acceleration

  • VirtaulBox는 3D Acceleration를 제공한다.
  • 보통 VM에서 3D기능을 사용하면 속도가 느려지는데
  • 3D Acceleration는 호스트(실제 컴퓨터의)의 3D 하드웨어를 사용하게 한다.
  • (가상머신 -> 실제 하드웨어 접근)

Chromium

  • 3D Acceleration은 Chromium 라이브러리를 기반으로 만들어졌다.
  • Chromium은 OpenGL기반으로 3D Graphic을 ‘remote rendering’ 할 수 있는 라이브러리다.
  • Client/Server 구조
  • VBox는 Chromium에 새로운 프로토콜을 추가했다.
    • VBoxHGCM
    • HGCM : Host/Guest Communication Manager
  • 이 프로토콜을 사용하면, GuestOS에서 실행 중인 Chromium Client가 Host OS 실행 중인 Chromium 서버와 통신이 가능하다.
  • Linux에서는 /dev/vboxuser, /dev/vboxguest로 IOCTL 통신하는데,
  • 이를 쉽게 사용할 수 있도록 구현한 Chromium python library가 존재한다. (3dpwn)

3dpwn (VBoxHGCM wrapper) library

  • Client의 역할은 connect, disconnect, send chromium message이다.
  • CRMessage Structure

제목 없음

1
2
3
4
5
6
7
8
9
10
11
12
typedef union {
CRMessageHeader header;
CRMessageOpcodes opcodes;
CRMessageRedirPtr redirptr;
CRMessageWriteback writeback;
CRMessageReadback readback;
CRMessageReadPixels readPixels;
CRMessageMulti multi;
CRMessageFlowControl flowControl;
CRMessageNewClient newclient;
CRMessageGather gather;
} CRMessage;
1
2
3
4
typedef struct {
CRMessageType type;
unsigned int conn_id;
} CRMessageHeader;
1
2
3
4
typedef struct CRMessageOpcodes {
CRMessageHeader header;
unsigned int numOpcodes;
} CRMessageOpcodes;
  • Client가 보내야할 Message의 구조는 위와 같이 Opcode + data의 형태이다.
  • 통신에 필요한 주요 함수
  • hgcm_connect
  • hgcm_disconnect
  • hgcm_call(conn_id,function,params)
  • function
    • SHCRGL_GUEST_FN_WRITE_BUFFER
    • SHCRGL_GUEST_FN_WRITE_READ_BUFFERED
  • 3dpwn에서는 crmsg함수를 통해 통신한다.

Flow

1
2
3
4
5
6
7
8
9
10
11
def alloc_buf(client, sz, msg='a'):
buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, sz, 0, msg])
return buf

def crmsg(client, msg, bufsz=0x1000):
''' Allocate a buffer, write a Chromium message to it, and dispatch it. '''
assert len(msg) <= bufsz
buf = alloc_buf(client, bufsz, msg)
# buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, bufsz, 0, msg])
_, res, _ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [buf, "A"*bufsz, 1337])
return res
  • crmsg함수는 위에 CRMessage 형태의 msg를 인자로 받으며, alloc_buf를 호출한다.
  • alloc_buf에서는 SHCRGL_GUEST_FN_WRITE_BUFFER를 인자로 hgcm_call을 하게 된다.
  • 이 때, 세번째 인자는 [0,sz,0,msg] 이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
case SHCRGL_GUEST_FN_WRITE_BUFFER:
{
Log(("svcCall: SHCRGL_GUEST_FN_WRITE_BUFFER\n"));
/* Verify parameter count and types. */
if (cParms != SHCRGL_CPARMS_WRITE_BUFFER)
{
rc = VERR_INVALID_PARAMETER;
}
else
if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /*iBufferID*/
|| paParms[1].type != VBOX_HGCM_SVC_PARM_32BIT /*cbBufferSize*/
|| paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /*ui32Offset*/
|| paParms[3].type != VBOX_HGCM_SVC_PARM_PTR /*pBuffer*/
)
{
rc = VERR_INVALID_PARAMETER;
}
else
{
/* Fetch parameters. */
uint32_t iBuffer = paParms[0].u.uint32;
uint32_t cbBufferSize = paParms[1].u.uint32;
uint32_t ui32Offset = paParms[2].u.uint32;
uint8_t *pBuffer = (uint8_t *)paParms[3].u.pointer.addr;
uint32_t cbBuffer = paParms[3].u.pointer.size;

/* Execute the function. */
CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, cbBufferSize);
if (!pSvcBuffer || ((uint64_t)ui32Offset+cbBuffer)>cbBufferSize)
{
rc = VERR_INVALID_PARAMETER;
}
else
{
memcpy((void*)((uintptr_t)pSvcBuffer->pData+ui32Offset), pBuffer, cbBuffer);

/* Return the buffer id */
paParms[0].u.uint32 = pSvcBuffer->uiId;
}
}

break;
}
  • parameter의 개수와 타입 검사를 한 후 세 번째 인자인 [0,sz,0,msg]를 알맞게 파싱해준다. ([bufID, bufSZ,offset,msg])
  • bufID와, msg의 크기를 인자로 svcGetBuffer함수를 호출하는데 이때 반환 값은 CRVBOXSVCBUFFER_t 구조체이다.
1
2
3
4
5
6
typedef struct _CRVBOXSVCBUFFER_t {
uint32_t uiId;
uint32_t uiSize;
void* pData;
_CRVBOXSVCBUFFER_t *pNext, *pPrev;
} CRVBOXSVCBUFFER_t;
  • CRVBOXSVCBUFFER_t 구조는 위와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static CRVBOXSVCBUFFER_t* svcGetBuffer(uint32_t iBuffer, uint32_t cbBufferSize)
{
CRVBOXSVCBUFFER_t* pBuffer;

if (iBuffer)
{
pBuffer = g_pCRVBoxSVCBuffers;
while (pBuffer)
{
if (pBuffer->uiId == iBuffer)
{
if (cbBufferSize && pBuffer->uiSize!=cbBufferSize)
{
static int shown=0;

if (shown<20)
{
shown++;
LogRel(("OpenGL: svcGetBuffer: Invalid buffer(%i) size %i instead of %i\n",
iBuffer, pBuffer->uiSize, cbBufferSize));
}
return NULL;
}
return pBuffer;
}
pBuffer = pBuffer->pNext;
}
return NULL;
}
  • svcGetBuffer함수는 인자로 받은 bufID,Size에 맞는 버퍼가 서버에 존재한다면 해당 주소를 반환한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    else /*allocate new buffer*/
{
pBuffer = (CRVBOXSVCBUFFER_t*) RTMemAlloc(sizeof(CRVBOXSVCBUFFER_t));
if (pBuffer)
{
pBuffer->pData = RTMemAlloc(cbBufferSize);
if (!pBuffer->pData)
{
LogRel(("OpenGL: svcGetBuffer: Not enough memory (%d)\n", cbBufferSize));
RTMemFree(pBuffer);
return NULL;
}
pBuffer->uiId = ++g_CRVBoxSVCBufferID;
if (!pBuffer->uiId)
{
pBuffer->uiId = ++g_CRVBoxSVCBufferID;
}
Assert(pBuffer->uiId);
pBuffer->uiSize = cbBufferSize;
pBuffer->pPrev = NULL;
pBuffer->pNext = g_pCRVBoxSVCBuffers;
if (g_pCRVBoxSVCBuffers)
{
g_pCRVBoxSVCBuffers->pPrev = pBuffer;
}
g_pCRVBoxSVCBuffers = pBuffer;
}
else
{
LogRel(("OpenGL: svcGetBuffer: Not enough memory (%d)\n", sizeof(CRVBOXSVCBUFFER_t)));
}
return pBuffer;
}
}
  • 그렇지 않다면 새로 버퍼를 할당하고, 할당한 주소를 반환한다.
1
memcpy((void*)((uintptr_t)pSvcBuffer->pData+ui32Offset), pBuffer, cbBuffer);
  • 이 후 할당받은 CRVBOXSVCBUFFER_t 구조체 버퍼의 pdata에 인자로 주었던 offset만큼 더한 위치에 msg를 복사한다.
  • 그림으로 정리하면 아래와 같다.

23

  • hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [id, sz, offset, msg])에서 msg를 pdata+offset에 저장.

  • alloc_buf호출 이 후 SHCRGL_GUEST_FN_WRITE_READ_BUFFERED를 인자로 다시 한 번 hgcm_call를 호출한다.
  • 이 때, 세번째 인자는 [buf, “A”*bufsz, 1337] 이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
case SHCRGL_GUEST_FN_WRITE_READ_BUFFERED:
{
Log(("svcCall: SHCRGL_GUEST_FN_WRITE_READ_BUFFERED\n"));

/* Verify parameter count and types. */
if (cParms != SHCRGL_CPARMS_WRITE_READ_BUFFERED)
{
rc = VERR_INVALID_PARAMETER;
}
else
if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* iBufferID */
|| paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* pWriteback */
|| paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* cbWriteback */
|| !paParms[0].u.uint32 /*iBufferID can't be 0 here*/
)
{
rc = VERR_INVALID_PARAMETER;
}
else
{
/* Fetch parameters. */
uint32_t iBuffer = paParms[0].u.uint32;
uint8_t *pWriteback = (uint8_t *)paParms[1].u.pointer.addr;
uint32_t cbWriteback = paParms[1].u.pointer.size;

CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, 0);
if (!pSvcBuffer)
{
LogRel(("OpenGL: svcCall(WRITE_READ_BUFFERED): Invalid buffer (%d)\n", iBuffer));
rc = VERR_INVALID_PARAMETER;
break;
}

uint8_t *pBuffer = (uint8_t *)pSvcBuffer->pData;
uint32_t cbBuffer = pSvcBuffer->uiSize;

/* Execute the function. */
rc = crVBoxServerClientWrite(u32ClientID, pBuffer, cbBuffer);
if (!RT_SUCCESS(rc))
{
Assert(VERR_NOT_SUPPORTED==rc);
svcClientVersionUnsupported(0, 0);
}

rc = crVBoxServerClientRead(u32ClientID, pWriteback, &cbWriteback);

if (RT_SUCCESS(rc))
{
/* Update parameters.*/
paParms[1].u.pointer.size = cbWriteback;
}
/* Return the required buffer size always */
paParms[2].u.uint32 = cbWriteback;

svcFreeBuffer(pSvcBuffer);
}

break;
}
  • parameter의 개수와 타입 검사를 한 후 세 번째 인자인 [buf, “A”bufsz, 1337] 를 알맞게 파싱해준다.
  • 그리고 다시 bufID와, 0을 인자로 svcGetBuffer함수를 호출한다.
  • 반환된 pSvcBuffer를 통해 변수 pBuffer, cbBuffer를 설정하고 crVBoxServerClientWrite함수를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int32_t crVBoxServerClientWrite(uint32_t u32ClientID, uint8_t *pBuffer, uint32_t cbBuffer)
{
CRClient *pClient=NULL;
int32_t rc = crVBoxServerClientGet(u32ClientID, &pClient);

if (RT_FAILURE(rc))
return rc;

CRASSERT(pBuffer);

/* This should never fire unless we start to multithread */
CRASSERT(pClient->conn->pBuffer==NULL && pClient->conn->cbBuffer==0);

pClient->conn->pBuffer = pBuffer;
pClient->conn->cbBuffer = cbBuffer;
#ifdef VBOX_WITH_CRHGSMI
CRVBOXHGSMI_CMDDATA_ASSERT_CLEANED(&pClient->conn->CmdData);
#endif

crVBoxServerInternalClientWriteRead(pClient);

return VINF_SUCCESS;
}
  • pClient 변수에 인자 정보를 저장하고 crVBoxServerInternalClientWriteRead 함수를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
static void crVBoxServerInternalClientWriteRead(CRClient *pClient)
{
...
crNetRecv();
CRASSERT(pClient->conn->pBuffer==NULL && pClient->conn->cbBuffer==0);
CRVBOXHGSMI_CMDDATA_ASSERT_CLEANED(&pClient->conn->CmdData);

crServerServiceClients();
crStateResetCurrentPointers(&cr_server.current);
...
}
  • crServerServiceClients 함수를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void
crServerServiceClients(void)
{
RunQueue *q;

q = getNextClient(GL_FALSE); /* don't block */
while (q)
{
ClientStatus stat = crServerServiceClient(q);
if (stat == CLIENT_NEXT && cr_server.run_queue->next) {
/* advance to next client */
cr_server.run_queue = cr_server.run_queue->next;
}
q = getNextClient(GL_FALSE);
}
}
  • crServerServiceClient 함수를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static ClientStatus
crServerServiceClient(const RunQueue *qEntry)
{
CRMessage *msg;
CRConnection *conn;

...
/* Force scissor, viewport and projection matrix update in
* crServerSetOutputBounds().
*/
cr_server.currentSerialNo = 0;

/* Commands get dispatched here */
crServerDispatchMessage( conn, msg, len );

crNetFree( conn, msg );
...
}
  • crServerDispatchMessage 함수를 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void
crServerDispatchMessage(CRConnection *conn, CRMessage *msg, int cbMsg)
{
...

msg_opcodes = (const CRMessageOpcodes *) msg;
opcodeBytes = (msg_opcodes->numOpcodes + 3) & ~0x03;
...

data_ptr = (const char *) msg_opcodes + sizeof(CRMessageOpcodes) + opcodeBytes;
data_ptr_end = (const char *)msg_opcodes + cbMsg; // Pointer to the first byte after message data
...

if (fUnpack)
{
crUnpack(data_ptr, /* first command's operands */
data_ptr_end, /* first byte after command's operands*/
data_ptr - 1, /* first command's opcode */
msg_opcodes->numOpcodes, /* how many opcodes */
&(cr_server.dispatch)); /* the CR dispatch table */
}
...
}
  • 해당 함수는 msg를 CRMessageOpcodes로 캐스팅 후 crUnpack함수를 호출한다.
  • 여기서 data_ptr에는 Opcode+data에서 Opcode를 제외한 data의 주소가 들어간다.
  • crUnpack 함수에서 Opcode값에 따른 로직을 처리하게 된다.
  • crVBoxServerClientWrite 함수 호출 이 후, svcFreeBuffer 함수를 호출하여 할당한 버퍼를 Free 시킨다.

따라서 alloc_buf에서 할당한 메모리는 crmsg가 호출되기 전까지 Free되지 않으므로 Heap Spray가 가능하다.


Next Posting

5

  • CVE-2019-2525 : crUnpackExtendGetAttribLocation
  • CVE-2019-2548 : crServerDispatchReadPixels
  • 각각 함수에서 취약점이 발견되었으며 이 두개를 활용하여 VM Exploit이 가능하다.

Reference