0%

VirtualBox HGCM协议研究

文章首发于安全KER https://www.anquanke.com/post/id/238643

0x00 前言

最近开始研究VirtualBox虚拟机逃逸漏洞,针对于VirtualBox的虚拟机逃逸,我们重点关注它的HGCM(host-guest communication mechanism)协议,本文将结合源码分析和动态调试来分析此协议,最后我们还将实现一个HGCM协议的调用库。

0x01 VirtualBox 通信协议

引言

VirtualBox中一个名为HGCM的协议相当于一个RPC,其作用是可以让Guest里的程序通过接口调用Host中的服务程序中的函数。该协议的接口封装在vboxguest驱动程序中。

在Guest系统中,通过VBoxGuestAdditions.iso安装了一个名为vboxguest的驱动程序,该驱动程序主要就是提供接口给Guset系统里的程序,用于与Host主机进行通信。

除了vboxguest驱动,Guset还安装有vboxsf驱动和vboxvideo,其中vboxsf仍然使用的是vboxguest的接口,而vboxvideo则是VirtualBox虚拟出来的显示设备的驱动程序,独立于前面两个驱动。由此可见,Guest与Host之前的通信关键在于vboxguest驱动,因此,我们的研究将从该驱动出发。

该驱动源码位于src\VBox\Additions\common\VBoxGuest目录,以Linux系统为例,其源文件为VBoxGuest-linux.c,首先从file_operations结构体可以看到有哪些操作

1
2
3
4
5
6
7
8
9
10
11
static struct file_operations   g_FileOpsUser =
{
owner: THIS_MODULE,
open: vgdrvLinuxOpen,
release: vgdrvLinuxRelease,
#ifdef HAVE_UNLOCKED_IOCTL
unlocked_ioctl: vgdrvLinuxIOCtl,
#else
ioctl: vgdrvLinuxIOCtl,
#endif
};

GUEST IOCTL

可以看到定义了vgdrvLinuxIOCtl用于进行接口的访问,跟踪该函数,可以发现其调用了vgdrvLinuxIOCtlSlow函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
static int vgdrvLinuxIOCtlSlow(struct file *pFilp, unsigned int uCmd, unsigned long ulArg, PVBOXGUESTSESSION pSession)
{
int rc;
VBGLREQHDR Hdr;
PVBGLREQHDR pHdr;
uint32_t cbBuf;

Log6(("vgdrvLinuxIOCtlSlow: pFilp=%p uCmd=%#x ulArg=%p pid=%d/%d\n", pFilp, uCmd, (void *)ulArg, RTProcSelf(), current->pid));

/*
* Read the header.
*/
if (RT_FAILURE(RTR0MemUserCopyFrom(&Hdr, ulArg, sizeof(Hdr))))
{
Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx,) failed; uCmd=%#x\n", ulArg, uCmd));
return -EFAULT;
}
if (RT_UNLIKELY(Hdr.uVersion != VBGLREQHDR_VERSION))
{
Log(("vgdrvLinuxIOCtlSlow: bad header version %#x; uCmd=%#x\n", Hdr.uVersion, uCmd));
return -EINVAL;
}

/*
* Buffer the request.
* Note! The header is revalidated by the common code.
*/
cbBuf = RT_MAX(Hdr.cbIn, Hdr.cbOut);
if (RT_UNLIKELY(cbBuf > _1M*16))
{
Log(("vgdrvLinuxIOCtlSlow: too big cbBuf=%#x; uCmd=%#x\n", cbBuf, uCmd));
return -E2BIG;
}
if (RT_UNLIKELY( Hdr.cbIn < sizeof(Hdr)
|| (cbBuf != _IOC_SIZE(uCmd) && _IOC_SIZE(uCmd) != 0)))
{
Log(("vgdrvLinuxIOCtlSlow: bad ioctl cbBuf=%#x _IOC_SIZE=%#x; uCmd=%#x\n", cbBuf, _IOC_SIZE(uCmd), uCmd));
return -EINVAL;
}
pHdr = RTMemAlloc(cbBuf);
if (RT_UNLIKELY(!pHdr))
{
LogRel(("vgdrvLinuxIOCtlSlow: failed to allocate buffer of %d bytes for uCmd=%#x\n", cbBuf, uCmd));
return -ENOMEM;
}
if (RT_FAILURE(RTR0MemUserCopyFrom(pHdr, ulArg, Hdr.cbIn)))
{
Log(("vgdrvLinuxIOCtlSlow: copy_from_user(,%#lx, %#x) failed; uCmd=%#x\n", ulArg, Hdr.cbIn, uCmd));
RTMemFree(pHdr);
return -EFAULT;
}
if (Hdr.cbIn < cbBuf)
RT_BZERO((uint8_t *)pHdr + Hdr.cbIn, cbBuf - Hdr.cbIn);

/*
* Process the IOCtl.
*/
rc = VGDrvCommonIoCtl(uCmd, &g_DevExt, pSession, pHdr, cbBuf);
.........................................................

可以看到,函数中首先将用户传入的数据转为VBGLREQHDR Hdr;结构体,该结构体定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct VBGLREQHDR
{
/** IN: The request input size, and output size if cbOut is zero.
* @sa VMMDevRequestHeader::size */
uint32_t cbIn;
/** IN: Structure version (VBGLREQHDR_VERSION)
* @sa VMMDevRequestHeader::version */
uint32_t uVersion;
/** IN: The VMMDev request type, set to VBGLREQHDR_TYPE_DEFAULT unless this is a
* kind of VMMDev request.
* @sa VMMDevRequestType, VMMDevRequestHeader::requestType */
uint32_t uType;
/** OUT: The VBox status code of the operation, out direction only. */
int32_t rc;
/** IN: The output size. This is optional - set to zero to use cbIn as the
* output size. */
uint32_t cbOut;
/** Reserved / filled in by kernel, MBZ.
* @sa VMMDevRequestHeader::fRequestor */
uint32_t uReserved;
} VBGLREQHDR;

然后判断一些信息是否符合要求,这里,归纳如下

1
2
Hdr.uVersion = VBGLREQHDR_VERSION
Hdr.cbIn和Hdr.cbOut不能大于_1M*16

检查通过后,执行rc = VGDrvCommonIoCtl(uCmd, &g_DevExt, pSession, pHdr, cbBuf);,进入VGDrvCommonIoCtl函数,该函数位于VBoxGuest.cpp源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int VGDrvCommonIoCtl(uintptr_t iFunction, PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLREQHDR pReqHdr, size_t cbReq)
{
uintptr_t const iFunctionStripped = VBGL_IOCTL_CODE_STRIPPED(iFunction);
int rc;
...............................................................
/*
* Deal with variably sized requests first.
*/
rc = VINF_SUCCESS;
if ( iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_VMMDEV_REQUEST(0))
|| iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_VMMDEV_REQUEST_BIG) )
{
........
}
else if (RT_LIKELY(pReqHdr->uType == VBGLREQHDR_TYPE_DEFAULT))
{
if (iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_LOG(0)))
{
........
}
#ifdef VBOX_WITH_HGCM
else if (iFunction == VBGL_IOCTL_IDC_HGCM_FAST_CALL) /* (is variable size, but we don't bother encoding it) */
{
.........
}
else if ( iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL(0))
# if ARCH_BITS == 64
|| iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_32(0))
# endif
)
{
...........
}
else if (iFunctionStripped == VBGL_IOCTL_CODE_STRIPPED(VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA(0)))
{
..........
}
#endif /* VBOX_WITH_HGCM */
else
{
switch (iFunction)
{

由于我们想要进入HGCM相关的处理分支里,因此,想要满足pReqHdr->uType == VBGLREQHDR_TYPE_DEFAULT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
            switch (iFunction)
{
............................................
#ifdef VBOX_WITH_HGCM
case VBGL_IOCTL_HGCM_CONNECT:
REQ_CHECK_SIZES(VBGL_IOCTL_HGCM_CONNECT);
pReqHdr->rc = vgdrvIoCtl_HGCMConnect(pDevExt, pSession, (PVBGLIOCHGCMCONNECT)pReqHdr);
break;

case VBGL_IOCTL_HGCM_DISCONNECT:
REQ_CHECK_SIZES(VBGL_IOCTL_HGCM_DISCONNECT);
pReqHdr->rc = vgdrvIoCtl_HGCMDisconnect(pDevExt, pSession, (PVBGLIOCHGCMDISCONNECT)pReqHdr);
break;
#endif

这里的iFunction值就是我们在ioctl中传入的cmd,当cmd为 VBGL_IOCTL_HGCM_CONNECT或者VBGL_IOCTL_HGCM_DISCONNECT时,可以建立或者断开一个HGCM服务。在一般情况下,使用HGCM调用Host中的服务时,要经过三个步骤VBGL_IOCTL_HGCM_CONNECT->VBGL_IOCTL_HGCM_CALL->VBGL_IOCTL_HGCM_DISCONNECT,即打开服务->调用函数->关闭服务。可以在src\VBox\HostServices目录下看到这些服务以及它们的源码

1
2
3
4
5
6
7
8
src\VBox\HostServices
DragAndDrop
GuestControl
GuestProperties
HostChannel
SharedClipboard
SharedFolders
SharedOpenGL

从这些服务名大致能知道它们的作用,其中SharedClipboard用于在Host和Guest之间共享粘贴板SharedFolders用于共享文件夹,而SharedOpenGL用于3D图形加速
继续分析HGCM服务的调用

1
pReqHdr->rc = vgdrvIoCtl_HGCMConnect(pDevExt, pSession, (PVBGLIOCHGCMCONNECT)pReqHdr);

可以知道此时将pReqHdr这个VBGLREQHDR结构体指针强制转换为VBGLIOCHGCMCONNECT结构体指针,该结构体定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
typedef struct VBGLIOCHGCMCONNECT
{
/** The header. */
VBGLREQHDR Hdr;
union
{
struct
{
HGCMServiceLocation Loc;
} In;
struct
{
uint32_t idClient;
} Out;
} u;
} VBGLIOCHGCMCONNECT, RT_FAR *PVBGLIOCHGCMCONNECT;

/**
* HGCM service location.
* @ingroup grp_vmmdev_req
*/
typedef struct HGCMSERVICELOCATION
{
/** Type of the location. */
HGCMServiceLocationType type;

union
{
HGCMServiceLocationHost host;
} u;
} HGCMServiceLocation;

typedef enum
{
VMMDevHGCMLoc_Invalid = 0,
VMMDevHGCMLoc_LocalHost = 1,
VMMDevHGCMLoc_LocalHost_Existing = 2,
VMMDevHGCMLoc_SizeHack = 0x7fffffff
} HGCMServiceLocationType;

/**
* HGCM host service location.
* @ingroup grp_vmmdev_req
*/
typedef struct
{
char achName[128]; /**< This is really szName. */
} HGCMServiceLocationHost;

VBGL_IOCTL_HGCM_CONNECT

VbglR0HGCMInternalConnect

进入vgdrvIoCtl_HGCMConnect函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static int vgdrvIoCtl_HGCMConnect(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCONNECT pInfo)
{
int rc;
HGCMCLIENTID idClient = 0;

/*
* The VbglHGCMConnect call will invoke the callback if the HGCM
* call is performed in an ASYNC fashion. The function is not able
* to deal with cancelled requests.
*/
Log(("VBOXGUEST_IOCTL_HGCM_CONNECT: %.128s\n",
pInfo->u.In.Loc.type == VMMDevHGCMLoc_LocalHost || pInfo->u.In.Loc.type == VMMDevHGCMLoc_LocalHost_Existing
? pInfo->u.In.Loc.u.host.achName : "<not local host>"));

rc = VbglR0HGCMInternalConnect(&pInfo->u.In.Loc, pSession->fRequestor, &idClient,
vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT);
Log(("VBOXGUEST_IOCTL_HGCM_CONNECT: idClient=%RX32 (rc=%Rrc)\n", idClient, rc));
if (RT_SUCCESS(rc))
{
/*
* Append the client id to the client id table.
* If the table has somehow become filled up, we'll disconnect the session.
*/
unsigned i;
RTSpinlockAcquire(pDevExt->SessionSpinlock);
for (i = 0; i < RT_ELEMENTS(pSession->aHGCMClientIds); i++)
if (!pSession->aHGCMClientIds[i])
{
pSession->aHGCMClientIds[i] = idClient;
break;
}
RTSpinlockRelease(pDevExt->SessionSpinlock);
if (i >= RT_ELEMENTS(pSession->aHGCMClientIds))
{
LogRelMax(32, ("VBOXGUEST_IOCTL_HGCM_CONNECT: too many HGCMConnect calls for one session!\n"));
VbglR0HGCMInternalDisconnect(idClient, pSession->fRequestor, vgdrvHgcmAsyncWaitCallback, pDevExt, RT_INDEFINITE_WAIT);

pInfo->u.Out.idClient = 0;
return VERR_TOO_MANY_OPEN_FILES;
}
}
pInfo->u.Out.idClient = idClient;
return rc;
}

从该函数可以看出,它将调用VbglR0HGCMInternalConnect函数,然后返回一个idClient即客户端号,并将该号码缓存到pSession->aHGCMClientIds数组中,同时将其返回给Guest中的请求程序。我们继续跟进VbglR0HGCMInternalConnect函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
DECLR0VBGL(int) VbglR0HGCMInternalConnect(HGCMServiceLocation const *pLoc, uint32_t fRequestor, HGCMCLIENTID *pidClient,
PFNVBGLHGCMCALLBACK pfnAsyncCallback, void *pvAsyncData, uint32_t u32AsyncData)
{
int rc;
if ( RT_VALID_PTR(pLoc)
&& RT_VALID_PTR(pidClient)
&& RT_VALID_PTR(pfnAsyncCallback))
{
/* Allocate request */
VMMDevHGCMConnect *pHGCMConnect = NULL;
rc = VbglR0GRAlloc((VMMDevRequestHeader **)&pHGCMConnect, sizeof(VMMDevHGCMConnect), VMMDevReq_HGCMConnect);
if (RT_SUCCESS(rc))
{
/* Initialize request memory */
pHGCMConnect->header.header.fRequestor = fRequestor;

pHGCMConnect->header.fu32Flags = 0;

memcpy(&pHGCMConnect->loc, pLoc, sizeof(pHGCMConnect->loc));
pHGCMConnect->u32ClientID = 0;

/* Issue request */
rc = VbglR0GRPerform (&pHGCMConnect->header.header);
if (RT_SUCCESS(rc))
{
/* Check if host decides to process the request asynchronously. */
if (rc == VINF_HGCM_ASYNC_EXECUTE)
{
/* Wait for request completion interrupt notification from host */
pfnAsyncCallback(&pHGCMConnect->header, pvAsyncData, u32AsyncData);
}

rc = pHGCMConnect->header.result;
if (RT_SUCCESS(rc))
*pidClient = pHGCMConnect->u32ClientID;
}
VbglR0GRFree(&pHGCMConnect->header.header);
}
}
else
rc = VERR_INVALID_PARAMETER;
return rc;
}

该函数主要是新建了一个结构体,并从最开始ioctl操作中传入的结构体中复制HGCMServiceLocation结构体数据,然后传入VbglR0GRPerform函数。
VbglR0GRPerform函数实际上就是一个对inout汇编指令的封装,操作IO接口,可以知道,其请求的端口地址为g_vbgldata.portVMMDev + VMMDEV_PORT_OFF_REQUEST

VbglR0GRPerform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DECLR0VBGL(int) VbglR0GRPerform(VMMDevRequestHeader *pReq)
{
int rc = vbglR0Enter();
if (RT_SUCCESS(rc))
{
if (pReq)
{
RTCCPHYS PhysAddr = VbglR0PhysHeapGetPhysAddr(pReq);
if ( PhysAddr != 0
&& PhysAddr < _4G) /* Port IO is 32 bit. */
{
ASMOutU32(g_vbgldata.portVMMDev + VMMDEV_PORT_OFF_REQUEST, (uint32_t)PhysAddr);
/* Make the compiler aware that the host has changed memory. */
ASMCompilerBarrier();
rc = pReq->rc;
}
else
rc = VERR_VBGL_INVALID_ADDR;
}
else
rc = VERR_INVALID_PARAMETER;
}
return rc;
}

通过查找VMMDEV_PORT_OFF_REQUEST的引用,可以发现src\VBox\Devices\VMMDev\VMMDev.cpp文件,可以知道这是VirtualBox虚拟出来的IO设备,在vmmdevIOPortRegionMap函数中,通过PDMDevHlpIOPortRegister函数为VMMDEV_PORT_OFF_REQUESTIO端口注册了一个处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static DECLCALLBACK(int) vmmdevIOPortRegionMap(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
{
LogFlow(("vmmdevIOPortRegionMap: iRegion=%d GCPhysAddress=%RGp cb=%RGp enmType=%d\n", iRegion, GCPhysAddress, cb, enmType));
RT_NOREF3(iRegion, cb, enmType);
PVMMDEV pThis = RT_FROM_MEMBER(pPciDev, VMMDEV, PciDev);

Assert(enmType == PCI_ADDRESS_SPACE_IO);
Assert(iRegion == 0);
AssertMsg(RT_ALIGN(GCPhysAddress, 8) == GCPhysAddress, ("Expected 8 byte alignment. GCPhysAddress=%#x\n", GCPhysAddress));

/*
* Register our port IO handlers.
*/
int rc = PDMDevHlpIOPortRegister(pDevIns, (RTIOPORT)GCPhysAddress + VMMDEV_PORT_OFF_REQUEST, 1,
pThis, vmmdevRequestHandler, NULL, NULL, NULL, "VMMDev Request Handler");

因此我们在Guset中的ASMOutU32(g_vbgldata.portVMMDev + VMMDEV_PORT_OFF_REQUEST, (uint32_t)PhysAddr);请求最终被传入到虚拟设备中的vmmdevRequestHandler函数中进行处理。

vmmdevRequestHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* @callback_method_impl{FNIOMIOPORTOUT,
* Port I/O write andler for the generic request interface.}
*/
static DECLCALLBACK(int) vmmdevRequestHandler(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT Port, uint32_t u32, unsigned cb)
{
uint64_t tsArrival;
STAM_GET_TS(tsArrival);

RT_NOREF2(Port, cb);
PVMMDEV pThis = (VMMDevState *)pvUser;

/*
* The caller has passed the guest context physical address of the request
* structure. We'll copy all of it into a heap buffer eventually, but we
* will have to start off with the header.
*/
VMMDevRequestHeader requestHeader;
RT_ZERO(requestHeader);
PDMDevHlpPhysRead(pDevIns, (RTGCPHYS)u32, &requestHeader, sizeof(requestHeader));

.........................................................
if (pRequestHeader)
{
memcpy(pRequestHeader, &requestHeader, sizeof(VMMDevRequestHeader));

/* Try lock the request if it's a HGCM call and not crossing a page boundrary.
Saves on PGM interaction. */
VMMDEVREQLOCK Lock = { NULL, { 0, NULL } };
PVMMDEVREQLOCK pLock = NULL;
size_t cbLeft = requestHeader.size - sizeof(VMMDevRequestHeader);
if (cbLeft)
{
...............................
}

/*
* Feed buffered request thru the dispatcher.
*/
uint32_t fPostOptimize = 0;
PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
rcRet = vmmdevReqDispatcher(pThis, pRequestHeader, u32, tsArrival, &fPostOptimize, &pLock);
PDMCritSectLeave(&pThis->CritSect);

请求将被传入vmmdevReqDispatcher函数进行调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* Dispatch the request to the appropriate handler function.
*
* @returns Port I/O handler exit code.
* @param pThis The VMM device instance data.
* @param pReqHdr The request header (cached in host memory).
* @param GCPhysReqHdr The guest physical address of the request (for
* HGCM).
* @param tsArrival The STAM_GET_TS() value when the request arrived.
* @param pfPostOptimize HGCM optimizations, VMMDEVREQDISP_POST_F_XXX.
* @param ppLock Pointer to the lock info pointer (latter can be
* NULL). Set to NULL if HGCM takes lock ownership.
*/
static int vmmdevReqDispatcher(PVMMDEV pThis, VMMDevRequestHeader *pReqHdr, RTGCPHYS GCPhysReqHdr,
uint64_t tsArrival, uint32_t *pfPostOptimize, PVMMDEVREQLOCK *ppLock)
{
int rcRet = VINF_SUCCESS;
Assert(*pfPostOptimize == 0);

switch (pReqHdr->requestType)
{
...........................................
#ifdef VBOX_WITH_HGCM
case VMMDevReq_HGCMConnect:
vmmdevReqHdrSetHgcmAsyncExecute(pThis, GCPhysReqHdr, *ppLock);
pReqHdr->rc = vmmdevReqHandler_HGCMConnect(pThis, pReqHdr, GCPhysReqHdr);
Assert(pReqHdr->rc == VINF_HGCM_ASYNC_EXECUTE || RT_FAILURE_NP(pReqHdr->rc));
if (RT_SUCCESS(pReqHdr->rc))
*pfPostOptimize |= VMMDEVREQDISP_POST_F_NO_WRITE_OUT;
break;

case VMMDevReq_HGCMDisconnect:
vmmdevReqHdrSetHgcmAsyncExecute(pThis, GCPhysReqHdr, *ppLock);
pReqHdr->rc = vmmdevReqHandler_HGCMDisconnect(pThis, pReqHdr, GCPhysReqHdr);
Assert(pReqHdr->rc == VINF_HGCM_ASYNC_EXECUTE || RT_FAILURE_NP(pReqHdr->rc));
if (RT_SUCCESS(pReqHdr->rc))
*pfPostOptimize |= VMMDEVREQDISP_POST_F_NO_WRITE_OUT;
break;

# ifdef VBOX_WITH_64_BITS_GUESTS
case VMMDevReq_HGCMCall64:
# endif
case VMMDevReq_HGCMCall32:
vmmdevReqHdrSetHgcmAsyncExecute(pThis, GCPhysReqHdr, *ppLock);
pReqHdr->rc = vmmdevReqHandler_HGCMCall(pThis, pReqHdr, GCPhysReqHdr, tsArrival, ppLock);
Assert(pReqHdr->rc == VINF_HGCM_ASYNC_EXECUTE || RT_FAILURE_NP(pReqHdr->rc));
if (RT_SUCCESS(pReqHdr->rc))
*pfPostOptimize |= VMMDEVREQDISP_POST_F_NO_WRITE_OUT;
break;

case VMMDevReq_HGCMCancel:
pReqHdr->rc = vmmdevReqHandler_HGCMCancel(pThis, pReqHdr, GCPhysReqHdr);
break;

case VMMDevReq_HGCMCancel2:
pReqHdr->rc = vmmdevReqHandler_HGCMCancel2(pThis, pReqHdr);
break;
#endif /* VBOX_WITH_HGCM */
...........................................

VMMDevReq_HGCMConnect时,使用vmmdevReqHdrSetHgcmAsyncExecute函数设置异步返回值,这样Guset系统驱动的VbglR0HGCMInternalConnect函数时将通过pfnAsyncCallback(&pHGCMConnect->header, pvAsyncData, u32AsyncData);等待设备这里的操作完成并获取结果;设备这里将调用vmmdevReqHandler_HGCMConnect连接HGCM服务,继续跟踪,

vmmdevReqHandler_HGCMConnect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/** Handle VMMDevHGCMConnect request.
*
* @param pThis The VMMDev instance data.
* @param pHGCMConnect The guest request (cached in host memory).
* @param GCPhys The physical address of the request.
*/
int vmmdevHGCMConnect(PVMMDEV pThis, const VMMDevHGCMConnect *pHGCMConnect, RTGCPHYS GCPhys)
{
int rc = VINF_SUCCESS;

PVBOXHGCMCMD pCmd = vmmdevHGCMCmdAlloc(pThis, VBOXHGCMCMDTYPE_CONNECT, GCPhys, pHGCMConnect->header.header.size, 0,
pHGCMConnect->header.header.fRequestor);
if (pCmd)
{
vmmdevHGCMConnectFetch(pHGCMConnect, pCmd);

/* Only allow the guest to use existing services! */
ASSERT_GUEST(pHGCMConnect->loc.type == VMMDevHGCMLoc_LocalHost_Existing);
pCmd->u.connect.pLoc->type = VMMDevHGCMLoc_LocalHost_Existing;

vmmdevHGCMAddCommand(pThis, pCmd);
rc = pThis->pHGCMDrv->pfnConnect(pThis->pHGCMDrv, pCmd, pCmd->u.connect.pLoc, &pCmd->u.connect.u32ClientID);
if (RT_FAILURE(rc))
vmmdevHGCMRemoveCommand(pThis, pCmd);
}
else
{
rc = VERR_NO_MEMORY;
}

return rc;
}

函数中主要是调用了rc = pThis->pHGCMDrv->pfnConnect(pThis->pHGCMDrv, pCmd, pCmd->u.connect.pLoc, &pCmd->u.connect.u32ClientID);进行服务连接,其中pThis在vmmdevIOPortRegionMap函数中初始化

1
PVMMDEV pThis = RT_FROM_MEMBER(pPciDev, VMMDEV, PciDev);

pThis->pHGCMDrv在vmmdevConstruct函数中被初始化

1
pThis->pHGCMDrv = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIHGCMCONNECTOR);

通过调试,可以知道pThis->pHGCMDrv->pfnConnect最终指向的是iface_hgcmConnect函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Devices/VMMDev/VMMDevHGCM.cpp
450 /* Only allow the guest to use existing services! */
451 ASSERT_GUEST(pHGCMConnect->loc.type == VMMDevHGCMLoc_LocalHost_Existing);
452 pCmd->u.connect.pLoc->type = VMMDevHGCMLoc_LocalHost_Existing;
453
454 vmmdevHGCMAddCommand(pThis, pCmd);
► 455 rc = pThis->pHGCMDrv->pfnConnect(pThis->pHGCMDrv, pCmd, pCmd->u.connect.pLoc, &pCmd->u.connect.u32ClientID);
456 if (RT_FAILURE(rc))
457 vmmdevHGCMRemoveCommand(pThis, pCmd);
458 }
459 else
460 {
pwndbg> s
599 /* HGCM connector interface */
600
601 static DECLCALLBACK(int) iface_hgcmConnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd,
602 PHGCMSERVICELOCATION pServiceLocation,
603 uint32_t *pu32ClientID)
► 604 {

其中iface_hgcmConnect函数源码如下

iface_hgcmConnect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static DECLCALLBACK(int) iface_hgcmConnect(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd,
PHGCMSERVICELOCATION pServiceLocation,
uint32_t *pu32ClientID)
{
Log9(("Enter\n"));

PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);

if ( !pServiceLocation
|| ( pServiceLocation->type != VMMDevHGCMLoc_LocalHost
&& pServiceLocation->type != VMMDevHGCMLoc_LocalHost_Existing))
{
return VERR_INVALID_PARAMETER;
}

/* Check if service name is a string terminated by zero*/
size_t cchInfo = 0;
if (RTStrNLenEx(pServiceLocation->u.host.achName, sizeof(pServiceLocation->u.host.achName), &cchInfo) != VINF_SUCCESS)
{
return VERR_INVALID_PARAMETER;
}

if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
return VERR_INVALID_STATE;
return HGCMGuestConnect(pDrv->pHGCMPort, pCmd, pServiceLocation->u.host.achName, pu32ClientID);
}

这里,对于pServiceLocation->type字段,其值必须为VMMDevHGCMLoc_LocalHost或者VMMDevHGCMLoc_LocalHost_Existing。检查通过以后,就会继续调用HGCMGuestConnect函数
HGCMGuestConnect函数是将数据封装为消息,然后调用hgcmMsgPosthgcmMsgPost最后会调用hgcmMsgPostInternal函数向HGCMThread实例发送消息

hgcmMsgPostInternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DECLINLINE(int) hgcmMsgPostInternal(HGCMMsgCore *pMsg, PHGCMMSGCALLBACK pfnCallback, bool fWait)
{
LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, pfnCallback = %p, fWait = %d\n", pMsg, pfnCallback, fWait));
Assert(pMsg);

pMsg->Reference(); /* paranoia? */

int rc = pMsg->Thread()->MsgPost(pMsg, pfnCallback, fWait);

pMsg->Dereference();

LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, rc = %Rrc\n", pMsg, rc));
return rc;
}

通过gdb调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCMThread.cpp
697 LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, pfnCallback = %p, fWait = %d\n", pMsg, pfnCallback, fWait));
698 Assert(pMsg);
699
700 pMsg->Reference(); /* paranoia? */
701
► 702 int rc = pMsg->Thread()->MsgPost(pMsg, pfnCallback, fWait);
703
704 pMsg->Dereference();
705
706 LogFlow(("MAIN::hgcmMsgPostInternal: pMsg = %p, rc = %Rrc\n", pMsg, rc));
707 return rc;
pwndbg> p pMsg->Thread()->MsgPost
$11 = {int (HGCMThread * const, HGCMMsgCore *, PHGCMMSGCALLBACK, bool)} 0x7fe5d8646a5c <HGCMThread::MsgPost(HGCMMsgCore*, int (*)(int, HGCMMsgCore*), bool)>

HGCMThread::MsgPost函数只是简单的将消息插入到消息队列,当HGCMThread的线程取出消息时,便会进行处理。HGCMThread的主线程函数为hgcmThread

hgcmThread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* The main HGCM thread handler. */
static DECLCALLBACK(void) hgcmThread(HGCMThread *pThread, void *pvUser)
{
LogFlowFunc(("pThread = %p, pvUser = %p\n", pThread, pvUser));

NOREF(pvUser);

bool fQuit = false;

while (!fQuit)
{
HGCMMsgCore *pMsgCore;
int rc = hgcmMsgGet(pThread, &pMsgCore);

if (RT_FAILURE(rc))
{
/* The error means some serious unrecoverable problem in the hgcmMsg/hgcmThread layer. */
AssertMsgFailed(("%Rrc\n", rc));
break;
}

uint32_t u32MsgId = pMsgCore->MsgId();

switch (u32MsgId)
{
case HGCM_MSG_CONNECT:
{
HGCMMsgMainConnect *pMsg = (HGCMMsgMainConnect *)pMsgCore;

LogFlowFunc(("HGCM_MSG_CONNECT pszServiceName %s, pu32ClientId %p\n",
pMsg->pszServiceName, pMsg->pu32ClientId));

/* Resolve the service name to the pointer to service instance.
*/
HGCMService *pService;
rc = HGCMService::ResolveService(&pService, pMsg->pszServiceName);

if (RT_SUCCESS(rc))
{
/* Call the service instance method. */
rc = pService->CreateAndConnectClient(pMsg->pu32ClientId,
0,
pMsg->pHGCMPort->pfnGetRequestor(pMsg->pHGCMPort, pMsg->pCmd),
pMsg->pHGCMPort->pfnIsCmdRestored(pMsg->pHGCMPort, pMsg->pCmd));

/* Release the service after resolve. */
pService->ReleaseService();
}
} break;

case HGCM_MSG_DISCONNECT:
{

当收到HGCM_MSG_CONNECT消息时,调用HGCMService::ResolveService(&pService, pMsg->pszServiceName)得到对应服务的句柄,该函数实际上就是一个链表查找的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/** The method obtains a referenced pointer to the service with
* specified name. The caller must call ReleaseService when
* the pointer is no longer needed.
*
* @param ppSvc Where to store the pointer to the service.
* @param pszServiceName The name of the service.
* @return VBox rc.
* @thread main HGCM
*/
/* static */ int HGCMService::ResolveService(HGCMService **ppSvc, const char *pszServiceName)
{
LogFlowFunc(("ppSvc = %p name = %s\n",
ppSvc, pszServiceName));

if (!ppSvc || !pszServiceName)
{
return VERR_INVALID_PARAMETER;
}

HGCMService *pSvc = sm_pSvcListHead;

while (pSvc)
{
if (strcmp(pSvc->m_pszSvcName, pszServiceName) == 0)
{
break;
}

pSvc = pSvc->m_pSvcNext;
}

LogFlowFunc(("lookup in the list is %p\n", pSvc));

if (pSvc == NULL)
{
*ppSvc = NULL;
return VERR_HGCM_SERVICE_NOT_FOUND;
}

pSvc->ReferenceService();

*ppSvc = pSvc;

return VINF_SUCCESS;
}

而该服务链表是在HGCM_MSG_LOAD时通过LoadService初始化的

1
2
3
4
5
6
7
8
9
case HGCM_MSG_LOAD:
{
HGCMMsgMainLoad *pMsg = (HGCMMsgMainLoad *)pMsgCore;

LogFlowFunc(("HGCM_MSG_LOAD pszServiceName = %s, pMsg->pszServiceLibrary = %s, pMsg->pUVM = %p\n",
pMsg->pszServiceName, pMsg->pszServiceLibrary, pMsg->pUVM));

rc = HGCMService::LoadService(pMsg->pszServiceLibrary, pMsg->pszServiceName, pMsg->pUVM, pMsg->pHgcmPort);
} break;

其中LoadService函数就是加载对应的名称的动态库,然后将句柄存储到链表中。
ResolveService得到服务模块句柄以后,就通过CreateAndConnectClient函数调用模块中初始化的函数

1
2
3
4
5
6
7
8
9
10
11
if (RT_SUCCESS(rc))
{
/* Call the service instance method. */
rc = pService->CreateAndConnectClient(pMsg->pu32ClientId,
0,
pMsg->pHGCMPort->pfnGetRequestor(pMsg->pHGCMPort, pMsg->pCmd),
pMsg->pHGCMPort->pfnIsCmdRestored(pMsg->pHGCMPort, pMsg->pCmd));

/* Release the service after resolve. */
pService->ReleaseService();
}

CreateAndConnectClient函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* Create a new client instance and connect it to the service.
*
* @param pu32ClientIdOut If not NULL, then the method must generate a new handle for the client.
* If NULL, use the given 'u32ClientIdIn' handle.
* @param u32ClientIdIn The handle for the client, when 'pu32ClientIdOut' is NULL.
* @param fRequestor The requestor flags, VMMDEV_REQUESTOR_LEGACY if not available.
* @param fRestoring Set if we're restoring a saved state.
* @return VBox status code.
*/
int HGCMService::CreateAndConnectClient(uint32_t *pu32ClientIdOut, uint32_t u32ClientIdIn, uint32_t fRequestor, bool fRestoring)
{
LogFlowFunc(("pu32ClientIdOut = %p, u32ClientIdIn = %d, fRequestor = %#x, fRestoring = %d\n",
pu32ClientIdOut, u32ClientIdIn, fRequestor, fRestoring));

/* Allocate a client information structure. */
HGCMClient *pClient = new (std::nothrow) HGCMClient(fRequestor);

if (!pClient)
{
Log1WarningFunc(("Could not allocate HGCMClient!!!\n"));
return VERR_NO_MEMORY;
}

uint32_t handle;

if (pu32ClientIdOut != NULL)
{
handle = hgcmObjGenerateHandle(pClient);
}
else
{
handle = hgcmObjAssignHandle(pClient, u32ClientIdIn);
}

LogFlowFunc(("client id = %d\n", handle));

AssertRelease(handle);

/* Initialize the HGCM part of the client. */
int rc = pClient->Init(this);

if (RT_SUCCESS(rc))
{
/* Call the service. */
HGCMMsgCore *pCoreMsg;

rc = hgcmMsgAlloc(m_pThread, &pCoreMsg, SVC_MSG_CONNECT, hgcmMessageAllocSvc);

if (RT_SUCCESS(rc))
{
HGCMMsgSvcConnect *pMsg = (HGCMMsgSvcConnect *)pCoreMsg;

pMsg->u32ClientId = handle;
pMsg->fRequestor = fRequestor;
pMsg->fRestoring = fRestoring;

rc = hgcmMsgSend(pMsg);

if (RT_SUCCESS(rc))
{
/* Add the client Id to the array. */
if (m_cClients == m_cClientsAllocated)
{
const uint32_t cDelta = 64;

/* Guards against integer overflow on 32bit arch and also limits size of m_paClientIds array to 4GB*/
if (m_cClientsAllocated < UINT32_MAX / sizeof(m_paClientIds[0]) - cDelta)
{
uint32_t *paClientIdsNew;

paClientIdsNew = (uint32_t *)RTMemRealloc(m_paClientIds,
(m_cClientsAllocated + cDelta) * sizeof(m_paClientIds[0]));
Assert(paClientIdsNew);

if (paClientIdsNew)
{
m_paClientIds = paClientIdsNew;
m_cClientsAllocated += cDelta;
}
else
{
rc = VERR_NO_MEMORY;
}
}
else
{
rc = VERR_NO_MEMORY;
}
}

m_paClientIds[m_cClients] = handle;
m_cClients++;
}
}
}

if (RT_FAILURE(rc))
{
hgcmObjDeleteHandle(handle);
}
else
{
if (pu32ClientIdOut != NULL)
{
*pu32ClientIdOut = handle;
}

ReferenceService();
}

LogFlowFunc(("rc = %Rrc\n", rc));
return rc;
}

可以知道,模块的id值最终被存入m_paClientIds[m_cClients],同时通过 *pu32ClientIdOut = handle;将值返回。
整个过程大概描述如下

VBGL_IOCTL_HGCM_CALL

在分析完VBGL_IOCTL_HGCM_CONNECT操作以后,接下来就是分析VBGL_IOCTL_HGCM_CALL,其路线与前面分析的类似,首先会将IOCTL传入的数据指针转为PVBGLIOCHGCMCALL类型

PVBGLIOCHGCMCALL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* For VBGL_IOCTL_HGCM_CALL and VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA.
*
* @note This is used by alot of HGCM call structures.
*/
typedef struct VBGLIOCHGCMCALL
{
/** Common header. */
VBGLREQHDR Hdr;
/** Input: The id of the caller. */
uint32_t u32ClientID;
/** Input: Function number. */
uint32_t u32Function;
/** Input: How long to wait (milliseconds) for completion before cancelling the
* call. This is ignored if not a VBGL_IOCTL_HGCM_CALL_TIMED or
* VBGL_IOCTL_HGCM_CALL_TIMED_32 request. */
uint32_t cMsTimeout;
/** Input: Whether a timed call is interruptible (ring-0 only). This is ignored
* if not a VBGL_IOCTL_HGCM_CALL_TIMED or VBGL_IOCTL_HGCM_CALL_TIMED_32
* request, or if made from user land. */
bool fInterruptible;
/** Explicit padding, MBZ. */
uint8_t bReserved;
/** Input: How many parameters following this structure.
*
* The parameters are either HGCMFunctionParameter64 or HGCMFunctionParameter32,
* depending on whether we're receiving a 64-bit or 32-bit request.
*
* The current maximum is 61 parameters (given a 1KB max request size,
* and a 64-bit parameter size of 16 bytes).
*
* @note This information is duplicated by Hdr.cbIn, but it's currently too much
* work to eliminate this. */
uint16_t cParms;
/* Parameters follow in form HGCMFunctionParameter aParms[cParms] */
} VBGLIOCHGCMCALL, RT_FAR *PVBGLIOCHGCMCALL;

经过一些列调用,会来到vgdrvIoCtl_HGCMCallInner函数

vgdrvIoCtl_HGCMCallInner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static int vgdrvIoCtl_HGCMCallInner(PVBOXGUESTDEVEXT pDevExt, PVBOXGUESTSESSION pSession, PVBGLIOCHGCMCALL pInfo,
uint32_t cMillies, bool fInterruptible, bool f32bit, bool fUserData,
size_t cbExtra, size_t cbData)
{
const uint32_t u32ClientId = pInfo->u32ClientID;
uint32_t fFlags;
size_t cbActual;
unsigned i;
int rc;

/*
* Some more validations.
*/
if (RT_LIKELY(pInfo->cParms <= VMMDEV_MAX_HGCM_PARMS)) /* (Just make sure it doesn't overflow the next check.) */
{ /* likely */}
else
{
LogRel(("VBOXGUEST_IOCTL_HGCM_CALL: cParm=%RX32 is not sane\n", pInfo->cParms));
return VERR_INVALID_PARAMETER;
}

cbActual = cbExtra + sizeof(*pInfo);
#ifdef RT_ARCH_AMD64
if (f32bit)
cbActual += pInfo->cParms * sizeof(HGCMFunctionParameter32);
else
#endif
cbActual += pInfo->cParms * sizeof(HGCMFunctionParameter);
if (RT_LIKELY(cbData >= cbActual))
{ /* likely */}
else
{
LogRel(("VBOXGUEST_IOCTL_HGCM_CALL: cbData=%#zx (%zu) required size is %#zx (%zu)\n",
cbData, cbData, cbActual, cbActual));
return VERR_INVALID_PARAMETER;
}
pInfo->Hdr.cbOut = (uint32_t)cbActual;

........................................................

else
rc = VbglR0HGCMInternalCall(pInfo, cbInfo, fFlags, pSession->fRequestor,
vgdrvHgcmAsyncWaitCallback, pDevExt, cMillies);
}
.............................................................
return rc;
}

从中可以看到cbActual += pInfo->cParms * sizeof(HGCMFunctionParameter);,并且该值最后赋值pInfo->Hdr.cbOut = (uint32_t)cbActual;,由此可见pInfo->cParms代表需要调用的函数的参数个数,而pInfo结构体下方就是cParms个HGCMFunctionParameter结构体对象。与VBGL_IOCTL_HGCM_CONNECT类似,最后驱动也是通过IO端口操作将数据发送到Host中的虚拟设备中,然后在设备的vmmdevReqDispatcher函数中处理。
如下代码

1
2
3
4
5
6
7
8
9
10
# ifdef VBOX_WITH_64_BITS_GUESTS
case VMMDevReq_HGCMCall64:
# endif
case VMMDevReq_HGCMCall32:
vmmdevReqHdrSetHgcmAsyncExecute(pThis, GCPhysReqHdr, *ppLock);
pReqHdr->rc = vmmdevReqHandler_HGCMCall(pThis, pReqHdr, GCPhysReqHdr, tsArrival, ppLock);
Assert(pReqHdr->rc == VINF_HGCM_ASYNC_EXECUTE || RT_FAILURE_NP(pReqHdr->rc));
if (RT_SUCCESS(pReqHdr->rc))
*pfPostOptimize |= VMMDEVREQDISP_POST_F_NO_WRITE_OUT;
break;

该操作仍然是异步处理,需要等待处理完成后回调函数响应,将结果通过IO端口传回Guest。操作主要是调用vmmdevHGCMCall来对相应的service里的函数进行调用。

vmmdevHGCMCall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/**
* Handles VMMDevHGCMCall request.
*
* @returns VBox status code that the guest should see.
* @param pThis The VMMDev instance data.
* @param pHGCMCall The request to handle (cached in host memory).
* @param cbHGCMCall Size of the entire request (including HGCM parameters).
* @param GCPhys The guest physical address of the request.
* @param enmRequestType The request type. Distinguishes 64 and 32 bit calls.
* @param tsArrival The STAM_GET_TS() value when the request arrived.
* @param ppLock Pointer to the lock info pointer (latter can be
* NULL). Set to NULL if HGCM takes lock ownership.
*/
int vmmdevHGCMCall(PVMMDEV pThis, const VMMDevHGCMCall *pHGCMCall, uint32_t cbHGCMCall, RTGCPHYS GCPhys,
VMMDevRequestType enmRequestType, uint64_t tsArrival, PVMMDEVREQLOCK *ppLock)
{
.............................................................
rc = vmmdevHGCMCallFetchGuestParms(pThis, pCmd, pHGCMCall, cbHGCMCall, enmRequestType, cbHGCMParmStruct);
if (RT_SUCCESS(rc))
{
/* Copy guest data to host parameters, so HGCM services can use the data. */
rc = vmmdevHGCMInitHostParameters(pThis, pCmd, (uint8_t const *)pHGCMCall);
if (RT_SUCCESS(rc))
{
/*
* Pass the function call to HGCM connector for actual processing
*/
vmmdevHGCMAddCommand(pThis, pCmd);

#if 0 /* DONT ENABLE - for performance hacking. */
if ( pCmd->u.call.u32Function == 9
&& pCmd->u.call.cParms == 5)
{
vmmdevHGCMRemoveCommand(pThis, pCmd);

if (pCmd->pvReqLocked)
{
VMMDevHGCMRequestHeader volatile *pHeader = (VMMDevHGCMRequestHeader volatile *)pCmd->pvReqLocked;
pHeader->header.rc = VINF_SUCCESS;
pHeader->result = VINF_SUCCESS;
pHeader->fu32Flags |= VBOX_HGCM_REQ_DONE;
}
else
{
VMMDevHGCMRequestHeader *pHeader = (VMMDevHGCMRequestHeader *)pHGCMCall;
pHeader->header.rc = VINF_SUCCESS;
pHeader->result = VINF_SUCCESS;
pHeader->fu32Flags |= VBOX_HGCM_REQ_DONE;
PDMDevHlpPhysWrite(pThis->pDevInsR3, GCPhys, pHeader, sizeof(*pHeader));
}
vmmdevHGCMCmdFree(pThis, pCmd);
return VINF_HGCM_ASYNC_EXECUTE; /* ignored, but avoids assertions. */
}
#endif

rc = pThis->pHGCMDrv->pfnCall(pThis->pHGCMDrv, pCmd,
pCmd->u.call.u32ClientID, pCmd->u.call.u32Function,
pCmd->u.call.cParms, pCmd->u.call.paHostParms, tsArrival);
...................................................
return rc;
}

可以看出,vmmdevHGCMCall中首先是使用vmmdevHGCMCallFetchGuestParms函数和vmmdevHGCMInitHostParameters函数,将参数从Guest中拷贝到了设备本地缓冲区中,然后通过pThis->pHGCMDrv->pfnCall调用了对应的函数。
通过调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Devices/VMMDev/VMMDevHGCM.cpp
1107 }
1108 #endif
1109
1110 rc = pThis->pHGCMDrv->pfnCall(pThis->pHGCMDrv, pCmd,
1111 pCmd->u.call.u32ClientID, pCmd->u.call.u32Function,
► 1112 pCmd->u.call.cParms, pCmd->u.call.paHostParms, tsArrival);
1113
1114 if (rc == VINF_HGCM_ASYNC_EXECUTE)
1115 {
1116 /*
1117 * Done. Just update statistics and return.
pwndbg> s
638 }
639
640 static DECLCALLBACK(int) iface_hgcmCall(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID,
641 uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms, uint64_t tsArrival)

可以知道该函数指针指向的是iface_hgcmCall函数

iface_hgcmCall

1
2
3
4
5
6
7
8
9
10
11
12
static DECLCALLBACK(int) iface_hgcmCall(PPDMIHGCMCONNECTOR pInterface, PVBOXHGCMCMD pCmd, uint32_t u32ClientID,
uint32_t u32Function, uint32_t cParms, PVBOXHGCMSVCPARM paParms, uint64_t tsArrival)
{
Log9(("Enter\n"));

PDRVMAINVMMDEV pDrv = RT_FROM_MEMBER(pInterface, DRVMAINVMMDEV, HGCMConnector);

if (!pDrv->pVMMDev || !pDrv->pVMMDev->hgcmIsActive())
return VERR_INVALID_STATE;

return HGCMGuestCall(pDrv->pHGCMPort, pCmd, u32ClientID, u32Function, cParms, paParms, tsArrival);
}

该函数简单的调用了HGCMGuestCall函数,而HGCMGuestCall函数继续调用HGCMService::GuestCall函数,同样也是通过hgcmMsgPost将消息挂到队列中,等待hgcmServiceThread线程取出消息并处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
* The service thread. Loads the service library and calls the service entry points.
*/
DECLCALLBACK(void) hgcmServiceThread(HGCMThread *pThread, void *pvUser)
{
HGCMService *pSvc = (HGCMService *)pvUser;
AssertRelease(pSvc != NULL);
/* Cache required information to avoid unnecessary pMsgCore access. */
uint32_t u32MsgId = pMsgCore->MsgId();

switch (u32MsgId)
{
case SVC_MSG_GUESTCALL:
{
HGCMMsgCall *pMsg = (HGCMMsgCall *)pMsgCore;

LogFlowFunc(("SVC_MSG_GUESTCALL u32ClientId = %d, u32Function = %d, cParms = %d, paParms = %p\n",
pMsg->u32ClientId, pMsg->u32Function, pMsg->cParms, pMsg->paParms));

HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT);

if (pClient)
{
pSvc->m_fntable.pfnCall(pSvc->m_fntable.pvService, (VBOXHGCMCALLHANDLE)pMsg, pMsg->u32ClientId,
HGCM_CLIENT_DATA(pSvc, pClient), pMsg->u32Function,
pMsg->cParms, pMsg->paParms, pMsg->tsArrival);

hgcmObjDereference(pClient);
}
else
{
rc = VERR_HGCM_INVALID_CLIENT_ID;
}
} break;

代码中,通过HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT);获取到了HGCMClient服务对象,然后通过pSvc->m_fntable.pfnCall进入了对应服务的处理函数。
调试如下

1
2
3
4
5
6
7
8
9
10
11
12
In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedClipboard/service.cpp
407 void *pvClient,
408 uint32_t u32Function,
409 uint32_t cParms,
410 VBOXHGCMSVCPARM paParms[],
411 uint64_t tsArrival)
► 412 {
413 RT_NOREF_PV(tsArrival);
414 int rc = VINF_SUCCESS;
415
416 LogRel2(("svcCall: u32ClientID = %d, fn = %d, cParms = %d, pparms = %d\n",
417 u32ClientID, u32Function, cParms, paParms));

此时我们进入的是 SharedClipboard服务的程序svcCall函数。对于HostServices目录下的各种服务都有一个svcCall函数的实现,由此可见,svcCall函数是服务程序的处理机入口。从代码可以看出这个函数是在VBoxHGCMSvcLoad中注册的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable)
{
int rc = VINF_SUCCESS;
g_pHelpers = ptable->pHelpers;

ptable->cbClient = sizeof (VBOXCLIPBOARDCLIENTDATA);

ptable->pfnUnload = svcUnload;
ptable->pfnConnect = svcConnect;
ptable->pfnDisconnect = svcDisconnect;
ptable->pfnCall = svcCall;
ptable->pfnHostCall = svcHostCall;
ptable->pfnSaveState = svcSaveState;
ptable->pfnLoadState = svcLoadState;
ptable->pfnRegisterExtension = svcRegisterExtension;
ptable->pfnNotify = NULL;
ptable->pvService = NULL;

/* Service specific initialization. */
rc = svcInit ();
.................................................

至此,我们对于VBGL_IOCTL_HGCM_CALL调用Service中的函数的整个流程也有所清楚了。

VBGL_IOCTL_IDC_DISCONNECT

对于VBGL_IOCTL_IDC_DISCONNECT,流程与前面类似,比较简单,调用了对应服务的DisconnectClient函数,然后使用hgcmObjDereference(pClient);将服务句柄从设备缓存列表中移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
case HGCM_MSG_DISCONNECT:
{
HGCMMsgMainDisconnect *pMsg = (HGCMMsgMainDisconnect *)pMsgCore;

LogFlowFunc(("HGCM_MSG_DISCONNECT u32ClientId = %d\n",
pMsg->u32ClientId));

HGCMClient *pClient = (HGCMClient *)hgcmObjReference(pMsg->u32ClientId, HGCMOBJ_CLIENT);

if (!pClient)
{
rc = VERR_HGCM_INVALID_CLIENT_ID;
break;
}

/* The service the client belongs to. */
HGCMService *pService = pClient->pService;

/* Call the service instance to disconnect the client. */
rc = pService->DisconnectClient(pMsg->u32ClientId, false);

hgcmObjDereference(pClient);
} break;

至此,我们对HGCM协议已经有了进一步的深刻了解。

0x02 HGCM调用库封装

经过上面的协议源代码分析,我们可以很轻松的写出HGCM的调用方法,国外niklasb大牛已经做了一个python版的封装库名为3dpwn,而这里,我们自己同样实现了一个C语言版

hgcm.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#ifndef HGM_HELPER_H
#define HGM_HELPER_H

#define VBGLREQHDR_VERSION 0x10001
#define VBGLREQHDR_TYPE_DEFAULT 0
#define VERR_INTERNAL_ERROR -225

#define VBGL_IOCTL_CODE_SIZE(func, size) (0xc0005600 + (size<<16) + func)

#define VBGL_IOCTL_HGCM_CONNECT VBGL_IOCTL_CODE_SIZE(4, VBGL_IOCTL_HGCM_CONNECT_SIZE)
#define VBGL_IOCTL_HGCM_CONNECT_SIZE sizeof(VBGLIOCHGCMCONNECT)

# define VBGL_IOCTL_HGCM_DISCONNECT VBGL_IOCTL_CODE_SIZE(5, VBGL_IOCTL_HGCM_DISCONNECT_SIZE)
# define VBGL_IOCTL_HGCM_DISCONNECT_SIZE sizeof(VBGLIOCHGCMDISCONNECT)

#define IOCTL_HGCM_CALL 7

/** Guest Physical Memory Address; limited to 64 bits.*/
typedef uint64_t RTGCPHYS64;
/** Unsigned integer which can contain a 64 bits GC pointer. */
typedef uint64_t RTGCUINTPTR64;
/** Guest context pointer, 64 bits.
*/
typedef RTGCUINTPTR64 RTGCPTR64;

typedef uint8_t bool;


typedef struct VBGLREQHDR
{
/** IN: The request input size, and output size if cbOut is zero.
* @sa VMMDevRequestHeader::size */
uint32_t cbIn;
/** IN: Structure version (VBGLREQHDR_VERSION)
* @sa VMMDevRequestHeader::version */
uint32_t uVersion;
/** IN: The VMMDev request type, set to VBGLREQHDR_TYPE_DEFAULT unless this is a
* kind of VMMDev request.
* @sa VMMDevRequestType, VMMDevRequestHeader::requestType */
uint32_t uType;
/** OUT: The VBox status code of the operation, out direction only. */
int32_t rc;
/** IN: The output size. This is optional - set to zero to use cbIn as the
* output size. */
uint32_t cbOut;
/** Reserved / filled in by kernel, MBZ.
* @sa VMMDevRequestHeader::fRequestor */
uint32_t uReserved;
} VBGLREQHDR;

/**
* HGCM host service location.
* @ingroup grp_vmmdev_req
*/
typedef struct
{
char achName[128]; /**< This is really szName. */
} HGCMServiceLocationHost;

typedef enum
{
VMMDevHGCMLoc_Invalid = 0,
VMMDevHGCMLoc_LocalHost = 1,
VMMDevHGCMLoc_LocalHost_Existing = 2,
VMMDevHGCMLoc_SizeHack = 0x7fffffff
} HGCMServiceLocationType;

/**
* HGCM service location.
* @ingroup grp_vmmdev_req
*/
typedef struct HGCMSERVICELOCATION
{
/** Type of the location. */
HGCMServiceLocationType type;

union
{
HGCMServiceLocationHost host;
} u;
} HGCMServiceLocation;

typedef struct VBGLIOCHGCMCONNECT
{
/** The header. */
VBGLREQHDR Hdr;
union
{
struct
{
HGCMServiceLocation Loc;
} In;
struct
{
uint32_t idClient;
} Out;
} u;
} VBGLIOCHGCMCONNECT;

/**
* For VBGL_IOCTL_HGCM_CALL and VBGL_IOCTL_HGCM_CALL_WITH_USER_DATA.
*
* @note This is used by alot of HGCM call structures.
*/
typedef struct VBGLIOCHGCMCALL
{
/** Common header. */
VBGLREQHDR Hdr;
/** Input: The id of the caller. */
uint32_t u32ClientID;
/** Input: Function number. */
uint32_t u32Function;
/** Input: How long to wait (milliseconds) for completion before cancelling the
* call. This is ignored if not a VBGL_IOCTL_HGCM_CALL_TIMED or
* VBGL_IOCTL_HGCM_CALL_TIMED_32 request. */
uint32_t cMsTimeout;
/** Input: Whether a timed call is interruptible (ring-0 only). This is ignored
* if not a VBGL_IOCTL_HGCM_CALL_TIMED or VBGL_IOCTL_HGCM_CALL_TIMED_32
* request, or if made from user land. */
bool fInterruptible;
/** Explicit padding, MBZ. */
uint8_t bReserved;
/** Input: How many parameters following this structure.
*
* The parameters are either HGCMFunctionParameter64 or HGCMFunctionParameter32,
* depending on whether we're receiving a 64-bit or 32-bit request.
*
* The current maximum is 61 parameters (given a 1KB max request size,
* and a 64-bit parameter size of 16 bytes).
*
* @note This information is duplicated by Hdr.cbIn, but it's currently too much
* work to eliminate this. */
uint16_t cParms;
/* Parameters follow in form HGCMFunctionParameter aParms[cParms] */
} VBGLIOCHGCMCALL;



/**
* HGCM parameter type.
*/
typedef enum
{
VMMDevHGCMParmType_Invalid = 0,
VMMDevHGCMParmType_32bit = 1,
VMMDevHGCMParmType_64bit = 2,
VMMDevHGCMParmType_PhysAddr = 3, /**< @deprecated Doesn't work, use PageList. */
VMMDevHGCMParmType_LinAddr = 4, /**< In and Out */
VMMDevHGCMParmType_LinAddr_In = 5, /**< In (read; host<-guest) */
VMMDevHGCMParmType_LinAddr_Out = 6, /**< Out (write; host->guest) */
VMMDevHGCMParmType_LinAddr_Locked = 7, /**< Locked In and Out */
VMMDevHGCMParmType_LinAddr_Locked_In = 8, /**< Locked In (read; host<-guest) */
VMMDevHGCMParmType_LinAddr_Locked_Out = 9, /**< Locked Out (write; host->guest) */
VMMDevHGCMParmType_PageList = 10, /**< Physical addresses of locked pages for a buffer. */
VMMDevHGCMParmType_Embedded = 11, /**< Small buffer embedded in request. */
VMMDevHGCMParmType_ContiguousPageList = 12, /**< Like PageList but with physically contiguous memory, so only one page entry. */
VMMDevHGCMParmType_SizeHack = 0x7fffffff
} HGCMFunctionParameterType;

# pragma pack(4)

typedef struct
{
HGCMFunctionParameterType type;
union
{
uint32_t value32;
uint64_t value64;
struct
{
uint32_t size;

union
{
RTGCPHYS64 physAddr;
RTGCPTR64 linearAddr;
} u;
} Pointer;
struct
{
uint32_t size; /**< Size of the buffer described by the page list. */
uint32_t offset; /**< Relative to the request header, valid if size != 0. */
} PageList;
struct
{
uint32_t fFlags : 8; /**< VBOX_HGCM_F_PARM_*. */
uint32_t offData : 24; /**< Relative to the request header, valid if cb != 0. */
uint32_t cbData; /**< The buffer size. */
} Embedded;
} u;
} HGCMFunctionParameter64;


typedef struct VBGLIOCHGCMDISCONNECT
{
/** The header. */
VBGLREQHDR Hdr;
union
{
struct
{
uint32_t idClient;
} In;
} u;
} VBGLIOCHGCMDISCONNECT;

#endif

hgcm.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include "hgcm.h"

void die(char *msg) {
perror(msg);
exit(-1);
}


//device fd
int fd;

int hgcm_connect(const char *service_name) {
VBGLIOCHGCMCONNECT data = {
.Hdr.cbIn = sizeof(VBGLIOCHGCMCONNECT),
.Hdr.uVersion = VBGLREQHDR_VERSION,
.Hdr.uType = VBGLREQHDR_TYPE_DEFAULT,
.Hdr.rc = VERR_INTERNAL_ERROR,
.Hdr.cbOut = sizeof(VBGLREQHDR) + sizeof(uint32_t),
.Hdr.uReserved = 0,
.u.In.Loc.type = VMMDevHGCMLoc_LocalHost_Existing
};
memset(data.u.In.Loc.u.host.achName,0,128);
strncpy(data.u.In.Loc.u.host.achName,service_name,128);
ioctl(fd,VBGL_IOCTL_HGCM_CONNECT,&data);
if (data.Hdr.rc) { //error
return -1;
}
return data.u.Out.idClient;
}

HGCMFunctionParameter64 arg_buf[0x100];

int hgcm_call(int client_id,int func,char *params_fmt,...) {
va_list ap;
char *p,*bval,*type;
uint32_t ival;
uint64_t lval;
HGCMFunctionParameter64 params;
uint16_t index = 0;

va_start(ap,params_fmt);
for(p = params_fmt;*p;p++) {
if(*p!='%') {
continue;
}

switch (*++p) {
case 'u': //整数类型
ival = va_arg(ap,uint32_t);
params.type = VMMDevHGCMParmType_32bit;
params.u.value64 = 0;
params.u.value32 = ival;
arg_buf[index++] = params;
break;
case 'l':
lval = va_arg(ap,uint64_t);
params.type = VMMDevHGCMParmType_64bit;
params.u.value64 = lval;
arg_buf[index++] = params;
case 'b': //buffer类型
type = va_arg(ap,char *);
bval = va_arg(ap,char *);
ival = va_arg(ap,uint32_t);
if (!strcmp(type,"in")) {
params.type = VMMDevHGCMParmType_LinAddr_In;
} else if (!strcmp(type,"out")) {
params.type = VMMDevHGCMParmType_LinAddr_Out;
} else {
params.type = VMMDevHGCMParmType_LinAddr;
}
params.u.Pointer.size = ival;
params.u.Pointer.u.linearAddr = (uintptr_t)bval;
arg_buf[index++] = params;
break;
}
}
va_end(ap);
//printf("params count=%d\n",index);
uint8_t *data_buf = (uint8_t *)malloc(sizeof(VBGLIOCHGCMCALL) + sizeof(HGCMFunctionParameter64)*index);
VBGLIOCHGCMCALL data = {
.Hdr.cbIn = sizeof(VBGLIOCHGCMCALL) + sizeof(HGCMFunctionParameter64)*index,
.Hdr.uVersion = VBGLREQHDR_VERSION,
.Hdr.uType = VBGLREQHDR_TYPE_DEFAULT,
.Hdr.rc = VERR_INTERNAL_ERROR,
.Hdr.cbOut = sizeof(VBGLIOCHGCMCALL) + sizeof(HGCMFunctionParameter64)*index,
.Hdr.uReserved = 0,
.u32ClientID = client_id,
.u32Function = func,
.cMsTimeout = 100000, //忽略
.fInterruptible = 0,
.bReserved = 0,
.cParms = index
};
memcpy(data_buf,&data,sizeof(VBGLIOCHGCMCALL));
memcpy(data_buf+sizeof(VBGLIOCHGCMCALL),arg_buf,sizeof(HGCMFunctionParameter64)*index);

/*for (int i=0;i<sizeof(VBGLIOCHGCMCALL)+sizeof(HGCMFunctionParameter64)*index;i++) {
printf("%02x",data_buf[i]);
}
printf("\n");*/

ioctl(fd,VBGL_IOCTL_CODE_SIZE(IOCTL_HGCM_CALL,sizeof(VBGLIOCHGCMCALL) + sizeof(HGCMFunctionParameter64)*index),data_buf);
int error = ((VBGLIOCHGCMCALL *)data_buf)->Hdr.rc;
free(data_buf);

if (error) { //error
return error;
}
/*for (int i=0;i<sizeof(VBGLIOCHGCMCALL)+sizeof(HGCMFunctionParameter64)*index;i++) {
printf("%02x",data_buf[i]);
}
printf("\n");*/

return 0;
}

int hgcm_disconnect(int client_id) {
VBGLIOCHGCMDISCONNECT data = {
.Hdr.cbIn = sizeof(VBGLIOCHGCMDISCONNECT),
.Hdr.uVersion = VBGLREQHDR_VERSION,
.Hdr.uType = VBGLREQHDR_TYPE_DEFAULT,
.Hdr.rc = VERR_INTERNAL_ERROR,
.Hdr.cbOut = sizeof(VBGLREQHDR),
.Hdr.uReserved = 0,
.u.In.idClient = client_id,
};
ioctl(fd,VBGL_IOCTL_HGCM_DISCONNECT,&data);
if (data.Hdr.rc) { //error
return -1;
}
return 0;
}

int main() {
//打开设备
fd = open("/dev/vboxuser",O_RDWR);
if (fd == -1) {
die("open device error");
}
int idClient = hgcm_connect("VBoxGuestPropSvc");
printf("idClient=%d\n",idClient);
char ans[0x100] = {0};
int ret = hgcm_call(idClient,2,"%b%b","in","foo",4,"in","bar",4);
ret = hgcm_call(idClient,1,"%b%b%u%u","in","foo",4,"out",ans,0x100,0,0);

printf("%s\n",ans);
printf("%d\n",hgcm_disconnect(idClient));
}

0x03 感想

学习漏洞挖掘,不应该只依赖于现成的库或工具,就像本文,虽然niklasb大牛已经封装了3dpwn库,但是对于我们研究员来说,还是得先自己弄懂,自己动手写工具,才能明白其本质。

0x04 参考链接

Investigating generic problems with the Linux Guest Additions
corelabs-Breaking_Out_of_VirtualBox_through_3D_Acceleration-Francisco_Falcon.pdf