From a268416760763cc4d7a431df5866ff4fd9d4b0e5 Mon Sep 17 00:00:00 2001 From: masthoon Date: Sun, 19 Jul 2020 14:21:52 -0700 Subject: [PATCH] Python3 bindings and get virtual page API --- CMakeLists.txt | 5 +- builder.py | 1 + src/lib/kdmp-parser-structs.h | 76 ++++++++- src/lib/kdmp-parser.cc | 133 +++++++++++++++ src/lib/kdmp-parser.h | 34 ++++ src/python/CMakeLists.txt | 27 +++ src/python/python-kdmp.cc | 309 ++++++++++++++++++++++++++++++++++ src/python/python-kdmp.h | 139 +++++++++++++++ src/tests/CMakeLists.txt | 4 +- src/tests/tests.py | 24 +++ src/tests/tests_bindings.py | 28 +++ 11 files changed, 774 insertions(+), 6 deletions(-) create mode 100644 src/python/CMakeLists.txt create mode 100644 src/python/python-kdmp.cc create mode 100644 src/python/python-kdmp.h create mode 100644 src/tests/tests_bindings.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 7775e81..7424a26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required (VERSION 3.8) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_POSITION_INDEPENDENT_CODE True) OPTION(BUILD_TESTS "Build and run tests" OFF) @@ -12,6 +13,8 @@ project(kdmp-parser) add_subdirectory(src/lib) add_subdirectory(src/parser) add_subdirectory(src/testapp) +add_subdirectory(src/python) if(BUILD_TESTS) add_subdirectory(src/tests) -endif() \ No newline at end of file +endif() + diff --git a/builder.py b/builder.py index 7892d2e..38e622c 100644 --- a/builder.py +++ b/builder.py @@ -60,6 +60,7 @@ def build(arch, configuration, tests_on): cmake_config = ( 'cmake', f'-DCMAKE_RUNTIME_OUTPUT_DIRECTORY={output_dir}', + f'-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={output_dir}', f'-DCMAKE_BUILD_TYPE={configuration}', f'-DBUILD_TESTS={tests_on}' ) diff --git a/src/lib/kdmp-parser-structs.h b/src/lib/kdmp-parser-structs.h index 8d4a363..51da3af 100644 --- a/src/lib/kdmp-parser-structs.h +++ b/src/lib/kdmp-parser-structs.h @@ -194,8 +194,9 @@ static_assert(sizeof(KDMP_PARSER_PHYSMEM_DESC) == 0x20, "PHYSICAL_MEMORY_DESCRIPTOR's size looks wrong."); struct KDMP_PARSER_BMP_HEADER64 : public DisplayUtils { - static const uint32_t ExpectedSignature = 0x504D4453; // 'PMDS' - static const uint32_t ExpectedValidDump = 0x504D5544; // 'PMUD' + static const uint32_t ExpectedSignature = 0x504D4453; // 'PMDS' + static const uint32_t ExpectedSignature2 = 0x504D4446; // 'PMDF' + static const uint32_t ExpectedValidDump = 0x504D5544; // 'PMUD' // // Should be FDMP. @@ -248,7 +249,7 @@ struct KDMP_PARSER_BMP_HEADER64 : public DisplayUtils { // Integrity check the headers. // - if (Signature != ExpectedSignature) { + if (Signature != ExpectedSignature && Signature != ExpectedSignature2) { printf("KDMP_PARSER_BMP_HEADER64::Signature looks wrong.\n"); return false; } @@ -779,3 +780,72 @@ static_assert(offsetof(KDMP_PARSER_HEADER64, Comment) == 0xfb0, static_assert(offsetof(KDMP_PARSER_HEADER64, BmpHeader) == 0x2000, "The offset of BmpHeaders looks wrong."); + + +struct Page { + + // + // Page size. + // + + static const uint64_t Size = 0x1000; + + // + // Page align an address. + // + + static uint64_t Align(const uint64_t Address) { return Address & ~0xfff; } + + // + // Extract the page offset off an address. + // + + static uint64_t Offset(const uint64_t Address) { return Address & 0xfff; } +}; + + +// +// Structure for parsing a PTE. +// + +union MMPTE_HARDWARE { + struct { + uint64_t Present : 1; + uint64_t Write : 1; + uint64_t UserAccessible : 1; + uint64_t WriteThrough : 1; + uint64_t CacheDisable : 1; + uint64_t Accessed : 1; + uint64_t Dirty : 1; + uint64_t LargePage : 1; + uint64_t Available : 4; + uint64_t PageFrameNumber : 36; + uint64_t ReservedForHardware : 4; + uint64_t ReservedForSoftware : 11; + uint64_t NoExecute : 1; + }; + + uint64_t AsUINT64; + + MMPTE_HARDWARE(const uint64_t Value) : AsUINT64(Value) {} +}; + +// +// Structure to parse a virtual address. +// + +union VIRTUAL_ADDRESS { + struct { + uint64_t Offset : 12; + uint64_t PtIndex : 9; + uint64_t PdIndex : 9; + uint64_t PdPtIndex : 9; + uint64_t Pml4Index : 9; + uint64_t Reserved : 16; + }; + uint64_t AsUINT64; + VIRTUAL_ADDRESS(const uint64_t Value) : AsUINT64(Value) {} +}; + +static_assert(sizeof(MMPTE_HARDWARE) == 8); +static_assert(sizeof(VIRTUAL_ADDRESS) == 8); \ No newline at end of file diff --git a/src/lib/kdmp-parser.cc b/src/lib/kdmp-parser.cc index 72e4b41..63ace6e 100644 --- a/src/lib/kdmp-parser.cc +++ b/src/lib/kdmp-parser.cc @@ -86,6 +86,22 @@ const KDMP_PARSER_CONTEXT *KernelDumpParser::GetContext() { return &DmpHdr_->ContextRecord; } +const BugCheckParameters_t KernelDumpParser::GetBugCheckParameters() { + + // + // Give the user a view of the bugcheck parameters. + // + BugCheckParameters_t Parameters = { + DmpHdr_->BugCheckCode, + DmpHdr_->BugCheckCodeParameter[0], + DmpHdr_->BugCheckCodeParameter[1], + DmpHdr_->BugCheckCodeParameter[2], + DmpHdr_->BugCheckCodeParameter[3], + }; + + return Parameters; +} + DumpType_t KernelDumpParser::GetDumpType() { return DmpHdr_->DumpType; } bool KernelDumpParser::MapFile() { return FileMap_.MapFile(PathFile_); } @@ -217,6 +233,23 @@ bool KernelDumpParser::BuildPhysmemFullDump() { return true; } +const uint64_t KernelDumpParser::PhyRead8(const uint64_t PhysicalAddress) const { + + // + // Get the physical page and read from the offset. + // + + const uint8_t * PhysicalPage = GetPhysicalPage(Page::Align(PhysicalAddress)); + + if (!PhysicalPage) { + printf("Internal page table parsing failed!\n"); + return 0; + } + + return *reinterpret_cast(PhysicalPage + Page::Offset(PhysicalAddress)); + +} + const Physmem_t &KernelDumpParser::GetPhysmem() { return Physmem_; } void KernelDumpParser::ShowContextRecord(const uint32_t Prefix = 0) const { @@ -301,6 +334,10 @@ void KernelDumpParser::ShowAllStructures(const uint32_t Prefix = 0) const { DmpHdr_->Show(Prefix); } +const uint64_t KernelDumpParser::GetDirectoryTableBase() const { + return DmpHdr_->DirectoryTableBase; +} + const uint8_t * KernelDumpParser::GetPhysicalPage(const uint64_t PhysicalAddress) const { @@ -324,3 +361,99 @@ KernelDumpParser::GetPhysicalPage(const uint64_t PhysicalAddress) const { return Pair->second; } + +const uint64_t +KernelDumpParser::VirtTranslate(const uint64_t VirtualAddress, const uint64_t DirectoryTableBase) const { + + // + // If DirectoryTableBase is null ; use the one from the dump header and clear PCID bits (bits 11:0). + // + + uint64_t LocalDTB = Page::Align(GetDirectoryTableBase()); + + if (DirectoryTableBase) { + LocalDTB = Page::Align(DirectoryTableBase); + } + + // + // Stole code from @yrp604 and @0vercl0k. + // + + const VIRTUAL_ADDRESS GuestAddress(VirtualAddress); + const MMPTE_HARDWARE Pml4(LocalDTB); + const uint64_t Pml4Base = Pml4.PageFrameNumber * Page::Size; + const uint64_t Pml4eGpa = Pml4Base + GuestAddress.Pml4Index * 8; + const MMPTE_HARDWARE Pml4e(PhyRead8(Pml4eGpa)); + if (!Pml4e.Present) { + printf("Invalid page map level 4, address translation failed!\n"); + return 0; + } + + const uint64_t PdptBase = Pml4e.PageFrameNumber * Page::Size; + const uint64_t PdpteGpa = PdptBase + GuestAddress.PdPtIndex * 8; + const MMPTE_HARDWARE Pdpte(PhyRead8(PdpteGpa)); + if (!Pdpte.Present) { + printf("Invalid page directory pointer table, address translation failed!\n"); + return 0; + } + + // + // huge pages: + // 7 (PS) - Page size; must be 1 (otherwise, this entry references a page + // directory; see Table 4-1 + // + + const uint64_t PdBase = Pdpte.PageFrameNumber * Page::Size; + if (Pdpte.LargePage) { + return (PdBase & 0xffff'ffff'c000'0000) + (VirtualAddress & 0x3fff'ffff); + } + + const uint64_t PdeGpa = PdBase + GuestAddress.PdIndex * 8; + const MMPTE_HARDWARE Pde(PhyRead8(PdeGpa)); + if (!Pde.Present) { + printf("Invalid page directory entry, address translation failed!\n"); + return 0; + } + + // + // large pages: + // 7 (PS) - Page size; must be 1 (otherwise, this entry references a page + // table; see Table 4-18 + // + + const uint64_t PtBase = Pde.PageFrameNumber * Page::Size; + if (Pde.LargePage) { + return (PtBase & 0xffff'ffff'ffe0'0000) + (VirtualAddress & 0x1f'ffff); + } + + const uint64_t PteGpa = PtBase + GuestAddress.PtIndex * 8; + const MMPTE_HARDWARE Pte(PhyRead8(PteGpa)); + if (!Pte.Present) { + printf("Invalid page table entry, address translation failed!\n"); + return 0; + } + + const uint64_t PageBase = Pte.PageFrameNumber * 0x1000; + return PageBase + GuestAddress.Offset; +} + + +const uint8_t * +KernelDumpParser::GetVirtualPage(const uint64_t VirtualAddress, const uint64_t DirectoryTableBase) const { + + // + // First remove offset and translate the virtual address. + // + + const uint64_t PhysicalAddress = VirtTranslate(Page::Align(VirtualAddress), DirectoryTableBase); + + if (!PhysicalAddress) { + return nullptr; + } + + // + // Then get the physical page. + // + + return GetPhysicalPage(PhysicalAddress); +} \ No newline at end of file diff --git a/src/lib/kdmp-parser.h b/src/lib/kdmp-parser.h index 9714909..b3d8610 100644 --- a/src/lib/kdmp-parser.h +++ b/src/lib/kdmp-parser.h @@ -8,6 +8,11 @@ using Physmem_t = std::unordered_map; +struct BugCheckParameters_t { + uint32_t BugCheckCode; + uint64_t BugCheckCodeParameter[4]; +}; + class KernelDumpParser { public: KernelDumpParser(); @@ -25,6 +30,12 @@ class KernelDumpParser { const KDMP_PARSER_CONTEXT *GetContext(); + // + // Give the bugcheck parameters to the user. + // + + const BugCheckParameters_t GetBugCheckParameters(); + // // Get the type of dump. // @@ -61,7 +72,30 @@ class KernelDumpParser { const uint8_t *GetPhysicalPage(const uint64_t PhysicalAddress) const; + // + // Get the directory table base. + // + + const uint64_t GetDirectoryTableBase() const; + + // + // Translate a virtual address to physical address using a directory table base. + // + + const uint64_t VirtTranslate(const uint64_t VirtualAddress, const uint64_t DirectoryTableBase = 0) const; + + // + // Get the content of a virtual address. + // + + const uint8_t *GetVirtualPage(const uint64_t VirtualAddress, const uint64_t DirectoryTableBase = 0) const; + private: + // + // Utility function to read an uint64_t from a physical address. + // + + const uint64_t PhyRead8(const uint64_t PhysicalAddress) const; // // Build a map of physical addresses / page data pointers for full dump. // diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt new file mode 100644 index 0000000..3786019 --- /dev/null +++ b/src/python/CMakeLists.txt @@ -0,0 +1,27 @@ +# Mastho - 2020 +link_libraries(kdmp-parser) +find_package (Python3 COMPONENTS Interpreter Development) + +# Python on Windows Debug build only load $_d.pyd and require python_d.exe +if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32) + set(PYMODULE kdmp_d CACHE INTERNAL "") +else() + set(PYMODULE kdmp CACHE INTERNAL "") +endif() + +# Require Python3 interpreter and development libs +if(NOT Python3_Interpreter_FOUND OR (NOT Python3_Development_FOUND) OR (CMAKE_BUILD_TYPE STREQUAL "Debug" AND WIN32 AND NOT Python3_LIBRARY_DEBUG)) + message(WARNING "Python3 development library not found") + set(PYMODULE "" CACHE INTERNAL "") + return() +endif() + +Python3_add_library(${PYMODULE} SHARED python-kdmp.cc) + +# On Windows Python load $.pyd :: On Linux remove lib$ prefix +if(WIN32) + set_target_properties(${PYMODULE} PROPERTIES SUFFIX ".pyd") +else() + set_target_properties(${PYMODULE} PROPERTIES PREFIX "") +endif() + diff --git a/src/python/python-kdmp.cc b/src/python/python-kdmp.cc new file mode 100644 index 0000000..b7e4130 --- /dev/null +++ b/src/python/python-kdmp.cc @@ -0,0 +1,309 @@ +#include "python-kdmp.h" + +// +// Python Dump instance creation (allocate and initialize kernel dump object with .dmp path). +// >>> Dump(filepath) +// + +PyObject* NewDumpParser(PyTypeObject *Type, PyObject *Args, PyObject *Kwds) { + + // + // Allocate and zero PythonDumpParser. + // + PythonDumpParser *Self = reinterpret_cast(Type->tp_alloc(Type, 0)); + Self->DumpParser = nullptr; + + // + // Parse Python argument (expect a string i.e. the file path of the dump). + // *PyErr_Format returns nullptr to raise the exception* + // + + char *DumpPath = nullptr; + if (!PyArg_ParseTuple(Args,"s", &DumpPath)) { + DeleteDumpParser(reinterpret_cast(Self)); + return PyErr_Format(PyExc_TypeError, "dump() expected a string"); + } + + // + // Initialize the internal KernelDumpParser and validate the dump file. + // + + Self->DumpParser = new KernelDumpParser(); + if (!Self->DumpParser->Parse(DumpPath)) { + DeleteDumpParser(reinterpret_cast(Self)); + return PyErr_Format(PyExc_ValueError, "dump() invalid path"); + } + + // + // Return the new instance of PythonDumpParser to Python. + // + + return reinterpret_cast(Self); +} + +// +// Python Dump instance destruction. +// >>> del dump_instance +// + +void DeleteDumpParser(PyObject *Object) { + + // + // Release internal KernelDumpParser object . + // + PythonDumpParser *Self = reinterpret_cast(Object); + + if (Self->DumpParser) + { + delete Self->DumpParser; + Self->DumpParser = NULL; + } + + // + // Free type reference and self. + // + + PyTypeObject *Type = Py_TYPE(Self); + Type->tp_free(Self); +} + + + +// +// Python Dump instance method to retrieve the DumpType. +// >>> dump_instance.type() # return int +// + +PyObject* DumpParserGetType(PyObject *Object, PyObject *NotUsed) { + + // + // Get the dump type (FullDump, KernelDump or BMPDump). + // + + PythonDumpParser *Self = reinterpret_cast(Object); + return PyLong_FromUnsignedLong(Self->DumpParser->GetDumpType()); +} + +// +// Python Dump instance method to retrieve the register context. +// >>> dump_instance.context() # return dict(str -> int) +// + +PyObject* DumpParserGetContext(PyObject *Object, PyObject *NotUsed) { + + // + // Get the dump context (commons registers). + // + + PythonDumpParser *Self = reinterpret_cast(Object); + + const KDMP_PARSER_CONTEXT *C = Self->DumpParser->GetContext(); + + // + // Create a Python dict object with lowercase register name and value. + // + + PyObject *Context = PyDict_New(); + + PyDict_SetItemString(Context, "rax", PyLong_FromUnsignedLongLong(C->Rax)); + PyDict_SetItemString(Context, "rbx", PyLong_FromUnsignedLongLong(C->Rbx)); + PyDict_SetItemString(Context, "rcx", PyLong_FromUnsignedLongLong(C->Rcx)); + PyDict_SetItemString(Context, "rdx", PyLong_FromUnsignedLongLong(C->Rdx)); + PyDict_SetItemString(Context, "rsi", PyLong_FromUnsignedLongLong(C->Rsi)); + PyDict_SetItemString(Context, "rdi", PyLong_FromUnsignedLongLong(C->Rdi)); + PyDict_SetItemString(Context, "rip", PyLong_FromUnsignedLongLong(C->Rip)); + PyDict_SetItemString(Context, "rsp", PyLong_FromUnsignedLongLong(C->Rsp)); + PyDict_SetItemString(Context, "rbp", PyLong_FromUnsignedLongLong(C->Rbp)); + PyDict_SetItemString(Context, "r8", PyLong_FromUnsignedLongLong(C->R8)); + PyDict_SetItemString(Context, "r9", PyLong_FromUnsignedLongLong(C->R9)); + PyDict_SetItemString(Context, "r10", PyLong_FromUnsignedLongLong(C->R10)); + PyDict_SetItemString(Context, "r11", PyLong_FromUnsignedLongLong(C->R11)); + PyDict_SetItemString(Context, "r12", PyLong_FromUnsignedLongLong(C->R12)); + PyDict_SetItemString(Context, "r13", PyLong_FromUnsignedLongLong(C->R13)); + PyDict_SetItemString(Context, "r14", PyLong_FromUnsignedLongLong(C->R14)); + PyDict_SetItemString(Context, "r15", PyLong_FromUnsignedLongLong(C->R15)); + + // + // Get the DirectoryTableBase from the dump and return the created dict to Python. + // + PyDict_SetItemString(Context, "dtb", PyLong_FromUnsignedLongLong(Self->DumpParser->GetDirectoryTableBase())); + + return Context; +} + +// +// Python Dump instance method to retrieve the bugcheck parameters. +// >>> dump_instance.bugcheck() # return dict +// + +PyObject* DumpParserGetBugCheckParameters(PyObject *Object, PyObject *NotUsed) { + + // + // Retrieve the bugcheck parameters. + // + + PythonDumpParser *Self = reinterpret_cast(Object); + + const BugCheckParameters_t Parameters = Self->DumpParser->GetBugCheckParameters(); + + PyObject *PythonParamsList = PyList_New(4); + + for (uint64_t idx = 0; idx < 4; idx++) + PyList_SetItem(PythonParamsList, idx, PyLong_FromUnsignedLongLong(Parameters.BugCheckCodeParameter[idx])); + + // + // Create a Python dict object with code and parameters. + // + + PyObject *PythonParams = PyDict_New(); + + PyDict_SetItemString(PythonParams, "code", PyLong_FromUnsignedLong(Parameters.BugCheckCode)); + PyDict_SetItemString(PythonParams, "parameters", PythonParamsList); + + return PythonParams; +} + +// +// Python Dump instance method to get a physical page from a physical address. +// >>> dump_instance.get_physical_page(addr) # return bytes +// + +PyObject* DumpParserGetPhysicalPage(PyObject *Object, PyObject *Args) { + + // + // Parse Python argument (expect one unsigned long long integer). + // + + PythonDumpParser *Self = reinterpret_cast(Object); + + uint64_t PhysicalAddress = 0; + if (!PyArg_ParseTuple(Args, "K", &PhysicalAddress)) { + return PyErr_Format(PyExc_TypeError, "get_physical_page() expected an integer"); + } + + // + // Get the physical page and return it as bytes. + // + + const uint8_t *Page = Self->DumpParser->GetPhysicalPage(PhysicalAddress); + + if (!Page) { + return PyErr_Format(PyExc_ValueError, "get_physical_page() invalid address"); + } + + return PyBytes_FromStringAndSize(reinterpret_cast(Page), Page::Size); +} + +// +// Python Dump instance method to perform address translation (physical to virtual). +// >>> dump_instance.virt_translate(addr, [dtb]) # return int +// + +PyObject* DumpParserVirtTranslate(PyObject *Object, PyObject *Args) { + + // + // Parse Python argument (expect one or two unsigned long long integer). + // + + PythonDumpParser *Self = reinterpret_cast(Object); + + uint64_t VirtualAddress = 0; + uint64_t DirectoryTableBase = 0; + if (!PyArg_ParseTuple(Args, "K|K", &VirtualAddress, &DirectoryTableBase)) { + return PyErr_Format(PyExc_TypeError, "translate_address() expected one or two integer"); + } + + // + // Retrieve the physical address (parse pages tables in the dump). + // + + const uint64_t PhysicalAddress = Self->DumpParser->VirtTranslate(VirtualAddress, DirectoryTableBase); + + if (!PhysicalAddress) { + return PyErr_Format(PyExc_ValueError, "translate_address() invalid address"); + } + + return PyLong_FromUnsignedLongLong(PhysicalAddress); +} + +// +// Python Dump instance method to get a page from a virtual address. +// >>> dump_instance.get_virtual_page(addr, [dtb]) # return bytes +// + +PyObject* DumpParserGetVirtualPage(PyObject *Object, PyObject *Args) { + + // + // Parse Python argument (expect one or two unsigned long long integer). + // + + PythonDumpParser *Self = reinterpret_cast(Object); + + uint64_t VirtualAddress = 0; + uint64_t DirectoryTableBase = 0; + if (!PyArg_ParseTuple(Args, "K|K", &VirtualAddress, &DirectoryTableBase)) { + return PyErr_Format(PyExc_TypeError, "get_virtual_page() expected one or two integer"); + } + + const uint8_t *Page = Self->DumpParser->GetVirtualPage(VirtualAddress, DirectoryTableBase); + + if (!Page) { + return PyErr_Format(PyExc_ValueError, "get_virtual_page() invalid address"); + } + + return PyBytes_FromStringAndSize(reinterpret_cast(Page), Page::Size); +} + +// +// KDMP Module initialization function. +// + +PyMODINIT_FUNC +PyInit_kdmp(void) +{ + + // + // Initialize python. + // + + Py_Initialize(); + + // + // Register PythonDumpParserType. + // + + if(PyType_Ready(&PythonDumpParserType) < 0) + return nullptr; + + // + // Expose the kdmp module. + // + + PyObject *Module = PyModule_Create(&KDMPModule); + if (!Module) { + return nullptr; + } + + // + // Expose the PythonDumpParserType to Python in kdmp module. + // >>> kdmp.Dump class + // + + Py_INCREF(&PythonDumpParserType); + if (PyModule_AddObject(Module, "Dump", (PyObject *)&PythonDumpParserType) < 0) { + Py_DECREF(&PythonDumpParserType); + Py_DECREF(Module); + return NULL; + } + + // + // Expose DumpType constants to Python. + // >>> kdmp.FullDump ... + // + + PyModule_AddIntConstant(Module, "FullDump", DumpType_t::FullDump); + PyModule_AddIntConstant(Module, "KernelDump", DumpType_t::KernelDump); + PyModule_AddIntConstant(Module, "BMPDump", DumpType_t::BMPDump); + + return Module; +} diff --git a/src/python/python-kdmp.h b/src/python/python-kdmp.h new file mode 100644 index 0000000..6152e38 --- /dev/null +++ b/src/python/python-kdmp.h @@ -0,0 +1,139 @@ +// Mastho - 2020 + +#define PY_SSIZE_T_CLEAN + +#include +#include "kdmp-parser.h" + +#if PY_MINOR_VERSION >= 8 +#define IS_PY3_8 1 +#else +#define IS_PY3_8 0 +#endif + +// +// Python object handling all interactions with the library. +// + +typedef struct { + PyObject_HEAD + KernelDumpParser* DumpParser; +} PythonDumpParser; + +// +// Python Dump type functions declarations (class instance creation and instance destruction). +// + +PyObject* NewDumpParser(PyTypeObject *Type, PyObject *Args, PyObject *Kwds); +void DeleteDumpParser(PyObject *Object); + +// +// Python Dump object methods functions declarations. +// + +PyObject* DumpParserGetType(PyObject *Object, PyObject *NotUsed); +PyObject* DumpParserGetContext(PyObject *Object, PyObject *NotUsed); +PyObject* DumpParserGetPhysicalPage(PyObject *Object, PyObject *Args); +PyObject* DumpParserVirtTranslate(PyObject *Object, PyObject *Args); +PyObject* DumpParserGetVirtualPage(PyObject *Object, PyObject *Args); +PyObject* DumpParserGetBugCheckParameters(PyObject *Object, PyObject *NotUsed); + +// +// Object methods of Python Dump type. +// + + +PyMethodDef DumpObjectMethod[] = { + {"type", DumpParserGetType, METH_NOARGS, "Show Dump Type (FullDump, KernelDump)"}, + {"context", DumpParserGetContext, METH_NOARGS, "Get Register Context"}, + {"get_physical_page", DumpParserGetPhysicalPage, METH_VARARGS, "Get Physical Page Content"}, + {"virt_translate", DumpParserVirtTranslate, METH_VARARGS, "Translate Virtual to Physical Address"}, + {"get_virtual_page", DumpParserGetVirtualPage, METH_VARARGS, "Get Virtual Page Content"}, + {"bugcheck", DumpParserGetBugCheckParameters, METH_NOARGS, "Get BugCheck Parameters"}, + {nullptr, nullptr, 0, nullptr} +}; + +// +// Define PythonDumpParserType (size, name, initialization & destruction functions and object methods). +// + +static PyTypeObject PythonDumpParserType = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "kdmp.Dump", /* tp_name */ + sizeof(PythonDumpParser), /* tp_basicsize */ + 0, /* tp_itemsize */ + DeleteDumpParser, /* tp_dealloc */ +#if IS_PY3_8 + 0, /* tp_vectorcall_offset */ +#else + nullptr, /* tp_print */ +#endif + nullptr, /* tp_getattr */ + nullptr, /* tp_setattr */ + nullptr, /* tp_compare */ + nullptr, /* tp_repr */ + nullptr, /* tp_as_number */ + nullptr, /* tp_as_sequence */ + nullptr, /* tp_as_mapping */ + nullptr, /* tp_hash */ + nullptr, /* tp_call */ + nullptr, /* tp_str */ + nullptr, /* tp_getattro */ + nullptr, /* tp_setattro */ + nullptr, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT, /* tp_flags */ + "Dump object", /* tp_doc */ + nullptr, /* tp_traverse */ + nullptr, /* tp_clear */ + nullptr, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + nullptr, /* tp_iter */ + nullptr, /* tp_iternext */ + DumpObjectMethod, /* tp_methods */ + nullptr, /* tp_members */ + nullptr, /* tp_getset */ + nullptr, /* tp_base */ + nullptr, /* tp_dict */ + nullptr, /* tp_descr_get */ + nullptr, /* tp_descr_set */ + 0, /* tp_dictoffset */ + nullptr, /* tp_init */ + nullptr, /* tp_alloc */ + NewDumpParser, /* tp_new */ + nullptr, /* tp_free */ + nullptr, /* tp_is_gc */ + nullptr, /* tp_bases */ + nullptr, /* tp_mro */ + nullptr, /* tp_cache */ + nullptr, /* tp_subclasses */ + nullptr, /* tp_weaklist */ + nullptr, /* tp_del */ + 0, /* tp_version_tag */ + nullptr, /* tp_finalize */ +#if IS_PY3_8 + nullptr, /* tp_vectorcall */ + 0, /* bpo-37250: kept for backwards compatibility in CPython 3.8 only */ +#endif +}; + +// +// KDMP Module definition. +// + +static struct PyModuleDef KDMPModule = { + PyModuleDef_HEAD_INIT, /* m_base */ + "kdmp", /* m_name */ + nullptr, /* m_doc */ + -1, /* m_size */ + nullptr, /* m_methods */ + nullptr, /* m_slots */ + nullptr, /* m_traverse */ + nullptr, /* m_clear */ + nullptr, /* m_free */ +}; + +// +// KDMP Module initialization function. +// + +PyMODINIT_FUNC PyInit_kdmp(void); \ No newline at end of file diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index d745430..8289ba9 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -6,6 +6,6 @@ endif() add_custom_target( NativeTests ALL - COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests.py --bindir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} - DEPENDS testapp + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tests.py --bindir ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} --pymodule ${PYMODULE} + DEPENDS testapp ${PYMODULE} ) \ No newline at end of file diff --git a/src/tests/tests.py b/src/tests/tests.py index d042c67..c1c7be1 100644 --- a/src/tests/tests.py +++ b/src/tests/tests.py @@ -19,9 +19,27 @@ def test(bin_dir, dmp_path): print('Launching "{0}"..'.format(' '.join(cmd))) return subprocess.call(cmd) +def test_python(script_dir, bin_dir, dmp_path, pymodule): + py_exe = sys.executable + if pymodule == 'kdmp_d': + if py_exe.endswith('3.exe'): + py_exe = py_exe.replace('3.exe', '_d.exe') + else: + py_exe = py_exe.replace('.exe', '_d.exe') + cmd = ( + py_exe, + os.path.join(script_dir, 'tests_bindings.py'), + os.path.abspath(bin_dir), + dmp_path + ) + + print('Launching "{0}"..'.format(' '.join(cmd))) + return subprocess.call(cmd) + def main(): parser = argparse.ArgumentParser('Run test') parser.add_argument('--bindir', required = True) + parser.add_argument('--pymodule', required = True, nargs = '?') args = parser.parse_args() script_dir = os.path.dirname(__file__) @@ -49,6 +67,12 @@ def main(): if test(args.bindir, dmp_path) != 0: print(f'{args.bindir}/{dmp_path} test failed, bailing.') return 1 + + # Run python bindings tests + if args.pymodule: + if test_python(script_dir, args.bindir, dmp_path, args.pymodule) != 0: + print(f'{args.bindir}/{dmp_path} python test failed, bailing.') + return 1 print('All good!') return 0 diff --git a/src/tests/tests_bindings.py b/src/tests/tests_bindings.py new file mode 100644 index 0000000..8d65443 --- /dev/null +++ b/src/tests/tests_bindings.py @@ -0,0 +1,28 @@ +import sys +sys.path.append(sys.argv[1]) + +from kdmp import Dump, FullDump, BMPDump + +dmp = Dump(sys.argv[2]) +assert(dmp.type() == FullDump or dmp.type() == BMPDump) + +ctx = dmp.context() +dtb = ctx['dtb'] & ~0xfff # remove PCID + +assert(ctx['rip'] == 0xfffff805108776a0) +assert(dtb == 0x6d4000) + +page = dmp.get_physical_page(0x5000) +assert(page[0x34:0x38] == b'MSFT') + +assert(dmp.virt_translate(0xfffff78000000000) == 0x0000000000c2f000) +assert(dmp.virt_translate(0xfffff80513370000) == 0x000000003d555000) + +assert(dmp.get_virtual_page(0xfffff78000000000) == dmp.get_physical_page(0x0000000000c2f000)) +assert(dmp.get_virtual_page(0xfffff80513370000) == dmp.get_physical_page(0x000000003d555000)) + +v = 0xfffff80513568000 +assert(dmp.get_virtual_page(v) == dmp.get_physical_page(dmp.virt_translate(v))) + +print("Python tests: All good!") +sys.exit(0) \ No newline at end of file