当前位置: 首页 > 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;随时监听是否有客户端的连…...