热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

实现虚拟机逃逸:OracleVirtualBoxVHWAUAF权限提升漏洞分析

 一、前言我在STAR Labs进行为期一个月的实习期间,主要负责对VirturalBox的分析,在此过程中我学到了许多关于漏洞挖掘、漏洞分类、根本原因分析和漏洞利用的只是。在这篇文章中,将详细介绍我

 

一、前言

我在STAR Labs进行为期一个月的实习期间,主要负责对VirturalBox的分析,在此过程中我学到了许多关于漏洞挖掘、漏洞分类、根本原因分析和漏洞利用的只是。在这篇文章中,将详细介绍我在实习期间发现的一个UAF漏洞,以及我如何利用该漏洞编写虚拟机逃逸漏洞利用的细节。在撰写本文时,产品的最新版本是VirtualBox 6.1.2 r135662。

 

二、建立环境

这篇文章所使用的测试环境是Windows 10宿主机和Windows 7虚拟机。在测试环境中,我们将虚拟机配置为使用VBoxVGA图形控制器,并且选中“启用2D视频加速”的选项。

在发现任何漏洞或者编写任何漏洞利用之前,我们应该首先创建一个调试环境,以便于定位引起崩溃的根本原因并对漏洞利用进行调试。VirtualBox受到进程加固的保护,因此在VirtualBox的发行版本中,不可能(或者很难)将用户级别调试器附加到VirtualBox进程中。但幸运的是,VirtualBox是一个开源软件,可以编译一个未经加固的调试版本,而这样的版本就允许附加调试器了。我的导师anhdaden使用了附加调试器的VirtualBox 6.1.0 r135406版本,这对于我直接进行调试很有帮助。

在这里,引用@_niklasb的描述:

最后,我们还需要掌握一些关于编写Windows内核驱动程序的知识。这对于漏洞利用来说是必要的,因为我们将在虚拟机中与宿主机模拟的设备进行交互,而这些设备只能在内核级访问。在编写WDM驱动程序的过程中,我们参考了以下资料:

https://web.archive.org/web/20080514132017/

http://www.catch22.net/tuts/kernel1.asp

我们并不需要对这一领域有太过深入的掌握,就可以理解漏洞利用的过程。

 

三、背景

我们所发现的漏洞位于宿主机与虚拟机的共享内存接口(HGSMI)提供的VirtualBox视频加速(VBVA)功能中。要使用这个功能,虚拟机内核驱动程序需要映射物理地址0xE0000000以执行内存映射的I/O(MMIO)。虚拟机需要使用格式化的HGSMI命令写入物理地址0xE0000000的VRAM缓冲区,指示其正在使用哪个通道,以及其他详细信息。之后,虚拟机应向IO接口VGA_PORT_HGSMI_GUEST(0x3d0)发送输出指令,以允许模拟设备开始处理。我们可以使用HGSMIBufferProcess函数看到详细信息。为了避免产生额外的工作量,我利用了voidsecurity针对这一攻击面编写的另一个漏洞的漏洞利用代码。

对于VBVA服务,它是由vbvaChannelHandler函数负责处理。这里支持发送多种VBVA命令,但其中的一个VBVA_VHWA_CMD命令中存在问题,该命令用于视频硬件加速(VHWA)。通过使用调试器跟踪函数调用,我们可以确定VHWA命令的实际处理程序。

vbvaChannelHandler
|_ vbvaVHWAHandleCommand
|_ vbvaVHWACommandSubmit(Inner)
|_ pThisCC->pDrv->pfnVHWACommandProcess = Display::i_handleVHWACommandProcess
|_ pFramebuffer->ProcessVHWACommand = VBoxOverlayFrameBuffer.ProcessVHWACommand
|_ mOverlay.OnVHWACommand= VBoxQGLOverlay::onVHWACommand
|_ mCmdPipe.postCmd = VBoxVHWACommandElementProcessor::postCmd
|_ pCmd->setData
|_ RTListAppend(&mCommandList, &pCmd->ListNode);
*once command is added to list, it is then processed
VBoxQGLOverlay::onVHWACommandEvent
|_ mCmdPipe.getCmd
|_ processCmd = VBoxQGLOverlay::processCmd
|_ vboxDoVHWACmd = VBoxQGLOverlay::vboxDoVHWACmd
|_ vboxDoVHWACmdExec = VBoxQGLOverlay::vboxDoVHWACmdExec

VBoxQGLOverlay::vboxDoVHWACmdExec将会是最重要的一个函数,因为其中包含VHWA命令的处理内容。

 

四、漏洞分析

现在,我们已经大致熟悉了代码,接下来就可以深入研究易受攻击的VHWA命令。在VBoxQGLOverlay::vboxDoVHWACmdExec中,有各种命令可以对对象进行分配、删除和操作,CTF挑战者可能对这块比较熟悉。

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:4669
void VBoxQGLOverlay::vboxDoVHWACmdExec(void RT_UNTRUSTED_VOLATILE_GUEST *pvCmd, int /*VBOXVHWACMD_TYPE*/ enmCmdInt, bool fGuestCmd)
{
struct VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *pCmd = (struct VBOXVHWACMD RT_UNTRUSTED_VOLATILE_GUEST *)pvCmd;
VBOXVHWACMD_TYPE enmCmd = (VBOXVHWACMD_TYPE)enmCmdInt;
switch (enmCmd)
{
...
case VBOXVHWACMD_TYPE_SURF_CREATE:
{
VBOXVHWACMD_SURF_CREATE RT_UNTRUSTED_VOLATILE_GUEST *pBody = VBOXVHWACMD_BODY(pCmd, VBOXVHWACMD_SURF_CREATE);
Assert(!mGlOn == !mOverlayImage.hasSurfaces());
initGl();
makeCurrent();
vboxSetGlOn(true);
pCmd->rc = mOverlayImage.vhwaSurfaceCreate(pBody);
...
case VBOXVHWACMD_TYPE_SURF_OVERLAY_UPDATE:
{
VBOXVHWACMD_SURF_OVERLAY_UPDATE RT_UNTRUSTED_VOLATILE_GUEST *pBody = VBOXVHWACMD_BODY(pCmd, VBOXVHWACMD_SURF_OVERLAY_UPDATE);
Assert(!mGlOn == !mOverlayImage.hasSurfaces());
initGl();
makeCurrent();
pCmd->rc = mOverlayImage.vhwaSurfaceOverlayUpdate(pBody);
...

我们所分析的漏洞位于VBOXVHWACMD_TYPE_SURF_CREATE中。当使用VBOXVHWACMD_TYPE_SURF_CREATE命令时,将会调用VBoxVHWAImage::vhwaSurfaceCreate,它可以创建一个新的VBoxVHWASurfaceBase对象。指向该VBoxVHWASurfaceBase对象的指针将存储在调用对象的mSurfHandleTable成员中,该成员只是由句柄索引的指针数组。

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2287
int VBoxVHWAImage::vhwaSurfaceCreate(struct VBOXVHWACMD_SURF_CREATE RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
...
VBoxVHWASurfaceBase *surf = NULL;
...
if (format.isValid())
{
surf = new VBoxVHWASurfaceBase(this,
surfSize,
primaryRect,
QRect(0, 0, surfSize.width(), surfSize.height()),
mViewport,
format,
pSrcBltCKey, pDstBltCKey, pSrcOverlayCKey, pDstOverlayCKey,
#ifdef VBOXVHWA_USE_TEXGROUP
0,
#endif
fFlags);
}
...
handle = mSurfHandleTable.put(surf);
pCmd->SurfInfo.hSurf = (VBOXVHWA_SURFHANDLE)handle;

但是,当启用了某些命令标志,而没有创建新的VBoxVHWASurfaceBase对象时,会将surf设置为已经存在的VBoxVHWASurfaceBase对象。

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2287
int VBoxVHWAImage::vhwaSurfaceCreate(struct VBOXVHWACMD_SURF_CREATE RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
...
VBoxVHWASurfaceBase *surf = NULL;
...
if (pCmd->SurfInfo.surfCaps & VBOXVHWA_SCAPS_PRIMARYSURFACE)
{
bNoPBO = true;
bPrimary = true;
VBoxVHWASurfaceBase *pVga = vgaSurface(); /* == mDisplay.getVGA() == mDisplay.mSurfVGA */
...
surf = pVga;
...
handle = mSurfHandleTable.put(surf);
pCmd->SurfInfo.hSurf = (VBOXVHWA_SURFHANDLE)handle;

当我们跟随这个代码路径时,发现mSurfHandleTable将会保存对mDisplay对象的mSurfVGA的引用。但是,这个mSurfVGA成员可以在其他功能实现期间(例如:调整大小的功能)被替换。虚拟机可以触发对屏幕大小的调整,在屏幕大小调整后,将会执行以下代码。

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:3752
void VBoxVHWAImage::resize(const VBoxFBSizeInfo &size)
{
...
VBoxVHWASurfaceBase *pDisplay = mDisplay.setVGA(NULL);
if (pDisplay)
delete pDisplay;
...
pDisplay = new VBoxVHWASurfaceBase(this,
dispSize,
dispRect,
dispRect,
dispRect, /* we do not know viewport at the stage of precise, set as a
disp rect, it will be updated on repaint */
format,
NULL, NULL, NULL, NULL,
#ifdef VBOXVHWA_USE_TEXGROUP
0,
#endif
0 /* VBOXVHWAIMG_TYPE fFlags */);

尽管mDisplay成员的mSurfVGA已经释放,并使用新的分配进行更新,但是mSurfHandleTable仍然将保留指向已释放的旧VBoxVHWASurfaceBase对象的指针。这样将会产生一个“释放后使用”(UAF)的场景,因为其他VHWA命令(例如:VBOXVHWACMD_TYPE_SURF_OVERLAY_UPDATE)仍然可以通过其句柄访问这个释放后的指针,并进行各种操作。

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2823
int VBoxVHWAImage::vhwaSurfaceOverlayUpdate(struct VBOXVHWACMD_SURF_OVERLAY_UPDATE RT_UNTRUSTED_VOLATILE_GUEST *pCmd)
{
VBoxVHWASurfaceBase *pSrcSurf = handle2Surface(pCmd->u.in.hSrcSurf); /*pSrcSurf = freed chunk*/
AssertReturn(pSrcSurf, VERR_INVALID_PARAMETER);
VBoxVHWASurfList *pList = pSrcSurf->getComplexList();
...

如果要从虚拟机内核驱动程序执行调整大小的操作,可以使用另一个VBVA命令来替代VHWA命令(VBVA_VHWA_CMD)。VBVA_INFO_SCREEN命令会以调用调整大小为结束,从而让我们能够触发UAF。

// src/VBox/Devices/Graphics/DevVGA_VBVA.cpp:2444
static DECLCALLBACK(int) vbvaChannelHandler(void *pvHandler, uint16_t u16ChannelInfo,
void RT_UNTRUSTED_VOLATILE_GUEST *pvBuffer, HGSMISIZE cbBuffer)
{
...
switch (u16ChannelInfo)
{
...
case VBVA_INFO_SCREEN:
rc = VERR_INVALID_PARAMETER;
if (cbBuffer >= sizeof(VBVAINFOSCREEN))
rc = vbvaInfoScreen(pThisCC, (VBVAINFOSCREEN RT_UNTRUSTED_VOLATILE_GUEST *)pvBuffer);
break;

vbvaInfoScreen
|_ vbvaResize
|_ pThisCC->pDrv->pfnVBVAResize = Display::i_displayVBVAResize

 

五、漏洞利用



5.1 堆喷射

考虑到我们发现的UAF漏洞的性质,漏洞利用的第一步需要通过另一个受控制的分配来回收释放后的分配。由于我们的宿主机是Windows 10计算机,所以堆将会经过低分片堆(LFH)和各种各样令人困惑的机制处理。我们有一种相对可靠的方式来进行回收分配,就是在VirtualBox代码中找到一个原语,该原语允许虚拟机具有与VBoxVHWASurfaceBase相同大小的分配,同时还允许我们控制分配的内容。最后使用的一个原语是drvNATNetworkUp_AllocBuf,它在发送以太网帧时被调用。我在这里直接使用了导师编写的用于堆喷射的代码,从而节约了用来理解以太网协议的很多时间。要了解该原语,我们需要为其分配16字节对齐的大小,用于存储来自虚拟机的数据。下图说明了堆喷射的用法:


5.2 rip控制

通过堆喷射,我们可以控制损坏的VBoxVHWASurfaceBase对象的vtable成员。那么,我们如何利用它来实现rip控制呢?
在检查VBoxVHWASurfaceBase的有效vtable时,我们注意到,其中唯一包含的条目是指向向量删除析构函数的函数指针。因此,一旦我们知道可以写入的内存区域地址,就可以编写一个伪造的vtable,以替换该vtable成员,使其指向受控制的vtable,并通过VBOXVHWACMD_TYPE_SURF_DESTROY VHWA命令删除VBoxVHWASurfaceBase对象,从而是我们可以控制指令指针rip

目前,我们必须先实现信息泄露,然后才能继续实现漏洞利用。


5.3 获取信息泄露

我花费了很长的时间来查看各种VHWA命令,以寻找是否可能让某些指针泄露回虚拟机的VRAM。最后,我发现这是一个死胡同。经过一些尝试,anhdaden带我走上了寻找信息泄露的正确道路。这种技术基于虚拟机能够在内存中控制的VRAM MMIO缓冲区(如前所述)。我的虚拟机配置为256MB视频内存,VRAM缓冲区的大小已经达到了0x10000000字节。有了如此巨大的空间,我们就可以猜测宿主机中该缓冲区的虚拟地址了。在重新启动几次虚拟机后,我注意到缓冲区分配在如下的几个地址上:

start end
0x00000000ACB0000-0x00000001ACB0000
0x00000000AEE0000-0x00000001AEE0000
0x00000000B4F0000-0x00000001B4F0000
0x00000000B1A0000-0x00000001B1A0000
0x00000000ADE0000-0x00000001ADE0000
0x00000000A670000-0x00000001A670000
0x00000000B0B0000-0x00000001B0B0000
0x00000000AC10000-0x00000001AC10000

这个范围非常巨大,即使我们猜测地址类似于0x00000000C000000,但它也仍然位于VRAM缓冲区内。有了这些信息后,我们就可以构建更好的原语来泄露DLL地址,并可能形成任意读/写原语。

但是,仍然有一个小问题,尽管我们知道可控的VRAM缓冲区中可能存在一个类似于0x00000000C000000的任意地址,但是我们不知道这个地址与缓冲区起始位置的偏移量。幸运的是,有一种方法可以解决这个问题。

我们来看看VBoxVHWAImage::vhwaSurfaceOverlayUpdate函数,该函数实现了VBOXVHWACMD_TYPE_SURF_OVERLAY_UPDATE命令。由于源代码具有许多实际上内联的函数调用,同时还包括编译后已优化的其他宏,因此我发现,使用反编译器来检查函数会更加容易一些。

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2823
signed __int64 __fastcall VBoxVHWAImage::vhwaSurfaceOverlayUpdate(__int64 _this, uint8_t *_pCmd)
{
...
pSrcSurf = *(VBoxVHWASurfaceBase **)(*(_QWORD *)(_this + 72) + 8i64 * (unsigned int)*((_QWORD *)_pCmd + 4));
...
pList = (__int64 ***)pSrcSurf->mComplexList; /* [1] */
...
if ( _bittest(&v24, 9u) )
{
...
}
else
{
...
if ( _bittest(&v25, 0xEu) )
*(_QWORD *)(pList + 0x18) = pSrcSurf; /* [2] */
...
}

查看其中的[1]行,pList是从VBoxVHWASurfaceBase对象pSrcSurf中获取的指针。通过之前的堆喷射,我们可以控制pList的值。

随后,在[2]行中,取消引用pList,并将偏移量为0x18的成员设置为指向pSrcSurf的指针。如果我们可以将pList设置为指向VRAM缓冲区,那么我们就可以从堆中泄露指针!此外,如果pList = 0x00000000C000000,则指针将放置在0x00000000C000018处,我们可以扫描内存中的这一更改,并根据VRAM中指针的索引来计算VRAM缓冲区的基址。

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2823
signed __int64 __fastcall VBoxVHWAImage::vhwaSurfaceOverlayUpdate(__int64 _this, uint8_t *_pCmd)
{
...
for ( i = **(__int64 ***)pList; i != *(__int64 **)pList; i = (__int64 *)*i )
VBoxVHWAImage::vhwaDoSurfaceOverlayUpdate(this, pDstSurf, (VBoxVHWASurfaceBase *)i[2], pCmd);
...

值得注意的是,pList的第一个成员应该是指向单链列表的有效指针,否则如上面的代码所示,虚拟机在遍历时会发生崩溃。因此,地址0x00000000C000000应该包含一个有效的链表指针。在不知道VRAM缓冲区基址的情况下,解决这一问题的简单方法是在VRAM上喷射0x00000000C000000,这样就可以可靠地防止崩溃的发生。

uint64_t* v = (uint64_t*)&vram[0x1000];
for (int i = 0; i <0x6000000 / 8; i++) {
v[i] = 0x00000000C000000;
}


5.4 DLL信息泄露

至此,我们已经知道了VRAM缓冲区的基址,并且也将其泄露到堆中。仅凭这些信息,我们仍然无法将rip更改为有意义的地址。我们需要创建一个DLL信息泄露,让我们能够进入到ROP链中,并执行一些有意义的代码。为实现这一目标,我再次使用了VBOXVHWACMD_TYPE_SURF_OVERLAY_UPDATE

// src/VBox/Frontends/VirtualBox/src/VBoxFBOverlay.cpp:2823
signed __int64 __fastcall VBoxVHWAImage::vhwaSurfaceOverlayUpdate(__int64 _this, uint8_t *_pCmd)
{
...
for ( i = **(__int64 ***)pList; i != *(__int64 **)pList; i = (__int64 *)*i )
VBoxVHWAImage::vhwaDoSurfaceOverlayUpdate(this, pDstSurf, (VBoxVHWASurfaceBase *)i[2], pCmd);
...

如前所示,VBoxVHWAImage::vhwaSurfaceOverlayUpdate遍历pList的第一个成员指向的单链表(由我们控制),并在每个链表节点的0x10偏移量处的VBoxVHWASurfaceBase指针上调用VBoxVHWAImage::vhwaDoSurfaceOverlayUpdate

__int64 __fastcall VBoxVHWAImage::vhwaDoSurfaceOverlayUpdate(__int64 _this, VBoxVHWASurfaceBase *_pDstSurf, VBoxVHWASurfaceBase *_pSrcSurf, _DWORD *pCmd)
{
...
if ( _bittest(&v9, 0xCu) )
{
result = _pSrcSurf->field_90;
_pSrcSurf->field_78 = result;
}
...
}

VBoxVHWAImage::vhwaDoSurfaceOverlayUpdate中,实现了上述操作,在这里我们控制指针_pSrcSurf的值。通过上面的代码片段,我们可以将一个值从某个内存地址复制到另一个较低的内存地址。但这对我们有什么用呢?

在阅读@_niklasb的其他幻灯片时,他提到在Windows宿主机上,与VRAM缓冲区直接相邻的内存区域是另一个包含有关VRAM缓冲区本身的某些元数据的区域。我们可以在调试器中验证上述发现,实际上,内存区域已设置为具有读/写权限。将这一点与我们刚刚讨论过的原语结合在一起,我们就可以将“VRam”指针(指向VBoxDD.dll的指针)重复复制到较低的地址。由于这个元数据与虚拟机可读取的VRAM缓冲区相邻,因此我们只需将DLL指针“拖动”到页边界上,就可以直接进入到我们的VRAM中。然后,我们就能轻松地从内存中读取该指针!
实现的过程要稍微复杂一些,但是总体思路是在VRAM内形成一个有效的链表,该链表针对递减的地址反复调用VBoxVHWAImage::vhwaDoSurfaceOverlayUpdate

另外,这种技术非常适用于内存转储。

如果这样就结束任务,那么破坏元数据的多余指针将会导致宿主机产生蓝屏(BSOD)。因此,应该重复使用相同的技术,将多余的指针归零。


5.5 rop2win

有了DLL泄露后,我们现在就可以编写一个ROP链来调用VirtualAlloc,分配一个RWX区域,将我们的Shellcode复制到该区域并跳转。为了从析构函数调用转到我们的ROP链,我们可以将rip设置为错误值,从而导致崩溃,并检查寄存器的状态。在这种情况下,rax的值指向我们的VRAM缓冲区。因此,可以使用以下小工具来转到我们的ROP链。

In 6.1.2 r135662 (Release ver.)
0x0000000180042b3e: xchg eax, esp; ror byte ptr [rax - 0x75], 0x5c; and al, 0x30; add rsp, 0x20; pop rdi; ret;

这样一来,我们就完成了全部的漏洞利用链!由于过程中破坏了元数据,因此虚拟机将无法继续正常运行,但我们已经可以成功弹出了计算器。

演示视频:https://starlabs.sg/blog/2020/VBoxVHWASurfaceBase-demo.mp4

 

六、参考

[1] https://googleprojectzero.blogspot.com/2017/08/bypassing-virtualbox-process-hardening.html
[2] https://www.virtualbox.org/wiki/Windows%20build%20instructions
[3] https://github.com/phoenhex/files/blob/master/slides/thinking_outside_the_virtualbox.pdf
[4] http://www.catch22.net/tuts/kernel1.asp
[5] https://www.voidsecurity.in/2018/08/from-compiler-optimization-to-code.html
[6] https://github.com/phoenhex/files/blob/master/slides/unboxing_your_virtualboxes.pdf


推荐阅读
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 31.项目部署
    目录1一些概念1.1项目部署1.2WSGI1.3uWSGI1.4Nginx2安装环境与迁移项目2.1项目内容2.2项目配置2.2.1DEBUG2.2.2STAT ... [详细]
  • 本文介绍了在Windows系统上使用C语言命令行参数启动程序并传递参数的方法,包括接收参数程序的代码和bat文件的编写方法,同时给出了程序运行的结果。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
author-avatar
淼淼妈妈的指国度an
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有