[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH 1/1] kdd.c: Add support for initial handshake in KD protocol for Win 7, 8 and 10 (64 bit)
Hi, At 21:24 -0500 on 05 Nov (1572989067), Julian Tuminaro wrote: > Current implementation of find_os is based on the hard-coded values for > different Windows version. It uses the value for get the address to > start looking for DOS header in the given specified range. However, this > is not scalable to all version of Windows as it will require us to keep > adding new entries and also due to KASLR, chances of not hitting the PE > header is significant. We implement a way for 64-bit systems to use IDT > entry to get a valid exception/interrupt handler and then move back into > the memory to find the valid DOS header. Since IDT entries are protected > by PatchGuard, we think our assumption that IDT entries will not be > corrupted is valid for our purpose. Once we have the image base, we > search for the DBGKD_GET_VERSION64 structure type in .data section to > get information required for handshake. Thank you very much for this - it looks super-useful! Does this technique only work if the guest kernel has debugging enabled, or can it work on all systems? I have some commetns on the code, below. > /* Windows version details */ > typedef struct { > uint32_t build; > @@ -62,6 +64,7 @@ typedef struct { > uint32_t version; /* +-> NtBuildNumber */ > uint32_t modules; /* +-> PsLoadedModuleList */ > uint32_t prcbs; /* +-> KiProcessorBlock */ > + uint32_t kddl; This needs a comment describing the Windows name of what it points to. > +/** > + * @brief Parse the memory at \a filebase as a valid DOS header and get > virtual > + * address offset and size for any given section name (if it exists) > + * > + * @param s Pointer to the kdd_state structure > + * @param filebase Base address of the file structure > + * @param sectname Pointer to the section name c-string to look for > + * @param vaddr Pointer to write the virtual address of section start to > + * (if found) > + * @param visze Pointer to write the section size to (if found) > + * > + * @return -1 on failure to find the section name > + * @return 0 on success > + */ > +static int get_pe64_sections(kdd_state *s, uint64_t filebase, char *sectname, > + uint64_t *vaddr, uint32_t *vsize) These new functions don't belong in the 'Utility functions' section. Please move them to beside the other OS-finding code. > +{ > + uint8_t buf[0x30]; PE_SECT_ENT_SZ, please. > + uint64_t pe_hdr; > + uint64_t sect_start; > + uint16_t num_sections; > + int ret; > + > + ret = -1; > + > + if (!s->os.w64) { > + return ret; > + } > + > + // read PE header offset > + if (kdd_read_virtual(s, s->cpuid, filebase + DOS_HDR_PE_OFF, > DOS_HDR_PE_SZ, > + buf) != DOS_HDR_PE_SZ) { > + return -1; > + } > + pe_hdr = filebase + *(uint32_t *)buf; Here and elsewhere, please read directly into the variables, e.g.: uint32_t pe_hdr; kdd_read_virtual(s, s->cpuid, filebase + DOS_HDR_PE_OFF, sizeof pe_hdr, &pe_hdr); pe_hdr += filebase; That gives neater code and avoids any confusion about the sizes of various copies and buffers. > + > + // read number of sections > + if (kdd_read_virtual(s, s->cpuid, pe_hdr + PE_NUM_SECTION_OFF, > + PE_NUM_SECTION_SZ, &buf) != PE_NUM_SECTION_SZ) { > + return -1; > + } > + num_sections = *(uint16_t *)buf; This needs a check for very large numbers -- loading 65,535 section headers might take a long time. > + // read size of optional header > + if (kdd_read_virtual(s, s->cpuid, pe_hdr + PE_OPT_HDR_SZ_OFF, > + PE_OPT_HDR_SZ_SZ, &buf) != PE_OPT_HDR_SZ_SZ) { > + return -1; > + } > + > + // 0x18 is the size of PE header > + sect_start = pe_hdr + PE_HDR_SZ + *(uint16_t *)buf; > + > + for (int i = 0; i < num_sections; i++) { > + if (kdd_read_virtual(s, s->cpuid, sect_start + (i * PE_SECT_ENT_SZ), > + PE_SECT_ENT_SZ, &buf) != PE_SECT_ENT_SZ) { > + return -1; > + } > + > + if (!strncmp(sectname, (char *)(buf + PE_SECT_NAME_OFF), > + PE_SECT_NAME_SZ)) { > + *vaddr = filebase + *(uint32_t *)(buf + PE_SECT_RVA_OFF); > + *vsize = *(uint32_t *)(buf + PE_SECT_VSIZE_OFF); > + ret = 0; > + break; Just 'return 0' will do here, and.. > + } > + } > + > + return ret; return -1 here, and drop 'ret'. > +} > + > +/** > + * @brief Get the OS information like base address, minor version, > + * PsLoadedModuleList and DebuggerDataList (basically the fields of > + * DBGKD_GET_VERSION64 struture required to do handshake?). > + * > + * This is done by reading the IDT entry for divide-by-zero exception and > + * searching back into the memory for DOS header (which is our kernel base). > + * Once we have the kernel base, we parse the PE header and look for kernel > + * base address in the .data section. Once we have possible values, we look > for > + * DBGKD_GET_VERSION64 block by using following heuristics on the address > which > + * has the kernel base: > + * > + * - at address [-0x10], it should have 0xf as the MajorVersion > + * - at address [+0x8], it should have a valid kernel memory address > pointing > + * in .data > + * - at address [+0x10], it should have a valid kernel memory address > pointing > + * in .data > + * > + * @param s Pointer to the kdd state > + */ > +static void get_os_info_64(kdd_state *s) > +{ > + kdd_ctrl ctrl; > + int ret; > + uint64_t buf; > + uint64_t idt0_addr; > + uint64_t base; > + uint64_t caddr; > + uint64_t data_base; > + uint32_t data_size; > + uint64_t modptr; > + uint64_t kddl; > + uint16_t minor; > + uint8_t dbgkd_get_version64[0x28]; DBGKD_GET_VERSION64_SZ, please > + > + /* TODO: right now, we are forcing this to 1 (as we only support 64 bit > + * system, however, we should use kdd_state or hvm calls to check if we > are > + * in 64-bit > + */ > + s->os.w64 = 1; At the point where you call this, s->os == unknown_os, so you are modifying unknown_os here! Please declare another static kdd_os to fill in here. Then you can set w64=1 in that without needing any TODOs or checks here. I would also prefer if this function just returned 0/-1 - then you don't need a 'goto fail' path. You can just return -1 everywhere, and you can call it from the bottom of 'find_os()', as: if (get_os_info_64(s) != 0) s->os = unknown_os; > + // if we are no in 64-bit mode, fail > + if (!s->os.w64) { > + goto fail; > + } > + > + // get control registers for our os > + ret = kdd_get_ctrl(s->guest, s->cpuid, &ctrl, s->os.w64); > + if (ret) { > + goto fail; > + } > + > + // read the div-by-zero handler function address > + kdd_read_virtual(s, s->cpuid, ctrl.c64.idt_base + 8, 8, &buf); > + idt0_addr = ((uint64_t)buf << 32) & 0xffffffff00000000; > + > + kdd_read_virtual(s, s->cpuid, ctrl.c64.idt_base, 8, &buf); > + idt0_addr |= ((buf >> 32) & 0xffff0000); > + idt0_addr |= (buf & 0xffff); > + > + KDD_LOG(s, "idt0 addr: %p\n", (void *)idt0_addr); > + printf("idt0 addr: %p\n", (void *)idt0_addr); Please drop all printf() calls and just use KDD_LOG() / KDD_DEBUG > + // get the page start and look for "MZ" file header > + > + base = idt0_addr & ~(PAGE_SIZE - 1); > + // printf("%p\n", (void *)base); Please remove all commented-out code. > + while (1) { Please set some reasonable limit to how far this will search. Presumably we expect to find the header within < 1 GB ? > + uint16_t val; > + if (kdd_read_virtual(s, s->cpuid, base, 2, &val) != 2) { > + // just move going back?? this is bad though > + printf("ran into unmapped region without finding PE header\n"); KDD_DEBUG() and return false. > + } > + > + if (val == 0x5a4d) { // MZ > + // printf("maybe success\n"); > + break; > + } > + > + base -= PAGE_SIZE; > + } > + > + KDD_LOG(s, "base: %p\n", (void *)base); > + > + // found the data section start > + if (get_pe64_sections(s, base, ".data", &data_base, &data_size)) { > + goto fail; > + } > + > + // look for addresses which has kernel base written into it > + caddr = data_base; > + > + modptr = 0; > + kddl = 0; > + minor = 0; > + > + while (caddr < data_base + data_size) { This needs a limit: data_size came from guest memory and we don't want to do 500 million iterations here. > + if (kdd_read_virtual(s, s->cpuid, caddr, SIZE_PTR64, &buf) != > + SIZE_PTR64) { > + // reached end and found nothing > + goto fail; > + } > + > + // if we found base in the memory addresses > + if (buf == base) { > + // read the DBGKD_GET_VERSION64 struct > + if (kdd_read_virtual(s, s->cpuid, caddr - DBGKD_KERN_BASE_OFF, > + DBGKD_GET_VERSION64_SZ, dbgkd_get_version64) == > + DBGKD_GET_VERSION64_SZ) { > + // check if major version is 0xf > + if (dbgkd_get_version64[0] == '\x0f') { > + > + // read minor version, PsLoadedModuleList pointer and > + // DebuggerDataList > + modptr = > + *(uint64_t *)(dbgkd_get_version64 + > DBGKD_MOD_LIST_OFF); > + kddl = *(uint64_t *)(dbgkd_get_version64 + > DBGKD_KDDL_OFF); > + minor = > + *(uint16_t *)(dbgkd_get_version64 + DBGKD_MINOR_OFF); I think this code would be much cleaner if you defined a struct with the correct fields instead of trying to parse them by hand out of a buffer. > + // do heuristic check > + if (modptr && kddl && modptr != kddl && kddl != base && > + base != modptr && modptr >= data_base && > + modptr < (data_base + data_size) && > + kddl >= data_base && > + kddl < (data_base + data_size)) { > + // my_memdump(s, caddr - 0x10, 0x30); > + break; > + } > + > + } > + } > + > + } > + > + caddr += sizeof(void *); > + } We need a test here for whether we ran off the end of the while() loop without finding the magic struct. > + // TODO: use KDD_LOG? > + printf("base: %p\n", (void *)base); > + printf("modules list: %p\n", (void *)modptr); > + printf("kddl: %p\n", (void *)kddl); > + printf("minor version: 0x%hx\n", minor); > + > + s->os.base = base; > + s->os.modules = modptr - base; > + s->os.kddl = kddl - base; > + s->os.build = (uint32_t) minor; > + return; > + > +fail: > + // XXX: TODO: handle failure case > + s->os = unknown_os; > + return; > +} > + > > > /***************************************************************************** > * How to send packets and acks. > @@ -534,6 +915,12 @@ static void kdd_handle_handshake(kdd_state *s) > { > /* Figure out what we're looking at */ > find_os(s); > + > + /* if unknown os, use the idt method */ > + if (!s->os.base) { > + get_os_info_64(s); > + } As I said above, please call get_os_info_64() from find_os() instead of calling it from here. > kdd_send_string(s, "[kdd: %s @0x%"PRIx64"]\r\n", s->os.name, s->os.base); > > /* Respond with some details about the debugger stub we simulate */ > @@ -555,7 +942,7 @@ static void kdd_handle_handshake(kdd_state *s) > s->txp.cmd.shake.u3[2] = 0x55; > s->txp.cmd.shake.kern_addr = s->os.base; > s->txp.cmd.shake.mods_addr = s->os.base + s->os.modules; > - s->txp.cmd.shake.data_addr = 0; /* Debugger data probably doesn't exist > */ > + s->txp.cmd.shake.data_addr = s->os.base + s->os.kddl; // 0; /* Debugger > data probably doesn't exist */ Please only set this if s->os.kddl is != 0. As I said, overall this looks like a really useful improvement and I look forward to seing your revised patch! Thanks, Tim. _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxxxxxxxxx https://lists.xenproject.org/mailman/listinfo/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |