当前位置: 首页 > news >正文

CTF-pwn-虚拟化-vmmware 前置

文章目录

  • 参考
  • vmware逃逸简介
  • 虚拟机和主机通信机制(guest to host)
    • 共享内存(弃用)
    • backdoor机制
    • Message_Send和Message_Recv
    • GuestRPC
      • 实例`RpcOutSendOneRawWork`
      • 实例 `vmware-rpctool 'info-get guestinfo.ip'`
      • 各个步骤对应的backdoor操作
        • Open RPC channel
        • Send RPC command length
        • Send RPC command data
        • Recieve RPC reply length
        • Receive RPC reply data
        • Finish receiving RPC reply
        • Close RPC channel
    • VMWareRPCChannel 和 BufferedRPCChannel 类
  • 配置
  • 调试
  • 工具

参考

2018-RealWorld-Station-Escape

【技术分享】实战VMware虚拟机逃逸漏洞
VMware 逃逸基础知识
从0到1的虚拟机逃逸三部曲
虚拟机逃逸入门(一)
RealWorldCTF2018 Station Escape

VMware 逃逸基础知识

vmware逃逸简介

  1. 虚拟机操作系统发送敏感请求,使操作系统陷入内核态
  2. 某些特权指令会进入ring0以下的状态,即交给Hypervisor处理
  3. 利用Hypervisor的脆弱性漏洞使得Hypervisor执行完特权指令后不产生指令状态的返回,使得执行完指令后依然停留在内核态
  4. 实现了提权后,可以渗透到Hypervisor和虚拟机的其他区域,破坏虚拟化的隔离机制,完成逃逸操作。

(或者说虚拟机执行某些操作会发送请求到主机vmx,然后主机vmx来处理请求,通过利用vmx中的漏洞来使得达到提权目的)
在这里插入图片描述

比如我们发现了一个 Hypervisor 中的漏洞,可以被利用来阻止特权指令执行后的正常状态转换。

  1. 正常情况:
Guest OS 执行特权指令 -> Trap 到 Hypervisor -> Hypervisor 执行指令 -> 返回 Guest OS 用户态
  1. 利用漏洞后:
Guest OS 执行特权指令 -> Trap 到 Hypervisor -> Hypervisor 执行指令 
-> 漏洞阻止状态转换 -> Guest OS 保持在内核态

具体实现可能如下:

  1. 攻击者发现 Hypervisor 在处理某些特定的特权指令时存在缓冲区溢出漏洞。

  2. 攻击者构造一个特殊的输入,触发这个缓冲区溢出。

  3. 溢出的数据覆盖了 Hypervisor 中负责状态转换的代码或数据结构。

  4. 当 Hypervisor 执行完特权指令,准备返回 Guest OS 时,由于关键代码被覆盖,无法正确执行状态转换。

  5. 结果是 Guest OS 在特权指令执行后仍保持在内核态,而不是正常转回用户态。

这种攻击可能导致严重的安全问题,因为它打破了用户态和内核态的隔离,可能被进一步利用来提升权限或执行其他恶意操作。

虚拟机和主机通信机制(guest to host)

https://sysprogs.com/legacy/articles/kdvmware/guestrpc.shtml

经常使用vmware虚拟机的人一定会熟悉其拖拽功能,即Guest和host之间的文件传递以及复制之类的操作,都是基于拖拽实现的,拖拽的背后是Guest和host之间的通信机制。而Vm类型的逃逸中,利用的就是该通信机制,这类机制被设计是现在了vmtools当中,高版本的vmware,vmtools消失,直接被自带安装。

共享内存(弃用)

  • 优点:速度快
  • 缺点:需要持续检查状态位,导致100%CPU占用
  • 例子:假设我们有一个共享内存区域,虚拟机和主机都可以访问:
    struct SharedMemory {int newRequestFlag;char requestData[1024];
    };// 在虚拟机中持续检查新请求
    while(1) {if(sharedMemory->newRequestFlag) {processRequest(sharedMemory->requestData);sharedMemory->newRequestFlag = 0;}
    }
    

backdoor机制

相关源码

open-vm-tools/lib/backdoor/backdoor_def.h#define BDOOR_MAGIC 0x564D5868/* Low-bandwidth backdoor port number for the IN/OUT interface. */#define BDOOR_PORT        0x5658/* Flags used by the hypercall interface. */#define   BDOOR_CMD_GETMHZ                    1
/** BDOOR_CMD_APMFUNCTION is used by:** o The FrobOS code, which instead should either program the virtual chipset*   (like the new BIOS code does, Matthias Hausner offered to implement that),*   or not use any VM-specific code (which requires that we correctly*   implement "power off on CLI HLT" for SMP VMs, Boris Weissman offered to*   implement that)** o The old BIOS code, which will soon be jettisoned*/
#define   BDOOR_CMD_APMFUNCTION               2 /* CPL0 only. */
#define   BDOOR_CMD_GETDISKGEO                3
#define   BDOOR_CMD_GETPTRLOCATION            4
#define   BDOOR_CMD_SETPTRLOCATION            5
#define   BDOOR_CMD_GETSELLENGTH              6
#define   BDOOR_CMD_GETNEXTPIECE              7
#define   BDOOR_CMD_SETSELLENGTH              8
#define   BDOOR_CMD_SETNEXTPIECE              9
#define   BDOOR_CMD_GETVERSION               10
#define   BDOOR_CMD_GETDEVICELISTELEMENT     11
……………………还有很多open-vm-tools/lib/include/backdoor_types.h
typedef union {struct {DECLARE_REG_NAMED_STRUCT(ax);size_t size; /* Register bx. */DECLARE_REG_NAMED_STRUCT(cx);DECLARE_REG_NAMED_STRUCT(dx);DECLARE_REG_NAMED_STRUCT(si);DECLARE_REG_NAMED_STRUCT(di);} in;struct {DECLARE_REG_NAMED_STRUCT(ax);DECLARE_REG_NAMED_STRUCT(bx);DECLARE_REG_NAMED_STRUCT(cx);DECLARE_REG_NAMED_STRUCT(dx);DECLARE_REG_NAMED_STRUCT(si);DECLARE_REG_NAMED_STRUCT(di);} out;
} Backdoor_proto;open-vm-tools/lib/backdoor/backdoorGcc64.c.hvoid
Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT
{uint64 dummy;__asm__ __volatile__(
#ifdef __APPLE__/** Save %rbx on the stack because the Mac OS GCC doesn't want us to* clobber it - it erroneously thinks %rbx is the PIC register.* (Radar bug 7304232)*/"pushq %%rbx"           "\n\t"
#endif"pushq %%rax"           "\n\t""movq 40(%%rax), %%rdi" "\n\t""movq 32(%%rax), %%rsi" "\n\t""movq 24(%%rax), %%rdx" "\n\t""movq 16(%%rax), %%rcx" "\n\t""movq  8(%%rax), %%rbx" "\n\t""movq   (%%rax), %%rax" "\n\t""inl %%dx, %%eax"       "\n\t"  /* NB: There is no inq instruction */"xchgq %%rax, (%%rsp)"  "\n\t" //恢复之前的压入rax并将rax刚开始的内容给栈"movq %%rdi, 40(%%rax)" "\n\t""movq %%rsi, 32(%%rax)" "\n\t""movq %%rdx, 24(%%rax)" "\n\t""movq %%rcx, 16(%%rax)" "\n\t""movq %%rbx,  8(%%rax)" "\n\t""popq          (%%rax)" "\n\t"//恢复原来rax刚开始部分
#ifdef __APPLE__"popq %%rbx"            "\n\t"
#endif: "=a" (dummy) 
//       输出操作数:
// : "=a" (dummy)
// 这定义了输出操作数。"=a" 表示使用 %rax 寄存器,结果存储在 dummy 变量中。: "0" (myBp)
//       输入操作数:
// : "0" (myBp)
// 这定义了输入操作数。"0" 表示使用与第一个输出操作数相同的寄存器(即 %rax),值来自 myBp。
// myBp 被加载到 rax 中(通过 "0" (myBp) 指定)。/** vmware can modify the whole VM state without the compiler knowing* it. So far it does not modify EFLAGS. --hpreg*/:
#ifndef __APPLE__/* %rbx is unchanged at the end of the function on Mac OS. */"rbx",
#endif"rcx", "rdx", "rsi", "rdi", "memory");
}

工作原理:

  1. 使用特殊的I/O指令:
    VMware截获了特定的I/O指令(在这个例子中是’in’指令),并将其解释为后门调用。

  2. 魔术数字和命令:
    使用预定义的魔术数字(BDOOR_MAGIC)和命令码来指定操作类型。

  3. 寄存器传参:
    通过特定寄存器传递参数和接收结果。

unsigned __declspec(naked) GetMousePos()
{__asm{mov eax, 564D5868h  // 设置魔术数字 (BDOOR_MAGIC)mov ecx, 4          // 设置命令码 (BDOOR_CMD_GETPTRLOCATION)mov edx, 5658h      // 设置I/O端口 (BDOOR_PORT)in eax, dx          // 执行后门调用ret                 // 返回结果(在eax中)}
}

这段代码做了以下事情:

  1. 设置eax为魔术数字,表明这是一个后门调用。
  2. 设置ecx为4,指示我们要获取鼠标位置。
  3. 设置edx为特殊的I/O端口号。
  4. 执行’in’指令,这在VMware中会被截获并处理。
  5. 结果存储在eax中返回。

在主函数中:

void main()
{unsigned mousepos = GetMousePos();printf("Mouse cursor pos: x=%d,y=%d\n", mousepos >> 16, mousepos & 0xFFFF);
}
  1. 调用GetMousePos()获取鼠标位置。
  2. 高16位表示X坐标,低16位表示Y坐标。

举例说明:

假设我们在VMware虚拟机中运行这个程序:

  1. 在真实机器上:

    运行结果: 程序崩溃,因为'in'指令在用户模式下是不允许的。
    
  2. 在VMware虚拟机中,鼠标在(100, 200)位置:

    运行结果: Mouse cursor pos: x=100,y=200
    
  3. 在VMware虚拟机中,鼠标在(500, 300)位置:

    运行结果: Mouse cursor pos: x=500,y=300
    

其中有一条特权指令,in,这条指令在正常的操作系统执行会报错,但是在vm中的guest机器执行这条指令,这个异常会被 vmtools捕获,然后传递给vmware-vmx.exe进行通信操作。

重点在于,backdoor普通用户也可以执行,所以,guest中,执行相应的代码,让操作系统陷入hypervisor层,然后再利用backdoor和host进行通信,触发此bug。

Message_Send和Message_Recv

Message相关函数是客户机应用程序和 VMware 之间的内部通信通道的第二层,open-vmtools中也有实现。Message_Send和Message_Recv等,它们是建立在 backdoor 机制之上的更高级别的抽象。它们使用 backdoor 作为底层通信渠道,但提供了更易用和更灵活的接口。

  1. Message_OpenAllocated 简化版:
Bool Message_OpenAllocated(uint32 proto, Message_Channel *chan, char *receiveBuffer, size_t receiveBufferSize)
{Backdoor_proto bp;bp.in.cx.halfs.high = MESSAGE_TYPE_OPEN;bp.in.size = proto | GUESTMSG_FLAG_COOKIE;bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;Backdoor(&bp);if (!(bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS)) {return FALSE;}chan->id = bp.in.dx.halfs.high;chan->cookieHigh = bp.out.si.word;chan->cookieLow = bp.out.di.word;chan->in = (unsigned char *)receiveBuffer;chan->inAlloc = receiveBufferSize;chan->inPreallocated = receiveBuffer != NULL;return TRUE;
}

Message_OpenAllocated:

  • 功能:打开一个预分配的通信通道。
  • 参数:协议类型、通道结构指针、接收缓冲区及其大小。
  • 过程:
    • 使用 Backdoor 机制发送打开通道请求。
    • 如果成功,设置通道 ID 和 cookie。
    • 初始化接收缓冲区信息。
  • 返回:成功返回 TRUE,失败返回 FALSE。
  1. Message_Open简化版:
Message_Channel* Message_Open(uint32 proto)
{Message_Channel *chan = malloc(sizeof(Message_Channel));if (chan == NULL) {return NULL;}if (!Message_OpenAllocated(proto, chan, NULL, 0)) {free(chan);return NULL;}return chan;
}

Message_Open:

  • 功能:分配并打开一个新的通信通道。
  • 过程:
    • 分配 Message_Channel 结构。
    • 调用 Message_OpenAllocated 初始化通道。
  • 返回:成功返回通道指针,失败返回 NULL。
  1. Message_Send 简化版:
Bool Message_Send(Message_Channel *chan, const unsigned char *buf, size_t bufSize)
{Backdoor_proto bp;bp.in.cx.halfs.high = MESSAGE_TYPE_SENDSIZE;bp.in.dx.halfs.high = chan->id;bp.in.si.word = chan->cookieHigh;bp.in.di.word = chan->cookieLow;bp.in.size = bufSize;bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;Backdoor(&bp);if (!(bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS)) {return FALSE;}if (bp.in.cx.halfs.high & MESSAGE_STATUS_HB) {// 高带宽传输Backdoor_proto_hb bphb;// 设置 bphb...Backdoor_HbOut(&bphb);} else {// 低带宽传输while (bufSize > 0) {// 设置 bp 发送数据块...Backdoor(&bp);// 更新 buf 和 bufSize...}}return TRUE;
}

Message_Send:

  • 功能:通过通道发送消息。
  • 参数:通道指针、消息缓冲区、消息大小。
  • 过程:
    • 首先发送消息大小。
    • 根据是否支持高带宽,选择发送方式:
      • 高带宽:一次性发送整个消息。
      • 低带宽:分块发送消息。
  • 返回:成功返回 TRUE,失败返回 FALSE。
  1. Message_Receive 简化版:
Bool Message_Receive(Message_Channel *chan, unsigned char **buf, size_t *bufSize)
{Backdoor_proto bp;bp.in.cx.halfs.high = MESSAGE_TYPE_RECVSIZE;bp.in.dx.halfs.high = chan->id;bp.in.si.word = chan->cookieHigh;bp.in.di.word = chan->cookieLow;bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;Backdoor(&bp);if (!(bp.in.cx.halfs.high & MESSAGE_STATUS_SUCCESS)) {return FALSE;}if (!(bp.in.cx.halfs.high & MESSAGE_STATUS_DORECV)) {*bufSize = 0;return TRUE;}*bufSize = bp.out.bx.word;// 分配或检查缓冲区大小...if (bp.in.cx.halfs.high & MESSAGE_STATUS_HB) {// 高带宽接收// 使用 Backdoor_HbIn...} else {// 低带宽接收// 循环使用 Backdoor 接收数据...}return TRUE;
}

Message_Receive:

  • 功能:从通道接收消息。
  • 参数:通道指针、接收缓冲区指针的指针、接收大小的指针。
  • 过程:
    • 检查是否有消息可接收。
    • 如果有,获取消息大小。
    • 根据是否支持高带宽,选择接收方式。
    • 分配或检查接收缓冲区大小。
  • 返回:成功返回 TRUE,失败返回 FALSE。
  1. Message_CloseAllocated 简化版:
void Message_CloseAllocated(Message_Channel *chan)
{Backdoor_proto bp;if (chan == NULL) {return;}bp.in.cx.halfs.high = MESSAGE_TYPE_CLOSE;bp.in.dx.halfs.high = chan->id;bp.in.si.word = chan->cookieHigh;bp.in.di.word = chan->cookieLow;bp.in.cx.halfs.low = BDOOR_CMD_MESSAGE;Backdoor(&bp);if (!chan->inPreallocated && chan->in != NULL) {free(chan->in);}chan->in = NULL;chan->inAlloc = 0;
}

. Message_CloseAllocated:

  • 功能:关闭一个预分配的通信通道。
  • 参数:通道指针。
  • 过程:
    • 发送关闭通道请求。
    • 释放接收缓冲区(如果是动态分配的)。
    • 重置通道信息。
  1. Message_Close 简化版:
void Message_Close(Message_Channel *chan)
{if (chan == NULL) {return;}Message_CloseAllocated(chan);free(chan);
}

Message_Close:

  • 功能:关闭并释放一个通信通道。
  • 参数:通道指针。
  • 过程:
    • 调用 Message_CloseAllocated 关闭通道。
    • 释放通道结构内存。

GuestRPC

在backdoor和Message_Send/Receive基础上实现的更为灵活的通信方式

Backdoor -> Message_Send/Receive -> GuestRPC
每一层都建立在下一层的基础之上,提供更高级的抽象和功能。

当执行一个 GuestRPC 调用时,数据流通常是:
GuestRPC 命令 -> Message_Send 处理 -> Backdoor , 如Rpcout_start->Message_OpenAllocated->Backdoor

执行单个 GuestRPC 调用由一系列请求组成:

  • 打开 GuestRPC 通道
  • 发送命令长度
  • 发送命令数据
  • 接收回复大小
  • 接收回复数据
  • 发出接收结束信号
  • 关闭GuestRPC 通道

每个请求过程如下
在这里插入图片描述

/open-vm-tools/lib/rpcOut/rpcout.c/open-vm-tools/lib/include/rpcout.htypedef struct RpcOut RpcOut;RpcOut *RpcOut_Construct(void);
void RpcOut_Destruct(RpcOut *out);
Bool RpcOut_start(RpcOut *out);
Bool RpcOut_send(RpcOut *out, char const *request, size_t reqLen,Bool *rpcStatus, char const **reply, size_t *repLen);
Bool RpcOut_stop(RpcOut *out);/** This is the only method needed to send a message to vmware for* 99% of uses. I'm leaving the others defined here so people know* they can be exported again if the need arises. [greg]*/
Bool RpcOut_sendOne(char **reply, size_t *repLen, char const *reqFmt, ...);/* * A version of the RpcOut_sendOne function that works with UTF-8* strings and other data that would be corrupted by Win32's* FormatMessage function (which is used by RpcOut_sendOne()).*/Bool RpcOut_SendOneRaw(void *request, size_t reqLen, char **reply, size_t *repLen);/* * A variant of the RpcOut_SendOneRaw in which the caller supplies the* receive buffer so as to avoid the need to call malloc internally.* Useful in situations where calling malloc is not allowed.*/Bool RpcOut_SendOneRawPreallocated(void *request, size_t reqLen, char *reply,size_t repLen);

这段代码定义了 RpcOut 模块的接口,用于实现从虚拟机向 VMware 虚拟化层发送 RPC (Remote Procedure Call) 请求。

  1. 通信机制:
    RpcOut 提供了一种机制,允许虚拟机内部的程序与 VMware 虚拟化层进行通信。

  2. 发送请求:
    虚拟机可以使用这个接口向 VMware 发送各种请求,如获取信息、执行操作等。

  3. 接收响应:
    RpcOut 不仅可以发送请求,还可以接收来自 VMware 的响应。

  4. 灵活性:
    提供了多种方法来构造和发送 RPC 请求,适应不同的使用场景。

  5. 主要功能:

    • RpcOut_sendOne: 最常用的方法,用于发送单个 RPC 请求并接收响应。
    • RpcOut_SendOneRaw: 处理 UTF-8 字符串和可能被 Win32 FormatMessage 函数破坏的数据。
    • RpcOut_SendOneRawPreallocated: 允许调用者提供接收缓冲区,避免内部 malloc 调用。
  6. 生命周期管理:
    提供了构造函数 (RpcOut_Construct) 和析构函数 (RpcOut_Destruct) 来管理 RpcOut 对象的生命周期。

  7. 会话控制:
    包含 RpcOut_start 和 RpcOut_stop 方法,用于开始和结束 RPC 会话。

/open-vm-tools/lib/include/rpin.hRpcIn *RpcIn_Construct(GMainContext *mainCtx,RpcIn_Callback dispatch,gpointer clientData);Bool RpcIn_start(RpcIn *in, unsigned int delay,RpcIn_ErrorFunc *errorFunc,RpcIn_ClearErrorFunc *clearErrorFunc,void *errorData);#else /* } { */#include "dbllnklst.h"/** Type for old RpcIn callbacks. Don't use this anymore - this is here* for backwards compatibility.*/
typedef Bool
(*RpcIn_Callback)(char const **result,     // OUTsize_t *resultLen,       // OUTconst char *name,        // INconst char *args,        // INsize_t argsSize,         // INvoid *clientData);       // INRpcIn *RpcIn_Construct(DblLnkLst_Links *eventQueue);Bool RpcIn_start(RpcIn *in, unsigned int delay,RpcIn_Callback resetCallback, void *resetClientData,RpcIn_ErrorFunc *errorFunc,RpcIn_ClearErrorFunc *clearErrorFunc,void *errorData);/** Don't use this function anymore - it's here only for backwards compatibility.* Use RpcIn_RegisterCallbackEx() instead.*/
void RpcIn_RegisterCallback(RpcIn *in, const char *name,RpcIn_Callback callback, void *clientData);void RpcIn_UnregisterCallback(RpcIn *in, const char *name);unsigned int RpcIn_SetRetVals(char const **result, size_t *resultLen,const char *resultVal, Bool retVal);#endif /* } */void RpcIn_Destruct(RpcIn *in);
void RpcIn_stop(RpcIn *in);

RpcIn 模块的主要用途是处理从 VMware 虚拟化层到虚拟机内部的 RPC (Remote Procedure Call) 请求。RpcIn 模块为虚拟机内部的程序提供了一个框架,用于接收和处理来自 VMware 虚拟化层的 RPC 请求。

  1. 接收请求:
    RpcIn 允许虚拟机内部的程序接收来自 VMware 虚拟化层的 RPC 请求。

  2. 回调机制:
    提供了注册回调函数的机制(RpcIn_RegisterCallback),用于处理特定类型的 RPC 请求。

  3. 事件驱动:
    使用事件队列(DblLnkLst_Links *eventQueue)来处理异步的 RPC 请求。

  4. 生命周期管理:
    提供了构造函数(RpcIn_Construct)和析构函数(RpcIn_Destruct)来管理 RpcIn 对象的生命周期。

  5. 启动和停止:
    包含 RpcIn_start 和 RpcIn_stop 方法,用于开始和结束 RPC 监听。

  6. 错误处理:
    支持错误处理函数(RpcIn_ErrorFunc)和清除错误函数(RpcIn_ClearErrorFunc)。

  7. 灵活性:
    允许设置延迟(delay)和重置回调(resetCallback),以适应不同的使用场景。

  8. 响应设置:
    提供 RpcIn_SetRetVals 函数来设置 RPC 调用的返回值。

  9. 向后兼容:
    保留了一些旧的接口(如旧版的 RpcIn_RegisterCallback),以保持向后兼容性。

  10. 多平台支持:
    通过条件编译(#ifdef __cplusplus),支持在 C 和 C++ 环境中使用。

  11. 自定义处理:
    允许注册自定义的回调函数来处理特定名称的 RPC 请求。

  12. 动态注册和注销:
    提供了注册(RpcIn_RegisterCallback)和注销(RpcIn_UnregisterCallback)回调的方法,允许动态地添加或移除 RPC 处理程序。

实例RpcOutSendOneRawWork


/**-----------------------------------------------------------------------------** RpcOutSendOneRawWork --**    Helper function to make VMware execute a RPCI command.  See*    RpcOut_SendOneRaw and RpcOut_SendOneRawPreallocated.**-----------------------------------------------------------------------------*/static Bool
RpcOutSendOneRawWork(void *request,         // IN: RPCI commandsize_t reqLen,         // IN: Size of request bufferchar *callerReply,     // IN: caller supplied reply buffersize_t callerReplyLen, // IN: size of caller supplied bufchar **reply,          // OUT: Resultsize_t *repLen)        // OUT: Length of the result
{Bool status;Bool rpcStatus;/* Stack allocate so this can be used in kernel logging.  See 1389199. */RpcOut out;char const *myReply;size_t myRepLen;Debug("Rpci: Sending request='%s'\n", (char *)request);RpcOutInitialize(&out);if (!RpcOut_startWithReceiveBuffer(&out, callerReply, callerReplyLen)) {myReply = "RpcOut: Unable to open the communication channel";myRepLen = strlen(myReply);if (callerReply != NULL) {unsigned s = MIN(callerReplyLen - 1, myRepLen);ASSERT(reply == NULL);memcpy(callerReply, myReply, s);callerReply[s] = '\0';}if (reply != NULL) {*reply = NULL;}return FALSE;}status = RpcOut_send(&out, request, reqLen,&rpcStatus, &myReply, &myRepLen);/* On failure, we already have the description of the error */Debug("Rpci: Sent request='%s', reply='%s', len=%"FMTSZ"u, ""status=%d, rpcStatus=%d\n",(char *)request, myReply, myRepLen, status, rpcStatus);if (reply != NULL) {/** If we got a non-NULL reply, make a copy of it, because the reply* we got back is inside the channel buffer, which will get destroyed* at the end of this function.*/if (myReply != NULL) {/** We previously used strdup to duplicate myReply, but that* breaks if you are sending binary (not string) data over the* backdoor. Don't assume the data is a string.*/*reply = malloc(myRepLen + 1);if (*reply != NULL) {memcpy(*reply, myReply, myRepLen);/** The message layer already writes a trailing NUL but we might* change that someday, so do it again here.*/(*reply)[myRepLen] = 0;}} else {/** Our reply was NULL, so just pass the NULL back up to the caller.*/*reply = NULL;}/** Only set the length if the caller wanted it and if we got a good* reply.*/if (repLen != NULL && *reply != NULL) {*repLen = myRepLen;}}if (RpcOut_stop(&out) == FALSE) {/** We couldn't stop the channel. Free anything we allocated, give our* client a reply of NULL, and return FALSE.*/if (reply != NULL) {free(*reply);*reply = NULL;}Debug("Rpci: unable to close the communication channel\n");status = FALSE;}return status && rpcStatus;
}

在 RpcOutSendOneRawWork 函数中就体现了这一过程,RpcOutSendOneRawWork 函数的作用是将一段原始的数据打包为消息并通过 VMware 的 RPC 协议发送给另一台虚拟机或者宿主机,该函数主要调用了三个函数:

  • RpcOut_startWithReceiveBuffer:最终调用 Message_OpenAllocated 函数执行MESSAGE_TYPE_OPEN 过程。
  • RpcOut_send:最终调用了 Message_Send 和 Message_Receive 两个函数。
    Message_Send:先执行 MESSAGE_TYPE_SENDSIZE 过程发送消息长度,然后循环进行 MESSAGE_TYPE_SENDPAYLOAD 过程直到把消息发送完。
    Message_Receive:先执行 MESSAGE_TYPE_RECVSIZE 过程获取接收消息长度,然后循环执行 MESSAGE_TYPE_RECVPAYLOAD 过程直到把消息接收完。
  • RpcOut_stop:最终调用 Message_CloseAllocated 函数执行 MESSAGE_TYPE_CLOSE 过程。

实例 vmware-rpctool 'info-get guestinfo.ip'

在这里插入图片描述

  1. 用户输入命令:
    用户在虚拟机内执行 vmware-rpctool 'info-get guestinfo.ip'

  2. vmware-rpctool 处理:

    • vmware-rpctool 解析命令,识别为 “info-get” 操作
    • 准备 RPC 请求内容:“info-get guestinfo.ip”
  3. RpcOut 初始化:

    • vmware-rpctool 内部初始化 RpcOut 结构
  4. 发送请求(RpcOut):

    • 调用 RpcOut_send 函数
    • 请求通过预定义通道(如 VSockets 或 backdoor)发送到 VMX
  5. VMX 接收请求(RpcIn):

    • VMX 中的 RpcIn 模块接收到请求
    • 触发 HandleRpcIn 函数处理incoming请求
  6. 请求解析:

    • HandleRpcIn 函数解析 “info-get guestinfo.ip” 请求
  7. GuestRPC 表查找:

    • VMX 在 GuestRPC 表中查找 “info-get” 对应的处理函数
  8. 执行处理函数:

    • 找到并执行 HandleInfoGet 函数
    • 此函数专门处理 “info-get” 类型的请求
  9. 获取 IP 地址:

    • HandleInfoGet 函数识别 “guestinfo.ip” 参数
    • 调用内部函数获取虚拟机的 IP 地址
  10. 准备响应:

    • 假设 IP 为 “192.168.1.100”
    • 准备响应字符串,如 “1 192.168.1.100”
    • “1” 表示成功,后面跟着实际 IP
  11. 设置响应:

    • 使用 RpcIn_SetRetVals 函数设置响应内容
  12. 发送响应(RpcIn):

    • 调用 RpcInSend 函数,将响应发送回虚拟机
  13. 虚拟机接收响应(RpcOut):

    • RpcOut_send 函数在虚拟机端接收响应
  14. 响应处理:

    • vmware-rpctool 解析响应,提取 IP 地址
  15. 显示结果:

    • vmware-rpctool 将 IP 地址显示到控制台
  16. 完成:

    • 命令执行完毕,用户看到 IP 地址输出

各个步骤对应的backdoor操作

Open RPC channel

RPC subcommand:00h

调用IN(OUT)前,需要设置的寄存器内容:

EAX = 564D5868h - magic number
EBX = 49435052h - RPC open magic number ('RPCI')
ECX(HI) = 0000h - subcommand number
ECX(LO) = 001Eh - command number
EDX(LO) = 5658h - port number

返回值:

ECX = 00010000h: success / 00000000h: failure
EDX(HI) = RPC channel number

该功能用于打开 RPC 的 channel ,其中 ECX 会返回是否成功,EDX 返回值会返回一个 channel 的编号,在后续的 RPC 通信中,将使用该编号。这里需要注意的是,在单个虚拟机中只能同时使用 8 个 channel(#0 - #7),当尝试打开第 9 个 channel 的时候,会检查其他 channel 的打开时间,如果时间过了某一个值,会将超时的 channel 关闭,再把这个 channel 的编号返回;如果都没有超时,create channel 会失败。

为了防止进程扰乱 RPC 的交互,建立一个通道时, VMware 会生产两个 cookie 值,用它们来发送和接受数据。

我们可以使用如下函数实现 Open RPC channel 的过程:

void channel_open(int *cookie1, int *cookie2, int *channel_num, int *res) {asm("movl %%eax,%%ebx\n\t""movq %%rdi,%%r10\n\t""movq %%rsi,%%r11\n\t""movq %%rdx,%%r12\n\t""movq %%rcx,%%r13\n\t""movl $0x564d5868,%%eax\n\t""movl $0xc9435052,%%ebx\n\t""movl $0x1e,%%ecx\n\t""movl $0x5658,%%edx\n\t""out %%eax,%%dx\n\t""movl %%edi,(%%r10)\n\t""movl %%esi,(%%r11)\n\t""movl %%edx,(%%r12)\n\t""movl %%ecx,(%%r13)\n\t"::: "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r8", "%r10", "%r11", "%r12", "%r13");
}
Send RPC command length

RPC subcommand:01h

调用:

EAX = 564D5868h - magic number
EBX = command length (not including the terminating NULL)
ECX(HI) = 0001h - subcommand number
ECX(LO) = 001Eh - command number
EDX(HI) = channel number
EDX(LO) = 5658h - port number

返回值:

ECX = 00810000h: success / 00000000h: failure

在发送 RPC command 前,需要先发送 RPC command 的长度,需要注意的是,此时我们输入的 channel number 所指向的 channel 必须处于已经 open 的状态。 ECX 会返回是否成功发送。具体实现如下:

void channel_set_len(int cookie1, int cookie2, int channel_num, int len, int *res) {asm("movl %%eax,%%ebx\n\t""movq %%r8,%%r10\n\t""movl %%ecx,%%ebx\n\t""movl $0x564d5868,%%eax\n\t""movl $0x0001001e,%%ecx\n\t""movw $0x5658,%%dx\n\t""out %%eax,%%dx\n\t""movl %%ecx,(%%r10)\n\t"::: "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r10");
}
Send RPC command data

RPC subcommand:02h
调用:

EAX = 564D5868h - magic number
EBX = 4 bytes from the command data (the first byte in LSB)
ECX(HI) = 0002h - subcommand number
ECX(LO) = 001Eh - command number
EDX(HI) = channel number
EDX(LO) = 5658h - port number

返回值:

ECX = 000010000h: success / 00000000h: failure

该功能必须在Send RPC command length后使用,每次只能发送4个字节。例如,如果要发送命令machine.id.get,那么必须要调用4次,分别为:

EBX set to 6863616Dh ("mach")
EBX set to 2E656E69h ("ine.")
EBX set to 672E6469h ("id.g")
EBX set to 00007465h ("et\x00\x00")

ECX会返回是否成功,具体实现如下:


void channel_send_data(int cookie1,int cookie2,int channel_num,int len,char *data,int *res){asm("pushq %%rbp\n\t""movq %%r9,%%r10\n\t""movq %%r8,%%rbp\n\t""movq %%rcx,%%r11\n\t""movq $0,%%r12\n\t""1:\n\t""movq %%r8,%%rbp\n\t""add %%r12,%%rbp\n\t""movl (%%rbp),%%ebx\n\t""movl $0x564d5868,%%eax\n\t""movl $0x0002001e,%%ecx\n\t""movw $0x5658,%%dx\n\t""out %%eax,%%dx\n\t""addq $4,%%r12\n\t""cmpq %%r12,%%r11\n\t""ja 1b\n\t""movl %%ecx,(%%r10)\n\t""popq %%rbp\n\t":::"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11","%r12");
Recieve RPC reply length

RPC subcommand:03h

调用:

EAX = 564D5868h - magic number
ECX(HI) = 0003h - subcommand number
ECX(LO) = 001Eh - command number
EDX(HI) = channel number
EDX(LO) = 5658h - port number

返回值:

EBX = reply length (not including the terminating NULL)
ECX = 00830000h: success / 00000000h: failure

接收 RPC reply 的长度。需要注意的是所有的 RPC command 都会返回至少 2 个字节的 reply 的数据,其中 1 表示 success ,0 表示 failure ,即使 VMware 无法识别 RPC command ,也会返回 0 Unknown command 作为 reply 。也就是说,reply 数据的前两个字节始终表示 RPC command 命令的状态。

void channel_recv_reply_len(int cookie1, int cookie2, int channel_num, int *len, int *res) {asm("movl %%eax,%%ebx\n\t""movq %%r8,%%r10\n\t""movq %%rcx,%%r11\n\t""movl $0x564d5868,%%eax\n\t""movl $0x0003001e,%%ecx\n\t""movw $0x5658,%%dx\n\t""out %%eax,%%dx\n\t""movl %%ecx,(%%r10)\n\t""movl %%ebx,(%%r11)\n\t"::: "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r10", "%r11");
}
Receive RPC reply data

RPC subcommand:04h

调用:


EAX = 564D5868h - magic number
EBX = reply type from subcommand 03h
ECX(HI) = 0004h - subcommand number
ECX(LO) = 001Eh - command number
EDX(HI) = channel number
EDX(LO) = 5658h - port number

返回:

EBX = 4 bytes from the reply data (the first byte in LSB)
ECX = 00010000h: success / 00000000h: failure

EBX 中存放的值是 reply type( reply type from subcommand 03h 就是之前的rpc成功还是失败) ,他决定了执行的路径(根据不同的 reply type 值,程序会执行不同的处理逻辑。也就是说, reply type 决定了后续的执行路径)。和发送数据一样,每次只能够接受 4 个字节的数据(所以vmx也是一样每次rpc请求只会返回四个字节过来,所以需要多次rpc请求才能将相关结果全部返回过来)。需要注意的是,在 Recieve RPC reply length 中提到过,应答数据的前两个字节始终表示 RPC command 的状态。举例说明,如果我们使用 RPC command 询问 machine.id.get ,如果成功的话,会返回 1 virtual machine id,否则为 0 No machine id 。

void channel_recv_data(int cookie1, int cookie2, int channel_num, int offset, char *data, int *res) {asm("pushq %%rbp\n\t""movq %%r9,%%r10\n\t""movq %%r8,%%rbp\n\t""movq %%rcx,%%r11\n\t""movq $1,%%rbx\n\t""movl $0x564d5868,%%eax\n\t""movl $0x0004001e,%%ecx\n\t""movw $0x5658,%%dx\n\t""in %%dx,%%eax\n\t""add %%r11,%%rbp\n\t""movl %%ebx,(%%rbp)\n\t""movl %%ecx,(%%r10)\n\t""popq %%rbp\n\t"::: "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r10", "%r11", "%r12");
Finish receiving RPC reply

RPC subcommand:05h

调用:

EAX = 564D5868h - magic number
EBX = reply type from subcommand 04h
ECX(HI) = 0005h - subcommand number
ECX(LO) = 001Eh - command number
EDX(HI) = channel number
EDX(LO) = 5658h - port number

返回:

ECX = 00010000h: success / 00000000h: failure

和前文所述一样,在 EBX 中存储的是 reply type from subcommand 03h 。在接收完 reply 的数据后,调用此命令。如果没有通过 Receive RPC reply data 接收完整个 reply 数据(四个字节)的话(就是 Receive RPC reply data执行失败),就会返回 failure 。

void channel_recv_finish(int cookie1, int cookie2, int channel_num, int *res) {asm("movl %%eax,%%ebx\n\t""movq %%rcx,%%r10\n\t""movq $0x1,%%rbx\n\t""movl $0x564d5868,%%eax\n\t""movl $0x0005001e,%%ecx\n\t""movw $0x5658,%%dx\n\t""out %%eax,%%dx\n\t""movl %%ecx,(%%r10)\n\t"::: "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", "%r10");
}
Close RPC channel

RPC subcommand:06h
调用:


EAX = 564D5868h - magic number
ECX(HI) = 0006h - subcommand number
ECX(LO) = 001Eh - command number
EDX(HI) = channel number
EDX(LO) = 5658h - port number

返回:

ECX = 00010000h: success / 00000000h: failure

关闭channel。

void channel_close(int cookie1,int cookie2,int channel_num,int *res){asm("movl %%eax,%%ebx\n\t""movq %%rcx,%%r10\n\t""movl $0x564d5868,%%eax\n\t""movl $0x0006001e,%%ecx\n\t""movw $0x5658,%%dx\n\t""out %%eax,%%dx\n\t""movl %%ecx,(%%r10)\n\t":::"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10");
}

VMWareRPCChannel 和 BufferedRPCChannel 类

vmx还提供了一种方便的面向对象的方式来执行 GuestRPC 命令:VMWareRPCChannel 和 BufferedRPCChannel 类。通过使用 VMWareRPCChannel 类,可以执行 VMWare 支持的任意 GuestRPC 请求

VMWareRPCChannel 和 BufferedRPCChannel 是通过 VMware 实现虚拟机和主机之间通信的类。它们用于管理和优化远程过程调用(RPC)的数据传输。它们是对之前一系列的封装

BufferedRPCChannel 是在 VMWareRPCChannel 基础上实现的一个增强类,主要增加了缓冲机制以优化数据传输

配置

vmmware脚本安装
一般题目会提供 vmware 版本和 patch 过的 vmware-vmx 二进制文件,这就需要我们能够找到对应版本的 vmware 安装脚本。找到对应的linux安装脚本(只要能让 vmware-vmx 跑起来就可以 ),如果装不上也可以选择找这个驱动项目下载下来然后手动编译安装

git clone https://github.com/mkubecek/vmware-host-modules.gitcd vmware-host-modules
git checkout w15.5.0之后分别编译两个驱动并安装即可。注意选择 gcc 版本,否则容易编译失败。cd vmmon-only
make
cd ../vmnet-only
make
cd ..
sudo insmod vmmon.o
sudo insmod vmnet.o

vmmon 和 vmnet 是 VMware 虚拟机中的两个重要模块,它们分别负责不同的功能:

  1. vmmon (VMware Monitor):

    • 这是一个内核模块,负责虚拟机的核心功能,如CPU、内存和硬件设备的虚拟化。
    • 它模拟了一个完整的计算机系统,包括CPU、内存、磁盘、网卡等硬件设备,使得虚拟机能够像运行在物理硬件上一样运行操作系统和应用程序。
    • 比如,当你在 VMware 虚拟机中运行 Windows 操作系统时,vmmon 模块就负责将你的物理 CPU 和内存资源虚拟化,让 Windows 系统感觉自己是在独立的硬件上运行。
  2. vmnet (VMware Network):

    • 这是一个用于虚拟网络的内核模块。
    • 它负责创建和管理虚拟网络设备,如虚拟交换机、虚拟路由器等,以及虚拟机之间的网络连接。
    • 比如,当你在 VMware 虚拟机中设置了一个"桥接"网络模式时,vmnet 模块就会创建一个虚拟交换机,将虚拟机的网卡连接到物理网络上,使虚拟机能够像物理机一样访问外部网络。

存在虚拟机嵌套,最好使用带有英特尔的 CPU 的电脑进行。

 sudo ./VMware-Workstation-Full-15.5.0-14665864.x86_64.bundle  安装全选默认就行

然后将题目给的有漏洞的 vmx_patched 替换原来的 vmx 。

sudo cp vmware-vmx_patched /usr/lib/vmware/bin/vmware-vmx 
将当前目录下的 vmware-vmx_patched 文件复制到 /usr/lib/vmware/bin/ 目录,并命名为 vmware-vmx。
如果目标位置已经存在同名文件,这个命令会覆盖它。

另外启动虚拟机最好在 show applications 中点击 vmware 图标启动而不是运行下面的命令启动,因为直接运行下面的命令是直接启动 vmware 用户进程,缺少安装驱动的过程,而点击 vmware 图标是运行一个完整的 vmware 启动脚本

安装虚拟机
在安装好的 vmware 中安装 ubuntu 18.04.1 。
在这里插入图片描述
启动过程慢的惊人,我电脑太辣鸡了
在这里插入图片描述
成功,心情舒畅

调试

  • 在host里我们使用sudo gdb ./vmware-vmx_patched -q启动gdb,
  • 之后启动VMware和guest,然后使用ps -aux | grep vmware-vmx得到进程pid,在gdb中使用attach $pidattach到该进程,
  • -为了方便先echo 0 > /proc/sys/kernel/randomize_va_space关闭地址随机化,
  • 之后b* 0x0000555555554000 + 偏移 下个断点再continue让虚拟机进程继续。
  • 然后虚拟机里执行相关的rpc函数来动态调试跟进到漏洞

工具

bindiff下载和使用

相关文章:

CTF-pwn-虚拟化-vmmware 前置

文章目录 参考vmware逃逸简介虚拟机和主机通信机制(guest to host)共享内存(弃用)backdoor机制Message_Send和Message_RecvGuestRPC实例RpcOutSendOneRawWork实例 vmware-rpctool info-get guestinfo.ip各个步骤对应的backdoor操作Open RPC channelSend …...

thinkphp8结合layui2.9 图片上传验证

<?php declare (strict_types 1);namespace app\index\validate;use think\Validate;class Upload extends Validate {/*** 定义验证规则* 格式&#xff1a;字段名 > [规则1,规则2...]** var array*/protected $rule [image > fileExt:jpg,png|fileSize:204800|fi…...

农村污水处理难题:探索低成本高效解决方案

农村污水处理难题&#xff1a;探索低成本高效解决方案 农村污水处理作为国家生态文明建设的重要一环&#xff0c;面临着诸多挑战&#xff0c;尤其是技术落后、管理分散、资源匮乏等问题。物联网技术的引入&#xff0c;为解决这些痛点提供了创新途径&#xff0c;实现了对污水处…...

lightningcss介绍及使用

lightningcss介绍及使用 一款使用 rust 编写的 css 解析器&#xff0c;转换器、及压缩器。 特性 特别快&#xff1a;可以在毫秒级别解析、压缩大量的 css 文件&#xff0c;而且比其他工具的打包结果更小给值添加类型&#xff1a;许多其他css解析器会将值解析成一个无类型的 …...

HTTP服务的应用

1、编辑json请求参数&#xff1b; 2、把json发送到服务url&#xff0c;接收服务的返回参数&#xff1b; 3、解析返回参数。 procedure TfrmCustomQuery.btnFullUpdateClick(Sender: TObject); varfrm: TfrmInputQueryConditionEX;b_OK: Boolean;sBeginDate, sEndDate, sJSON…...

uni-app:踩坑路---scroll-view内使用fixed定位,无效的问题

前言&#xff1a; emmm&#xff0c;说起来这个问题整得还挺好笑的&#xff0c;本人在公司内&#xff0c;奋笔疾书写代码&#xff0c;愉快的提交测试的时候&#xff0c;测试跟我说&#xff0c;在苹果手机上你这个样式有bug&#xff0c;我倒是要看看&#xff0c;是什么bug。 安卓…...

MySQL4.索引及视图

1.建库 create database mydb15_indexstu; use mydb15_indexstu;2.建表 2.1 student表学&#xff08;sno&#xff09;号为主键&#xff0c;姓名&#xff08;sname&#xff09;不能重名&#xff0c;性别&#xff08;ssex&#xff09;仅能输入男或女&#xff0c;默认所在系别&a…...

MongoDB - 聚合阶段 $match、$sort、$limit

文章目录 1. $match 聚合阶段1. 构造测试数据2. $match 示例3. $match 示例 2. $sort 聚合阶段1. 排序一致性问题2. $sort 示例 3. $limit 聚合阶段 1. $match 聚合阶段 $match 接受一个指定查询条件的文档。 $match 阶段语法&#xff1a; { $match: { <query> } }$ma…...

ModuleNotFoundError: No module named ‘scrapy.utils.reqser‘

在scrapy中使用scrapy-rabbitmq-scheduler会出现报错 ModuleNotFoundError: No module named scrapy.utils.reqser原因是新的版本的scrapy已经摒弃了该方法,但是scrapy-rabbitmq-scheduler 没有及时的更新,所以此时有两种解决方法 方法一.将scrapy回退至旧版本,找到对应的旧版…...

vue3+ts+vite+electron+electron-packager打包成exe文件

目录 1、创建vite项目 2、添加需求文件 3、根据package.json文件安装依赖 4、打包 5、electron命令运行 6、electron-packager打包成exe文件 Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron 1、创建vite项目 npm create vitelatest 2、添…...

使用脚本搭建MySQL数据库基础环境

数据库的基本概念 数据&#xff08;Data&#xff09; 描述事物的符号记录 包括数字&#xff0c;文字&#xff0c;图形。图像&#xff0c;声音&#xff0c;档案记录等。 以记录形式按统一格式进行存储 表 将不同的记录组织在一起 用来储存具体数据 数据库 表的集合&#xff0c;是…...

Parameter index out of range (2 > number of parameters, which is 1【已解决】

文章目录 1、SysLogMapper.xml添加注释导致的2、解决方法3、总结 1、SysLogMapper.xml添加注释导致的 <!--定义一个查询方法&#xff0c;用于获取日志列表--><!--方法ID为getLogList&#xff0c;返回类型com.main.server.api.model.SysLogModel,参数类型为com.main.se…...

rk3588s 定制版 USB adb , USB2.0与USB3.0 区别,adb 由typeC 转换到USB3.0(第二部分)

硬件资源&#xff1a; rk3588s 核心板定制的地板 软件资源&#xff1a; 网盘上的 android12 源码 1 硬件上 客户只想使用 type c 接口中的 usb2.0 OTG 。在硬件上&#xff0c;甚至连 CC芯片都没有连接。 关于一些前置的知识。 1 USB2.0 与 USB3.0 的区别。 usb3.0 兼容2.0 …...

Cookie与Session 实现登录操作

Cookie Cookie 是网络编程中使用最广泛的一项技术&#xff0c;主要用于辨识用户身份。 客户端&#xff08;浏览器&#xff09;与网站服务端通讯的过程如下图所示&#xff1a; 从图中看&#xff0c;服务端既要返回 Cookie 给客户端&#xff0c;也要读取客户端提交的 Cookie。所…...

通过IEC104转MQTT网关轻松接入阿里云平台

随着智能电网和物联网技术的飞速发展&#xff0c;电力系统中的传统IEC 104协议设备正面临向现代化、智能化转型的迫切需求。阿里云作为全球领先的云计算服务提供商&#xff0c;其强大的物联网平台为IEC 104设备的接入与数据处理提供了强大的支持。本文将深入探讨钡铼网关在MQTT…...

lua 游戏架构 之 游戏 AI (五)ai_autofight_find_way

这段Lua脚本定义了一个名为 ai_autofight_find_way 的类&#xff0c;继承自 ai_base 类。 lua 游戏架构 之 游戏 AI &#xff08;一&#xff09;ai_base-CSDN博客文章浏览阅读238次。定义了一套接口和属性&#xff0c;可以基于这个基础类派生出具有特定行为的AI组件。例如&…...

vue3+openLayers点击标记事件

<template><!--地图--><div class"distributeMap" id"distributeMap"></div> </template> <script lang"ts" setup> import { onMounted, reactive } from "vue"; import { Feature, Map, View }…...

深入分析 Android ContentProvider (三)

文章目录 深入分析 Android ContentProvider (三)ContentProvider 的高级使用和性能优化1. 高级使用场景1.1. 数据分页加载示例&#xff1a;分页加载 1.2. 使用 Loader 实现异步加载示例&#xff1a;使用 CursorLoader 加载数据 1.3. ContentProvider 与权限管理示例&#xff1…...

养宠浮毛异味双困扰?性价比高的宠物空气净化器推荐

家里养了两只银渐层&#xff0c;谁懂啊&#xff01;一下班打开家门就看到家里飘满了猫浮毛雪&#xff0c;空气中还传来隐隐约约的异味。每天不是在吸毛的路上&#xff0c;就是在洗猫砂盆的路上&#xff0c;而且空气中的浮毛还很难清理干净&#xff0c;这是最让人头疼的问题。 …...

maven项目容器化运行之3-优雅的利用Jenkins和maven使用docker插件调用远程docker构建服务并在1Panel中运行

一.背景 在《maven项目容器化运行之1》中&#xff0c;我们开启了1Panel环境中docker构建服务给到了局域网。在《maven项目容器化运行之2》中&#xff0c;我们基本实现了maven工程创建、远程调用docker构建镜像、在1Panel选择镜像运行容器三大步骤。 但是&#xff0c;存在一个问…...

docker 打包orbbec

docker pull humble容器 sudo docker run -it osrf/ros:humble-desktop docker 启动容器 sudo docker run -u root --device/dev/bus/usb:/dev/bus/usb -it -v /home/wl:/share --name wl4 osrf/ros:humble-desktop /bin/bash新开一个终端 查看本地存在的容器&#xff1a;…...

无涯·问知财报解读,辅助更加明智的决策

财报解读就像是给公司做一次全面的体检&#xff0c;是理解公司内部运作机制和市场表现的一把钥匙&#xff0c;能够有效帮助投资者、分析师、管理层以及所有市场参与者判断一家公司的健康程度和发展潜力。 星环科技无涯问知的财经库内置了企业年报及财经类信息&#xff0c;并对…...

【Apache Doris】数据副本问题排查指南

【Apache Doris】数据副本问题排查指南 一、问题现象二、问题定位三、问题处理 本文主要分享Doris中数据副本异常的问题现象、问题定位以及如何处理此类问题。 一、问题现象 问题日志 查询报错 Failed to initialize storage reader, tablet{tablet_id}.xxx.xxx问题说明 查…...

【HarmonyOS】关于鸿蒙消息推送的心得体会(二)

【HarmonyOS】关于鸿蒙消息推送的心得体会&#xff08;二&#xff09; 前言 推送功能的开发与传统功能开发还是有很大区别。首先最大的区别点就在于需要多部门之间的协同&#xff0c;作为鸿蒙客户端开发&#xff0c;你需要和产品&#xff0c;运营&#xff0c;以及后台开发一起…...

零基础入门:创建一个简单的Python爬虫管理系统

摘要&#xff1a; 本文将手把手教你&#xff0c;从零开始构建一个简易的Python爬虫管理系统&#xff0c;无需编程基础&#xff0c;轻松掌握数据抓取技巧。通过实战演练&#xff0c;你将学会设置项目、编写基本爬虫代码、管理爬取任务与数据&#xff0c;为个人研究或企业需求奠…...

【Node.js基础04】node.js模块化

一&#xff1a;什么是模块化 在Node.js中&#xff0c;每个文件都可视为一个独立的模块。模块化提高了代码的复用性&#xff0c;按需加载&#xff0c;具有独立的作用域 二&#xff1a;如何实现多个文件间导入和导出 1 CommonJS标准&#xff08;默认&#xff09;-导入和导出 …...

数据库——单表查询

一、建立数据库mydb8_worker mysql> use mydb8_worker; 二、建立表 1.创建表 mysql> create table t_worker(department_id int(11) not null comment 部门号,-> worder_id int(11) primary key not null comment 职工号,-> worker_date date not null comment…...

dsa加训

refs: OI Wiki - OI Wiki (oi-wiki.org) 1. 枚举 POJ 2811 熄灯问题 refs : OpenJudge - 2811:熄灯问题 如果要枚举每个灯开或者不开的情况&#xff0c;总计2^30种情况&#xff0c;显然T。 不过我们可以发现&#xff1a;若第i行的某个灯亮了&#xff0c;那么有且仅有第i行和第…...

SpringBoot源码(1)ApplicationContext和BeanFactory

1、调用getBean方法 SpringBootApplication public class SpringBootDemoApplication {public static void main(String[] args) {ConfigurableApplicationContext applicationContext SpringApplication.run(SpringBootDemoApplication.class, args);applicationContext.get…...

CANoe编程实例--TCP/IP通信

1、简介 本实例将使用目前常用的开发工具C#来开发服务器端&#xff0c;以CANoe端作为客户端。服务器端和客户端&#xff0c;通过TCP/IP连接&#xff0c;实现数据交换。 首先在服务器端建立一个监听Socket&#xff0c;自动创建一个监听线程&#xff0c;随时监听是否有客户端的连…...

Neuron协议网关的北向应用插件开发

目录 概述 指令处理层开发​ 应用层开发​ .open​ .close​ .init​ .uninit​ .start​ .stop​ .setting​ .request​ 插件设置文件​ 适配华为的思路 概述 最近研究了一段时间的Neuron协议网关&#xff0c;前面的博文也提到它虽然能够把数据发到华为的IoT平台上…...

【BUG】已解决:You are using pip version 10.0.1, however version 21.3.1 is available.

You are using pip version 10.0.1, however version 21.3.1 is available. 目录 You are using pip version 10.0.1, however version 21.3.1 is available. 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#…...

electron-builder打包vue2项目不显示element-ui图标

1、使用版本 vue ^2.6.14element-ui ^2.15.14vue-cli-plugin-electron-builder 2.1.1 2、解决办法 1&#xff09; 如果是简单的图标可以使用图片代替&#xff08;这种对于elementui组件的图标还是不会显示&#xff09; 2&#xff09;在vue.config.js配置 const { defineCon…...

controller层-请求格式为json-请求方法为get

前置条件 get请求映射&#xff0c;内容和PostMapping一致&#xff0c;需要请求参数更换为get数据 请求过程&#xff1a;用户请求--初始化DispatcherServlet及对接和分发用户请求--controller--service 用户请求&#xff1a;http://ip:port/user/getinfo 请求方法&#xff1a;ge…...

【Linux】网络通信基础:应用层协议、HTTP、序列化与会话管理

文章目录 前言1. 应用层自定义协议与序列化1.1 什么是应用层&#xff1f;1.2 再谈 "协议"1.3 序列化 和 反序列化 2. HTTP 协议3. 认识 URL(统一资源定位符)4. urlencode和urldecode5. HTTP 协议请求与响应格式5.1 HTTP 请求5.2 HTTP 响应 6. HTTP 的方法6.1 GET 方法…...

@NotNull、@NotEmpty 和 @NotBlank 区别

NotNull、NotEmpty 和 NotBlank 是 Java Bean Validation (JSR 380) 规范中定义的注解&#xff0c;通常用于验证对象的属性是否满足特定的条件。这些注解常用于后端验证&#xff0c;确保接收到的数据符合预期。 NotNull 用途&#xff1a;验证一个对象是否不为null。 注意&#…...

大模型应用—大模型赋能网络爬虫

大模型赋能网络爬虫 简单来说,网页抓取就是从网站抓取数据和内容,然后将这些数据保存为XML、Excel或SQL格式。除了用于生成潜在客户、监控竞争对手和市场研究外,网页抓取工具还可以用于自动化你的数据收集过程。 借助AI网页抓取工具,可以解决手动或纯基于代码的抓取工具的…...

在 Qt 中获取 MouseMove 事件

在编写 Qt 程序时&#xff0c;我希望在鼠标移动时&#xff08;即使鼠标在另一个窗口上&#xff09;能够调用 mouseMoveEvent(QMouseEvent* event) 方法。目前&#xff0c;在我的 mainwindow.cpp 文件中&#xff0c;我有如下代码&#xff1a; void MainWindow::mouseMoveEvent(…...

自动驾驶系列—智能巡航辅助功能中的路口通行功能介绍

自动驾驶系列—智能巡航辅助功能中的车道中央保持功能介绍 自动驾驶系列—智能巡航辅助功能中的车道变换功能介绍 自动驾驶系列—智能巡航辅助功能中的横向避让功能介绍 自动驾驶系列—智能巡航辅助功能中的路口通行功能介绍 文章目录 2. 功能定义3. 功能原理4. 传感器架构5. 实…...

如何为WordPress网站设置多语言站点

随着全球化的发展&#xff0c;拥有一个支持多语言的站点已成为提升用户体验、扩大受众范围的重要手段。本文将详细介绍如何为WordPress网站设置多语言站点&#xff0c;提供两种最佳方案详解&#xff0c;帮助您轻松实现多语言站点的搭建与管理。无论您是选择在同一站点内发布多语…...

【RHCE】综合真机实验(shell完成)

目录 题目&#xff1a; 需求描述 实操 一、服务端&#xff08;servera&#xff09; 1.ip配置 2.更改主机名 3.创建本地仓库 4.DNS服务 1.下载软件包和防火墙允许 2.配置主配置文件 3.配置区域文件 1.named.exam 2.named.fangxiang 4.重启服务 5.验证结果&#x…...

【Python】成功解决conda创建虚拟环境时出现的CondaHTTPError: HTTP 000 CONNECTION FAILED错误

【Python】成功解决conda创建虚拟环境时出现的CondaHTTPError: HTTP 000 CONNECTION FAILED错误 &#x1f308; 欢迎莅临我的个人主页&#x1f448;这里是我深耕Python编程、机器学习和自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;并乐于分享知识与经验的小天地&a…...

苹果笔记本电脑如何优化系统 苹果电脑系统优化软件哪个好 cleanmymac x怎么用

随着时间的推移&#xff0c;你可能会发现你的MacBook运行速度变慢&#xff0c;甚至在执行一些基本任务时也会感觉到卡顿。这不仅影响了工作效率&#xff0c;也大大降低了使用体验。但别担心&#xff0c;优化你的Mac系统比做早餐还简单。本文将用一种轻松的风格向你介绍7种简单易…...

Vue数组操作之sort详解

在 Vue.js 中&#xff0c;sort() 方法用于对数组进行排序。它会改变原数组&#xff0c;并返回排序后的数组。默认情况下&#xff0c;sort() 方法按照字母顺序&#xff08;Unicode 编码顺序&#xff09;对数组中的元素进行排序。如果需要按照其他规则排序&#xff0c;可以传递一…...

解决 Android 应用安装错误:INSTALL_FAILED_BAD_PERMISSION_GROUP

解决 Android 应用安装错误&#xff1a;INSTALL_FAILED_BAD_PERMISSION_GROUP 在开发 Android 应用时&#xff0c;我们有时会遇到安装错误。这篇文章将讨论一种常见的错误&#xff1a;INSTALL_FAILED_BAD_PERMISSION_GROUP&#xff0c;并介绍解决方法。 问题描述 在尝试安装…...

浅谈断言之JSON断言

浅谈断言之JSON断言 JSON断言是Apache JMeter中一个非常实用的功能&#xff0c;它允许用户验证HTTP响应中的JSON数据是否符合预期。这对于API测试尤为重要&#xff0c;因为JSON&#xff08;JavaScript Object Notation&#xff09;是Web服务间通信的常用数据格式。通过精确地检…...

【学习笔记】无人机系统(UAS)的连接、识别和跟踪(四)-无人机认证与授权

引言 3GPP TS 23.256 技术规范&#xff0c;主要定义了3GPP系统对无人机&#xff08;UAV&#xff09;的连接性、身份识别、跟踪及A2X&#xff08;Aircraft-to-Everything&#xff09;服务的支持。 3GPP TS 23.256 技术规范&#xff1a; 【免费】3GPPTS23.256技术报告-无人机系…...

1万+台网络设备运维如何选择支撑工具?

针对1万台网络设备的运维管理&#xff0c;需要采取一套系统化、自动化且高效的管理方法与策略。“工欲善其事&#xff0c;必先利其器”&#xff0c;以下结合一些关键步骤探讨运维支撑软件工具的方案。 1 建立完善的设备档案 设备信息记录&#xff1a; 为每台设备建立详细的…...

Spring Boot集成Spring Batch快速入门Demo

1.什么是Spring Batch&#xff1f; Spring Batch 是一个轻量级的开源框架&#xff0c;它提供了一种简单的方式来处理大量的数据。它基于Spring框架&#xff0c;提供了一套批处理框架&#xff0c;可以处理各种类型的批处理任务&#xff0c;如ETL、数据导入/导出、报表生成等。S…...

Linux 文件系统

在 Linux 中&#xff0c;所有的文件都是从根目录开始的&#xff0c;且所有的设备都是文件。例如&#xff0c;一块硬盘是一个文件&#xff0c;这块硬盘上的分区也是一个文件&#xff0c;声卡也是一个文件。 这种表示设备的文件叫做设备文件。设备文件一般来说是保存在/dev这个目…...