【原文链接】: https://www.zerodayinitiative.com/blog/2017/8/1/pythonizing-the-vmware-backdoor
【原文作者】: Abdul-Aziz Hariri
在我的前一个 VMware blog 中, 我详细介绍了如何利用影响拖放功能并通过后门 RPC 触发的 Use-After-Free 漏洞. 我的一位 ZDI 同事 Vincent Lee 在读完以后, 让我添加更多关于后门接口的信息. 由于工具也是我心中的一个主题, 我决定组合两者在一篇博客文章中.
这篇博客文章涵盖一些后门功能, 特别是 RPC 接口, 并通过几种方法在 Pyhton 中编写工具以加快分析, 模糊测试和 VMware 后门 RPCI 利用的开发.
后门接口概述
VMware 使用后门接口通道进行虚拟机到宿主机的通讯. 后门支持多个命令. 这些命令可以在 lib/include/backdoor_def.h 中找到:
#define BDOOR_CMD_GETMHZ 1
#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
#define BDOOR_CMD_TOGGLEDEVICE 12
#define BDOOR_CMD_GETGUIOPTIONS 13
#define BDOOR_CMD_SETGUIOPTIONS 14
#define BDOOR_CMD_GETSCREENSIZE 15
#define BDOOR_CMD_MONITOR_CONTROL 16 /* Disabled by default. */
#define BDOOR_CMD_GETHWVERSION 17
#define BDOOR_CMD_OSNOTFOUND 18 /* CPL0 only. */
#define BDOOR_CMD_GETUUID 19
#define BDOOR_CMD_GETMEMSIZE 20
#define BDOOR_CMD_HOSTCOPY 21 /* Devel only. */
//#define BDOOR_CMD_SERVICE_VM 22 /* Not in use. Never shipped. */
#define BDOOR_CMD_GETTIME 23 /* Deprecated -> GETTIMEFULL. */
#define BDOOR_CMD_STOPCATCHUP 24
#define BDOOR_CMD_PUTCHR 25 /* Disabled by default. */
#define BDOOR_CMD_ENABLE_MSG 26 /* Devel only. */
#define BDOOR_CMD_GOTO_TCL 27 /* Devel only. */
#define BDOOR_CMD_INITPCIOPROM 28 /* CPL 0 only. */
//#define BDOOR_CMD_INT13 29 /* Not in use. */
#define BDOOR_CMD_MESSAGE 30
后门使用 in/out 指令触发后门功能.
要创建一个后门请求, 我们需要下列要素:
- BDOOR_MAGIC(0x564d5868) 值设置到 EAX
- BDOOR_PORT 0x5658/0x5659(低/高带宽) 设置到 DX
- 后门命令编号设置在 ECX 的低半部分
- 任何命令特定的参数放在 EBX
- 执行 in 指令
例如, 如果我们需要执行定义在 backdoor_def.h 中的 BDOOR_CMD_GETMHZ 命令:
#define BDOOR_CMD_GETMHZ 1
在汇编中, 它看上去像下面这样:
mov eax, 564D5868h
mov ecx, 1 //BDOOR_CMD_GETMHZ
mov edx, 5658h
in eax, dx
对于我们真正感兴趣的 RPCI 请求, ECX 的低半部分应该设置为 BDOOR_CMD_MESSAGE(或 0x1E), 而高半部分应该设置为消息类型.
以下是步骤:
- BDOOR_MAGIC(0x564d5868) 值设置到 EAX
- BDOOR_PORT 0x5658/0x5659(低/高带宽) 设置在 DX
- ECX 的低半部分设置为 BDOOR_CMD_MESSAGE(0x1E)
- ECX 的高半部分设置为 MESSAGETYPE*, 其中类型是定义在 guest_msg_def.h 中的枚举:
/* Basic request types */
typedef enum {
MESSAGE_TYPE_OPEN,
MESSAGE_TYPE_SENDSIZE,
MESSAGE_TYPE_SENDPAYLOAD,
MESSAGE_TYPE_RECVSIZE,
MESSAGE_TYPE_RECVPAYLOAD,
MESSAGE_TYPE_RECVSTATUS,
MESSAGE_TYPE_CLOSE,
} MessageType;
- EBX 设置为协议编号, 定义在 rpcout.h:
#define RPCI_PROTOCOL_NUM 0x49435052 /* 'RPCI' ;-) */
然后位或定义在 guest_msg_def.h 中的标志:#define GUESTMSG_FLAG_COOKIE 0x80000000
#define GUESTMSG_FLAG_ALL GUESTMSG_FLAG_COOKIE
- 最后, 执行 in 指令
例如, 如果我们想创建一个 MESSAGE_TYPE_OPEN 类型的 RPCI 请求, 它应该看起来像下面这样:
mov eax, 0x564D5868
mov ecx, 0x001e //MESSAGE_TYPE_OPEN
mov edx, 0x5658
mov ebx, 0xC9435052
in eax, dx
就 MESSAGE_TYPE_SENDSIZE 来说, 在 MESSAGE_TYPE_OPEN 完成之后, EBX 应该设置为有效载荷的大小:
mov eax, 0x564D5868
mov ecx, 0x1001e //MESSAGE_TYPE_SENDSIZE
mov edx, 0x5658
mov ebx, SIZE
in eax, dx
对于 MESSAGE_TYPE_CLOSE:
mov eax, 0x564D5868
mov ecx, 0x6001e //MESSAGE_TYPE_CLOSE
mov edx, 0x5658
mov ebx, SIZE
in eax, dx
把所有这些放在一个函数发送一个 RPCI 请求看起来应该像这样:
mov eax, 564D5868h
mov ecx, 1Eh
mov edx, 5658h
mov ebx, 0C9435052h
in eax, dx
mov eax, 564D5868h
mov ecx, 1001Eh
mov dx, 5658h
mov ebx, [esp + 28h]
in eax, dx
mov eax, 564D5868h
mov ecx, [esp + 28h]
mov ebx, 10000h
mov ebp, esi
mov dx, 5659h
mov esi, [esp + 24h]
cld
rep outs dx, byte ptr es : [edi]
mov eax, 564D5868h
mov ecx, 0006001eh
mov dx, 5658h
mov esi, ebp
in eax, dx
Python 化 RPCI 调用
编写发送 RPCI 请求的工具在 C/C++ 可以简单的使用开源的 VMtools 库. 对于我们自己在 ZDI 的使用, 我想创造一些东西, 帮助我们更快的编写概念验证(PoC), 更快的评估新的传入案例, 并最后使 RPCI 的模糊测试更容易. 因此, 我开始着手移植从 Python 发送 RPCI 请求的功能.
通过我们短暂的研究, 我想出两个方法来做这件事:
- 为 Python 写一个 C 扩展(CPython)
- 用于 Python 的 ctypes 外部函数库
最初, 当我开始做这件事时, 我甚至没有想到 ctypes. 当时我和同事 Jasiel Spelman 一起出席了 Recon Montreal. 我们在随便讨论, 其中一个话题就是 Pythonizing VMware RPCI. 那时候他说:"Oh-可以用 ctypes 来完成". 他还同意写下一节, 并解释如何通过 ctypes 来做.
ctypes 方法
[这一节由 Jasiel Spelman 编写]
我有一个在 Python 使用 "内联" 汇编的习惯. 在 2014, 我在博客上写了如何将 Python 注入到线程来进行进程自省, 作为其中一部分, 我们发布了 python_injector.py. 这种工具的其中一个好处是你可以像在本地执行 Python 一样在远程系统上执行 Python. 这起到了作用, 因为在纯 Python 中实现某些东西比使用编译的二进制文件更容易一些. 一个额外的好处是在大多数情况下, 你可以在 Python 模块内处理跨平台支持, 而不必为每个平台和 Python 版本的组合编译不同的二进制文件.
为了简洁起见, 我只打算介绍从 Windows 中执行此操作. 但是, 从 Linux 或 MacOS 执行此操作只是用适当的标志调用 mprotect 来代替 VirtualProtect. 我同样排除汇编本身, 因为 Abdul 在上面介绍了.
下面是涉及到的所有内容的片段:
import ctypes
from ctypes.wintypes import DWORD
from ctypes.wintypes import LPCSTR
from ctypes import CFUNCTYPE
from ctypes import addressof
ASSEMBLY = ''
PAGE_EXECUTE_READWRITE = 0x40
RPC_SEND_BUFFER = ctypes.create_string_buffer(ASSEMBLY)
_prototype = CFUNCTYPE(DWORD, LPCSTR, DWORD, use_last_error=True)
ctypes.windll.kernel32.VirtualProtect(addressof(RPC_SEND_BUFFER), len(RPC_SEND_BUFFER), PAGE_EXECUTE_READWRITE, 0)
_rpc_send = _prototype(addressof(RPC_SEND_BUFFER))
def rpc_send(buf):
return _rpc_send(buf, len(buf))
前几行导入 ctypes 模块, 并将一些项目引入我们的命名空间以使这些行更可读. 然后我们定义 ASSEMBLY 变量, 然而要查看汇编你需要参考前面的部分.
下一部分创建一个 ctypes 字符串缓冲区, 标记缓冲区是可执行的, 并为我们需要执行的汇编定义函数原型. 最后, 我们定义一个 Python 函数, 我们可以调用它来调用汇编本身.
我将展示一个处理汇编的方法, 我们使用优秀的 Keystone 汇编器来应对 Abdul 前面展示的一个实例. 我们复制使用 BDOOR_CMD_GETMHZ.
以下是涉及的片段:
from keystone import Ks
from keystone import KS_ARCH_X86
from keystone import KS_MODE_32
ks = Ks(KS_ARCH_X86, KS_MODE_32)
encoding, count = ks.asm(
b'mov eax, 564D5868h;'
b'mov ecx, 1;' #BDOOR_CMD_GETMHZ
b'mov edx, 5658h;'
b'in eax, dx'
)
ASSEMBLY = ''.join(map(chr, encoding))
首先, 我们导入 Keystone 库的相关部分. 然后, 我们创建一个 Keystone 汇编器对象. 这里, 我们传给它我们的汇编字符串, 如果我们在 32 位和 64 位上执行, 其中寄存器名称可能会发生变化, 并且值可能会根据我们要调用的内容而变化. 一旦 Keystone 返回了字节, 我们需要把它从整数转换为稍后我们可以使用的字符串.
注意, 你不需要执行这部分. 你可以为你的目标平台装配一次相关指令, 但是, 如果你要执行多个命令, 这可能是有益的.
C 扩展方法
如果 ctypes 不是你的东西, CPython 也可以工作, 我将演示在我与 Jasiel 交谈之前所采取的步骤. CPython 支持调用 C 函数, 并在变量和类属性上声明 C 类型. 在这种情况下, 这允许我们在 Python C 扩展中编写 ASM 函数, 并将其编译为 Python 模块.
虽然这需要在每次修改底层代码时进行编译, 但是最终我们仍然喜欢在 Python 中编写脚本.
C 扩展可以被多种方法实现. 简而言之, 可以这样做(Windows/用 VS 编译):
- 创建一个函数使用内联汇编发送一个 RPC 请求
__declspec(naked) void rpc_send(uint8_t *msg, uint32_t size)
{
__asm
{
pushad
….
….
popad
ret
}
}
- 添加一个 C 函数, 当调用 Python 函数时它会被调用:
static PyObject py_rpc_send (PyObject self, PyObject* args)
{
…
if (!PyArg_ParseTuple(args, "z#",&msg,&sz))
{
…
}
rpc_send(msg,sz);
…
}
在编译代码之后, 我们将得到一个 Python 扩展 .pyd, 我们可以从 Python 导入. 用 C 或 C++ 扩展 Python 的正式文档可以在这里找到.
总结
一个工具可以让我们很方便的编写模糊测试和漏洞利用, 特别是在 Python 中, 它可以很容易的集成到框架中, 甚至可以快速编写独立的脚本. 本系列的下一篇博客将介绍 VMware 的逆向, 以便可以嗅探 RPC 请求, 并且你可以在几周内看到它.
与往常一样, 你可以在 twitter 上找到我们 @abdhariri, @WanderingGlitch, 和 @thezdi. 你也可以在 Peppermill 找到我们.
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课