#include <errno.h>
#include <inttypes.h>
#include <netdb.h>
+#include <stddef.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "kdd.h"
+/*
+ * TODO: kdd_os is a type which is used to represent os array. Adding a
+ * variable here would result in adding a new field to each element in array.
+ * However, since most of the fields are part of the same struct that we are
+ * trying to read from memory, we have added kddl to this structure. If
+ * required, we can possibly separate the kddl value to someplace else
+ *
+ * We also use kddl of size uint32_t which is actually used to represent the
+ * offset from image base rather than actual address
+ */
/* Windows version details */
typedef struct {
uint32_t build;
uint32_t version; /* +-> NtBuildNumber */
uint32_t modules; /* +-> PsLoadedModuleList */
uint32_t prcbs; /* +-> KiProcessorBlock */
+ uint32_t kddl; /* +-> KdDebuggerList */
} kdd_os;
/* State of the debugger stub */
kdd_os os; /* OS-specific magic numbers */
} kdd_state;
+/**
+ * @brief Structure to represent DBGKD_GET_VERSION64
+ *
+ * reference: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdbgexts/ns-wdbgexts-_dbgkd_get_version64
+ */
+typedef struct {
+ uint16_t MajorVersion; /* usually 0xf for free build */
+ uint16_t MinorVersion; /* build number of target OS */
+ uint8_t ProtocolVersion; /* version of the debugger protocol */
+ uint8_t KdSecondaryVersion; /* secondary version number */
+ uint16_t Flags; /* set of bit flags for the current debugging session */
+ uint16_t MachineType; /* type of the target's processor */
+ uint8_t MaxPacketType; /* one plus the highest number for a debugger */
+ /* packet type recognized by the target */
+ uint8_t MaxStateChagne; /* one plus the highest number for a state */
+ /* change generated by the target */
+ uint8_t MaxManipulate; /* one more that the highest number, recognized */
+ /* by the target, for a command to manipulate the target */
+ uint8_t Simulation; /* indication if target is in simulated execution */
+ uint16_t Unused[1];
+ uint64_t KernBase; /* base address of the kernel image */
+ uint64_t PsLoadedModuleList; /* value of the kernel variable */
+ /* PsLoadedModuleList */
+ uint64_t DebuggerDataList; /* value of the kernel variable */
+ /* KdDebuggerDataBlock */
+} PACKED DBGKD_GET_VERSION64;
+
+/**
+ * @brief Structure to represent the section in PE headers
+ *
+ * reference: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers
+ */
+typedef struct {
+ uint8_t Name[8]; /* name of section */
+ uint32_t VirtualSize; /* total size of section in memory */
+ uint32_t VirtualAddr; /* offset from image base */
+ uint32_t SizeOfRawData; /* size of section in for object files */
+ uint32_t PointerToRawData; /* file pointer to first page in COFF */
+ uint32_t PointerToRelocations; /* file pointer to beginning of relocation entry */
+ uint32_t PointerToLinenumbers; /* file pointer to the beginning of line-number entries */
+ uint16_t NumberOfRelocations; /* number of relocation entries for the section */
+ uint16_t NumberOfLinenumbers; /* number of line-number entries for the section */
+ uint32_t Characteristics; /* flags that describe the characteristics of the section */
+} PACKED PE_SECTION_ENTRY;
+
+/**
+ * @brief Size of pointer on 64 machine
+ */
+#define SIZE_PTR64 8
+
+/**
+ * @brief Size of pointer on 32 machine
+ */
+#define SIZE_PTR32 4
+
+
+/*****************************************************************************
+ * PE and DOS Header related offsets
+ */
+
+/**
+ * @brief Offset in DOS header to look for PE header
+ */
+#define DOS_HDR_PE_OFF 0x3c
+
+/**
+ * @brief Size of PE header offset field in DOS header
+ */
+#define DOS_HDR_PE_SZ 4
+
+/**
+ * @brief Offset of number of sections field in PE header
+ */
+#define PE_NUM_SECTION_OFF 0x6
+
+/**
+ * @brief Size of number of sections field in PE header
+ */
+#define PE_NUM_SECTION_SZ 2
+
+/**
+ * @brief Offset of optional header size field in PE header
+ */
+#define PE_OPT_HDR_SZ_OFF 0x14
+
+/**
+ * @brief Size of optional header size field in PE header
+ */
+#define PE_OPT_HDR_SZ_SZ 2
+
+/**
+ * @brief Size of PE header
+ */
+#define PE_HDR_SZ 0x18
+
+/**
+ * @brief MZ header
+ */
+#define MZ_HEADER 0x5a4d
+
+/**
+ * @brief Limit on the number of sections to look for while iterating through
+ * PE sections
+ */
+#define NUM_SECT_LIMIT 100
+
+/**
+ * @brief Major Version for the DBGKD_GET_VERSION64 structure
+ */
+#define NT_MAJOR_VERSION 0xf
+
/*****************************************************************************
* Utility functions
*/
*/
static kdd_os os[] = {
- /* Build 64 MP Name &Kernel search base Range +Version +Modules +PRCBs (64b) */
- {2195, 0, 0, "w2k sp4 x32 UP", 0xffffffff80400000ULL, 0x00000000, 0x0006d57c, 0x0006e1b8, 0x0},
- {2195, 0, 1, "w2k sp4 x32 SMP", 0xffffffff80400000ULL, 0x00000000, 0x0006fa1c, 0x00084520, 0x0},
+ /* Build 64 MP Name &Kernel search base Range +Version +Modules +PRCBs (64b) +KDDL */
+ {2195, 0, 0, "w2k sp4 x32 UP", 0xffffffff80400000ULL, 0x00000000, 0x0006d57c, 0x0006e1b8, 0x0, 0},
+ {2195, 0, 1, "w2k sp4 x32 SMP", 0xffffffff80400000ULL, 0x00000000, 0x0006fa1c, 0x00084520, 0x0, 0},
// PAE/UP, PAE/SMP
- {2600, 0, 0, "xp sp2 x32 UP", 0xffffffff804d7000ULL, 0x00000000, 0x00075568, 0x00083b20, 0x0},
- {2600, 0, 1, "xp sp2 x32 SMP", 0xffffffff804d7000ULL, 0x00000000, 0x0007d0e8, 0x0008d4a0, 0x0},
+ {2600, 0, 0, "xp sp2 x32 UP", 0xffffffff804d7000ULL, 0x00000000, 0x00075568, 0x00083b20, 0x0, 0},
+ {2600, 0, 1, "xp sp2 x32 SMP", 0xffffffff804d7000ULL, 0x00000000, 0x0007d0e8, 0x0008d4a0, 0x0, 0},
// PAE/UP, PAE/SMP
- {2600, 0, 0, "xp sp3 x32 UP", 0xffffffff804d7000ULL, 0x00000000, 0x00075be8, 0x000841c0, 0x0},
- {2600, 0, 1, "xp sp3 x32 SMP", 0xffffffff804d7000ULL, 0x00000000, 0x0007c0e8, 0x0008c4c0, 0x0},
- {2600, 0, 0, "xp sp3 x32p UP", 0xffffffff804d7000ULL, 0x00000000, 0x0006e8e8, 0x0007cfc0, 0x0},
- {2600, 0, 1, "xp sp3 x32p SMP", 0xffffffff804d7000ULL, 0x00000000, 0x000760e8, 0x00086720, 0x0},
+ {2600, 0, 0, "xp sp3 x32 UP", 0xffffffff804d7000ULL, 0x00000000, 0x00075be8, 0x000841c0, 0x0, 0},
+ {2600, 0, 1, "xp sp3 x32 SMP", 0xffffffff804d7000ULL, 0x00000000, 0x0007c0e8, 0x0008c4c0, 0x0, 0},
+ {2600, 0, 0, "xp sp3 x32p UP", 0xffffffff804d7000ULL, 0x00000000, 0x0006e8e8, 0x0007cfc0, 0x0, 0},
+ {2600, 0, 1, "xp sp3 x32p SMP", 0xffffffff804d7000ULL, 0x00000000, 0x000760e8, 0x00086720, 0x0, 0},
- {3790, 0, 0, "w2k3 sp2 x32 UP", 0xffffffff80800000ULL, 0x00000000, 0x00097128, 0x000a8e48, 0x0},
- {3790, 0, 1, "w2k3 sp2 x32 SMP", 0xffffffff80800000ULL, 0x00000000, 0x0009d128, 0x000af9c8, 0x0},
- {3790, 0, 0, "w2k3 sp2 x32p UP", 0xffffffff80800000ULL, 0x00000000, 0x0008e128, 0x0009ffa8, 0x0},
- {3790, 0, 1, "w2k3 sp2 x32p SMP", 0xffffffff80800000ULL, 0x00000000, 0x00094128, 0x000a6ea8, 0x0},
- {3790, 1, 0, "w2k3 sp2 x64 UP", 0xfffff80001000000ULL, 0x00000000, 0x001765d0, 0x0019aae0, 0x0017b100},
- {3790, 1, 1, "w2k3 sp2 x64 SMP", 0xfffff80001000000ULL, 0x00000000, 0x001b05e0, 0x001d5100, 0x001b5300},
+ {3790, 0, 0, "w2k3 sp2 x32 UP", 0xffffffff80800000ULL, 0x00000000, 0x00097128, 0x000a8e48, 0x0, 0},
+ {3790, 0, 1, "w2k3 sp2 x32 SMP", 0xffffffff80800000ULL, 0x00000000, 0x0009d128, 0x000af9c8, 0x0, 0},
+ {3790, 0, 0, "w2k3 sp2 x32p UP", 0xffffffff80800000ULL, 0x00000000, 0x0008e128, 0x0009ffa8, 0x0, 0},
+ {3790, 0, 1, "w2k3 sp2 x32p SMP", 0xffffffff80800000ULL, 0x00000000, 0x00094128, 0x000a6ea8, 0x0, 0},
+ {3790, 1, 0, "w2k3 sp2 x64 UP", 0xfffff80001000000ULL, 0x00000000, 0x001765d0, 0x0019aae0, 0x0017b100, 0},
+ {3790, 1, 1, "w2k3 sp2 x64 SMP", 0xfffff80001000000ULL, 0x00000000, 0x001b05e0, 0x001d5100, 0x001b5300, 0},
- {6000, 0, 1, "vista sp0 x32p", 0xffffffff81800000ULL, 0x00000000, 0x000a4de4, 0x00111db0, 0x0},
- {6001, 0, 1, "vista sp1 x32p", 0xffffffff81000000ULL, 0x0f000000, 0x000af0c4, 0x00117c70, 0x0},
+ {6000, 0, 1, "vista sp0 x32p", 0xffffffff81800000ULL, 0x00000000, 0x000a4de4, 0x00111db0, 0x0, 0},
+ {6001, 0, 1, "vista sp1 x32p", 0xffffffff81000000ULL, 0x0f000000, 0x000af0c4, 0x00117c70, 0x0, 0},
- {6001, 1, 1, "w2k8 sp0 x64", 0xfffff80001000000ULL, 0x0f000000, 0x00140bf0, 0x001c5db0, 0x00229640},
+ {6001, 1, 1, "w2k8 sp0 x64", 0xfffff80001000000ULL, 0x0f000000, 0x00140bf0, 0x001c5db0, 0x00229640, 0},
- {7600, 1, 1, "win7 sp0 x64", 0xfffff80001000000ULL, 0x0f000000, 0x001af770, 0x0023de50, 0x002a8900},
+ {7600, 1, 1, "win7 sp0 x64", 0xfffff80001000000ULL, 0x0f000000, 0x001af770, 0x0023de50, 0x002a8900, 0},
- {7601, 0, 1, "win7 sp1 x32p", 0xffffffff81800000ULL, 0x0f000000, 0x000524c4, 0x00149850, 0x0},
- {7601, 1, 1, "win7 sp1 x64", 0xfffff80001000000ULL, 0x0f000000, 0x001b2770, 0x00240e90, 0x002ab900},
+ {7601, 0, 1, "win7 sp1 x32p", 0xffffffff81800000ULL, 0x0f000000, 0x000524c4, 0x00149850, 0x0, 0},
+ {7601, 1, 1, "win7 sp1 x64", 0xfffff80001000000ULL, 0x0f000000, 0x001b2770, 0x00240e90, 0x002ab900, 0},
};
// 1381, 0, 0, "NT4 sp?", 0xffffffff80100000, ?, ?
-static kdd_os unknown_os = {0, 0, 0, "unknown OS", 0, 0, 0, 0, 0};
+static kdd_os unknown_os = {0, 0, 0, "unknown OS", 0, 0, 0, 0, 0, 0};
static int check_os(kdd_state *s)
{
return 1;
}
+/**
+ * @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)
+{
+ uint64_t pe_hdr = 0;
+ uint64_t sect_start = 0;
+ uint16_t num_sections = 0;
+ uint16_t opt_hdr_sz = 0;
+ PE_SECTION_ENTRY pe_sect;
+
+ if (!s->os.w64)
+ return -1;
+
+ /* read PE header offset */
+ if (kdd_read_virtual(s, s->cpuid, filebase + DOS_HDR_PE_OFF, DOS_HDR_PE_SZ,
+ &pe_hdr) != DOS_HDR_PE_SZ)
+ return -1;
+
+ pe_hdr += filebase;
+
+ /* read number of sections */
+ if (kdd_read_virtual(s, s->cpuid, pe_hdr + PE_NUM_SECTION_OFF,
+ PE_NUM_SECTION_SZ, &num_sections) != PE_NUM_SECTION_SZ)
+ return -1;
+
+ /* read number of section upto a limit */
+ if (num_sections > NUM_SECT_LIMIT)
+ num_sections = NUM_SECT_LIMIT;
+
+ /* read size of optional header */
+ if (kdd_read_virtual(s, s->cpuid, pe_hdr + PE_OPT_HDR_SZ_OFF,
+ PE_OPT_HDR_SZ_SZ, &opt_hdr_sz) != PE_OPT_HDR_SZ_SZ)
+ return -1;
+
+ /* 0x18 is the size of PE header */
+ sect_start = pe_hdr + PE_HDR_SZ + opt_hdr_sz;
+
+ for (int i = 0; i < num_sections; i++) {
+ if (kdd_read_virtual(s, s->cpuid, sect_start + (i * sizeof(pe_sect)),
+ sizeof(pe_sect), &pe_sect) != sizeof(pe_sect))
+ return -1;
+
+ if (!strncmp(sectname, (char *)pe_sect.Name, sizeof(pe_sect.Name))) {
+ *vaddr = filebase + pe_sect.VirtualAddr;
+ *vsize = pe_sect.VirtualSize;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * @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 = 0;
+ uint64_t kddl = 0;
+ uint16_t minor = 0;
+ uint64_t dbgkd_addr;
+ DBGKD_GET_VERSION64 dbgkd_get_version64;
+ /* Maybe 1GB is too big for the limit to search? */
+ uint32_t search_limit = (1024 * 1024 * 1024) / PAGE_SIZE; /*1GB/PageSize*/
+ uint64_t efer;
+
+ /* if we are not in 64-bit mode, fail */
+ if (kdd_rdmsr(s->guest, s->cpuid, 0xc0000080, &efer) || !(efer & (1 << 8)))
+ goto fail;
+
+ s->os.w64 = 1;
+
+ /* 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);
+
+ /*
+ * get the page start and look for "MZ" file header - we limit the search
+ * in 1GB range above the current page base address
+ */
+
+ base = idt0_addr & ~(PAGE_SIZE - 1);
+ KDD_LOG(s, "%p\n", (void *)base);
+
+ while (search_limit) {
+ uint16_t val;
+ if (kdd_read_virtual(s, s->cpuid, base, 2, &val) != 2) {
+ /* just move going back?? this is bad though */
+ KDD_LOG(s, "ran into unmapped region without finding PE header\n");
+ goto fail;
+ }
+
+ if (val == MZ_HEADER) // MZ
+ break;
+
+ base -= PAGE_SIZE;
+ search_limit -= 1;
+ }
+
+ 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;
+
+ search_limit = (1024 * 1024 * 512) / SIZE_PTR64;
+ while (caddr < data_base + data_size && search_limit) {
+ if (kdd_read_virtual(s, s->cpuid, caddr, SIZE_PTR64, &buf) !=
+ SIZE_PTR64)
+ goto fail; /* reached end and found nothing */
+
+ /* if we found base in the memory addresses */
+ if (buf == base) {
+ /* read the DBGKD_GET_VERSION64 struct */
+ dbgkd_addr = caddr - offsetof(DBGKD_GET_VERSION64, KernBase);
+ if (kdd_read_virtual(s, s->cpuid, dbgkd_addr,
+ sizeof(DBGKD_GET_VERSION64), &dbgkd_get_version64) ==
+ sizeof(DBGKD_GET_VERSION64)) {
+ /* check if major version is 0xf */
+ if (dbgkd_get_version64.MajorVersion == NT_MAJOR_VERSION) {
+
+ /* read minor version, PsLoadedModuleList pointer and
+ * DebuggerDataList
+ */
+ modptr = dbgkd_get_version64.PsLoadedModuleList;
+ kddl = dbgkd_get_version64.DebuggerDataList;
+ minor = dbgkd_get_version64.MinorVersion;
+
+ /* 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))
+ break;
+ }
+ }
+
+ }
+
+ caddr += SIZE_PTR64;
+ search_limit -= 1;
+ }
+
+ if (caddr < data_base + data_size) {
+ /* if found, set the field and return */
+
+ KDD_LOG(s, "base: %p\n", (void *)base);
+ KDD_LOG(s, "modules list: %p\n", (void *)modptr);
+ KDD_LOG(s, "kddl: %p\n", (void *)kddl);
+ KDD_LOG(s, "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:
+ s->os = unknown_os;
+}
+
/* Figure out what OS we're dealing with */
static void find_os(kdd_state *s)
{
int i;
- uint64_t limit;
+ uint64_t limit;
/* We may already have the right one */
if (check_os(s))
if (check_os(s))
return;
}
- s->os = unknown_os;
+
+ get_os_info_64(s);
}
{
/* Figure out what we're looking at */
find_os(s);
+
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 */
s->txp.cmd.shake.u1 = 0x01010101;
s->txp.cmd.shake.status = KDD_STATUS_SUCCESS;
s->txp.cmd.shake.u2 = 0x02020202;
- s->txp.cmd.shake.v_major = 0xf;
+ s->txp.cmd.shake.v_major = NT_MAJOR_VERSION;
s->txp.cmd.shake.v_minor = s->os.build;
s->txp.cmd.shake.proto = 6;
s->txp.cmd.shake.flags = (0x02 /* ??? */
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.kddl ? s->os.base + s->os.kddl : 0;
KDD_LOG(s, "Client initial handshake: %s\n", s->os.name);
kdd_send_cmd(s, KDD_CMD_SHAKE, 0);