首页
社区
课程
招聘
[翻译]虚拟化漏洞专题 5: Pythonizing the Vmware Backdoor
2017-11-18 09:32 4839

[翻译]虚拟化漏洞专题 5: Pythonizing the Vmware Backdoor

2017-11-18 09:32
4839
 

【原文链接】: 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 指令触发后门功能.

 

要创建一个后门请求, 我们需要下列要素:

  1. BDOOR_MAGIC(0x564d5868) 值设置到 EAX
  2. BDOOR_PORT 0x5658/0x5659(低/高带宽) 设置到 DX
  3. 后门命令编号设置在 ECX 的低半部分
  4. 任何命令特定的参数放在 EBX
  5. 执行 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), 而高半部分应该设置为消息类型.

 

以下是步骤:

  1. BDOOR_MAGIC(0x564d5868) 值设置到 EAX
  2. BDOOR_PORT 0x5658/0x5659(低/高带宽) 设置在 DX
  3. ECX 的低半部分设置为 BDOOR_CMD_MESSAGE(0x1E)
  4. 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;
    
  5. 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
    
  6. 最后, 执行 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 请求的功能.

 

通过我们短暂的研究, 我想出两个方法来做这件事:

  1. 为 Python 写一个 C 扩展(CPython)
  2. 用于 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 编译):

  1. 创建一个函数使用内联汇编发送一个 RPC 请求
    __declspec(naked) void rpc_send(uint8_t *msg, uint32_t size)
    { 
        __asm
        {
        pushad 
         ….
         ….
        popad
        ret
        }
    }
    
  2. 添加一个 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 找到我们.


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞0
打赏
分享
最新回复 (1)
雪    币: 6103
活跃值: (1207)
能力值: (RANK:30 )
在线值:
发帖
回帖
粉丝
CCkicker 2017-11-23 15:07
2
0
感谢分享!期待后续!
游客
登录 | 注册 方可回帖
返回