From 49ba556bb8575bc3962440fcaea8c285105d3688 Mon Sep 17 00:00:00 2001 From: ufrisk Date: Sun, 18 Nov 2018 19:43:23 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 5 + MemProcFS.sln | 109 + MemProcFS/MemProcFS.vcxproj | 95 + MemProcFS/MemProcFS.vcxproj.filters | 48 + MemProcFS/MemProcFS.vcxproj.user | 4 + MemProcFS/dokan.h | 865 ++++++++ MemProcFS/fileinfo.h | 1248 +++++++++++ MemProcFS/memprocfs.c | 88 + MemProcFS/public.h | 406 ++++ MemProcFS/vfs.c | 482 +++++ MemProcFS/vfs.h | 60 + MemProcFS/vmmdll.h | 566 +++++ README.md | 107 + files/MemProcFS.exe | Bin 0 -> 17920 bytes files/dissect/cstruct/__init__.py | 33 + files/dissect/cstruct/cstruct.py | 1918 +++++++++++++++++ files/plugins/m_vmemd.dll | Bin 0 -> 12800 bytes files/plugins/pym_procstruct/__init__.py | 9 + .../plugins/pym_procstruct/pym_procstruct.py | 202 ++ files/python36/information.txt | 4 + files/vmm.dll | Bin 0 -> 101376 bytes files/vmm.lib | Bin 0 -> 9702 bytes files/vmmdll.h | 566 +++++ files/vmmpy.py | 537 +++++ files/vmmpy_example.py | 428 ++++ files/vmmpyc.pyd | Bin 0 -> 31744 bytes files/vmmpycplugin.dll | Bin 0 -> 16384 bytes files/vmmpyplugin.py | 353 +++ m_vmemd/m_vmemd.c | 135 ++ m_vmemd/m_vmemd.vcxproj | 95 + m_vmemd/m_vmemd.vcxproj.filters | 30 + m_vmemd/m_vmemd.vcxproj.user | 4 + m_vmemd/vmmdll.h | 566 +++++ vmm/device.c | 164 ++ vmm/device.h | 72 + vmm/devicefile.c | 82 + vmm/devicefile.h | 16 + vmm/devicepcileechdll.c | 149 ++ vmm/devicepcileechdll.h | 16 + vmm/m_ldrmodules.c | 265 +++ vmm/m_ldrmodules.h | 17 + vmm/m_status.c | 274 +++ vmm/m_status.h | 17 + vmm/m_virt2phys.c | 215 ++ vmm/m_virt2phys.h | 17 + vmm/pcileech_dll.h | 398 ++++ vmm/pluginmanager.c | 422 ++++ vmm/pluginmanager.h | 76 + vmm/statistics.c | 308 +++ vmm/statistics.h | 100 + vmm/util.c | 199 ++ vmm/util.h | 74 + vmm/vmm.c | 1089 ++++++++++ vmm/vmm.h | 505 +++++ vmm/vmm.vcxproj | 133 ++ vmm/vmm.vcxproj.filters | 113 + vmm/vmm.vcxproj.user | 4 + vmm/vmmdll.c | 866 ++++++++ vmm/vmmdll.def | 45 + vmm/vmmdll.h | 566 +++++ vmm/vmmproc.c | 364 ++++ vmm/vmmproc.h | 33 + vmm/vmmproc_windows.c | 1009 +++++++++ vmm/vmmproc_windows.h | 116 + vmm/vmmvfs.c | 337 +++ vmm/vmmvfs.h | 40 + vmm_example/vmm_example.vcxproj | 140 ++ vmm_example/vmm_example.vcxproj.filters | 30 + vmm_example/vmm_example.vcxproj.user | 4 + vmm_example/vmmdll.h | 566 +++++ vmm_example/vmmdll_example.c | 581 +++++ vmmpyc/vmmdll.h | 566 +++++ vmmpyc/vmmpyc.c | 853 ++++++++ vmmpyc/vmmpyc.vcxproj | 100 + vmmpyc/vmmpyc.vcxproj.filters | 30 + vmmpyc/vmmpyc.vcxproj.user | 4 + vmmpycplugin/vmmdll.h | 566 +++++ vmmpycplugin/vmmpycplugin.c | 363 ++++ vmmpycplugin/vmmpycplugin.vcxproj | 101 + vmmpycplugin/vmmpycplugin.vcxproj.filters | 30 + vmmpycplugin/vmmpycplugin.vcxproj.user | 4 + 81 files changed, 21002 insertions(+) create mode 100644 .gitignore create mode 100644 MemProcFS.sln create mode 100644 MemProcFS/MemProcFS.vcxproj create mode 100644 MemProcFS/MemProcFS.vcxproj.filters create mode 100644 MemProcFS/MemProcFS.vcxproj.user create mode 100644 MemProcFS/dokan.h create mode 100644 MemProcFS/fileinfo.h create mode 100644 MemProcFS/memprocfs.c create mode 100644 MemProcFS/public.h create mode 100644 MemProcFS/vfs.c create mode 100644 MemProcFS/vfs.h create mode 100644 MemProcFS/vmmdll.h create mode 100644 README.md create mode 100644 files/MemProcFS.exe create mode 100644 files/dissect/cstruct/__init__.py create mode 100644 files/dissect/cstruct/cstruct.py create mode 100644 files/plugins/m_vmemd.dll create mode 100644 files/plugins/pym_procstruct/__init__.py create mode 100644 files/plugins/pym_procstruct/pym_procstruct.py create mode 100644 files/python36/information.txt create mode 100644 files/vmm.dll create mode 100644 files/vmm.lib create mode 100644 files/vmmdll.h create mode 100644 files/vmmpy.py create mode 100644 files/vmmpy_example.py create mode 100644 files/vmmpyc.pyd create mode 100644 files/vmmpycplugin.dll create mode 100644 files/vmmpyplugin.py create mode 100644 m_vmemd/m_vmemd.c create mode 100644 m_vmemd/m_vmemd.vcxproj create mode 100644 m_vmemd/m_vmemd.vcxproj.filters create mode 100644 m_vmemd/m_vmemd.vcxproj.user create mode 100644 m_vmemd/vmmdll.h create mode 100644 vmm/device.c create mode 100644 vmm/device.h create mode 100644 vmm/devicefile.c create mode 100644 vmm/devicefile.h create mode 100644 vmm/devicepcileechdll.c create mode 100644 vmm/devicepcileechdll.h create mode 100644 vmm/m_ldrmodules.c create mode 100644 vmm/m_ldrmodules.h create mode 100644 vmm/m_status.c create mode 100644 vmm/m_status.h create mode 100644 vmm/m_virt2phys.c create mode 100644 vmm/m_virt2phys.h create mode 100644 vmm/pcileech_dll.h create mode 100644 vmm/pluginmanager.c create mode 100644 vmm/pluginmanager.h create mode 100644 vmm/statistics.c create mode 100644 vmm/statistics.h create mode 100644 vmm/util.c create mode 100644 vmm/util.h create mode 100644 vmm/vmm.c create mode 100644 vmm/vmm.h create mode 100644 vmm/vmm.vcxproj create mode 100644 vmm/vmm.vcxproj.filters create mode 100644 vmm/vmm.vcxproj.user create mode 100644 vmm/vmmdll.c create mode 100644 vmm/vmmdll.def create mode 100644 vmm/vmmdll.h create mode 100644 vmm/vmmproc.c create mode 100644 vmm/vmmproc.h create mode 100644 vmm/vmmproc_windows.c create mode 100644 vmm/vmmproc_windows.h create mode 100644 vmm/vmmvfs.c create mode 100644 vmm/vmmvfs.h create mode 100644 vmm_example/vmm_example.vcxproj create mode 100644 vmm_example/vmm_example.vcxproj.filters create mode 100644 vmm_example/vmm_example.vcxproj.user create mode 100644 vmm_example/vmmdll.h create mode 100644 vmm_example/vmmdll_example.c create mode 100644 vmmpyc/vmmdll.h create mode 100644 vmmpyc/vmmpyc.c create mode 100644 vmmpyc/vmmpyc.vcxproj create mode 100644 vmmpyc/vmmpyc.vcxproj.filters create mode 100644 vmmpyc/vmmpyc.vcxproj.user create mode 100644 vmmpycplugin/vmmdll.h create mode 100644 vmmpycplugin/vmmpycplugin.c create mode 100644 vmmpycplugin/vmmpycplugin.vcxproj create mode 100644 vmmpycplugin/vmmpycplugin.vcxproj.filters create mode 100644 vmmpycplugin/vmmpycplugin.vcxproj.user diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9afaa1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.vs +*pycache* +/files/vmm_example.exe +/files/lib +/files/temp diff --git a/MemProcFS.sln b/MemProcFS.sln new file mode 100644 index 0000000..5cbc781 --- /dev/null +++ b/MemProcFS.sln @@ -0,0 +1,109 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2050 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MemProcFS", "MemProcFS\MemProcFS.vcxproj", "{0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5}" + ProjectSection(ProjectDependencies) = postProject + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} = {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vmm", "vmm\vmm.vcxproj", "{18BEBDF1-73F2-4ABB-BDBE-507ADAB32494}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m_vmemd", "m_vmemd\m_vmemd.vcxproj", "{41D93156-9DCB-4F19-BF02-993CEC361A81}" + ProjectSection(ProjectDependencies) = postProject + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} = {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vmmpyc", "vmmpyc\vmmpyc.vcxproj", "{8F227E6C-6156-4F7D-965F-BDB9B75FFB4E}" + ProjectSection(ProjectDependencies) = postProject + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} = {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vmmpycplugin", "vmmpycplugin\vmmpycplugin.vcxproj", "{D32F2480-E640-4239-96DA-E31CCA2F13BA}" + ProjectSection(ProjectDependencies) = postProject + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} = {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "python", "python", "{0E07FB2B-5955-4CAC-AD31-E8352D7912A3}" + ProjectSection(SolutionItems) = preProject + files\vmmpy.py = files\vmmpy.py + files\vmmpy_example.py = files\vmmpy_example.py + files\vmmpyplugin.py = files\vmmpyplugin.py + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vmm_example", "vmm_example\vmm_example.vcxproj", "{7B05E64E-569F-4EF5-8F37-FA5B39B64227}" + ProjectSection(ProjectDependencies) = postProject + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} = {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dissect.cstruct", "dissect.cstruct", "{28BE8236-5D12-4D09-B87D-5AB79A85B54A}" + ProjectSection(SolutionItems) = preProject + files\dissect\cstruct\__init__.py = files\dissect\cstruct\__init__.py + files\dissect\cstruct\cstruct.py = files\dissect\cstruct\cstruct.py + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins.pym_procstruct", "plugins.pym_procstruct", "{BF8E8135-E8FA-49CF-A3CB-DA35B0581A77}" + ProjectSection(SolutionItems) = preProject + files\plugins\pym_procstruct\__init__.py = files\plugins\pym_procstruct\__init__.py + files\plugins\pym_procstruct\pym_procstruct.py = files\plugins\pym_procstruct\pym_procstruct.py + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5}.Debug|x64.ActiveCfg = Debug|x64 + {0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5}.Debug|x64.Build.0 = Debug|x64 + {0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5}.Debug|x86.ActiveCfg = Debug|x64 + {0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5}.Release|x64.ActiveCfg = Release|x64 + {0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5}.Release|x64.Build.0 = Release|x64 + {0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5}.Release|x86.ActiveCfg = Release|x64 + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494}.Debug|x64.ActiveCfg = Debug|x64 + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494}.Debug|x64.Build.0 = Debug|x64 + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494}.Debug|x86.ActiveCfg = Debug|x64 + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494}.Release|x64.ActiveCfg = Release|x64 + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494}.Release|x64.Build.0 = Release|x64 + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494}.Release|x86.ActiveCfg = Release|x64 + {41D93156-9DCB-4F19-BF02-993CEC361A81}.Debug|x64.ActiveCfg = Debug|x64 + {41D93156-9DCB-4F19-BF02-993CEC361A81}.Debug|x64.Build.0 = Debug|x64 + {41D93156-9DCB-4F19-BF02-993CEC361A81}.Debug|x86.ActiveCfg = Debug|x64 + {41D93156-9DCB-4F19-BF02-993CEC361A81}.Release|x64.ActiveCfg = Release|x64 + {41D93156-9DCB-4F19-BF02-993CEC361A81}.Release|x64.Build.0 = Release|x64 + {41D93156-9DCB-4F19-BF02-993CEC361A81}.Release|x86.ActiveCfg = Release|x64 + {8F227E6C-6156-4F7D-965F-BDB9B75FFB4E}.Debug|x64.ActiveCfg = Debug|x64 + {8F227E6C-6156-4F7D-965F-BDB9B75FFB4E}.Debug|x64.Build.0 = Debug|x64 + {8F227E6C-6156-4F7D-965F-BDB9B75FFB4E}.Debug|x86.ActiveCfg = Debug|x64 + {8F227E6C-6156-4F7D-965F-BDB9B75FFB4E}.Release|x64.ActiveCfg = Release|x64 + {8F227E6C-6156-4F7D-965F-BDB9B75FFB4E}.Release|x64.Build.0 = Release|x64 + {8F227E6C-6156-4F7D-965F-BDB9B75FFB4E}.Release|x86.ActiveCfg = Release|x64 + {D32F2480-E640-4239-96DA-E31CCA2F13BA}.Debug|x64.ActiveCfg = Debug|x64 + {D32F2480-E640-4239-96DA-E31CCA2F13BA}.Debug|x64.Build.0 = Debug|x64 + {D32F2480-E640-4239-96DA-E31CCA2F13BA}.Debug|x86.ActiveCfg = Debug|x64 + {D32F2480-E640-4239-96DA-E31CCA2F13BA}.Release|x64.ActiveCfg = Release|x64 + {D32F2480-E640-4239-96DA-E31CCA2F13BA}.Release|x64.Build.0 = Release|x64 + {D32F2480-E640-4239-96DA-E31CCA2F13BA}.Release|x86.ActiveCfg = Release|x64 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Debug|x64.ActiveCfg = Debug|x64 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Debug|x64.Build.0 = Debug|x64 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Debug|x86.ActiveCfg = Debug|Win32 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Debug|x86.Build.0 = Debug|Win32 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Release|x64.ActiveCfg = Release|x64 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Release|x64.Build.0 = Release|x64 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Release|x86.ActiveCfg = Release|Win32 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {28BE8236-5D12-4D09-B87D-5AB79A85B54A} = {0E07FB2B-5955-4CAC-AD31-E8352D7912A3} + {BF8E8135-E8FA-49CF-A3CB-DA35B0581A77} = {0E07FB2B-5955-4CAC-AD31-E8352D7912A3} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {25E47F6A-275C-46EB-9121-6EECCE1B8075} + EndGlobalSection +EndGlobal diff --git a/MemProcFS/MemProcFS.vcxproj b/MemProcFS/MemProcFS.vcxproj new file mode 100644 index 0000000..44c6613 --- /dev/null +++ b/MemProcFS/MemProcFS.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {0EEC1AAA-D3BD-4DC1-8EAD-7629617A12A5} + MemProcFS + 8.1 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + + + + Level3 + Disabled + true + true + + + $(OutDir)\lib\$(TargetName).pdb + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + UseLinkTimeCodeGeneration + $(OutDir)\lib\$(TargetName).pdb + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MemProcFS/MemProcFS.vcxproj.filters b/MemProcFS/MemProcFS.vcxproj.filters new file mode 100644 index 0000000..37da230 --- /dev/null +++ b/MemProcFS/MemProcFS.vcxproj.filters @@ -0,0 +1,48 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {48be56a3-760b-4123-bfa4-2df9018c3445} + + + {bb78aad0-c3f0-457d-beb5-0a916ec07fd7} + + + + + Header Files\dokan + + + Header Files\dokan + + + Header Files\dokan + + + Header Files\vmm + + + Header Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/MemProcFS/MemProcFS.vcxproj.user b/MemProcFS/MemProcFS.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/MemProcFS/MemProcFS.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MemProcFS/dokan.h b/MemProcFS/dokan.h new file mode 100644 index 0000000..f99f82f --- /dev/null +++ b/MemProcFS/dokan.h @@ -0,0 +1,865 @@ +/* + Dokan : user-mode file system library for Windows + + Copyright (C) 2015 - 2018 Adrien J. and Maxime C. + Copyright (C) 2007 - 2011 Hiroki Asakawa + + http://dokan-dev.github.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see . +*/ + +#ifndef DOKAN_H_ +#define DOKAN_H_ + +/** Do not include NTSTATUS. Fix duplicate preprocessor definitions */ +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include + +#include "fileinfo.h" +#include "public.h" + +#ifdef _EXPORTING +/** Export dokan API see also dokan.def for export */ +#define DOKANAPI __stdcall +#else +/** Import dokan API */ +#define DOKANAPI __declspec(dllimport) __stdcall +#endif + +/** Change calling convention to standard call */ +#define DOKAN_CALLBACK __stdcall + +#ifdef __cplusplus +extern "C" { +#endif + +/** @file */ + +/** + * \defgroup Dokan Dokan + * \brief Dokan Library const and methods + */ +/** @{ */ + +/** The current Dokan version (ver 1.2.0). \ref DOKAN_OPTIONS.Version */ +#define DOKAN_VERSION 120 +/** Minimum Dokan version (ver 1.1.0) accepted. */ +#define DOKAN_MINIMUM_COMPATIBLE_VERSION 110 +/** Maximum number of dokan instances.*/ +#define DOKAN_MAX_INSTANCES 32 +/** Driver file name including the DOKAN_MAJOR_API_VERSION */ +#define DOKAN_DRIVER_NAME L"dokan" DOKAN_MAJOR_API_VERSION L".sys" +/** Network provider name including the DOKAN_MAJOR_API_VERSION */ +#define DOKAN_NP_NAME L"Dokan" DOKAN_MAJOR_API_VERSION + +/** @} */ + +/** + * \defgroup DOKAN_OPTION DOKAN_OPTION + * \brief All DOKAN_OPTION flags used in DOKAN_OPTIONS.Options + * \see DOKAN_FILE_INFO + */ +/** @{ */ + +/** Enable ouput debug message */ +#define DOKAN_OPTION_DEBUG 1 +/** Enable ouput debug message to stderr */ +#define DOKAN_OPTION_STDERR 2 +/** Use alternate stream */ +#define DOKAN_OPTION_ALT_STREAM 4 +/** Enable mount drive as write-protected */ +#define DOKAN_OPTION_WRITE_PROTECT 8 +/** Use network drive - Dokan network provider needs to be installed */ +#define DOKAN_OPTION_NETWORK 16 +/** Use removable drive */ +#define DOKAN_OPTION_REMOVABLE 32 +/** Use mount manager */ +#define DOKAN_OPTION_MOUNT_MANAGER 64 +/** Mount the drive on current session only */ +#define DOKAN_OPTION_CURRENT_SESSION 128 +/** Enable Lockfile/Unlockfile operations. Otherwise Dokan will take care of it */ +#define DOKAN_OPTION_FILELOCK_USER_MODE 256 + +/** @} */ + +/** + * \struct DOKAN_OPTIONS + * \brief Dokan mount options used to describe Dokan device behavior. + * \see DokanMain + */ +typedef struct _DOKAN_OPTIONS { + /** Version of the Dokan features requested (version "123" is equal to Dokan version 1.2.3). */ + USHORT Version; + /** Number of threads to be used internally by Dokan library. More threads will handle more events at the same time. */ + USHORT ThreadCount; + /** Features enabled for the mount. See \ref DOKAN_OPTION. */ + ULONG Options; + /** FileSystem can store anything here. */ + ULONG64 GlobalContext; + /** Mount point. Can be "M:\" (drive letter) or "C:\mount\dokan" (path in NTFS). */ + LPCWSTR MountPoint; + /** + * UNC Name for the Network Redirector + * \see Support for UNC Naming + */ + LPCWSTR UNCName; + /** Max timeout in milliseconds of each request before Dokan gives up. */ + ULONG Timeout; + /** Allocation Unit Size of the volume. This will affect the file size. */ + ULONG AllocationUnitSize; + /** Sector Size of the volume. This will affect the file size. */ + ULONG SectorSize; +} DOKAN_OPTIONS, *PDOKAN_OPTIONS; + +/** + * \struct DOKAN_FILE_INFO + * \brief Dokan file information on the current operation. + */ +typedef struct _DOKAN_FILE_INFO { + /** + * Context that can be used to carry information between operations. + * The context can carry whatever type like \c HANDLE, struct, int, + * internal reference that will help the implementation understand the request context of the event. + */ + ULONG64 Context; + /** Reserved. Used internally by Dokan library. Never modify. */ + ULONG64 DokanContext; + /** A pointer to DOKAN_OPTIONS which was passed to DokanMain. */ + PDOKAN_OPTIONS DokanOptions; + /** + * Process ID for the thread that originally requested a given I/O operation. + */ + ULONG ProcessId; + /** + * Requesting a directory file. + * Must be set in \ref DOKAN_OPERATIONS.ZwCreateFile if the file appears to be a folder. + */ + UCHAR IsDirectory; + /** Flag if the file has to be deleted during DOKAN_OPERATIONS. Cleanup event. */ + UCHAR DeleteOnClose; + /** Read or write is paging IO. */ + UCHAR PagingIo; + /** Read or write is synchronous IO. */ + UCHAR SynchronousIo; + /** Read or write directly from data source without cache */ + UCHAR Nocache; + /** If \c TRUE, write to the current end of file instead of using the Offset parameter. */ + UCHAR WriteToEndOfFile; +} DOKAN_FILE_INFO, *PDOKAN_FILE_INFO; + +/** + * \brief FillFindData Used to add an entry in FindFiles operation + * \return 1 if buffer is full, otherwise 0 (currently it never returns 1) + */ +typedef int(WINAPI *PFillFindData)(PWIN32_FIND_DATAW, PDOKAN_FILE_INFO); + +/** + * \brief FillFindStreamData Used to add an entry in FindStreams + * \return 1 if buffer is full, otherwise 0 (currently it never returns 1) + */ +typedef int(WINAPI *PFillFindStreamData)(PWIN32_FIND_STREAM_DATA, + PDOKAN_FILE_INFO); + +// clang-format off + +/** + * \struct DOKAN_OPERATIONS + * \brief Dokan API callbacks interface + * + * DOKAN_OPERATIONS is a struct of callbacks that describe all Dokan API operations + * that will be called when Windows access to the filesystem. + * + * If an error occurs, return NTSTATUS (https://support.microsoft.com/en-us/kb/113996). + * Win32 Error can be converted to \c NTSTATUS with \ref DokanNtStatusFromWin32 + * + * All callbacks can be set to \c NULL or return \c STATUS_NOT_IMPLEMENTED + * if supporting one of them is not desired. Be aware that returning such values to important callbacks + * such as DOKAN_OPERATIONS.ZwCreateFile / DOKAN_OPERATIONS.ReadFile / ... would make the filesystem not work or become unstable. + */ +typedef struct _DOKAN_OPERATIONS { + /** + * \brief CreateFile Dokan API callback + * + * CreateFile is called each time a request is made on a file system object. + * + * In case \c OPEN_ALWAYS & \c CREATE_ALWAYS are successfully opening an + * existing file, \c STATUS_OBJECT_NAME_COLLISION should be returned instead of \c STATUS_SUCCESS . + * This will inform Dokan that the file has been opened and not created during the request. + * + * If the file is a directory, CreateFile is also called. + * In this case, CreateFile should return \c STATUS_SUCCESS when that directory + * can be opened and DOKAN_FILE_INFO.IsDirectory has to be set to \c TRUE. + * On the other hand, if DOKAN_FILE_INFO.IsDirectory is set to \c TRUE + * but the path targets a file, \c STATUS_NOT_A_DIRECTORY must be returned. + * + * DOKAN_FILE_INFO.Context can be used to store Data (like \c HANDLE) + * that can be retrieved in all other requests related to the Context. + * To avoid memory leak, Context needs to be released in DOKAN_OPERATIONS.Cleanup. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param SecurityContext SecurityContext, see https://msdn.microsoft.com/en-us/library/windows/hardware/ff550613(v=vs.85).aspx + * \param DesiredAccess Specifies an ACCESS_MASK value that determines the requested access to the object. + * \param FileAttributes Specifies one or more FILE_ATTRIBUTE_XXX flags, which represent the file attributes to set if a file is created or overwritten. + * \param ShareAccess Type of share access, which is specified as zero or any combination of FILE_SHARE_* flags. + * \param CreateDisposition Specifies the action to perform if the file does or does not exist. + * \param CreateOptions Specifies the options to apply when the driver creates or opens the file. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see See ZwCreateFile for more information about the parameters of this callback (MSDN). + * \see DokanMapKernelToUserCreateFileFlags + */ + NTSTATUS(DOKAN_CALLBACK *ZwCreateFile)(LPCWSTR FileName, + PDOKAN_IO_SECURITY_CONTEXT SecurityContext, + ACCESS_MASK DesiredAccess, + ULONG FileAttributes, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief Cleanup Dokan API callback + * + * Cleanup request before \ref CloseFile is called. + * + * When DOKAN_FILE_INFO.DeleteOnClose is \c TRUE, the file in Cleanup must be deleted. + * See DeleteFile documentation for explanation. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param DokanFileInfo Information about the file or directory. + * \see DeleteFile + * \see DeleteDirectory + */ + void(DOKAN_CALLBACK *Cleanup)(LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief CloseFile Dokan API callback + * + * Clean remaining Context + * + * CloseFile is called at the end of the life of the context. + * Anything remaining in \ref DOKAN_FILE_INFO.Context must be cleared before returning. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param DokanFileInfo Information about the file or directory. + */ + void(DOKAN_CALLBACK *CloseFile)(LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief ReadFile Dokan API callback + * + * ReadFile callback on the file previously opened in DOKAN_OPERATIONS.ZwCreateFile. + * It can be called by different threads at the same time, so the read/context has to be thread safe. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param Buffer Read buffer that has to be filled with the read result. + * \param BufferLength Buffer length and read size to continue with. + * \param ReadLength Total data size that has been read. + * \param Offset Offset from where the read has to be continued. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see WriteFile + */ + NTSTATUS(DOKAN_CALLBACK *ReadFile)(LPCWSTR FileName, + LPVOID Buffer, + DWORD BufferLength, + LPDWORD ReadLength, + LONGLONG Offset, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief WriteFile Dokan API callback + * + * WriteFile callback on the file previously opened in DOKAN_OPERATIONS.ZwCreateFile + * It can be called by different threads at the same time, sp the write/context has to be thread safe. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param Buffer Data that has to be written. + * \param NumberOfBytesToWrite Buffer length and write size to continue with. + * \param NumberOfBytesWritten Total number of bytes that have been written. + * \param Offset Offset from where the write has to be continued. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see ReadFile + */ + NTSTATUS(DOKAN_CALLBACK *WriteFile)(LPCWSTR FileName, + LPCVOID Buffer, + DWORD NumberOfBytesToWrite, + LPDWORD NumberOfBytesWritten, + LONGLONG Offset, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief FlushFileBuffers Dokan API callback + * + * Clears buffers for this context and causes any buffered data to be written to the file. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *FlushFileBuffers)(LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief GetFileInformation Dokan API callback + * + * Get specific information on a file. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param Buffer BY_HANDLE_FILE_INFORMATION struct to fill. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *GetFileInformation)(LPCWSTR FileName, + LPBY_HANDLE_FILE_INFORMATION Buffer, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief FindFiles Dokan API callback + * + * List all files in the requested path + * \ref DOKAN_OPERATIONS.FindFilesWithPattern is checked first. If it is not implemented or + * returns \c STATUS_NOT_IMPLEMENTED, then FindFiles is called, if implemented. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param FillFindData Callback that has to be called with PWIN32_FIND_DATAW that contain file information. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see FindFilesWithPattern + */ + NTSTATUS(DOKAN_CALLBACK *FindFiles)(LPCWSTR FileName, + PFillFindData FillFindData, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief FindFilesWithPattern Dokan API callback + * + * Same as \ref DOKAN_OPERATIONS.FindFiles but with a search pattern. + * + * \param PathName Path requested by the Kernel on the FileSystem. + * \param SearchPattern Search pattern. + * \param FillFindData Callback that has to be called with PWIN32_FIND_DATAW that contains file information. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see FindFiles + */ + NTSTATUS(DOKAN_CALLBACK *FindFilesWithPattern)(LPCWSTR PathName, + LPCWSTR SearchPattern, + PFillFindData FillFindData, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief SetFileAttributes Dokan API callback + * + * Set file attributes on a specific file + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param FileAttributes FileAttributes to set on file. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *SetFileAttributes)(LPCWSTR FileName, + DWORD FileAttributes, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief SetFileTime Dokan API callback + * + * Set file attributes on a specific file + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param CreationTime Creation FILETIME. + * \param LastAccessTime LastAccess FILETIME. + * \param LastWriteTime LastWrite FILETIME. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *SetFileTime)(LPCWSTR FileName, + CONST FILETIME *CreationTime, + CONST FILETIME *LastAccessTime, + CONST FILETIME *LastWriteTime, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief DeleteFile Dokan API callback + * + * Check if it is possible to delete a file. + * + * DeleteFile will also be called with DOKAN_FILE_INFO.DeleteOnClose set to \c FALSE + * to notify the driver when the file is no longer requested to be deleted. + * + * The file in DeleteFile should not be deleted, but instead the file + * must be checked as to whether or not it can be deleted, + * and \c STATUS_SUCCESS should be returned (when it can be deleted) or + * appropriate error codes, such as \c STATUS_ACCESS_DENIED or + * \c STATUS_OBJECT_NAME_NOT_FOUND, should be returned. + * + * When \c STATUS_SUCCESS is returned, a Cleanup call is received afterwards with + * DOKAN_FILE_INFO.DeleteOnClose set to \c TRUE. Only then must the closing file + * be deleted. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see DeleteDirectory + * \see Cleanup + */ + NTSTATUS(DOKAN_CALLBACK *DeleteFile)(LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief DeleteDirectory Dokan API callback + * + * Check if it is possible to delete a directory. + * + * DeleteDirectory will also be called with DOKAN_FILE_INFO.DeleteOnClose set to \c FALSE + * to notify the driver when the file is no longer requested to be deleted. + * + * The Directory in DeleteDirectory should not be deleted, but instead + * must be checked as to whether or not it can be deleted, + * and \c STATUS_SUCCESS should be returned (when it can be deleted) or + * appropriate error codes, such as \c STATUS_ACCESS_DENIED, + * \c STATUS_OBJECT_PATH_NOT_FOUND, or \c STATUS_DIRECTORY_NOT_EMPTY, should + * be returned. + * + * When \c STATUS_SUCCESS is returned, a Cleanup call is received afterwards with + * DOKAN_FILE_INFO.DeleteOnClose set to \c TRUE. Only then must the closing file + * be deleted. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or \c NTSTATUS appropriate to the request result. + * \ref DeleteFile + * \ref Cleanup + */ + NTSTATUS(DOKAN_CALLBACK *DeleteDirectory)(LPCWSTR FileName, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief MoveFile Dokan API callback + * + * Move a file or directory to a new destination + * + * \param FileName Path for the file to be moved. + * \param NewFileName Path for the new location of the file. + * \param ReplaceIfExisting If destination already exists, can it be replaced? + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *MoveFile)(LPCWSTR FileName, + LPCWSTR NewFileName, + BOOL ReplaceIfExisting, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief SetEndOfFile Dokan API callback + * + * SetEndOfFile is used to truncate or extend a file (physical file size). + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param ByteOffset File length to set. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *SetEndOfFile)(LPCWSTR FileName, + LONGLONG ByteOffset, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief SetAllocationSize Dokan API callback + * + * SetAllocationSize is used to truncate or extend a file. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param AllocSize File length to set. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *SetAllocationSize)(LPCWSTR FileName, + LONGLONG AllocSize, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief LockFile Dokan API callback + * + * Lock file at a specific offset and data length. + * This is only used if \ref DOKAN_OPTION_FILELOCK_USER_MODE is enabled. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param ByteOffset Offset from where the lock has to be continued. + * \param Length Data length to lock. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see UnlockFile + */ + NTSTATUS(DOKAN_CALLBACK *LockFile)(LPCWSTR FileName, + LONGLONG ByteOffset, + LONGLONG Length, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief UnlockFile Dokan API callback + * + * Unlock file at a specific offset and data length. + * This is only used if \ref DOKAN_OPTION_FILELOCK_USER_MODE is enabled. + * + * \param FileName File path requested by the Kernel on the FileSystem. + * \param ByteOffset Offset from where the lock has to be continued. + * \param Length Data length to lock. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see LockFile + */ + NTSTATUS(DOKAN_CALLBACK *UnlockFile)(LPCWSTR FileName, + LONGLONG ByteOffset, + LONGLONG Length, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief GetDiskFreeSpace Dokan API callback + * + * Retrieves information about the amount of space that is available on a disk volume. + * It consits of the total amount of space, the total amount of free space, and + * the total amount of free space available to the user that is associated with the calling thread. + * + * Neither GetDiskFreeSpace nor \ref GetVolumeInformation + * save the DOKAN_FILE_INFO.Context. + * Before these methods are called, \ref ZwCreateFile may not be called. + * (ditto \ref CloseFile and \ref Cleanup) + * + * \param FreeBytesAvailable Amount of available space. + * \param TotalNumberOfBytes Total size of storage space + * \param TotalNumberOfFreeBytes Amount of free space + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or \c NTSTATUS appropriate to the request result. + * \see GetDiskFreeSpaceEx function (MSDN) + * \see GetVolumeInformation + */ + NTSTATUS(DOKAN_CALLBACK *GetDiskFreeSpace)(PULONGLONG FreeBytesAvailable, + PULONGLONG TotalNumberOfBytes, + PULONGLONG TotalNumberOfFreeBytes, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief GetVolumeInformation Dokan API callback + * + * Retrieves information about the file system and volume associated with the specified root directory. + * + * Neither GetVolumeInformation nor GetDiskFreeSpace + * save the \ref DOKAN_FILE_INFO#Context. + * Before these methods are called, \ref ZwCreateFile may not be called. + * (ditto \ref CloseFile and \ref Cleanup) + * + * FileSystemName could be anything up to 10 characters. + * But Windows check few feature availability based on file system name. + * For this, it is recommended to set NTFS or FAT here. + * + * \c FILE_READ_ONLY_VOLUME is automatically added to the + * FileSystemFlags if \ref DOKAN_OPTION_WRITE_PROTECT was + * specified in DOKAN_OPTIONS when the volume was mounted. + * + * \param VolumeNameBuffer A pointer to a buffer that receives the name of a specified volume. + * \param VolumeNameSize The length of a volume name buffer. + * \param VolumeSerialNumber A pointer to a variable that receives the volume serial number. + * \param MaximumComponentLength A pointer to a variable that receives the maximum length. + * \param FileSystemFlags A pointer to a variable that receives flags associated with the specified file system. + * \param FileSystemNameBuffer A pointer to a buffer that receives the name of the file system. + * \param FileSystemNameSize The length of the file system name buffer. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see GetVolumeInformation function (MSDN) + * \see GetDiskFreeSpace + */ + NTSTATUS(DOKAN_CALLBACK *GetVolumeInformation)(LPWSTR VolumeNameBuffer, + DWORD VolumeNameSize, + LPDWORD VolumeSerialNumber, + LPDWORD MaximumComponentLength, + LPDWORD FileSystemFlags, + LPWSTR FileSystemNameBuffer, + DWORD FileSystemNameSize, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief Mounted Dokan API callback + * + * Called when Dokan successfully mounts the volume. + * + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see Unmounted + */ + NTSTATUS(DOKAN_CALLBACK *Mounted)(PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief Unmounted Dokan API callback + * + * Called when Dokan is unmounting the volume. + * + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or \c NTSTATUS appropriate to the request result. + * \see Unmounted + */ + NTSTATUS(DOKAN_CALLBACK *Unmounted)(PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief GetFileSecurity Dokan API callback + * + * Get specified information about the security of a file or directory. + * + * Return \c STATUS_NOT_IMPLEMENTED to let dokan library build a sddl of the current process user with authenticate user rights for context menu. + * Return \c STATUS_BUFFER_OVERFLOW if buffer size is too small. + * + * \since Supported since version 0.6.0. The version must be specified in \ref DOKAN_OPTIONS.Version. + * \param FileName File path requested by the Kernel on the FileSystem. + * \param SecurityInformation A SECURITY_INFORMATION value that identifies the security information being requested. + * \param SecurityDescriptor A pointer to a buffer that receives a copy of the security descriptor of the requested file. + * \param BufferLength Specifies the size, in bytes, of the buffer. + * \param LengthNeeded A pointer to the variable that receives the number of bytes necessary to store the complete security descriptor. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see SetFileSecurity + * \see GetFileSecurity function (MSDN) + */ + NTSTATUS(DOKAN_CALLBACK *GetFileSecurity)(LPCWSTR FileName, + PSECURITY_INFORMATION SecurityInformation, + PSECURITY_DESCRIPTOR SecurityDescriptor, + ULONG BufferLength, + PULONG LengthNeeded, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief SetFileSecurity Dokan API callback + * + * Sets the security of a file or directory object. + * + * \since Supported since version 0.6.0. The version must be specified in \ref DOKAN_OPTIONS.Version. + * \param FileName File path requested by the Kernel on the FileSystem. + * \param SecurityInformation Structure that identifies the contents of the security descriptor pointed by \a SecurityDescriptor param. + * \param SecurityDescriptor A pointer to a SECURITY_DESCRIPTOR structure. + * \param BufferLength Specifies the size, in bytes, of the buffer. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + * \see GetFileSecurity + * \see SetFileSecurity function (MSDN) + */ + NTSTATUS(DOKAN_CALLBACK *SetFileSecurity)(LPCWSTR FileName, + PSECURITY_INFORMATION SecurityInformation, + PSECURITY_DESCRIPTOR SecurityDescriptor, + ULONG BufferLength, + PDOKAN_FILE_INFO DokanFileInfo); + + /** + * \brief FindStreams Dokan API callback + * + * Retrieve all NTFS Streams informations on the file. + * This is only called if \ref DOKAN_OPTION_ALT_STREAM is enabled. + * + * \since Supported since version 0.8.0. The version must be specified in \ref DOKAN_OPTIONS.Version. + * \param FileName File path requested by the Kernel on the FileSystem. + * \param FillFindStreamData Callback that has to be called with PWIN32_FIND_STREAM_DATA that contain stream information. + * \param DokanFileInfo Information about the file or directory. + * \return \c STATUS_SUCCESS on success or NTSTATUS appropriate to the request result. + */ + NTSTATUS(DOKAN_CALLBACK *FindStreams)(LPCWSTR FileName, + PFillFindStreamData FillFindStreamData, + PDOKAN_FILE_INFO DokanFileInfo); + +} DOKAN_OPERATIONS, *PDOKAN_OPERATIONS; + +// clang-format on + +/** + * \defgroup DokanMainResult DokanMainResult + * \brief \ref DokanMain returns error codes + */ +/** @{ */ + +/** Dokan mount succeed. */ +#define DOKAN_SUCCESS 0 +/** Dokan mount error. */ +#define DOKAN_ERROR -1 +/** Dokan mount failed - Bad drive letter. */ +#define DOKAN_DRIVE_LETTER_ERROR -2 +/** Dokan mount failed - Can't install driver. */ +#define DOKAN_DRIVER_INSTALL_ERROR -3 +/** Dokan mount failed - Driver answer that something is wrong. */ +#define DOKAN_START_ERROR -4 +/** + * Dokan mount failed. + * Can't assign a drive letter or mount point. + * Probably already used by another volume. + */ +#define DOKAN_MOUNT_ERROR -5 +/** + * Dokan mount failed. + * Mount point is invalid. + */ +#define DOKAN_MOUNT_POINT_ERROR -6 +/** + * Dokan mount failed. + * Requested an incompatible version. + */ +#define DOKAN_VERSION_ERROR -7 + +/** @} */ + +/** + * \defgroup Dokan Dokan + */ +/** @{ */ + +/** + * \brief Mount a new Dokan Volume. + * + * This function block until the device is unmounted. + * If the mount fails, it will directly return a \ref DokanMainResult error. + * + * \param DokanOptions a \ref DOKAN_OPTIONS that describe the mount. + * \param DokanOperations Instance of \ref DOKAN_OPERATIONS that will be called for each request made by the kernel. + * \return \ref DokanMainResult status. + */ +int DOKANAPI DokanMain(PDOKAN_OPTIONS DokanOptions, + PDOKAN_OPERATIONS DokanOperations); + +/** + * \brief Unmount a Dokan device from a driver letter. + * + * \param DriveLetter Dokan driver letter to unmount. + * \return \c TRUE if device was unmounted or False in case of failure or device not found. + */ +BOOL DOKANAPI DokanUnmount(WCHAR DriveLetter); + +/** + * \brief Unmount a Dokan device from a mount point + * + * \param MountPoint Mount point to unmount ("Z", "Z:", "Z:\", "Z:\MyMountPoint"). + * \return \c TRUE if device was unmounted or False in case of failure or device not found. + */ +BOOL DOKANAPI DokanRemoveMountPoint(LPCWSTR MountPoint); + +/** + * \brief Unmount a Dokan device from a mount point + * + * Same as \ref DokanRemoveMountPoint + * If Safe is \c TRUE, it will broadcast to all desktops and Shells + * Safe should not be used during DLL_PROCESS_DETACH + * + * \see DokanRemoveMountPoint + * + * \param MountPoint Mount point to unmount ("Z", "Z:", "Z:\", "Z:\MyMountPoint"). + * \param Safe Process is not in DLL_PROCESS_DETACH state. + * \return True if device was unmounted or False in case of failure or device not found. + */ +BOOL DOKANAPI DokanRemoveMountPointEx(LPCWSTR MountPoint, BOOL Safe); + +/** + * \brief Checks whether Name matches Expression + * + * \param Expression Expression can contain wildcard characters (? and *) + * \param Name Name to check + * \param IgnoreCase Case sensitive or not + * \return result if name matches the expression + */ +BOOL DOKANAPI DokanIsNameInExpression(LPCWSTR Expression, LPCWSTR Name, + BOOL IgnoreCase); + +/** + * \brief Get the version of Dokan. + * The returned ULONG is the version number without the dots. + * \return The version of Dokan + */ +ULONG DOKANAPI DokanVersion(); + +/** + * \brief Get the version of the Dokan driver. + * The returned ULONG is the version number without the dots. + * \return The version of Dokan driver. + */ +ULONG DOKANAPI DokanDriverVersion(); + +/** + * \brief Extends the timeout of the current IO operation in driver. + * + * \param Timeout Extended time in milliseconds requested. + * \param DokanFileInfo \ref DOKAN_FILE_INFO of the operation to extend. + * \return If the operation was successful. + */ +BOOL DOKANAPI DokanResetTimeout(ULONG Timeout, PDOKAN_FILE_INFO DokanFileInfo); + +/** + * \brief Get the handle to Access Token. + * + * This method needs be called in CreateFile callback. + * The caller must call CloseHandle + * for the returned handle. + * + * \param DokanFileInfo \ref DOKAN_FILE_INFO of the operation to extend. + * \return A handle to the account token for the user on whose behalf the code is running. + */ +HANDLE DOKANAPI DokanOpenRequestorToken(PDOKAN_FILE_INFO DokanFileInfo); + +/** + * \brief Get active Dokan mount points. + * + * \param list Allocate array of DOKAN_CONTROL. + * \param length Number of \ref DOKAN_CONTROL instances in list. + * \param uncOnly Get only instances that have UNC Name. + * \param nbRead Number of instances successfully retrieved. + * \return List retrieved or not. + */ +BOOL DOKANAPI DokanGetMountPointList(PDOKAN_CONTROL list, ULONG length, + BOOL uncOnly, PULONG nbRead); + +/** + * \brief Convert \ref DOKAN_OPERATIONS.ZwCreateFile parameters to CreateFile parameters. + * + * Dokan Kernel forward the DesiredAccess directly from the IRP_MJ_CREATE. + * This DesiredAccess has been converted from generic rights (user CreateFile request) to standard rights and will be converted back here. + * https://msdn.microsoft.com/windows/hardware/drivers/ifs/access-mask + * + * \param DesiredAccess DesiredAccess from \ref DOKAN_OPERATIONS.ZwCreateFile. + * \param FileAttributes FileAttributes from \ref DOKAN_OPERATIONS.ZwCreateFile. + * \param CreateOptions CreateOptions from \ref DOKAN_OPERATIONS.ZwCreateFile. + * \param CreateDisposition CreateDisposition from \ref DOKAN_OPERATIONS.ZwCreateFile. + * \param outDesiredAccess New CreateFile dwDesiredAccess. + * \param outFileAttributesAndFlags New CreateFile dwFlagsAndAttributes. + * \param outCreationDisposition New CreateFile dwCreationDisposition. + * \see CreateFile function (MSDN) + */ +void DOKANAPI DokanMapKernelToUserCreateFileFlags( + ACCESS_MASK DesiredAccess, ULONG FileAttributes, ULONG CreateOptions, ULONG CreateDisposition, + ACCESS_MASK* outDesiredAccess, DWORD *outFileAttributesAndFlags, DWORD *outCreationDisposition); + +/** + * \brief Convert WIN32 error to NTSTATUS + * + * https://support.microsoft.com/en-us/kb/113996 + * + * \param Error Win32 Error to convert + * \return NTSTATUS associate to the ERROR. + */ +NTSTATUS DOKANAPI DokanNtStatusFromWin32(DWORD Error); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif // DOKAN_H_ diff --git a/MemProcFS/fileinfo.h b/MemProcFS/fileinfo.h new file mode 100644 index 0000000..50ac60e --- /dev/null +++ b/MemProcFS/fileinfo.h @@ -0,0 +1,1248 @@ +/* + Dokan : user-mode file system library for Windows + + Copyright (C) 2015 - 2018 Adrien J. and Maxime C. + Copyright (C) 2007 - 2011 Hiroki Asakawa + + http://dokan-dev.github.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see . +*/ + +#ifndef FILEINFO_H_ +#define FILEINFO_H_ + +#define IRP_MJ_CREATE 0x00 +#define IRP_MJ_CREATE_NAMED_PIPE 0x01 +#define IRP_MJ_CLOSE 0x02 +#define IRP_MJ_READ 0x03 +#define IRP_MJ_WRITE 0x04 +#define IRP_MJ_QUERY_INFORMATION 0x05 +#define IRP_MJ_SET_INFORMATION 0x06 +#define IRP_MJ_QUERY_EA 0x07 +#define IRP_MJ_SET_EA 0x08 +#define IRP_MJ_FLUSH_BUFFERS 0x09 +#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a +#define IRP_MJ_SET_VOLUME_INFORMATION 0x0b +#define IRP_MJ_DIRECTORY_CONTROL 0x0c +#define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d +#define IRP_MJ_DEVICE_CONTROL 0x0e +#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f +#define IRP_MJ_SHUTDOWN 0x10 +#define IRP_MJ_LOCK_CONTROL 0x11 +#define IRP_MJ_CLEANUP 0x12 +#define IRP_MJ_CREATE_MAILSLOT 0x13 +#define IRP_MJ_QUERY_SECURITY 0x14 +#define IRP_MJ_SET_SECURITY 0x15 +#define IRP_MJ_POWER 0x16 +#define IRP_MJ_SYSTEM_CONTROL 0x17 +#define IRP_MJ_DEVICE_CHANGE 0x18 +#define IRP_MJ_QUERY_QUOTA 0x19 +#define IRP_MJ_SET_QUOTA 0x1a +#define IRP_MJ_PNP 0x1b +#define IRP_MJ_PNP_POWER IRP_MJ_PNP +#define IRP_MJ_MAXIMUM_FUNCTION 0x1b + +#define IRP_MN_LOCK 0x01 +#define IRP_MN_UNLOCK_SINGLE 0x02 +#define IRP_MN_UNLOCK_ALL 0x03 +#define IRP_MN_UNLOCK_ALL_BY_KEY 0x04 + +typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation, // 2 + FileBothDirectoryInformation, // 3 + FileBasicInformation, // 4 + FileStandardInformation, // 5 + FileInternalInformation, // 6 + FileEaInformation, // 7 + FileAccessInformation, // 8 + FileNameInformation, // 9 + FileRenameInformation, // 10 + FileLinkInformation, // 11 + FileNamesInformation, // 12 + FileDispositionInformation, // 13 + FilePositionInformation, // 14 + FileFullEaInformation, // 15 + FileModeInformation, // 16 + FileAlignmentInformation, // 17 + FileAllInformation, // 18 + FileAllocationInformation, // 19 + FileEndOfFileInformation, // 20 + FileAlternateNameInformation, // 21 + FileStreamInformation, // 22 + FilePipeInformation, // 23 + FilePipeLocalInformation, // 24 + FilePipeRemoteInformation, // 25 + FileMailslotQueryInformation, // 26 + FileMailslotSetInformation, // 27 + FileCompressionInformation, // 28 + FileObjectIdInformation, // 29 + FileCompletionInformation, // 30 + FileMoveClusterInformation, // 31 + FileQuotaInformation, // 32 + FileReparsePointInformation, // 33 + FileNetworkOpenInformation, // 34 + FileAttributeTagInformation, // 35 + FileTrackingInformation, // 36 + FileIdBothDirectoryInformation, // 37 + FileIdFullDirectoryInformation, // 38 + FileValidDataLengthInformation, // 39 + FileShortNameInformation, // 40 + FileIoCompletionNotificationInformation, // 41 + FileIoStatusBlockRangeInformation, // 42 + FileIoPriorityHintInformation, // 43 + FileSfioReserveInformation, // 44 + FileSfioVolumeInformation, // 45 + FileHardLinkInformation, // 46 + FileProcessIdsUsingFileInformation, // 47 + FileNormalizedNameInformation, // 48 + FileNetworkPhysicalNameInformation, // 49 + FileIdGlobalTxDirectoryInformation, // 50 + FileIsRemoteDeviceInformation, // 51 + FileUnusedInformation, // 52 + FileNumaNodeInformation, // 53 + FileStandardLinkInformation, // 54 + FileRemoteProtocolInformation, // 55 + + // + // These are special versions of these operations (defined earlier) + // which can be used by kernel mode drivers only to bypass security + // access checks for Rename and HardLink operations. These operations + // are only recognized by the IOManager, a file system should never + // receive these. + // + + FileRenameInformationBypassAccessCheck, // 56 + FileLinkInformationBypassAccessCheck, // 57 + + // + // End of special information classes reserved for IOManager. + // + + FileVolumeNameInformation, // 58 + FileIdInformation, // 59 + FileIdExtdDirectoryInformation, // 60 + FileReplaceCompletionInformation, // 61 + FileHardLinkFullIdInformation, // 62 + FileIdExtdBothDirectoryInformation, // 63 + FileDispositionInformationEx, // 64 + FileRenameInformationEx, // 65 + FileRenameInformationExBypassAccessCheck, // 66 + FileDesiredStorageClassInformation, // 67 + FileStatInformation, // 68 + FileMemoryPartitionInformation, // 69 + + FileMaximumInformation +} FILE_INFORMATION_CLASS, + *PFILE_INFORMATION_CLASS; + +typedef enum _FSINFOCLASS { + FileFsVolumeInformation = 1, + FileFsLabelInformation, // 2 + FileFsSizeInformation, // 3 + FileFsDeviceInformation, // 4 + FileFsAttributeInformation, // 5 + FileFsControlInformation, // 6 + FileFsFullSizeInformation, // 7 + FileFsObjectIdInformation, // 8 + FileFsDriverPathInformation, // 9 + FileFsVolumeFlagsInformation, // 10 + FileFsMaximumInformation +} FS_INFORMATION_CLASS, + *PFS_INFORMATION_CLASS; + +/** + * \struct FILE_ALIGNMENT_INFORMATION + * \brief Used as an argument to the ZwQueryInformationFile routine. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileAllInformation + */ +typedef struct _FILE_ALIGNMENT_INFORMATION { + /** + * The buffer alignment required by the underlying device. For a list of system-defined values, see DEVICE_OBJECT. + * The value must be one of the FILE_XXX_ALIGNMENT values defined in Wdm.h. + * For more information, see DEVICE_OBJECT and Initializing a Device Object. + */ + ULONG AlignmentRequirement; +} FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION; + +/** + * \struct FILE_NAME_INFORMATION + * \brief Used as argument to the ZwQueryInformationFile and ZwSetInformationFile routines. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileNameInformation + */ +typedef struct _FILE_NAME_INFORMATION { + /** + * Specifies the length, in bytes, of the file name string. + */ + ULONG FileNameLength; + /** + * Specifies the first character of the file name string. This is followed in memory by the remainder of the string. + */ + WCHAR FileName[1]; +} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION; + +/** + * \struct FILE_ATTRIBUTE_TAG_INFORMATION + * \brief Used as an argument to ZwQueryInformationFile. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileAttributeTagInformation + */ +typedef struct _FILE_ATTRIBUTE_TAG_INFORMATION { + /** + * Specifies one or more FILE_ATTRIBUTE_XXX flags. + * For descriptions of these flags, see the documentation of the GetFileAttributes function in the Microsoft Windows SDK. + */ + ULONG FileAttributes; + /** + * Specifies the reparse point tag. If the FileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT attribute flag, + * this member specifies the reparse tag. Otherwise, this member is unused. + */ + ULONG ReparseTag; +} FILE_ATTRIBUTE_TAG_INFORMATION, *PFILE_ATTRIBUTE_TAG_INFORMATION; + +/** + * \struct FILE_DISPOSITION_INFORMATION + * \brief Used as an argument to the ZwSetInformationFile routine. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileDispositionInformation + */ +typedef struct _FILE_DISPOSITION_INFORMATION { + /** + * Indicates whether the operating system file should delete the file when the file is closed. + * Set this member to TRUE to delete the file when it is closed. + * Otherwise, set to FALSE. Setting this member to FALSE has no effect if the handle was opened with FILE_FLAG_DELETE_ON_CLOSE. + */ + BOOLEAN DeleteFile; +} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION; + +/** + * \struct FILE_END_OF_FILE_INFORMATION + * \brief Used as an argument to the ZwSetInformationFile routine. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileEndOfFileInformation + */ +typedef struct _FILE_END_OF_FILE_INFORMATION { + /** + * The absolute new end of file position as a byte offset from the start of the file. + */ + LARGE_INTEGER EndOfFile; +} FILE_END_OF_FILE_INFORMATION, *PFILE_END_OF_FILE_INFORMATION; + +/** + * \struct FILE_VALID_DATA_LENGTH_INFORMATION + * \brief Used as an argument to ZwSetInformationFile. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileValidDataLengthInformation + */ +typedef struct _FILE_VALID_DATA_LENGTH_INFORMATION { + /** + * Specifies the new valid data length for the file. + * This parameter must be a positive value that is greater than the current valid data length, but less than or equal to the current file size. + */ + LARGE_INTEGER ValidDataLength; +} FILE_VALID_DATA_LENGTH_INFORMATION, *PFILE_VALID_DATA_LENGTH_INFORMATION; + +/** + * \struct FILE_BASIC_INFORMATION + * \brief Used as an argument to routines that query or set file information. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileBasicInformation and FileAllInformation + */ +typedef struct _FILE_BASIC_INFORMATION { + /** + * Specifies the time that the file was created. + */ + LARGE_INTEGER CreationTime; + /** + * Specifies the time that the file was last accessed. + */ + LARGE_INTEGER LastAccessTime; + /** + * Specifies the time that the file was last written to. + */ + LARGE_INTEGER LastWriteTime; + /** + * Specifies the last time the file was changed. + */ + LARGE_INTEGER ChangeTime; + /** + * Specifies one or more FILE_ATTRIBUTE_XXX flags. For descriptions of these flags, + * see the documentation for the GetFileAttributes function in the Microsoft Windows SDK. + */ + ULONG FileAttributes; +} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION; + +/** + * \struct FILE_STANDARD_INFORMATION + * \brief Used as an argument to routines that query or set file information. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileStandardInformation and FileAllInformation + */ +typedef struct _FILE_STANDARD_INFORMATION { + /** + * The file allocation size in bytes. Usually, this value is a multiple of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; + /** + * The end of file location as a byte offset. + */ + LARGE_INTEGER EndOfFile; + /** + * The number of hard links to the file. + */ + ULONG NumberOfLinks; + /** + * The delete pending status. TRUE indicates that a file deletion has been requested. + */ + BOOLEAN DeletePending; + /** + * The file directory status. TRUE indicates the file object represents a directory. + */ + BOOLEAN Directory; +} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; + +/** + * \struct FILE_POSITION_INFORMATION + * \brief Used as an argument to routines that query or set file information. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FilePositionInformation and FileAllInformation + */ +typedef struct _FILE_POSITION_INFORMATION { + /** + * The byte offset of the current file pointer. + */ + LARGE_INTEGER CurrentByteOffset; +} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION; + +/** + * \struct FILE_DIRECTORY_INFORMATION + * \brief Used to query detailed information for the files in a directory. + */ +typedef struct _FILE_DIRECTORY_INFORMATION { + /** + * Byte offset of the next FILE_DIRECTORY_INFORMATION entry, if multiple entries are present in a buffer. + * This member is zero if no other entries follow this one. + */ + ULONG NextEntryOffset; + /** + * Byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, + * in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order. + */ + ULONG FileIndex; + /** + * Time when the file was created. + */ + LARGE_INTEGER CreationTime; + /** + * Last time the file was accessed. + */ + LARGE_INTEGER LastAccessTime; + /** + * Last time information was written to the file. + */ + LARGE_INTEGER LastWriteTime; + /** + * Last time the file was changed. + */ + LARGE_INTEGER ChangeTime; + /** + * Absolute new end-of-file position as a byte offset from the start of the file. + * EndOfFile specifies the byte offset to the end of the file. + * Because this value is zero-based, it actually refers to the first free byte in the file. In other words, + * EndOfFile is the offset to the byte immediately following the last valid byte in the file. + */ + LARGE_INTEGER EndOfFile; + /** + * File allocation size, in bytes. Usually, this value is a multiple of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; + /** + * File attributes, which can be any valid combination of the following: + * + * \li \c FILE_ATTRIBUTE_READONLY + * \li \c FILE_ATTRIBUTE_HIDDEN + * \li \c FILE_ATTRIBUTE_SYSTEM + * \li \c FILE_ATTRIBUTE_DIRECTORY + * \li \c FILE_ATTRIBUTE_ARCHIVE + * \li \c FILE_ATTRIBUTE_NORMAL + * \li \c FILE_ATTRIBUTE_TEMPORARY + * \li \c FILE_ATTRIBUTE_COMPRESSED + */ + ULONG FileAttributes; + /** + * Specifies the length of the file name string. + */ + ULONG FileNameLength; + /** + * Specifies the first character of the file name string. + * This is followed in memory by the remainder of the string. + */ + WCHAR FileName[1]; +} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION; + +/** + * \struct FILE_FULL_DIR_INFORMATION + * \brief Used to query detailed information for the files in a directory. + */ +typedef struct _FILE_FULL_DIR_INFORMATION { + /** + * Byte offset of the next FILE_DIRECTORY_INFORMATION entry, if multiple entries are present in a buffer. + * This member is zero if no other entries follow this one. + */ + ULONG NextEntryOffset; + /** + * Byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, + * in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order. + */ + ULONG FileIndex; + /** + * Time when the file was created. + */ + LARGE_INTEGER CreationTime; + /** + * Last time the file was accessed. + */ + LARGE_INTEGER LastAccessTime; + /** + * Last time information was written to the file. + */ + LARGE_INTEGER LastWriteTime; + /** + * Last time the file was changed. + */ + LARGE_INTEGER ChangeTime; + /** + * Absolute new end-of-file position as a byte offset from the start of the file. + * EndOfFile specifies the byte offset to the end of the file. + * Because this value is zero-based, it actually refers to the first free byte in the file. In other words, + * EndOfFile is the offset to the byte immediately following the last valid byte in the file. + */ + LARGE_INTEGER EndOfFile; + /** + * File allocation size, in bytes. Usually, this value is a multiple of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; + /** + * File attributes, which can be any valid combination of the following: + * + * \li \c FILE_ATTRIBUTE_READONLY + * \li \c FILE_ATTRIBUTE_HIDDEN + * \li \c FILE_ATTRIBUTE_SYSTEM + * \li \c FILE_ATTRIBUTE_DIRECTORY + * \li \c FILE_ATTRIBUTE_ARCHIVE + * \li \c FILE_ATTRIBUTE_NORMAL + * \li \c FILE_ATTRIBUTE_TEMPORARY + * \li \c FILE_ATTRIBUTE_COMPRESSED + */ + ULONG FileAttributes; + /** + * Specifies the length of the file name string. + */ + ULONG FileNameLength; + /** + * Combined length, in bytes, of the extended attributes (EA) for the file. + */ + ULONG EaSize; + /** + * Specifies the first character of the file name string. + * This is followed in memory by the remainder of the string. + */ + WCHAR FileName[1]; +} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION; + +/** + * \struct FILE_ID_FULL_DIR_INFORMATION + * \brief Used to query detailed information for the files in a directory. + */ +typedef struct _FILE_ID_FULL_DIR_INFORMATION { + /** + * Byte offset of the next FILE_DIRECTORY_INFORMATION entry, if multiple entries are present in a buffer. + * This member is zero if no other entries follow this one. + */ + ULONG NextEntryOffset; + /** + * Byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, + * in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order. + */ + ULONG FileIndex; + /** + * Time when the file was created. + */ + LARGE_INTEGER CreationTime; + /** + * Last time the file was accessed. + */ + LARGE_INTEGER LastAccessTime; + /** + * Last time information was written to the file. + */ + LARGE_INTEGER LastWriteTime; + /** + * Last time the file was changed. + */ + LARGE_INTEGER ChangeTime; + /** + * Absolute new end-of-file position as a byte offset from the start of the file. + * EndOfFile specifies the byte offset to the end of the file. + * Because this value is zero-based, it actually refers to the first free byte in the file. In other words, + * EndOfFile is the offset to the byte immediately following the last valid byte in the file. + */ + LARGE_INTEGER EndOfFile; + /** + * File allocation size, in bytes. Usually, this value is a multiple of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; + /** + * File attributes, which can be any valid combination of the following: + * + * \li \c FILE_ATTRIBUTE_READONLY + * \li \c FILE_ATTRIBUTE_HIDDEN + * \li \c FILE_ATTRIBUTE_SYSTEM + * \li \c FILE_ATTRIBUTE_DIRECTORY + * \li \c FILE_ATTRIBUTE_ARCHIVE + * \li \c FILE_ATTRIBUTE_NORMAL + * \li \c FILE_ATTRIBUTE_TEMPORARY + * \li \c FILE_ATTRIBUTE_COMPRESSED + */ + ULONG FileAttributes; + /** + * Specifies the length of the file name string. + */ + ULONG FileNameLength; + /** + * Combined length, in bytes, of the extended attributes (EA) for the file. + */ + ULONG EaSize; + /** + * The 8-byte file reference number for the file. (Note that this is not the same as the 16-byte + * "file object ID" that was added to NTFS for Microsoft Windows 2000.) + */ + LARGE_INTEGER FileId; + /** + * Specifies the first character of the file name string. + * This is followed in memory by the remainder of the string. + */ + WCHAR FileName[1]; +} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION; + +/** + * \struct FILE_BOTH_DIR_INFORMATION + * \brief Used to query detailed information for the files in a directory. + */ +typedef struct _FILE_BOTH_DIR_INFORMATION { + /** + * Byte offset of the next FILE_DIRECTORY_INFORMATION entry, if multiple entries are present in a buffer. + * This member is zero if no other entries follow this one. + */ + ULONG NextEntryOffset; + /** + * Byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, + * in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order. + */ + ULONG FileIndex; + /** + * Time when the file was created. + */ + LARGE_INTEGER CreationTime; + /** + * Last time the file was accessed. + */ + LARGE_INTEGER LastAccessTime; + /** + * Last time information was written to the file. + */ + LARGE_INTEGER LastWriteTime; + /** + * Last time the file was changed. + */ + LARGE_INTEGER ChangeTime; + /** + * Absolute new end-of-file position as a byte offset from the start of the file. + * EndOfFile specifies the byte offset to the end of the file. + * Because this value is zero-based, it actually refers to the first free byte in the file. In other words, + * EndOfFile is the offset to the byte immediately following the last valid byte in the file. + */ + LARGE_INTEGER EndOfFile; + /** + * File allocation size, in bytes. Usually, this value is a multiple of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; + /** + * File attributes, which can be any valid combination of the following: + * + * \li \c FILE_ATTRIBUTE_READONLY + * \li \c FILE_ATTRIBUTE_HIDDEN + * \li \c FILE_ATTRIBUTE_SYSTEM + * \li \c FILE_ATTRIBUTE_DIRECTORY + * \li \c FILE_ATTRIBUTE_ARCHIVE + * \li \c FILE_ATTRIBUTE_NORMAL + * \li \c FILE_ATTRIBUTE_TEMPORARY + * \li \c FILE_ATTRIBUTE_COMPRESSED + */ + ULONG FileAttributes; + /** + * Specifies the length of the file name string. + */ + ULONG FileNameLength; + /** + * Combined length, in bytes, of the extended attributes (EA) for the file. + */ + ULONG EaSize; + /** + * Specifies the length, in bytes, of the short file name string. + */ + CCHAR ShortNameLength; + /** + * Unicode string containing the short (8.3) name for the file. + */ + WCHAR ShortName[12]; + /** + * Specifies the first character of the file name string. This is followed in memory by the remainder of the string. + */ + WCHAR FileName[1]; +} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; + +/** + * \struct FILE_ID_BOTH_DIR_INFORMATION + * \brief Used to query detailed information for the files in a directory. + */ +typedef struct _FILE_ID_BOTH_DIR_INFORMATION { + /** + * Byte offset of the next FILE_DIRECTORY_INFORMATION entry, if multiple entries are present in a buffer. + * This member is zero if no other entries follow this one. + */ + ULONG NextEntryOffset; + /** + * Byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, + * in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order. + */ + ULONG FileIndex; + /** + * Time when the file was created. + */ + LARGE_INTEGER CreationTime; + /** + * Last time the file was accessed. + */ + LARGE_INTEGER LastAccessTime; + /** + * Last time information was written to the file. + */ + LARGE_INTEGER LastWriteTime; + /** + * Last time the file was changed. + */ + LARGE_INTEGER ChangeTime; + /** + * Absolute new end-of-file position as a byte offset from the start of the file. + * EndOfFile specifies the byte offset to the end of the file. + * Because this value is zero-based, it actually refers to the first free byte in the file. In other words, + * EndOfFile is the offset to the byte immediately following the last valid byte in the file. + */ + LARGE_INTEGER EndOfFile; + /** + * File allocation size, in bytes. Usually, this value is a multiple of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; + /** + * File attributes, which can be any valid combination of the following: + * + * \li \c FILE_ATTRIBUTE_READONLY + * \li \c FILE_ATTRIBUTE_HIDDEN + * \li \c FILE_ATTRIBUTE_SYSTEM + * \li \c FILE_ATTRIBUTE_DIRECTORY + * \li \c FILE_ATTRIBUTE_ARCHIVE + * \li \c FILE_ATTRIBUTE_NORMAL + * \li \c FILE_ATTRIBUTE_TEMPORARY + * \li \c FILE_ATTRIBUTE_COMPRESSED + */ + ULONG FileAttributes; + /** + * Specifies the length of the file name string. + */ + ULONG FileNameLength; + /** + * Combined length, in bytes, of the extended attributes (EA) for the file. + */ + ULONG EaSize; + /** + * Specifies the length, in bytes, of the short file name string. + */ + CCHAR ShortNameLength; + /** + * Unicode string containing the short (8.3) name for the file. + */ + WCHAR ShortName[12]; + /** + * The 8-byte file reference number for the file. This number is generated and assigned to the file by the file system. + * (Note that the FileId is not the same as the 16-byte "file object ID" that was added to NTFS for Microsoft Windows 2000.) + */ + LARGE_INTEGER FileId; + /** + * Specifies the first character of the file name string. This is followed in memory by the remainder of the string. + */ + WCHAR FileName[1]; +} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION; + +/** + * \struct FILE_NAMES_INFORMATION + * \brief Used to query detailed information about the names of files in a directory. + */ +typedef struct _FILE_NAMES_INFORMATION { + /** + * Byte offset for the next FILE_NAMES_INFORMATION entry, if multiple entries are present in a buffer. + * This member is zero if no other entries follow this one. + */ + ULONG NextEntryOffset; + /** + * Byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, + * in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order. + */ + ULONG FileIndex; + /** + * Specifies the length of the file name string. + */ + ULONG FileNameLength; + /** + * Specifies the first character of the file name string. This is followed in memory by the remainder of the string. + */ + WCHAR FileName[1]; +} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION; + +#define ANSI_DOS_STAR ('<') +#define ANSI_DOS_QM ('>') +#define ANSI_DOS_DOT ('"') + +#define DOS_STAR (L'<') +#define DOS_QM (L'>') +#define DOS_DOT (L'"') + +/** + * \struct FILE_INTERNAL_INFORMATION + * \brief Used to query for the file system's 8-byte file reference number for a file. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileInternalInformation + */ +typedef struct _FILE_INTERNAL_INFORMATION { + /** + * The 8-byte file reference number for the file. This number is assigned by the file system and is file-system-specific. + * (Note that this is not the same as the 16-byte "file object ID" that was added to NTFS for Microsoft Windows 2000.) + */ + LARGE_INTEGER IndexNumber; +} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION; + +/** + * \struct FILE_ID_INFORMATION + * \brief Contains identification information for a file. + * + * This structure is returned from the GetFileInformationByHandleEx function when FileIdInfo is passed in the FileInformationClass parameter. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileIdInformation + */ +typedef struct _FILE_ID_INFORMATION { + /** + * The serial number of the volume that contains a file. + */ + ULONGLONG VolumeSerialNumber; + /** + * The 128-bit file identifier for the file. The file identifier and the volume serial number uniquely identify a file on a single computer. + * To determine whether two open handles represent the same file, combine the identifier and the volume serial number for each file and compare them. + */ + FILE_ID_128 FileId; +} FILE_ID_INFORMATION, *PFILE_ID_INFORMATION; + +/** + * \struct FILE_EA_INFORMATION + * \brief Used to query for the size of the extended attributes (EA) for a file. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileEaInformation and FileAllInformation + */ +typedef struct _FILE_EA_INFORMATION { + /** + * Specifies the combined length, in bytes, of the extended attributes for the file. + */ + ULONG EaSize; +} FILE_EA_INFORMATION, *PFILE_EA_INFORMATION; + +/** + * \struct FILE_ACCESS_INFORMATION + * \brief Used to query for or set the access rights of a file. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileAllInformation + */ +typedef struct _FILE_ACCESS_INFORMATION { + /** + * Flags that specify a set of access rights in the access mask of an access control entry. + * This member is a value of type ACCESS_MASK. + */ + ACCESS_MASK AccessFlags; +} FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION; + +/** + * \struct FILE_MODE_INFORMATION + * \brief Used to query or set the access mode of a file. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileAllInformation + */ +typedef struct _FILE_MODE_INFORMATION { + /** + * Specifies the mode in which the file will be accessed following a create-file or open-file operation. + * This parameter is either zero or the bitwise OR of one or more of the following file option flags: + * + * \li \c FILE_WRITE_THROUGH + * \li \c FILE_SEQUENTIAL_ONLY + * \li \c FILE_NO_INTERMEDIATE_BUFFERING + * \li \c FILE_SYNCHRONOUS_IO_ALERT + * \li \c FILE_SYNCHRONOUS_IO_NONALERT + * \li \c FILE_DELETE_ON_CLOSE + */ + ULONG Mode; +} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION; + +/** + * \struct FILE_ALL_INFORMATION + * \brief Structure is a container for several FILE_XXX_INFORMATION structures. + * + * The struct is requested during IRP_MJ_QUERY_INFORMATION with query FileAllInformation + */ +typedef struct _FILE_ALL_INFORMATION { + /** \see FILE_BASIC_INFORMATION */ + FILE_BASIC_INFORMATION BasicInformation; + /** \see FILE_STANDARD_INFORMATION */ + FILE_STANDARD_INFORMATION StandardInformation; + /** \see FILE_INTERNAL_INFORMATION */ + FILE_INTERNAL_INFORMATION InternalInformation; + /** \see FILE_EA_INFORMATION */ + FILE_EA_INFORMATION EaInformation; + /** \see FILE_ACCESS_INFORMATION */ + FILE_ACCESS_INFORMATION AccessInformation; + /** \see FILE_POSITION_INFORMATION */ + FILE_POSITION_INFORMATION PositionInformation; + /** \see FILE_MODE_INFORMATION */ + FILE_MODE_INFORMATION ModeInformation; + /** \see FILE_ALIGNMENT_INFORMATION */ + FILE_ALIGNMENT_INFORMATION AlignmentInformation; + /** \see FILE_NAME_INFORMATION */ + FILE_NAME_INFORMATION NameInformation; +} FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION; + +/** + * \struct FILE_ALLOCATION_INFORMATION + * \brief Used to set the allocation size for a file. + * + * The struct is requested during IRP_MJ_SET_INFORMATION with query FileAllocationInformation + */ +typedef struct _FILE_ALLOCATION_INFORMATION { + /** + * File allocation size, in bytes. Usually this value is a multiple + * of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; +} FILE_ALLOCATION_INFORMATION, *PFILE_ALLOCATION_INFORMATION; + +/** + * \struct FILE_LINK_INFORMATION + * \brief Used to create an NTFS hard link to an existing file. + * + * The struct is requested during IRP_MJ_SET_INFORMATION with query FileLinkInformation + */ +typedef struct _FILE_LINK_INFORMATION { + /** + * Set to TRUE to specify that if the link already exists, it should be replaced with the new link. + * Set to FALSE if the link creation operation should fail if the link already exists. + */ + BOOLEAN ReplaceIfExists; + /** + * If the link is to be created in the same directory as the file that is being linked to, + * or if the FileName member contains the full pathname for the link to be created, this is NULL. + * Otherwise it is a handle for the directory where the link is to be created. + */ + HANDLE RootDirectory; + /** + * Length, in bytes, of the file name string. + */ + ULONG FileNameLength; + /** + * The first character of the name to be assigned to the newly created link. + * This is followed in memory by the remainder of the string. + * If the RootDirectory member is NULL and the link is to be created in a different directory from the file that is being linked to, + * this member specifies the full pathname for the link to be created. Otherwise, it specifies only the file name. + * (See the Remarks section for ZwQueryInformationFile for details on the syntax of this file name string.) + */ + WCHAR FileName[1]; +} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION; + +/** + * \struct FILE_RENAME_INFORMATION + * \brief Used to rename a file. + * + * The struct is requested during IRP_MJ_SET_INFORMATION with query FileRenameInformation + */ +typedef struct _FILE_RENAME_INFORMATION { + /** + * Set to TRUE to specify that if a file with the given name already exists, it should be replaced with the given file. + * Set to FALSE if the rename operation should fail if a file with the given name already exists. + */ + BOOLEAN ReplaceIfExists; + /** + * If the file is not being moved to a different directory, + * or if the FileName member contains the full pathname, this member is NULL. Otherwise, + * it is a handle for the root directory under which the file will reside after it is renamed. + */ + HANDLE RootDirectory; + /** + * Length, in bytes, of the new name for the file. + */ + ULONG FileNameLength; + /** + * The first character of a wide-character string containing the new name for the file. + * This is followed in memory by the remainder of the string. If the RootDirectory member is NULL, + * and the file is being moved to a different directory, this member specifies the full pathname to be assigned to the file. + * Otherwise, it specifies only the file name or a relative pathname. + */ + WCHAR FileName[1]; +} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + +/** + * \struct FILE_STREAM_INFORMATION + * \brief Used to enumerate the streams for a file. + * + * The struct is requested during IRP_MJ_SET_INFORMATION query FileStreamInformation + */ +typedef struct _FILE_STREAM_INFORMATION { + /** + * The offset of the next FILE_STREAM_INFORMATION entry. + * This member is zero if no other entries follow this one. + */ + ULONG NextEntryOffset; + /** + * Length, in bytes, of the StreamName string. + */ + ULONG StreamNameLength; + /** + * Size, in bytes, of the stream. + */ + LARGE_INTEGER StreamSize; + /** + * File stream allocation size, in bytes. Usually this value is a multiple of the sector + * or cluster size of the underlying physical device. + */ + LARGE_INTEGER StreamAllocationSize; + /** + * Unicode string that contains the name of the stream. + */ + WCHAR StreamName[1]; +} FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION; + +/** + * \struct FILE_FS_LABEL_INFORMATION + * \brief Used to set the label for a file system volume. + * + * The struct is requested during IRP_MJ_SET_VOLUME_INFORMATION query FileFsLabelInformation + */ +typedef struct _FILE_FS_LABEL_INFORMATION { + /** + * Length, in bytes, of the name for the volume. + */ + ULONG VolumeLabelLength; + /** + * Name for the volume. + */ + WCHAR VolumeLabel[1]; +} FILE_FS_LABEL_INFORMATION, *PFILE_FS_LABEL_INFORMATION; + +/** + * \struct FILE_FS_VOLUME_INFORMATION + * \brief Used to query information about a volume on which a file system is mounted. + * + * The struct is requested during IRP_MJ_QUERY_VOLUME_INFORMATION query FileFsVolumeInformation + */ +typedef struct _FILE_FS_VOLUME_INFORMATION { + /** + * Time when the volume was created. + */ + LARGE_INTEGER VolumeCreationTime; + /** + * Serial number of the volume. + */ + ULONG VolumeSerialNumber; + /** + * Length, in bytes, of the name of the volume. + */ + ULONG VolumeLabelLength; + /** + * TRUE if the file system supports object-oriented file system objects, FALSE otherwise. + */ + BOOLEAN SupportsObjects; + /** + * Name of the volume. + */ + WCHAR VolumeLabel[1]; +} FILE_FS_VOLUME_INFORMATION, *PFILE_FS_VOLUME_INFORMATION; + +/** + * \struct FILE_FS_SIZE_INFORMATION + * \brief Used to query sector size information for a file system volume. + * + * The struct is requested during IRP_MJ_QUERY_VOLUME_INFORMATION query FileFsSizeInformation + */ +typedef struct _FILE_FS_SIZE_INFORMATION { + /** + * Total number of allocation units on the volume that are available to the user associated with the calling thread. + * If per-user quotas are in use, this value may be less than the total number of allocation units on the disk. + */ + LARGE_INTEGER TotalAllocationUnits; + /** + * Total number of free allocation units on the volume that are available to the user associated with the calling thread. + * If per-user quotas are in use, this value may be less than the total number of free allocation units on the disk. + */ + LARGE_INTEGER AvailableAllocationUnits; + /** + * Number of sectors in each allocation unit. + */ + ULONG SectorsPerAllocationUnit; + /** + * Number of bytes in each sector. + */ + ULONG BytesPerSector; +} FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION; + +/** + * \struct FILE_FS_FULL_SIZE_INFORMATION + * \brief Used to query sector size information for a file system volume. + * + * The struct is requested during IRP_MJ_QUERY_VOLUME_INFORMATION query FileFsFullSizeInformation + */ +typedef struct _FILE_FS_FULL_SIZE_INFORMATION { + /** + * Total number of allocation units on the volume that are available to the user associated with the calling thread. + * If per-user quotas are in use, this value may be less than the total number of allocation units on the disk. + */ + LARGE_INTEGER TotalAllocationUnits; + /** + * Total number of free allocation units on the volume that are available to the user associated with the calling thread. + * If per-user quotas are in use, this value may be less than the total number of free allocation units on the disk. + */ + LARGE_INTEGER CallerAvailableAllocationUnits; + /** + * Total number of free allocation units on the volume. + */ + LARGE_INTEGER ActualAvailableAllocationUnits; + /** + * Number of sectors in each allocation unit. + */ + ULONG SectorsPerAllocationUnit; + /** + * Number of bytes in each sector. + */ + ULONG BytesPerSector; +} FILE_FS_FULL_SIZE_INFORMATION, *PFILE_FS_FULL_SIZE_INFORMATION; + +/** + * \struct FILE_FS_ATTRIBUTE_INFORMATION + * \brief Used to query attribute information for a file system. + * + * The struct is requested during IRP_MJ_QUERY_VOLUME_INFORMATION query FileFsAttributeInformation + */ +typedef struct _FILE_FS_ATTRIBUTE_INFORMATION { + /** + * Bitmask of flags specifying attributes of the specified file system. + * \see https://msdn.microsoft.com/en-us/library/windows/hardware/ff540251(v=vs.85).aspx + */ + ULONG FileSystemAttributes; + /** + * Maximum file name component length, in bytes, supported by the specified file system. + * A file name component is that portion of a file name between backslashes. + */ + LONG MaximumComponentNameLength; + /** + * Length, in bytes, of the file system name. + */ + ULONG FileSystemNameLength; + /** + * File system name. + */ + WCHAR FileSystemName[1]; +} FILE_FS_ATTRIBUTE_INFORMATION, *PFILE_FS_ATTRIBUTE_INFORMATION; + +/** + * \struct FILE_NETWORK_OPEN_INFORMATION + * \brief Used as an argument to ZwQueryInformationFile. + * + * The struct is requested during IRP_MJ_QUERY_VOLUME_INFORMATION query FileNetworkOpenInformation + */ +typedef struct _FILE_NETWORK_OPEN_INFORMATION { + /** + * Specifies the time that the file was created. + */ + LARGE_INTEGER CreationTime; + /** + * Specifies the time that the file was last accessed. + */ + LARGE_INTEGER LastAccessTime; + /** + * Specifies he time that the file was last written to. + */ + LARGE_INTEGER LastWriteTime; + /** + * Specifies the time that the file was last changed. + */ + LARGE_INTEGER ChangeTime; + /** + * Specifies the file allocation size, in bytes. Usually, + * this value is a multiple of the sector or cluster size of the underlying physical device. + */ + LARGE_INTEGER AllocationSize; + /** + * Specifies the absolute end-of-file position as a byte offset from the start of the file. + * EndOfFile specifies the byte offset to the end of the file. Because this value is zero-based, + * it actually refers to the first free byte in the file. In other words, + * EndOfFile is the offset to the byte immediately following the last valid byte in the file. + */ + LARGE_INTEGER EndOfFile; + /** + * Specifies one or more FILE_ATTRIBUTE_XXX flags. For descriptions of these flags, + * see the documentation of the GetFileAttributes function in the Microsoft Windows SDK. + */ + ULONG FileAttributes; +} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION; + +/** + * \struct FILE_NETWORK_PHYSICAL_NAME_INFORMATION + * \brief Contains the full UNC physical pathname for a file or directory on a remote file share. + * + * The struct is requested during IRP_MJ_QUERY_VOLUME_INFORMATION query FileNetworkPhysicalNameInformation + */ +typedef struct _FILE_NETWORK_PHYSICAL_NAME_INFORMATION { + /** + * The length, in bytes, of the physical name in FileName. + */ + ULONG FileNameLength; + /** + * The full UNC path of the network file share of the target. + */ + WCHAR FileName[1]; +} FILE_NETWORK_PHYSICAL_NAME_INFORMATION, + *PFILE_NETWORK_PHYSICAL_NAME_INFORMATION; + +#define SL_RESTART_SCAN 0x01 +#define SL_RETURN_SINGLE_ENTRY 0x02 +#define SL_INDEX_SPECIFIED 0x04 +#define SL_FORCE_ACCESS_CHECK 0x01 + +#define SL_OPEN_PAGING_FILE 0x02 +#define SL_OPEN_TARGET_DIRECTORY 0x04 +#define SL_CASE_SENSITIVE 0x80 + +#define ALIGN_DOWN(length, type) ((ULONG)(length) & ~(sizeof(type) - 1)) + +#define ALIGN_UP(length, type) \ + (ALIGN_DOWN(((ULONG)(length) + sizeof(type) - 1), type)) + +#define ALIGN_DOWN_POINTER(address, type) \ + ((PVOID)((ULONG_PTR)(address) & ~((ULONG_PTR)sizeof(type) - 1))) + +#define ALIGN_UP_POINTER(address, type) \ + (ALIGN_DOWN_POINTER(((ULONG_PTR)(address) + sizeof(type) - 1), type)) + +#define WordAlign(Val) (ALIGN_UP(Val, WORD)) + +#define WordAlignPtr(Ptr) (ALIGN_UP_POINTER(Ptr, WORD)) + +#define LongAlign(Val) (ALIGN_UP(Val, LONG)) + +#define LongAlignPtr(Ptr) (ALIGN_UP_POINTER(Ptr, LONG)) + +#define QuadAlign(Val) (ALIGN_UP(Val, ULONGLONG)) + +#define QuadAlignPtr(Ptr) (ALIGN_UP_POINTER(Ptr, ULONGLONG)) + +#define IsPtrQuadAligned(Ptr) (QuadAlignPtr(Ptr) == (PVOID)(Ptr)) + +// from wdm.h +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 + +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7) +#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 +#define FILE_DISALLOW_EXCLUSIVE 0x00020000 +#endif /* _WIN32_WINNT >= _WIN32_WINNT_WIN7 */ +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) +#define FILE_SESSION_AWARE 0x00040000 +#endif /* _WIN32_WINNT >= _WIN32_WINNT_WIN7 */ + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +#define FILE_VALID_OPTION_FLAGS 0x00ffffff + +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +#define FILE_WRITE_TO_END_OF_FILE 0xffffffff +#define FILE_USE_FILE_POINTER_POSITION 0xfffffffe + +/** + * \struct UNICODE_STRING + * \brief Structure is used to define Unicode strings. + */ +typedef struct _UNICODE_STRING { + /** + * The length, in bytes, of the string stored in Buffer. + */ + USHORT Length; + /** + * The length, in bytes, of Buffer. + */ + USHORT MaximumLength; + /** + * Pointer to a buffer used to contain a string of wide characters. + */ + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +#endif // FILEINFO_H_ diff --git a/MemProcFS/memprocfs.c b/MemProcFS/memprocfs.c new file mode 100644 index 0000000..f135fa2 --- /dev/null +++ b/MemProcFS/memprocfs.c @@ -0,0 +1,88 @@ +// memprocfs.h : implementation of core functionality for the Memory Process File System +// This is just a thin loader for the virtual memory manager dll which contains the logic. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include +#include +#include "vmmdll.h" +#include "vfs.h" + +/* +* Retrieve the mount point from the command line arguments. If no '-mount' +* command line argument is given the default mount point will be: M: +* -- argc +* -- argv +* -- return = the mount point as a drive letter. +*/ +CHAR GetMountPoint(_In_ DWORD argc, _In_ char* argv[]) +{ + CHAR chMountMount = 'M'; + DWORD i = 1; + for(i = 0; i < argc - 1; i++) { + if(0 == strcmp(argv[i], "-mount")) { + chMountMount = argv[i + 1][0]; + break; + } + } + if((chMountMount > 'A' && chMountMount < 'Z') || (chMountMount > 'a' && chMountMount < 'z')) { + return chMountMount; + } + return 'M'; +} + +/* +* Main entry point of the memory process file system. The main function will +* load and initialize VMM.DLL then initialize the VMM.DLL plugin manager and +* then hand over control to vfs.c!VfsInitializeAndMount which will start the +* dokany virtual file system and mount it at the correct mount point. +* All 'interesting' functionality will take part in VMM.DLL - the memprocfs +* executable should be considered as a thin wrapper around VMM.DLL. +* -- argc +* -- argv +* -- return +*/ +int main(_In_ int argc, _In_ char* argv[]) +{ + // DEBUG STUFF BELOW: + //LPSTR szTMP[] = { "", "-device", "fpga" }; + //LPSTR szTMP[] = { "", "-device", "c:\\temp\\WIN10-16299-248-1.pmem", "-v" }; + //argv = szTMP; + //argc = sizeof(szTMP) / sizeof(LPSTR); + // MAIN FUNCTION PROPER BELOW: + BOOL result; + HMODULE hVMM; + VMMDLL_FUNCTIONS VmmDll; + hVMM = LoadLibraryExA("vmm.dll", NULL, LOAD_LIBRARY_SEARCH_APPLICATION_DIR); + if(!hVMM) { + printf("MemProcFS: Error loading vmm.dll - ensure vmm.dll resides in the memprocfs.exe application directory!\n"); + return 1; + } + VmmDll.InitializeReserved = (BOOL(*)(DWORD, LPSTR*))GetProcAddress(hVMM, "VMMDLL_InitializeReserved"); + VmmDll.ConfigGet = (BOOL(*)(ULONG64, PULONG64))GetProcAddress(hVMM, "VMMDLL_ConfigGet"); + VmmDll.ConfigSet = (BOOL(*)(ULONG64, ULONG64))GetProcAddress(hVMM, "VMMDLL_ConfigSet"); + VmmDll.VfsList = (BOOL(*)(LPCWSTR, PVMMDLL_VFS_FILELIST))GetProcAddress(hVMM, "VMMDLL_VfsList"); + VmmDll.VfsRead = (DWORD(*)(LPCWSTR, LPVOID, DWORD, PDWORD, ULONG64))GetProcAddress(hVMM, "VMMDLL_VfsRead"); + VmmDll.VfsWrite = (DWORD(*)(LPCWSTR, LPVOID, DWORD, PDWORD, ULONG64))GetProcAddress(hVMM, "VMMDLL_VfsWrite"); + VmmDll.VfsInitializePlugins = (BOOL(*)())GetProcAddress(hVMM, "VMMDLL_VfsInitializePlugins"); + if(!VmmDll.InitializeReserved || !VmmDll.ConfigGet || !VmmDll.VfsList || !VmmDll.VfsRead || !VmmDll.VfsWrite || !VmmDll.VfsInitializePlugins) { + printf("MemProcFS: Error loading vmm.dll - invalid version of vmm.dll found!\n"); + return 1; + } + argv[0] = "-vdll"; + result = VmmDll.InitializeReserved(argc, argv); + if(!result) { + // any error message will already be shown by the InitializeReserved function. + return 1; + } + VmmDll.ConfigSet(VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL, 1); + result = VmmDll.VfsInitializePlugins(); + if(!result) { + printf("MemProcFS: Error file system plugins in vmm.dll!\n"); + return 1; + } + VfsInitializeAndMount(GetMountPoint(argc, argv), &VmmDll); + ExitProcess(0); + return 0; +} diff --git a/MemProcFS/public.h b/MemProcFS/public.h new file mode 100644 index 0000000..85dda3f --- /dev/null +++ b/MemProcFS/public.h @@ -0,0 +1,406 @@ +/* + Dokan : user-mode file system library for Windows + + Copyright (C) 2015 - 2018 Adrien J. and Maxime C. + Copyright (C) 2007 - 2011 Hiroki Asakawa + + http://dokan-dev.github.io + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 3 of the License, or (at your option) any +later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program. If not, see . +*/ + +#ifndef PUBLIC_H_ +#define PUBLIC_H_ + +#ifndef DOKAN_MAJOR_API_VERSION +#define DOKAN_MAJOR_API_VERSION L"1" +#include +#endif + +#define DOKAN_DRIVER_VERSION 0x0000190 + +#define EVENT_CONTEXT_MAX_SIZE (1024 * 32) + +#define IOCTL_TEST \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_SET_DEBUG_MODE \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_EVENT_WAIT \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_EVENT_INFO \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_EVENT_RELEASE \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_EVENT_START \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_EVENT_WRITE \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) + +#define IOCTL_KEEPALIVE \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS) + +#define IOCTL_SERVICE_WAIT \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_RESET_TIMEOUT \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_GET_ACCESS_TOKEN \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80C, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_EVENT_MOUNTPOINT_LIST \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80D, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_MOUNTPOINT_CLEANUP \ + CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80E, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define DRIVER_FUNC_INSTALL 0x01 +#define DRIVER_FUNC_REMOVE 0x02 + +#define DOKAN_MOUNTED 1 +#define DOKAN_USED 2 +#define DOKAN_START_FAILED 3 + +#define DOKAN_DEVICE_MAX 10 + +#define DOKAN_DEFAULT_SECTOR_SIZE 512 +#define DOKAN_DEFAULT_ALLOCATION_UNIT_SIZE 512 +#define DOKAN_DEFAULT_DISK_SIZE 1024 * 1024 * 1024 + +// used in CCB->Flags and FCB->Flags +#define DOKAN_FILE_DIRECTORY 1 +#define DOKAN_FILE_DELETED 2 +#define DOKAN_FILE_OPENED 4 +#define DOKAN_DIR_MATCH_ALL 8 +#define DOKAN_DELETE_ON_CLOSE 16 +#define DOKAN_PAGING_IO 32 +#define DOKAN_SYNCHRONOUS_IO 64 +#define DOKAN_WRITE_TO_END_OF_FILE 128 +#define DOKAN_NOCACHE 256 +#define DOKAN_FILE_CHANGE_LAST_WRITE 512 + +// used in DOKAN_START->DeviceType +#define DOKAN_DISK_FILE_SYSTEM 0 +#define DOKAN_NETWORK_FILE_SYSTEM 1 + +/* + * This structure is used for copying UNICODE_STRING from the kernel mode driver + * into the user mode driver. + * https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879(v=vs.85).aspx + */ +typedef struct _DOKAN_UNICODE_STRING_INTERMEDIATE { + USHORT Length; + USHORT MaximumLength; + WCHAR Buffer[1]; +} DOKAN_UNICODE_STRING_INTERMEDIATE, *PDOKAN_UNICODE_STRING_INTERMEDIATE; + +/* + * This structure is used for copying ACCESS_STATE from the kernel mode driver + * into the user mode driver. + * https://msdn.microsoft.com/en-us/library/windows/hardware/ff538840(v=vs.85).aspx +*/ +typedef struct _DOKAN_ACCESS_STATE_INTERMEDIATE { + BOOLEAN SecurityEvaluated; + BOOLEAN GenerateAudit; + BOOLEAN GenerateOnClose; + BOOLEAN AuditPrivileges; + ULONG Flags; + ACCESS_MASK RemainingDesiredAccess; + ACCESS_MASK PreviouslyGrantedAccess; + ACCESS_MASK OriginalDesiredAccess; + + // Offset from the beginning of this structure to a SECURITY_DESCRIPTOR + // if 0 that means there is no security descriptor + ULONG SecurityDescriptorOffset; + + // Offset from the beginning of this structure to a + // DOKAN_UNICODE_STRING_INTERMEDIATE + ULONG UnicodeStringObjectNameOffset; + + // Offset from the beginning of this structure to a + // DOKAN_UNICODE_STRING_INTERMEDIATE + ULONG UnicodeStringObjectTypeOffset; +} DOKAN_ACCESS_STATE_INTERMEDIATE, *PDOKAN_ACCESS_STATE_INTERMEDIATE; + +typedef struct _DOKAN_ACCESS_STATE { + BOOLEAN SecurityEvaluated; + BOOLEAN GenerateAudit; + BOOLEAN GenerateOnClose; + BOOLEAN AuditPrivileges; + ULONG Flags; + ACCESS_MASK RemainingDesiredAccess; + ACCESS_MASK PreviouslyGrantedAccess; + ACCESS_MASK OriginalDesiredAccess; + PSECURITY_DESCRIPTOR SecurityDescriptor; + UNICODE_STRING ObjectName; + UNICODE_STRING ObjectType; +} DOKAN_ACCESS_STATE, *PDOKAN_ACCESS_STATE; + +/* + * This structure is used for copying IO_SECURITY_CONTEXT from the kernel mode + * driver into the user mode driver. + * https://msdn.microsoft.com/en-us/library/windows/hardware/ff550613(v=vs.85).aspx + */ +typedef struct _DOKAN_IO_SECURITY_CONTEXT_INTERMEDIATE { + DOKAN_ACCESS_STATE_INTERMEDIATE AccessState; + ACCESS_MASK DesiredAccess; +} DOKAN_IO_SECURITY_CONTEXT_INTERMEDIATE, + *PDOKAN_IO_SECURITY_CONTEXT_INTERMEDIATE; + +typedef struct _DOKAN_IO_SECURITY_CONTEXT { + DOKAN_ACCESS_STATE AccessState; + ACCESS_MASK DesiredAccess; +} DOKAN_IO_SECURITY_CONTEXT, *PDOKAN_IO_SECURITY_CONTEXT; + +typedef struct _CREATE_CONTEXT { + DOKAN_IO_SECURITY_CONTEXT_INTERMEDIATE SecurityContext; + ULONG FileAttributes; + ULONG CreateOptions; + ULONG ShareAccess; + ULONG FileNameLength; + + // Offset from the beginning of this structure to the string + ULONG FileNameOffset; +} CREATE_CONTEXT, *PCREATE_CONTEXT; + +typedef struct _CLEANUP_CONTEXT { + ULONG FileNameLength; + WCHAR FileName[1]; + +} CLEANUP_CONTEXT, *PCLEANUP_CONTEXT; + +typedef struct _CLOSE_CONTEXT { + ULONG FileNameLength; + WCHAR FileName[1]; + +} CLOSE_CONTEXT, *PCLOSE_CONTEXT; + +typedef struct _DIRECTORY_CONTEXT { + ULONG FileInformationClass; + ULONG FileIndex; + ULONG BufferLength; + ULONG DirectoryNameLength; + ULONG SearchPatternLength; + ULONG SearchPatternOffset; + WCHAR DirectoryName[1]; + WCHAR SearchPatternBase[1]; + +} DIRECTORY_CONTEXT, *PDIRECTORY_CONTEXT; + +typedef struct _READ_CONTEXT { + LARGE_INTEGER ByteOffset; + ULONG BufferLength; + ULONG FileNameLength; + WCHAR FileName[1]; +} READ_CONTEXT, *PREAD_CONTEXT; + +typedef struct _WRITE_CONTEXT { + LARGE_INTEGER ByteOffset; + ULONG BufferLength; + ULONG BufferOffset; + ULONG RequestLength; + ULONG FileNameLength; + WCHAR FileName[2]; + // "2" means to keep last null of contents to write +} WRITE_CONTEXT, *PWRITE_CONTEXT; + +typedef struct _FILEINFO_CONTEXT { + ULONG FileInformationClass; + ULONG BufferLength; + ULONG FileNameLength; + WCHAR FileName[1]; +} FILEINFO_CONTEXT, *PFILEINFO_CONTEXT; + +typedef struct _SETFILE_CONTEXT { + ULONG FileInformationClass; + ULONG BufferLength; + ULONG BufferOffset; + ULONG FileNameLength; + WCHAR FileName[1]; +} SETFILE_CONTEXT, *PSETFILE_CONTEXT; + +typedef struct _VOLUME_CONTEXT { + ULONG FsInformationClass; + ULONG BufferLength; +} VOLUME_CONTEXT, *PVOLUME_CONTEXT; + +typedef struct _LOCK_CONTEXT { + LARGE_INTEGER ByteOffset; + LARGE_INTEGER Length; + ULONG Key; + ULONG FileNameLength; + WCHAR FileName[1]; +} LOCK_CONTEXT, *PLOCK_CONTEXT; + +typedef struct _FLUSH_CONTEXT { + ULONG FileNameLength; + WCHAR FileName[1]; +} FLUSH_CONTEXT, *PFLUSH_CONTEXT; + +typedef struct _UNMOUNT_CONTEXT { + WCHAR DeviceName[64]; + ULONG Option; +} UNMOUNT_CONTEXT, *PUNMOUNT_CONTEXT; + +typedef struct _SECURITY_CONTEXT { + SECURITY_INFORMATION SecurityInformation; + ULONG BufferLength; + ULONG FileNameLength; + WCHAR FileName[1]; +} SECURITY_CONTEXT, *PSECURITY_CONTEXT; + +typedef struct _SET_SECURITY_CONTEXT { + SECURITY_INFORMATION SecurityInformation; + ULONG BufferLength; + ULONG BufferOffset; + ULONG FileNameLength; + WCHAR FileName[1]; +} SET_SECURITY_CONTEXT, *PSET_SECURITY_CONTEXT; + +typedef struct _EVENT_CONTEXT { + ULONG Length; + ULONG MountId; + ULONG SerialNumber; + ULONG ProcessId; + UCHAR MajorFunction; + UCHAR MinorFunction; + ULONG Flags; + ULONG FileFlags; + ULONG64 Context; + union { + DIRECTORY_CONTEXT Directory; + READ_CONTEXT Read; + WRITE_CONTEXT Write; + FILEINFO_CONTEXT File; + CREATE_CONTEXT Create; + CLOSE_CONTEXT Close; + SETFILE_CONTEXT SetFile; + CLEANUP_CONTEXT Cleanup; + LOCK_CONTEXT Lock; + VOLUME_CONTEXT Volume; + FLUSH_CONTEXT Flush; + UNMOUNT_CONTEXT Unmount; + SECURITY_CONTEXT Security; + SET_SECURITY_CONTEXT SetSecurity; + } Operation; +} EVENT_CONTEXT, *PEVENT_CONTEXT; + +#define WRITE_MAX_SIZE \ + (EVENT_CONTEXT_MAX_SIZE - sizeof(EVENT_CONTEXT) - 256 * sizeof(WCHAR)) + +typedef struct _EVENT_INFORMATION { + ULONG SerialNumber; + NTSTATUS Status; + ULONG Flags; + union { + struct { + ULONG Index; + } Directory; + struct { + ULONG Flags; + ULONG Information; + } Create; + struct { + LARGE_INTEGER CurrentByteOffset; + } Read; + struct { + LARGE_INTEGER CurrentByteOffset; + } Write; + struct { + UCHAR DeleteOnClose; + } Delete; + struct { + ULONG Timeout; + } ResetTimeout; + struct { + HANDLE Handle; + } AccessToken; + } Operation; + ULONG64 Context; + ULONG BufferLength; + UCHAR Buffer[8]; + +} EVENT_INFORMATION, *PEVENT_INFORMATION; + +#define DOKAN_EVENT_ALTERNATIVE_STREAM_ON 1 +#define DOKAN_EVENT_WRITE_PROTECT 2 +#define DOKAN_EVENT_REMOVABLE 4 +#define DOKAN_EVENT_MOUNT_MANAGER 8 +#define DOKAN_EVENT_CURRENT_SESSION 16 +#define DOKAN_EVENT_FILELOCK_USER_MODE 32 + +typedef struct _EVENT_DRIVER_INFO { + ULONG DriverVersion; + ULONG Status; + ULONG DeviceNumber; + ULONG MountId; + WCHAR DeviceName[64]; +} EVENT_DRIVER_INFO, *PEVENT_DRIVER_INFO; + +typedef struct _EVENT_START { + ULONG UserVersion; + ULONG DeviceType; + ULONG Flags; + WCHAR MountPoint[260]; + WCHAR UNCName[64]; + ULONG IrpTimeout; +} EVENT_START, *PEVENT_START; + +#pragma warning(push) +#pragma warning(disable : 4201) +typedef struct _DOKAN_RENAME_INFORMATION { +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1) + union { + BOOLEAN ReplaceIfExists; // FileRenameInformation + ULONG Flags; // FileRenameInformationEx + } DUMMYUNIONNAME; +#else + BOOLEAN ReplaceIfExists; +#endif + ULONG FileNameLength; + WCHAR FileName[1]; +} DOKAN_RENAME_INFORMATION, *PDOKAN_RENAME_INFORMATION; +#pragma warning(pop) + +typedef struct _DOKAN_LINK_INFORMATION { + BOOLEAN ReplaceIfExists; + ULONG FileNameLength; + WCHAR FileName[1]; +} DOKAN_LINK_INFORMATION, *PDOKAN_LINK_INFORMATION; + +/** +* \struct DOKAN_CONTROL +* \brief Dokan Control +*/ +typedef struct _DOKAN_CONTROL { + /** File System Type */ + ULONG Type; + /** Mount point. Can be "M:\" (drive letter) or "C:\mount\dokan" (path in NTFS) */ + WCHAR MountPoint[MAX_PATH]; + /** UNC name used for network volume */ + WCHAR UNCName[64]; + /** Disk Device Name */ + WCHAR DeviceName[64]; + /** Volume Device Object */ + PVOID64 DeviceObject; + /** Session ID of calling process */ + ULONG SessionId; +} DOKAN_CONTROL, *PDOKAN_CONTROL; + +#endif // PUBLIC_H_ diff --git a/MemProcFS/vfs.c b/MemProcFS/vfs.c new file mode 100644 index 0000000..6bd89cb --- /dev/null +++ b/MemProcFS/vfs.c @@ -0,0 +1,482 @@ +// vfs.c : implementation of functions related to virtual file system support. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include +#include +#include "vfs.h" +#include "vmmdll.h" +#pragma warning( push ) +#pragma warning( disable : 4005 ) +#include "dokan.h" +#pragma warning( pop ) +//#define dbg_GetTickCount64() GetTickCount64() +//#define dbg_wprintf(format, ...) { wprintf(format, ##__VA_ARGS__); } +#define dbg_wprintf(format, ...) {} +#define dbg_GetTickCount64() 0 + +//------------------------------------------------------------------------------- +// DEFINES, TYPEDEFS AND FORWARD DECLARATIONS BELOW: +//------------------------------------------------------------------------------- + +#define VFS_CONFIG_FILELIST_ITEMS 12 +#define VFS_CONFIG_FILELIST_MAGIC 0x7f646555caffee66 +typedef struct tdVFS_FILELIST { + QWORD magic; + struct tdVFS_FILELIST* FLink; + DWORD cFiles; + WIN32_FIND_DATAW pFiles[VFS_CONFIG_FILELIST_ITEMS]; +} VFS_FILELIST, *PVFS_FILELIST; + +BOOL VfsListVmmDirectory(_In_ LPWSTR wszDirectoryName); +VOID Vfs_UtilSplitPathFile(_Out_ WCHAR wszPath[MAX_PATH], _Out_ LPWSTR *pwcsFile, _In_ LPCWSTR wcsFileName); + +//------------------------------------------------------------------------------- +// FILELIST FUNCTIONALITY BELOW: +// (directory listing functions/structs for communicating between vfs and vfsproc). +//------------------------------------------------------------------------------- + +PVFS_FILELIST VfsFileList_Alloc() +{ + PVFS_FILELIST pFileList = LocalAlloc(LMEM_ZEROINIT, sizeof(VFS_FILELIST)); + if(pFileList) { + pFileList->magic = VFS_CONFIG_FILELIST_MAGIC; + } + return pFileList; +} + +VOID VfsFileList_Free(_Inout_ PVFS_FILELIST pFileList) +{ + PVFS_FILELIST pFileListFlink; + while(pFileList) { + pFileListFlink = pFileList->FLink; + LocalFree(pFileList); + pFileList = pFileListFlink; + } +} + +VOID VfsFileList_AddDirectoryFileInternal(_Inout_ PVFS_FILELIST pFileList, _In_ DWORD dwFileAttributes, _In_ FILETIME ftCreationTime, _In_ FILETIME ftLastAccessTime, _In_ FILETIME ftLastWriteTime, _In_ DWORD nFileSizeHigh, _In_ DWORD nFileSizeLow, _In_ LPSTR szName) +{ + DWORD i = 0; + PWIN32_FIND_DATAW pFindData; + // 1: check if required to allocate more FileList items + while(pFileList->cFiles == VFS_CONFIG_FILELIST_ITEMS) { + if(pFileList->FLink) { + pFileList = pFileList->FLink; + continue; + } + pFileList->FLink = VfsFileList_Alloc(); + if(!pFileList->FLink) { return; } + pFileList = pFileList->FLink; + } + // 2: locate item to fill into + pFindData = pFileList->pFiles + pFileList->cFiles; + pFileList->cFiles++; + // 3: fill + pFindData->dwFileAttributes = dwFileAttributes; + pFindData->ftCreationTime = ftCreationTime; + pFindData->ftLastAccessTime = ftLastAccessTime; + pFindData->ftLastWriteTime = ftLastWriteTime; + pFindData->nFileSizeHigh = nFileSizeHigh; + pFindData->nFileSizeLow = nFileSizeLow; + while(i < MAX_PATH && szName[i]) { + pFindData->cFileName[i] = szName[i]; + i++; + } + pFindData->cFileName[i] = 0; +} + +VOID VfsFileList_AddFile(_Inout_ HANDLE hFileList, _In_ LPSTR szName, _In_ QWORD cb, _In_ PVOID pvReserved) +{ + PVFS_FILELIST pFileList2 = (PVFS_FILELIST)hFileList; + if(pFileList2 && (pFileList2->magic == VFS_CONFIG_FILELIST_MAGIC)) { + VfsFileList_AddDirectoryFileInternal( + pFileList2, + FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, + ctxVfs->ftDefaultTime, + ctxVfs->ftDefaultTime, + ctxVfs->ftDefaultTime, + (DWORD)(cb >> 32), + (DWORD)cb, + szName + ); + } +} + +VOID VfsFileList_AddDirectory(_Inout_ HANDLE hFileList, _In_ LPSTR szName, _In_ PVOID pvReserved) +{ + PVFS_FILELIST pFileList2 = (PVFS_FILELIST)hFileList; + if(pFileList2 && (pFileList2->magic == VFS_CONFIG_FILELIST_MAGIC)) { + VfsFileList_AddDirectoryFileInternal( + pFileList2, + FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, + ctxVfs->ftDefaultTime, + ctxVfs->ftDefaultTime, + ctxVfs->ftDefaultTime, + 0, + 0, + szName + ); + } +} + +VOID VfsFileList_DokanFillAll(PVFS_FILELIST pFileList, PDOKAN_FILE_INFO DokanFileInfo, PFillFindData FillFindData) +{ + DWORD i; + do { + for(i = 0; i < pFileList->cFiles; i++) { + FillFindData(pFileList->pFiles + i, DokanFileInfo); + } + pFileList = pFileList->FLink; + } while(pFileList); +} + +PWIN32_FIND_DATAW VfsFileList_FindSingle(_In_ PVFS_FILELIST pFileList, _In_ LPWSTR wszFile) +{ + DWORD i; + do { + for(i = 0; i < pFileList->cFiles; i++) { + if(!wcscmp(wszFile, pFileList->pFiles[i].cFileName)) { + return pFileList->pFiles + i; + } + } + pFileList = pFileList->FLink; + } while(pFileList); + return NULL; +} + +//------------------------------------------------------------------------------- +// DIRECTORY LISTINGS READ CACHE BELOW: +// (caching is used to cache vmmproc directory listings for performance reasons) +//------------------------------------------------------------------------------- + +BOOL VfsCacheDirectory_GetSingle2(_In_ LPWSTR wszPath, _In_ LPWSTR wszFile, _Out_ PWIN32_FIND_DATAW pFindData, _Out_ PBOOL pIsDirectoryExisting) +{ + QWORD i, qwCurrentTickCount; + PWIN32_FIND_DATAW pFindDataCache; + qwCurrentTickCount = GetTickCount64(); + *pIsDirectoryExisting = FALSE; + EnterCriticalSection(&ctxVfs->CacheDirectoryLock); + for(i = 0; i < VMMVFS_CACHE_DIRECTORY_ENTRIES; i++) { + if(wcscmp(wszPath, ctxVfs->CacheDirectory[i].wszDirectoryName)) { continue; } + if(qwCurrentTickCount > ctxVfs->CacheDirectory[i].qwExpireTickCount64) { continue; } + *pIsDirectoryExisting = TRUE; + pFindDataCache = VfsFileList_FindSingle(ctxVfs->CacheDirectory[i].pFileList, wszFile); + if(!pFindDataCache) { + LeaveCriticalSection(&ctxVfs->CacheDirectoryLock); + return FALSE; + } + if(pFindData) { + memcpy(pFindData, pFindDataCache, sizeof(WIN32_FIND_DATAW)); + } + LeaveCriticalSection(&ctxVfs->CacheDirectoryLock); + return TRUE; + } + LeaveCriticalSection(&ctxVfs->CacheDirectoryLock); + return FALSE; +} + +BOOL VfsCacheDirectory_GetSingle(_In_ LPWSTR wszPath, _In_ LPWSTR wszFile, _Out_ PWIN32_FIND_DATAW pFindData) +{ + BOOL result, isDirectoryExisting; + result = VfsCacheDirectory_GetSingle2(wszPath, wszFile, pFindData, &isDirectoryExisting); + if(result) { return TRUE; } + if(isDirectoryExisting) { return FALSE; } + return VfsListVmmDirectory(wszPath) && VfsCacheDirectory_GetSingle2(wszPath, wszFile, pFindData, &isDirectoryExisting); +} + +BOOL VfsCacheDirectory_DokanFillDirectory(_In_ LPCWSTR wcsPathFileName, _In_ PFillFindData FillFindData, _Inout_ PDOKAN_FILE_INFO DokanFileInfo) +{ + QWORD i, qwCurrentTickCount; + qwCurrentTickCount = GetTickCount64(); + EnterCriticalSection(&ctxVfs->CacheDirectoryLock); + for(i = 0; i < VMMVFS_CACHE_DIRECTORY_ENTRIES; i++) { + if(wcscmp(wcsPathFileName, ctxVfs->CacheDirectory[i].wszDirectoryName)) { continue; } + if(qwCurrentTickCount > ctxVfs->CacheDirectory[i].qwExpireTickCount64) { continue; } + VfsFileList_DokanFillAll(ctxVfs->CacheDirectory[i].pFileList, DokanFileInfo, FillFindData); + LeaveCriticalSection(&ctxVfs->CacheDirectoryLock); + return TRUE; + } + LeaveCriticalSection(&ctxVfs->CacheDirectoryLock); + return FALSE; +} + +VOID VfsCacheDirectory_Put(_In_ LPCWSTR wcsDirectoryName, _In_ PVFS_FILELIST pFileList) +{ + EnterCriticalSection(&ctxVfs->CacheDirectoryLock); + ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].qwExpireTickCount64 = GetTickCount64() + VMMVFS_CACHE_DIRECTORY_LIFETIME_PROC_MS; + wcscpy_s(ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].wszDirectoryName, MAX_PATH, wcsDirectoryName); + VfsFileList_Free(ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].pFileList); + ctxVfs->CacheDirectory[ctxVfs->CacheDirectoryIndex].pFileList = pFileList; + ctxVfs->CacheDirectoryIndex = (ctxVfs->CacheDirectoryIndex + 1) % VMMVFS_CACHE_DIRECTORY_ENTRIES; + LeaveCriticalSection(&ctxVfs->CacheDirectoryLock); +} + +VOID VfsCacheDirectory_Close() +{ + DWORD i; + EnterCriticalSection(&ctxVfs->CacheDirectoryLock); + for(i = 0; i < VMMVFS_CACHE_DIRECTORY_ENTRIES; i++) { + ctxVfs->CacheDirectory[i].qwExpireTickCount64 = 0; + VfsFileList_Free(ctxVfs->CacheDirectory[i].pFileList); + ctxVfs->CacheDirectory[i].pFileList = NULL; + } + LeaveCriticalSection(&ctxVfs->CacheDirectoryLock); +} + +//------------------------------------------------------------------------------- +// UTILITY FUNCTIONS BELOW: +//------------------------------------------------------------------------------- + +BOOL VfsListVmmDirectory(_In_ LPWSTR wszDirectoryName) +{ + BOOL result; + PVFS_FILELIST pFileList = VfsFileList_Alloc(ctxVfs->ftDefaultTime); + VMMDLL_VFS_FILELIST VfsFileList; + if(!pFileList) { return FALSE; } + VfsFileList.h = (HANDLE)pFileList; + VfsFileList.pfnAddFile = VfsFileList_AddFile; + VfsFileList.pfnAddDirectory = VfsFileList_AddDirectory; + result = ctxVfs->pVmmDll->VfsList(wszDirectoryName, &VfsFileList); + if(!result) { + VfsFileList_Free(pFileList); + return FALSE; + } + VfsCacheDirectory_Put(wszDirectoryName, pFileList); // do not free pFileList since it's put into the cache + return TRUE; +} + +VOID Vfs_UtilSplitPathFile(_Out_ WCHAR wszPath[MAX_PATH], _Out_ LPWSTR *pwcsFile, _In_ LPCWSTR wcsFileName) +{ + DWORD i, iSplitFilePath = 0; + wcsncpy_s(wszPath, MAX_PATH, wcsFileName, _TRUNCATE); + for(i = 0; i < MAX_PATH; i++) { + if(wszPath[i] == '\\') { + iSplitFilePath = i; + } + if(wszPath[i] == 0) { + break; + } + } + wszPath[iSplitFilePath] = 0; + *pwcsFile = wszPath + iSplitFilePath + 1; +} + +//------------------------------------------------------------------------------- +// DOKAN CALLBACK FUNCTIONS BELOW: +//------------------------------------------------------------------------------- + +NTSTATUS DOKAN_CALLBACK +VfsCallback_CreateFile(LPCWSTR wcsFileName, PDOKAN_IO_SECURITY_CONTEXT SecurityContext, ACCESS_MASK DesiredAccess, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PDOKAN_FILE_INFO DokanFileInfo) +{ + UINT64 tmStart = dbg_GetTickCount64(); + NTSTATUS nt; + BOOL result; + WIN32_FIND_DATAW FindData; + WCHAR wszPath[MAX_PATH]; + LPWSTR wszFile; + dbg_wprintf(L"DEBUG:: -------- VfsCallback_CreateFile:\t\t 0x%08x %s\n", 0, wcsFileName); + UNREFERENCED_PARAMETER(SecurityContext); + UNREFERENCED_PARAMETER(FileAttributes); + UNREFERENCED_PARAMETER(CreateOptions); + // root directory + if(!wcscmp(wcsFileName, L"\\")) { + if(CreateDisposition == CREATE_ALWAYS) { + nt = ctxVfs->DokanNtStatusFromWin32(ERROR_ACCESS_DENIED); + dbg_wprintf(L"DEBUG:: %8x VfsCallback_CreateFile:\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName); + return nt; + } + DokanFileInfo->IsDirectory = TRUE; + dbg_wprintf(L"DEBUG:: %8x VfsCallback_CreateFile:\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName); + return STATUS_SUCCESS; + } + // other files + if(CreateDisposition == CREATE_ALWAYS) { + nt = ctxVfs->DokanNtStatusFromWin32(ERROR_ACCESS_DENIED); + dbg_wprintf(L"DEBUG:: %8x VfsCallback_CreateFile:\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName); + return nt; + } + Vfs_UtilSplitPathFile(wszPath, &wszFile, wcsFileName); + result = VfsCacheDirectory_GetSingle(wszPath[0] ? wszPath : L"\\", wszFile, &FindData); + if(!result) { + dbg_wprintf(L"DEBUG:: %8x VfsCallback_CreateFile:\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_FILE_INVALID, wcsFileName); + return STATUS_FILE_INVALID; + } + DokanFileInfo->IsDirectory = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? TRUE : FALSE; + DokanFileInfo->Nocache = TRUE; + nt = (CreateDisposition == OPEN_ALWAYS) ? STATUS_OBJECT_NAME_COLLISION : STATUS_SUCCESS; + dbg_wprintf(L"DEBUG:: %8x VfsCallback_CreateFile:\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName); + return nt; +} + +NTSTATUS DOKAN_CALLBACK +VfsCallback_GetFileInformation(_In_ LPCWSTR wcsFileName, _Inout_ LPBY_HANDLE_FILE_INFORMATION hfi, _In_ PDOKAN_FILE_INFO DokanFileInfo) +{ + UINT64 tmStart = dbg_GetTickCount64(); + BOOL result; + WIN32_FIND_DATAW FindData; + WCHAR wszPath[MAX_PATH]; + LPWSTR wszFile; + dbg_wprintf(L"DEBUG:: -------- VfsCallback_GetFileInformation:\t 0x%08x %s\n", 0, wcsFileName); + // matches: root directory + if(!wcscmp(wcsFileName, L"\\")) { + hfi->ftCreationTime = ctxVfs->ftDefaultTime; + hfi->ftLastWriteTime = ctxVfs->ftDefaultTime; + hfi->ftLastAccessTime = ctxVfs->ftDefaultTime; + hfi->nFileSizeHigh = 0; + hfi->nFileSizeLow = 0; + hfi->dwFileAttributes = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + dbg_wprintf(L"DEBUG:: %8x VfsCallback_GetFileInformation:\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName); + return STATUS_SUCCESS; + } + Vfs_UtilSplitPathFile(wszPath, &wszFile, wcsFileName); + result = VfsCacheDirectory_GetSingle((wszPath[0] ? wszPath : L"\\"), wszFile, &FindData); + if(!result) { + dbg_wprintf(L"DEBUG:: %8x VfsCallback_GetFileInformation:\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_FILE_NOT_AVAILABLE, wcsFileName); + return STATUS_FILE_NOT_AVAILABLE; + } + hfi->dwFileAttributes = FindData.dwFileAttributes; + hfi->ftCreationTime = FindData.ftCreationTime; + hfi->ftLastAccessTime = FindData.ftLastAccessTime; + hfi->ftLastWriteTime = FindData.ftLastWriteTime; + hfi->nFileSizeHigh = FindData.nFileSizeHigh; + hfi->nFileSizeLow = FindData.nFileSizeLow; + dbg_wprintf(L"DEBUG:: %8x VfsCallback_GetFileInformation:\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName); + return STATUS_SUCCESS; +} + +NTSTATUS DOKAN_CALLBACK +VfsCallback_FindFiles(LPCWSTR wcsFileName, PFillFindData FillFindData, PDOKAN_FILE_INFO DokanFileInfo) +{ + UINT64 tmStart = dbg_GetTickCount64(); + BOOL result; + dbg_wprintf(L"DEBUG:: -------- VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", 0, wcsFileName); + result = VfsCacheDirectory_DokanFillDirectory(wcsFileName, FillFindData, DokanFileInfo); + if(result) { + dbg_wprintf(L"DEBUG:: %8x VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName); + return STATUS_SUCCESS; + } + VfsListVmmDirectory((LPWSTR)wcsFileName); + VfsCacheDirectory_DokanFillDirectory(wcsFileName, FillFindData, DokanFileInfo); + dbg_wprintf(L"DEBUG:: %8x VfsCallback_FindFiles:\t\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), STATUS_SUCCESS, wcsFileName); + return STATUS_SUCCESS; +} + +NTSTATUS DOKAN_CALLBACK +VfsCallback_ReadFile(LPCWSTR wcsFileName, LPVOID Buffer, DWORD BufferLength, LPDWORD ReadLength, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo) +{ + UINT64 tmStart = dbg_GetTickCount64(); + NTSTATUS nt; + dbg_wprintf(L"DEBUG:: -------- VfsCallback_ReadFile:\t\t\t 0x%08x %s\n", 0, wcsFileName); + nt = ctxVfs->pVmmDll->VfsRead(wcsFileName, Buffer, BufferLength, ReadLength, Offset); + dbg_wprintf(L"DEBUG:: %8x VfsCallback_ReadFile:\t\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName); + return nt; +} + +NTSTATUS DOKAN_CALLBACK +VfsCallback_WriteFile(LPCWSTR wcsFileName, LPCVOID Buffer, DWORD NumberOfBytesToWrite, LPDWORD NumberOfBytesWritten, LONGLONG Offset, PDOKAN_FILE_INFO DokanFileInfo) +{ + UINT64 tmStart = dbg_GetTickCount64(); + NTSTATUS nt; + dbg_wprintf(L"DEBUG:: -------- VfsCallback_WriteFile:\t\t\t 0x%08x %s\n", 0, wcsFileName); + nt = ctxVfs->pVmmDll->VfsWrite(wcsFileName, (PBYTE)Buffer, NumberOfBytesToWrite, NumberOfBytesWritten, Offset); + dbg_wprintf(L"DEBUG:: %8x VfsCallback_WriteFile:\t\t\t 0x%08x %s\n", (DWORD)(dbg_GetTickCount64() - tmStart), nt, wcsFileName); + return nt; +} + +//------------------------------------------------------------------------------- +// VFS INITIALIZATION FUNCTIONALITY BELOW: +//------------------------------------------------------------------------------- + +VOID VfsClose() +{ + if(ctxVfs && ctxVfs->fInitialized) { + VfsCacheDirectory_Close(); + DeleteCriticalSection(&ctxVfs->CacheDirectoryLock); + } + if(ctxVfs) { + LocalFree(ctxVfs); + ctxVfs = NULL; + } +} + +VOID VfsInitializeAndMount(_In_ CHAR chMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll) +{ + int status; + HMODULE hModuleDokan = NULL; + PDOKAN_OPTIONS pDokanOptions = NULL; + PDOKAN_OPERATIONS pDokanOperations = NULL; + WCHAR wszMountPoint[] = { 'M', ':', '\\', 0 }; + SYSTEMTIME SystemTimeNow; + int(*fnDokanMain)(PDOKAN_OPTIONS, PDOKAN_OPERATIONS); + ULONG64 qwVersionMajor = 0, qwVersionMinor = 0, qwVersionRevision = 0; + // get versions + pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR, &qwVersionMajor); + pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR, &qwVersionMinor); + pVmmDll->ConfigGet(VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION, &qwVersionRevision); + // allocate + hModuleDokan = LoadLibraryExA("dokan1.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + fnDokanMain = (int(*)(PDOKAN_OPTIONS, PDOKAN_OPERATIONS))GetProcAddress(hModuleDokan, "DokanMain"); + if(!hModuleDokan || !fnDokanMain) { + printf("MOUNT: Failed. The required DOKANY file system library is not installed. \n"); + printf("Please download from : https://github.com/dokan-dev/dokany/releases/latest\n"); + goto fail; + } + pDokanOptions = (PDOKAN_OPTIONS)LocalAlloc(LMEM_ZEROINIT, sizeof(DOKAN_OPTIONS)); + pDokanOperations = (PDOKAN_OPERATIONS)LocalAlloc(LMEM_ZEROINIT, sizeof(DOKAN_OPERATIONS)); + if(!pDokanOptions || !pDokanOperations) { + printf("MOUNT: Failed (out of memory).\n"); + goto fail; + } + // allocate empty vfs context + ctxVfs = (PVMMVFS_CONFIG)LocalAlloc(LMEM_ZEROINIT, sizeof(VMMVFS_CONFIG)); + if(!ctxVfs) { goto fail; } + ctxVfs->pVmmDll = pVmmDll; + // set vfs context + GetSystemTime(&SystemTimeNow); + SystemTimeToFileTime(&SystemTimeNow, &ctxVfs->ftDefaultTime); + InitializeCriticalSection(&ctxVfs->CacheDirectoryLock); + ctxVfs->DokanNtStatusFromWin32 = (NTSTATUS(*)(DWORD))GetProcAddress(hModuleDokan, "DokanNtStatusFromWin32"); + ctxVfs->fInitialized = TRUE; + // set options + pDokanOptions->Version = DOKAN_VERSION; + pDokanOptions->Options |= DOKAN_OPTION_NETWORK; + pDokanOptions->UNCName = L"\\\\frizk.net\\MemoryProcessFileSystem"; + wszMountPoint[0] = chMountPoint; + pDokanOptions->MountPoint = wszMountPoint; + pDokanOptions->Timeout = 60000; + // set callbacks + pDokanOperations->ZwCreateFile = VfsCallback_CreateFile; + pDokanOperations->GetFileInformation = VfsCallback_GetFileInformation; + pDokanOperations->FindFiles = VfsCallback_FindFiles; + pDokanOperations->ReadFile = VfsCallback_ReadFile; + pDokanOperations->WriteFile = VfsCallback_WriteFile; + // enable + printf( + "MOUNTING THE MEMORY PROCESS FILE SYSTEM \n" \ + "===============================================================================\n" \ + "The Memory Process File System is mounted as: %S \n" \ + "Loaded VmmDll Version: %i.%i.%i \n" \ + "Memory from dump files or PCILeech supported devices are analyzed to provide \n" \ + "a convenient process file system for analysis purposes. \n" \ + " - File system is read-only when dump files are used. \n" \ + " - File system is read-write when FPGA hardware acquisition devices are used. \n" \ + " - Full support exists for x64 Windows operating systems. \n" \ + " - Limited support for other x64 operating systems. \n" \ + " - Memory Process File System: https://github.com/ufrisk/MemProcFS \n" \ + " - File system by: Ulf Frisk - pcileech@frizk.net - https://frizk.net \n" \ + "===============================================================================\n", + pDokanOptions->MountPoint, (DWORD)qwVersionMajor, (DWORD)qwVersionMinor, (DWORD)qwVersionRevision); + status = fnDokanMain(pDokanOptions, pDokanOperations); + while(status == DOKAN_SUCCESS) { + printf("MOUNT: ReMounting as drive %S\n", pDokanOptions->MountPoint); + status = fnDokanMain(pDokanOptions, pDokanOperations); + } + printf("MOUNT: Failed. Status Code: %i\n", status); +fail: + if(hModuleDokan) { FreeLibrary(hModuleDokan); } + if(pDokanOptions) { LocalFree(pDokanOptions); } + if(pDokanOperations) { LocalFree(pDokanOperations); } + VfsClose(); +} diff --git a/MemProcFS/vfs.h b/MemProcFS/vfs.h new file mode 100644 index 0000000..536b141 --- /dev/null +++ b/MemProcFS/vfs.h @@ -0,0 +1,60 @@ +// vfs.h : definitions related to virtual file system support. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __VFS_H__ +#define __VFS_H__ +#include +#include "vmmdll.h" + +typedef unsigned __int64 QWORD, *PQWORD; + +#define VMMVFS_CACHE_DIRECTORY_ENTRIES 15 +#define VMMVFS_CACHE_DIRECTORY_LIFETIME_PROC_MS 500 + +typedef struct tdVMMDLL_FUNCTIONS { + BOOL(*InitializeReserved)(_In_ DWORD argc, _In_ LPSTR argv[]); + BOOL(*VfsList)(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + DWORD(*VfsRead)(LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + DWORD(*VfsWrite)(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + BOOL(*VfsInitializePlugins)(); + BOOL(*ConfigGet)(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + BOOL(*ConfigSet)(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); +} VMMDLL_FUNCTIONS, *PVMMDLL_FUNCTIONS; + +typedef struct tdVMMVFS_CONFIG { + PVMMDLL_FUNCTIONS pVmmDll; + FILETIME ftDefaultTime; + NTSTATUS(*DokanNtStatusFromWin32)(DWORD Error); + CRITICAL_SECTION CacheDirectoryLock; + BOOL fInitialized; + QWORD CacheDirectoryIndex; + struct { + QWORD qwExpireTickCount64; + WCHAR wszDirectoryName[MAX_PATH]; + PVOID pFileList; + } CacheDirectory[VMMVFS_CACHE_DIRECTORY_ENTRIES]; +} VMMVFS_CONFIG, *PVMMVFS_CONFIG; + +PVMMVFS_CONFIG ctxVfs; + +/* +* Mount a drive backed by the Memory Process File System. The mounted file system +* will contain both a memory mapped ram files and the file system as seen from +* the target system kernel. NB! This action requires a loaded kernel module and +* that the Dokany file system library and driver have been installed. Please +* see: https://github.com/dokan-dev/dokany/releases +* This also initializes the globalcontext ctxVfs that should be closed by +* calling VfsClose on exit. +* -- chMountPoint +* -- pVmmDll +*/ +VOID VfsInitializeAndMount(_In_ CHAR chMountPoint, _In_ PVMMDLL_FUNCTIONS pVmmDll); + +/* +* Close a vfs sub-context in ctxVfs - if exists. +*/ +VOID VfsClose(); + +#endif /* __VFS_H__ */ diff --git a/MemProcFS/vmmdll.h b/MemProcFS/vmmdll.h new file mode 100644 index 0000000..623a09b --- /dev/null +++ b/MemProcFS/vmmdll.h @@ -0,0 +1,566 @@ +// vmmdll.h : header file to include in projects that use vmm.dll either as +// stand anlone projects or as native plugins to vmm.dll. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include + +#ifndef __VMMDLL_H__ +#define __VMMDLL_H__ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +/* +* RESERVED FUNCTION! DO NOT USE! +* Call other VMMDLL_Intialize functions to initialize VMM.DLL and the memory +* process file system. +*/ +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]); + +/* +* Initialize VMM.DLL from a memory dump file in raw format. VMM.DLL will be +* initialized in read-only mode. It's possible to optionally specify the page +* table base of the windows kernel (for full vmm features) or the page table +* base of a single 64-bit process in any x64 operating system. NB! usually it +* is not necessary to specify the PageTableBase - it will be auto-identified +* most often if the target is Windows. +* -- szFileName = the file name of the raw memory dump to use. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Intiailize VMM.DLL from a supported FPGA device over USB. VMM.DLL will be +* initialized in read/write mode upon success. Optionally it will be possible +* to specify the max physical address and the page table base of the kernel or +* process that should be investigated. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* -- szMaxPhysicalAddressOpt = max physical address of the target system as a +* hex string. Example: "0x8000000000". If zero value is given the max +* address will be auto-identified. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Initialize VMM.DLL from a the "Total Meltdown" CVE-2018-1038 vulnerability. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* initialized in read/write mode upon success. +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeTotalMeltdown(); + +/* +* Close an initialized instance of VMM.DLL and clean up all allocated resources +* including plugins, linked PCILeech.DLL and other memory resources. +* -- return = success/fail. +*/ +BOOL VMMDLL_Close(); + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +/* +* Options used together with the functions: VMMDLL_GetOption & VMMDLL_SetOption +* If VMM.DLL is chained with PCILEECH.DLL then required values will be passed +* along to PCILEECH.DLL when necessary. +* For more detailed information check the sources for individual device types. +*/ +#define VMMDLL_OPT_DEVICE_FPGA_PROBE_MAXPAGES 0x01 // RW +#define VMMDLL_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_RX 0x03 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_TX 0x04 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_READ 0x05 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE 0x06 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_WRITE 0x07 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_READ 0x08 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_RETRY_ON_ERROR 0x09 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DEVICE_ID 0x80 // R +#define VMMDLL_OPT_DEVICE_FPGA_FPGA_ID 0x81 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR 0x82 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR 0x83 // R + +#define VMMDLL_OPT_CORE_PRINTF_ENABLE 0x80000001 // RW +#define VMMDLL_OPT_CORE_VERBOSE 0x80000002 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R +#define VMMDLL_OPT_CORE_TARGET_SYSTEM 0x80000007 // R + +#define VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED 0x40000001 // R - 1/0 +#define VMMDLL_OPT_CONFIG_TICK_PERIOD 0x40000002 // RW - base tick period in ms +#define VMMDLL_OPT_CONFIG_READCACHE_TICKS 0x40000003 // RW - memory cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_TLBCACHE_TICKS 0x40000004 // RW - page table (tlb) cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL 0x40000005 // RW - process refresh (partial) period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL 0x40000006 // RW - process refresh (full) period (in ticks) +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR 0x40000007 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR 0x40000008 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION 0x40000009 // R +#define VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL 0x4000000A // RW - enable function call statistics (.status/statistics_fncall file) + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- pqwValue = pointer to ULONG64 to receive option value. +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- qwValue +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +// This is the core of the memory process file system. All implementation and +// analysis towards the file system is possible by using functionality below. +//----------------------------------------------------------------------------- + +#define VMMDLL_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMMDLL_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMMDLL_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMMDLL_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMMDLL_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +typedef struct tdVMMDLL_VFS_FILELIST { + VOID(*pfnAddFile) (_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved); + VOID(*pfnAddDirectory)(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved); + HANDLE h; +} VMMDLL_VFS_FILELIST, *PVMMDLL_VFS_FILELIST; + +/* +* Helper function macros for callbacks into the VMM_VFS_FILELIST structure. +*/ +#define VMMDLL_VfsList_AddFile(pFileList, szName, cb) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddFile(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, cb, NULL); } +#define VMMDLL_VfsList_AddDirectory(pFileList, szName) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddDirectory(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, NULL); } + +/* +* List a directory of files in the memory process file system. Directories and +* files will be listed by callbacks into functions supplied in the pFileList +* parameter. If information of an individual file is needed it's neccessary +* to list all files in its directory. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + +/* +* Read select parts of a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +* +*/ +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + +/* +* Write select parts to a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + +/* +* Utility functions for memory process file system read/write towards different +* underlying data representations. +*/ +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow); + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +// Function and structures to initialize and use the memory process file system +// plugin functionality. The plugin manager is started by a call to function: +// VMM_VfsInitializePlugins. Each built-in plugin and external plugin of which +// the DLL name matches m_*.dll will receive a call to its InitializeVmmPlugin +// function. The plugin/module may decide to call pfnPluginManager_Register to +// register plugins in the form of different names one or more times. +// Example of registration function in a plugin DLL below: +// 'VOID InitializeVmmPlugin(_In_ PVMM_PLUGIN_REGINFO pRegInfo)' +//----------------------------------------------------------------------------- + +/* +* Initialize all potential plugins, both built-in and external, that maps into +* the memory process file system. Please note that plugins are not loaded by +* default - they have to be explicitly loaded by calling this function. They +* will be unloaded on a general close of the vmm dll. +* -- return +*/ +BOOL VMMDLL_VfsInitializePlugins(); + +#define VMMDLL_PLUGIN_CONTEXT_MAGIC 0xc0ffee663df9301c +#define VMMDLL_PLUGIN_CONTEXT_VERSION 1 +#define VMMDLL_PLUGIN_REGINFO_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PLUGIN_REGINFO_VERSION 1 + +#define VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE 0x01 + +typedef struct tdVMMDLL_PLUGIN_CONTEXT { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD dwPID; + PHANDLE phModulePrivate; + PHANDLE phProcessPrivate; + PVOID pProcess; + LPSTR szModule; + LPSTR szPath; + PVOID pvReserved1; + PVOID pvReserved2; +} VMMDLL_PLUGIN_CONTEXT, *PVMMDLL_PLUGIN_CONTEXT; + +typedef struct tdVMMDLL_PLUGIN_REGINFO { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; + HMODULE hDLL; + HMODULE hReservedDll; // not for general use (only used for python). + BOOL(*pfnPluginManager_Register)(struct tdVMMDLL_PLUGIN_REGINFO *pPluginRegInfo); + PVOID pvReserved1; + PVOID pvReserved2; + // general plugin registration info to be filled out by the plugin below: + struct { + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + PVOID pvReserved1; + PVOID pvReserved2; + } reg_info; + // function plugin registration info to be filled out by the plugin below: + struct { + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + PVOID pvReserved1; + PVOID pvReserved2; + } reg_fn; +} VMMDLL_PLUGIN_REGINFO, *PVMMDLL_PLUGIN_REGINFO; + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +// Vmm core functaionlity such as read (and write) to both virtual and physical +// memory. NB! writing will only work if the target is supported - i.e. not a +// memory dump file... +// To read physical memory specify dwPID as (DWORD)-1 +//----------------------------------------------------------------------------- + +// FLAG used to supress the default read cache in calls to VMM_MemReadEx() +// which will lead to the read being fetched from the target system always. +// Cached page tables (used for translating virtual2physical) are still used. +#define VMMDLL_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMMDLL_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMMDLL_TARGET_UNKNOWN_X64 0x0001 +#define VMMDLL_TARGET_WINDOWS_X64 0x0002 + +typedef struct tdVMMDLL_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} VMMDLL_MEM_IO_SCATTER_HEADER, *PVMMDLL_MEM_IO_SCATTER_HEADER, **PPVMMDLL_MEM_IO_SCATTER_HEADER; + +/* +* Read memory in various non-contigious locations specified by the pointers to +* the items in the ppDMAs array. Result for each unit of work will be given +* individually. No upper limit of number of items to read, but no performance +* boost will be given if above hardware limit. Max size of each unit of work is +* one 4k page (4096 bytes). +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpDMAsRead = optional count of number of successfully read ppDMAs. +* -- flags = optional flags as given by VMM_FLAG_* +* -- return = the number of successfully read items. +*/ +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); + +/* +* Read a single 4096-byte page of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pbPage +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Read a contigious arbitrary amount of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious amount of memory and report the number of bytes read in pcbRead. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +* -- return = success/fail. NB! reads may report as success even if 0 bytes are +* read - it's recommended to verify pcbReadOpt parameter. +*/ +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + +/* +* Write a contigious arbitrary amount of memory. Please note some virtual memory +* such as pages of executables (such as DLLs) may be shared between different +* virtual memory over different processes. As an example a write to kernel32.dll +* in one process is likely to affect kernel32 in the whole system - in all +* processes. Heaps and Stacks and other memory are usually safe to write to. +* Please take care when writing to memory! +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Translate a virtual address to a physical address by walking the page tables +* of the specified process. +* -- dwPID +* -- qwVA +* -- pqwPA +* -- return = success/fail. +*/ +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +// Functionality below is mostly relating to Windows processes. +//----------------------------------------------------------------------------- + +/* +* Retrieve an active process given it's name. Please note that if multiple +* processes with the same name exists only one will be returned. If required to +* parse all processes with the same name please iterate over the PID list by +* calling VMMDLL_PidList together with VMMDLL_ProcessGetInformation. +* -- szProcName = process name (truncated max 15 chars) case insensitive. +* -- pdwPID = pointer that will receive PID on success. +* -- return +*/ +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + +/* +* List the PIDs in the system. +* -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. +* -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. +* -- return = success/fail. +*/ +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + +// flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define VMMDLL_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMMDLL_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMMDLL_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMMDLL_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +typedef struct tdVMMDLL_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMMDLL_MEMMAP_ENTRY, *PVMMDLL_MEMMAP_ENTRY; + +/* +* Retrieve memory map entries from the specified process. Memory map entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries bytes. +* If the pMemMapEntries is set to NULL the number of memory map entries will be +* given in the pcMemMapEntries parameter. +* -- dwPID +* -- pMemMapEntries = buffer of minimum length sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. +* -- pcMemMapEntries = pointer to number of memory map entries. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + +/* +* Retrieve a single memory map entry given a virtual address within that entrys +* range. +* -- dwPID +* -- pMemMapEntry +* -- va = virtual address in the memory map entry to retrieve. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules); + +typedef struct tdVMMDLL_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; +} VMMDLL_MODULEMAP_ENTRY, *PVMMDLL_MODULEMAP_ENTRY; + +/* +* Retrieve the module entries from the specified process. The module entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the +* pcModuleEntries is set to NULL the number of module entries will be given +* in the pcModuleEntries parameter. +* -- dwPID +* -- pModuleEntries = buffer of minimum length sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. +* -- pcModuleEntries = pointer to number of memory map entries. +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + +/* +* Retrieve a module (.exe or .dll or similar) given a module name. +* -- dwPID +* -- szModuleName +* -- pModuleEntry +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry); + +#define VMMDLL_PROCESS_INFORMATION_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PROCESS_INFORMATION_VERSION 1 + +typedef struct tdVMMDLL_PROCESS_INFORMATION { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; // as given by VMMDLL_TARGET_* + BOOL fUserOnly; // only user mode pages listed + DWORD dwPID; + DWORD dwState; + CHAR szName[16]; + ULONG64 paPML4; + ULONG64 paPML4_UserOpt; // may not exist + union { + struct { + ULONG64 vaEPROCESS; + ULONG64 vaPEB; + ULONG64 vaENTRY; + BOOL fWow64; + DWORD vaPEB32; // WoW64 only + } win; + } os; +} VMMDLL_PROCESS_INFORMATION, *PVMMDLL_PROCESS_INFORMATION; + +/* +* Retrieve various process information from a PID. Process information such as +* name, page directory bases and the process state may be retrieved. +* -- dwPID +* -- pProcessInformation = if null, size is given in *pcbProcessInfo +* -- pcbProcessInformation = size of pProcessInfo (in bytes) on entry and exit +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation); + +typedef struct tdVMMDLL_EAT_ENTRY { + ULONG64 vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMDLL_EAT_ENTRY, *PVMMDLL_EAT_ENTRY; + +typedef struct tdVMMDLL_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMDLL_IAT_ENTRY, *PVMMDLL_IAT_ENTRY; + +/* +* Retrieve information about: Data Directories, Sections, Export Address Table +* and Import Address Table (IAT). +* If the pData == NULL upon entry the number of entries of the pData array must +* have in order to be able to hold the data is returned. +* -- dwPID +* -- szModule +* -- pData +* -- cData +* -- pcData +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + + + +//----------------------------------------------------------------------------- +// VMM UTIL FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __VMMDLL_H__ */ diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb68517 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +The Memory Process File System: +=============================== +The Memory Process File System is an easy and convenient way of accessing physical memory as files a virtual file system. + +Easy trivial point and click memory analysis without the need for complicated commandline arguments! Access physical memory content and artifacts via files in a mounted virtual file system or via a feature rich .dll application library to include in your own projects! + +Analyze memory dump files - or even live memory in read-write mode via linked [pcileech](https://github.com/ufrisk/pcileech/) and [pcileech-fpga](https://github.com/ufrisk/pcileech-fpga/) devices! + +Use your favorite tools to analyze memory - use your favorite hex editors, your python and powershell scripts, your disassemblers - all will work trivally with the Memory Process File System by just reading and writing files! + +

+ + +Include the Memory Process File System in your Python or C/C++ programming projects! Almost everything in the Memory Process File System is exposed via an easy-to-use API for use in your own projects! The Plugin friendly architecture allows users to easily extend the Memory Process File System with native C .DLL plugins or Python .py plugins - providing additional analysis capabilities! + +Please check out the [project wiki](https://github.com/ufrisk/MemProcFS/wiki) for more in-depth detailed information about the file system itself, its API and its plugin modules! + +Fast and easy memory analysis via mounted file system: +====================================================== +No matter if you have no prior knowledge of memory analysis or are an advanced user the Memory Process File System (and the API) may be useful! Click around the memory objects in the file system + +

+ +Extensive Python and C/C++ API: +=============================== +Everything in the Memory Process File System is exposed as APIs. APIs exist for both C/C++ `vmmdll.h` and Python `vmmpy.py`. The file system itself is made available virtually via the API without the need to mount it. Specialized process analysis and process alteration functionality is made easy by calling API functionality. It is possible to read both virtual process memory as well as physical memory! The example below shows reading 0x20 bytes from physical address 0x1000: +``` +>>> from vmmpy import * +>>> VmmPy_InitializeFile('c:/temp/win10_memdump.raw') +>>> print(VmmPy_UtilFillHexAscii(VmmPy_MemRead(-1, 0x1000, 0x20))) +0000 e9 4d 06 00 01 00 00 00 01 00 00 00 3f 00 18 10 .M..........?... +0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +``` + +Modular Plugin Architecture: +============================ +Anyone is able to extend the Memory Process File System with custom plugins! It is as easy as dropping a python file in the correct directory or compiling a tiny C DLL. Existing functionality is already implemented as well documented C and Python plugins! + +Installing: +=========== +## Windows +Download or clone the Memory Process File System github repository. Pre-built binaries are found in the files folder. If the Memory Process File System is used as an API it is only dependant on the Microsoft Visual C++ Redistributables for Visual Studio 2017 (see below). + +The Memory Process File System is dependant in the Microsoft Visual C++ Redistributables for Visual Studio 2017. They can be downloaded from Microsoft [here](https://go.microsoft.com/fwlink/?LinkId=746572). Alternatively, if installing the Dokany file system driver please install the DokanSetup_redist version and it will install the required redistributables. + +Mounting the file system requires the Dokany file system library to be installed. Please download and install the latest version of Dokany at: https://github.com/dokan-dev/dokany/releases/latest It is recommended to download and install the DokanSetup_redist version. + +Python support requires Python 3.6. The user may specify the path to the Python 3.6 installation with the command line parameter `-pythonhome`, alternatively download [Python 3.6 - Windows x86-64 embeddable zip file](https://www.python.org/downloads/windows/) and unzip its contents into the `files/python36` folder when using Python modules in the file system. To use the Python API a normal Python 3.6 installation for Windows is required. + +PCILeech FPGA will require hardware as well as the _pcileech.dll_ and _FTD3XX.dll_ files to be dropped in the files folder. Please check out the [PCILeech](https://github.com/ufrisk/pcileech) project for instructions. + +## Linux +The memory process file system is not yet supported on Linux. Linux support is planned for both the C/C++ and Python API and is a prioritized work item. FUSE file system support will take longer. + +Examples: +========= +Start the Memory Process File System from the command line - possibly by using one of the examples below. + +Or register the memory dump extension with MemProcFS.exe so that the file system is mounted when double-clicking on a memory dump file! + +- mount the memory dump file as default M:
`memprocfs.exe -device c:\temp\win10x64-dump.raw` +- mount the memory dump file as default M with extra verbosity:
`memprocfs.exe -device c:\temp\win10x64-dump.raw -v` +- mount the memory dump file as S:
`memprocfs.exe -mount s -device c:\temp\win10x64-dump.raw` +- mount live target memory, in read/write mode, with PCILeech FPGA memory acquisition device:
`memprocfs.exe -device fpga` +- mount live target memory, in read/write mode, with TotalMeltdown vulnerability acquisition device:
`memprocfs.exe -device totalmeltdown` +- mount an arbitrary x64 memory dump by specifying the process or kernel page table base in the cr3 option:
`memprocfs.exe -device c:\temp\unknown-x64-dump.raw -cr3 0x1aa000` + +Documentation: +============== +For additional documentation please check out the [project wiki](https://github.com/ufrisk/MemProcFS/wiki) for in-depth detailed information about the file system itself, its API and its plugin modules! + +Building: +========= +Pre-built binaries and other supporting files are found in the files folder. The Memory Process File System binaries are built with Visual Studio 2017. No binaries currently exists for Linux (future support - please see Current Limitations & Future Development below). + +Detailed build instructions may be found in the [Wiki](https://github.com/ufrisk/MemProcFS/wiki) in the [Building](https://github.com/ufrisk/MemProcFS/wiki/Dev:-Building) section. + +Current Limitations & Future Development: +========================================= +The Memory Process File System is currently limited to analyzing Windows x64 memory dumps (other x64 dumps in a very limited way). Also, the Memory Process File System currently does not run on Linux. + +Please find some ideas for possible future expansions of the memory process file system listed below. This is a list of ideas - not a list of features that will be implemented. Even though some items are put as prioritized there is no guarantee that they will be implemented in a timely fashion. + +### Prioritized items: +- More/new plugins. +- Linux support - .so files for easy and convenient Linux API access from both C/C++ and Python. +- Additional core functionality (exported functions in .DLL). Please request in Issues section if ideas exist. + +### Other items: +- PFN support. +- Multithreading support in main library. +- Linux support in mounted FUSE file system. +- Support for analyzing x64 Linux, macOS and UEFI memory dumps. +- Support for non-x64 memory models (such as x86 32-bit). +- Hash lookup of executable memory pages in DB. + +Links: +====== +* Blog: http://blog.frizk.net +* Twitter: https://twitter.com/UlfFrisk +* PCILeech: https://github.com/ufrisk/pcileech/ +* YouTube: https://www.youtube.com/channel/UC2aAi-gjqvKiC7s7Opzv9rg + +Changelog: +=================== +v1.0 +* Initial Release. diff --git a/files/MemProcFS.exe b/files/MemProcFS.exe new file mode 100644 index 0000000000000000000000000000000000000000..d3d3974a071ffd445cb0c72ae3d87ffb0e6a0227 GIT binary patch literal 17920 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 +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# TODO: +# - Rework definition parsing, maybe pycparser? +# - Change expression implementation +# - Lazy reading? +from __future__ import print_function +import re +import sys +import ast +import pprint +import string +import struct +import ctypes as _ctypes +from io import BytesIO +from collections import OrderedDict + +try: + from builtins import bytes as newbytes +except ImportError: + newbytes = bytes + +PY3 = sys.version_info > (3,) +if PY3: + long = int + xrange = range + +DEBUG = False + +COLOR_RED = '\033[1;31m' +COLOR_GREEN = '\033[1;32m' +COLOR_YELLOW = '\033[1;33m' +COLOR_BLUE = '\033[1;34m' +COLOR_PURPLE = '\033[1;35m' +COLOR_CYAN = '\033[1;36m' +COLOR_WHITE = '\033[1;37m' +COLOR_NORMAL = '\033[1;0m' + +COLOR_BG_RED = '\033[1;41m\033[1;37m' +COLOR_BG_GREEN = '\033[1;42m\033[1;37m' +COLOR_BG_YELLOW = '\033[1;43m\033[1;37m' +COLOR_BG_BLUE = '\033[1;44m\033[1;37m' +COLOR_BG_PURPLE = '\033[1;45m\033[1;37m' +COLOR_BG_CYAN = '\033[1;46m\033[1;37m' +COLOR_BG_WHITE = '\033[1;47m\033[1;30m' + +PRINTABLE = string.digits + string.ascii_letters + string.punctuation + " " + +COMPILE_TEMPL = """ +class {name}(Structure): + def __init__(self, cstruct, structure, source=None): + self.structure = structure + self.source = source + super({name}, self).__init__(cstruct, structure.name, structure.fields) + + def _read(self, stream): + r = OrderedDict() + sizes = {{}} + bitreader = BitBuffer(stream, self.cstruct.endian) + +{read_code} + + return Instance(self, r, sizes) + + def add_fields(self, name, type_, offset=None): + raise NotImplementedError("Can't add fields to a compiled structure") + + def __repr__(self): + return '' +""" + + +class Error(Exception): + pass + + +class ParserError(Error): + pass + + +class CompilerError(Error): + pass + + +class ResolveError(Error): + pass + + +class NullPointerDereference(Error): + pass + + +def log(line, *args, **kwargs): + if not DEBUG: + return + + print(line.format(*args, **kwargs), file=sys.stderr) + + +class cstruct(object): + """Main class of cstruct. All types are registered in here. + + Args: + endian: The endianness to use when parsing. + pointer: The pointer type to use for Pointers. + """ + + DEF_CSTYLE = 1 + + def __init__(self, endian='<', pointer='uint64'): + self.endian = endian + + self.consts = {} + self.lookups = {} + self.typedefs = { + 'byte': 'int8', + 'ubyte': 'uint8', + 'uchar': 'uint8', + 'short': 'int16', + 'ushort': 'uint16', + 'long': 'int32', + 'ulong': 'uint32', + 'ulong64': 'uint64', + + 'u1': 'uint8', + 'u2': 'uint16', + 'u4': 'uint32', + 'u8': 'uint64', + + 'word': 'uint16', + 'dword': 'uint32', + + 'longlong': 'int64', + 'ulonglong': 'uint64', + + 'int': 'int32', + 'unsigned int': 'uint32', + + 'int8': PackedType(self, 'int8', 1, 'b'), + 'uint8': PackedType(self, 'uint8', 1, 'B'), + 'int16': PackedType(self, 'int16', 2, 'h'), + 'uint16': PackedType(self, 'uint16', 2, 'H'), + 'int32': PackedType(self, 'int32', 4, 'i'), + 'uint32': PackedType(self, 'uint32', 4, 'I'), + 'int64': PackedType(self, 'int64', 8, 'q'), + 'uint64': PackedType(self, 'uint64', 8, 'Q'), + 'float': PackedType(self, 'float', 4, 'f'), + 'double': PackedType(self, 'double', 8, 'd'), + 'char': CharType(self), + 'wchar': WcharType(self), + + 'int24': BytesInteger(self, 'int24', 3, True), + 'uint24': BytesInteger(self, 'uint24', 3, False), + 'int48': BytesInteger(self, 'int48', 6, True), + 'uint48': BytesInteger(self, 'uint48', 6, False), + + 'void': VoidType(), + } + + self.pointer = self.resolve(pointer) + + def addtype(self, name, t, replace=False): + """Add a type or type reference. + + Args: + name: Name of the type to be added. + t: The type to be added. Can be a str reference to another type + or a compatible type class. + + Raises: + ValueError: If the type already exists. + """ + name = name.lower() + if not replace and name.lower() in self.typedefs: + raise ValueError("Duplicate type: %s" % name) + + self.typedefs[name] = t + + def load(self, s, deftype=None, **kwargs): + """Parse structures from the given definitions using the given definition type. + + Definitions can be parsed using different parsers. Currently, there's + only one supported parser - DEF_CSTYLE. Parsers can add types and + modify this cstruct instance. Arguments can be passed to parsers + using kwargs. + + Args: + s: The definition to parse. + deftype: The definition type to parse the definitions with. + **kwargs: Keyword arguments for parsers. + """ + deftype = deftype or cstruct.DEF_CSTYLE + + if deftype == cstruct.DEF_CSTYLE: + parser = CStyleParser(self, **kwargs) + + parser.parse(s) + + def loadfile(self, s, deftype=None, **kwargs): + """Load structure definitions from a file. + + The given path will be read and parsed using the .load() function. + + Args: + s: The path to load definitions from. + deftype: The definition type to parse the definitions with. + **kwargs: Keyword arguments for parsers. + """ + with open(s, 'r') as fh: + self.load(fh.read(), deftype, **kwargs) + + def read(self, name, s): + """Parse data using a given type. + + Args: + name: Type name to read. + s: File-like object or byte string to parse. + + Returns: + The parsed data. + """ + return self.resolve(name).read(s) + + def resolve(self, name): + """Resolve a type name to get the actual type object. + + Types can be referenced using different names. When we want + the actual type object, we need to resolve these references. + + Args: + name: Type name to resolve. + + Returns: + The resolved type object. + + Raises: + ResolveError: If the type can't be resolved. + """ + t = name + if not isinstance(t, str): + return t + + for i in xrange(10): + if t.lower() not in self.typedefs: + raise ResolveError("Unknown type %s" % name) + + t = self.typedefs[t.lower()] + + if not isinstance(t, str): + return t + + raise ResolveError("Recursion limit exceeded while resolving type %s" % name) + + def __getattr__(self, attr): + if attr.lower() in self.typedefs: + return self.typedefs[attr.lower()] + + if attr in self.consts: + return self.consts[attr] + + raise AttributeError("Invalid Attribute: %s" % attr) + + +class Parser(object): + """Base class for definition parsers. + + Args: + cstruct: An instance of cstruct. + """ + + def __init__(self, cstruct): + self.cstruct = cstruct + + def parse(self, data): + """This function should parse definitions to cstruct types. + + Args: + data: Data to parse definitions from, usually a string. + """ + raise NotImplementedError() + + +class CStyleParser(Parser): + """Definition parser for C-like structure syntax. + + Args: + cstruct: An instance of cstruct + compiled: Whether structs should be compiled or not. + """ + + def __init__(self, cstruct, compiled=True): + self.compiled = compiled + super(CStyleParser, self).__init__(cstruct) + + # TODO: Implement proper parsing + def parse(self, data): + self._constants(data) + self._enums(data) + self._structs(data) + self._lookups(data, self.cstruct.consts) + + def _constants(self, data): + r = re.finditer(r'#define\s+(?P[^\s]+)\s+(?P[^\r\n]+)\s*\n', data) + for t in r: + d = t.groupdict() + v = d['value'].rsplit('//')[0] + + try: + v = ast.literal_eval(v) + except (ValueError, SyntaxError): + pass + + self.cstruct.consts[d['name']] = v + + def _enums(self, data): + r = re.finditer( + r'enum\s+(?P[^\s:{]+)\s*(:\s*(?P[^\s]+)\s*)?\{(?P[^}]+)\}\s*;', + data, + ) + for t in r: + d = t.groupdict() + + nextval = 0 + values = {} + for line in d['values'].split('\n'): + line, sep, comment = line.partition("//") + for v in line.split(","): + key, sep, val = v.partition("=") + key = key.strip() + val = val.strip() + if not key: + continue + if not val: + val = nextval + else: + val = Expression(self.cstruct, val).evaluate({}) + + nextval = val + 1 + + values[key] = val + + if not d['type']: + d['type'] = 'uint32' + + enum = Enum( + self.cstruct, d['name'], self.cstruct.resolve(d['type']), values + ) + self.cstruct.addtype(enum.name, enum) + + def _structs(self, data): + compiler = Compiler(self.cstruct) + r = re.finditer( + r'(#(?P(?:compile))\s+)?((?Ptypedef)\s+)?(?P[^\s]+)\s+(?P[^\s]+)?(?P\s*\{[^}]+\}(?P\s+[^;\n]+)?)?\s*;', + data, + ) + for t in r: + d = t.groupdict() + + if d['name']: + name = d['name'] + elif d['defs']: + name = d['defs'].strip().split(',')[0].strip() + else: + raise ParserError("No name for struct") + + if d['type'] == 'struct': + data = self._parse_fields(d['fields'][1:-1].strip()) + st = Structure(self.cstruct, name, data) + if d['flags'] == 'compile' or self.compiled: + st = compiler.compile(st) + elif d['typedef'] == 'typedef': + st = d['type'] + else: + continue + + if d['name']: + self.cstruct.addtype(d['name'], st) + + if d['defs']: + for td in d['defs'].strip().split(','): + td = td.strip() + self.cstruct.addtype(td, st) + + def _parse_fields(self, s): + fields = re.finditer( + r'(?P[^\s]+)\s+(?P[^\s\[:]+)(:(?P\d+))?(\[(?P[^;\n]*)\])?;', + s, + ) + r = [] + for f in fields: + d = f.groupdict() + if d['type'].startswith('//'): + continue + + type_ = self.cstruct.resolve(d['type']) + + d['name'] = d['name'].replace('(', '').replace(')', '') + + # Maybe reimplement lazy type references later + # _type = TypeReference(self, d['type']) + if d['count'] is not None: + if d['count'] == '': + count = None + else: + count = Expression(self.cstruct, d['count']) + try: + count = count.evaluate() + except Exception: + pass + + type_ = Array(self.cstruct, type_, count) + + if d['name'].startswith('*'): + d['name'] = d['name'][1:] + type_ = Pointer(self.cstruct, type_) + + field = Field(d['name'], type_, int(d['bits']) if d['bits'] else None) + r.append(field) + + return r + + def _lookups(self, data, consts): + r = re.finditer(r'\$(?P[^\s]+) = ({[^}]+})\w*\n', data) + + for t in r: + d = ast.literal_eval(t.group(2)) + self.cstruct.lookups[t.group(1)] = dict( + [(self.cstruct.consts[k], v) for k, v in d.items()] + ) + + +class Instance(object): + """Holds parsed structure data.""" + + def __init__(self, type_, values, sizes=None): + object.__setattr__(self, '_type', type_) + object.__setattr__(self, '_values', values) + object.__setattr__(self, '_sizes', sizes) + + def write(self, fh): + """Write this structure to a writable file-like object. + + Args: + fh: File-like objects that supports writing. + + Returns: + The amount of bytes written. + """ + return self.__dict__['_type'].write(fh, self) + + def dumps(self): + """Dump this structure to a byte string. + + Returns: + The raw bytes of this structure. + """ + s = BytesIO() + self.write(s) + return s.getvalue() + + def __getattr__(self, attr): + if attr not in self.__dict__['_type'].lookup: + raise AttributeError("Invalid attribute: %r" % attr) + + return self.__dict__['_values'][attr] + + def __setattr__(self, attr, value): + if attr not in self.__dict__['_type'].lookup: + raise AttributeError("Invalid attribute: %r" % attr) + + self.__dict__['_values'][attr] = value + + def __getitem__(self, item): + return self.__dict__['_values'][item] + + def __contains__(self, attr): + return attr in self.__dict__['_values'] + + def __repr__(self): + return '<%s %s>' % ( + self.__dict__['_type'].name, + ', '.join( + [ + '%s=%s' % (k, hex(v) if isinstance(v, (int, long)) else repr(v)) + for k, v in self.__dict__['_values'].items() + ] + ), + ) + + def __len__(self): + return len(self.dumps()) + + def _size(self, field): + return self.__dict__['_sizes'][field] + + +class PointerInstance(object): + """Like the Instance class, but for structures referenced by a pointer.""" + + def __init__(self, t, stream, addr, ctx): + self._stream = stream + self._type = t + self._addr = addr + self._ctx = ctx + self._value = None + + def _get(self): + log("Dereferencing pointer -> 0x{:016x} [{!r}]", self._addr, self._stream) + if self._addr == 0: + raise NullPointerDereference() + + if self._value is None: + pos = self._stream.tell() + self._stream.seek(self._addr) + if isinstance(self._type, Array): + r = self._type._read(self._stream, self._ctx) + else: + r = self._type._read(self._stream) + self._stream.seek(pos) + self._value = r + + return self._value + + def __getattr__(self, attr): + return getattr(self._get(), attr) + + def __str__(self): + return str(self._get()) + + def __nonzero__(self): + return self._addr != 0 + + def __repr__(self): + return "".format(self._type, self._addr) + + +class Expression(object): + """Expression parser for simple calculations in definitions.""" + + operators = [ + ('+', lambda a, b: a + b), + ('-', lambda a, b: a - b), + ('*', lambda a, b: a * b), + ('/', lambda a, b: a / b), + ('&', lambda a, b: a & b), + ('|', lambda a, b: a | b), + ('>>', lambda a, b: a >> b), + ('<<', lambda a, b: a << b), + ] + + def __init__(self, cstruct, expr): + self.cstruct = cstruct + self.expr = expr + + def evaluate(self, context=None): + context = context if context else {} + level = 0 + levels = [] + buf = '' + + for i in xrange(len(self.expr)): + if self.expr[i] == '(': + level += 1 + levels.append(buf) + buf = '' + continue + + if self.expr[i] == ')': + level -= 1 + val = self.evaluate_part(buf, context) + buf = levels.pop() + buf += str(val) + continue + + buf += self.expr[i] + + return self.evaluate_part(buf, context) + + def evaluate_part(self, e, v): + e = e.strip() + + for o in self.operators: + if o[0] in e: + a, b = e.rsplit(o[0], 1) + return o[1](self.evaluate_part(a, v), self.evaluate_part(b, v)) + + if e in v: + return v[e] + + if e.startswith('0x'): + return int(e, 16) + + if e in self.cstruct.consts: + return self.cstruct.consts[e] + + return int(e) + + def __repr__(self): + return self.expr + + +class BaseType(object): + """Base class for cstruct type classes.""" + + def __init__(self, cstruct): + self.cstruct = cstruct + + def reads(self, data): + """Parse the given data according to the type that implements this class. + + Args: + data: Byte string to parse. + + Returns: + The parsed value of this type. + """ + data = BytesIO(data) + return self._read(data) + + def dumps(self, data): + """Dump the given data according to the type that implements this class. + + Args: + data: Data to dump. + + Returns: + The resulting bytes. + """ + out = BytesIO() + self._write(out, data) + return out.getvalue() + + def read(self, obj, *args, **kwargs): + """Parse the given data according to the type that implements this class. + + Args: + obj: Data to parse. Can be a (byte) string or a file-like object. + + Returns: + The parsed value of this type. + """ + if isinstance(obj, (str, bytes, newbytes)): + return self.reads(obj) + + return self._read(obj) + + def write(self, stream, data): + """Write the given data to a writable file-like object according to the + type that implements this class. + + Args: + stream: Writable file-like object to write to. + data: Data to write. + + Returns: + The amount of bytes written. + """ + return self._write(stream, data) + + def _read(self, stream): + raise NotImplementedError() + + def _read_array(self, stream, count): + return [self._read(stream) for i in xrange(count)] + + def _read_0(self, stream): + raise NotImplementedError() + + def _write(self, stream, data): + raise NotImplementedError() + + def _write_array(self, stream, data): + num = 0 + for i in data: + num += self._write(stream, i) + return num + + def _write_0(self, stream, data): + raise NotImplementedError() + + def default(self): + """Return a default value of this type.""" + raise NotImplementedError() + + def default_array(self): + """Return a default array of this type.""" + raise NotImplementedError() + + def __getitem__(self, count): + return Array(self.cstruct, self, count) + + def __call__(self, *args, **kwargs): + if len(args) > 0: + return self.read(*args, **kwargs) + + r = self.default() + if kwargs: + for k, v in kwargs.items(): + setattr(r, k, v) + + return r + + +class RawType(BaseType): + """Base class for raw types that have a name and size.""" + + def __init__(self, cstruct, name=None, size=0): + self.name = name + self.size = size + super(RawType, self).__init__(cstruct) + + def __len__(self): + return self.size + + def __repr__(self): + if self.name: + return self.name + + return BaseType.__repr__(self) + + +class Structure(BaseType): + """Type class for structures.""" + + def __init__(self, cstruct, name, fields=None): + self.name = name + self.size = None + self.lookup = OrderedDict() + self.fields = fields if fields else [] + + for f in self.fields: + self.lookup[f.name] = f + + self._calc_offsets() + super(Structure, self).__init__(cstruct) + + def _calc_offsets(self): + offset = 0 + bitstype = None + bitsremaining = 0 + + for field in self.fields: + if field.bits: + if bitsremaining == 0 or field.type != bitstype: + bitstype = field.type + bitsremaining = bitstype.size * 8 + if offset is not None: + field.offset = offset + offset += bitstype.size + else: + field.offset = None + + bitsremaining -= field.bits + continue + + field.offset = offset + if offset is not None: + try: + offset += len(field.type) + except TypeError: + offset = None + + def _calc_size(self): + size = 0 + bitstype = None + bitsremaining = 0 + + for field in self.fields: + if field.bits: + if bitsremaining == 0 or field.type != bitstype: + bitstype = field.type + bitsremaining = bitstype.size * 8 + size += bitstype.size + + bitsremaining -= field.bits + continue + + fieldlen = len(field.type) + size += fieldlen + + if field.offset is not None: + size = max(size, field.offset + fieldlen) + + return size + + def _read(self, stream, *args, **kwargs): + log("[Structure::read] {} {}", self.name, self.size) + bitbuffer = BitBuffer(stream, self.cstruct.endian) + + struct_start = stream.tell() + + r = OrderedDict() + sizes = {} + for field in self.fields: + start = stream.tell() + ft = self.cstruct.resolve(field.type) + + if field.offset: + if start != struct_start + field.offset: + log( + "+ seeking to 0x{:x}+0x{:x} for {}".format( + struct_start, field.offset, field.name + ) + ) + stream.seek(struct_start + field.offset) + start = struct_start + field.offset + + if field.bits: + r[field.name] = bitbuffer.read(ft, field.bits) + continue + else: + bitbuffer.reset() + + if isinstance(ft, (Array, Pointer)): + v = ft._read(stream, r) + else: + v = ft._read(stream) + + sizes[field.name] = stream.tell() - start + r[field.name] = v + + return Instance(self, r, sizes) + + def _write(self, stream, data): + bitbuffer = BitBuffer(stream, self.cstruct.endian) + num = 0 + + for field in self.fields: + if field.bits: + bitbuffer.write(field.type, getattr(data, field.name), field.bits) + continue + + if bitbuffer._type: + bitbuffer.flush() + + num += field.type._write(stream, getattr(data, field.name)) + + # Flush bitbuffer + if bitbuffer._type: + bitbuffer.flush() + + return num + + def add_field(self, name, type_, offset=None): + """Add a field to this structure. + + Args: + name: The field name. + type_: The field type. + offset: The field offset. + """ + field = Field(name, type_, offset=offset) + self.fields.append(field) + self.lookup[name] = field + self.size = None + setattr(self, name, field) + + def default(self): + """Create and return an empty Instance from this structure. + + Returns: + An empty Instance from this structure. + """ + r = OrderedDict() + for field in self.fields: + r[field.name] = field.type.default() + + return Instance(self, r) + + def __len__(self): + if self.size is None: + self.size = self._calc_size() + + return self.size + + def __repr__(self): + return ''.format(self.name) + + def show(self, indent=0): + """Pretty print this structure.""" + if indent == 0: + print("struct {}".format(self.name)) + + for field in self.fields: + if field.offset is None: + offset = '0x??' + else: + offset = '0x{:02x}'.format(field.offset) + + print("{}+{} {} {}".format(' ' * indent, offset, field.name, field.type)) + + if isinstance(field.type, Structure): + field.type.show(indent + 1) + + +class BitBuffer(object): + """Implements a bit buffer that can read and write bit fields.""" + + def __init__(self, stream, endian): + self.stream = stream + self.endian = endian + + self._type = None + self._buffer = 0 + self._remaining = 0 + + def read(self, field_type, bits): + if self._remaining < 1 or self._type != field_type: + self._type = field_type + self._remaining = field_type.size * 8 + self._buffer = field_type._read(self.stream) + + if self.endian != '>': + v = self._buffer & ((1 << bits) - 1) + self._buffer >>= bits + self._remaining -= bits + else: + v = self._buffer & ( + ((1 << (self._remaining - bits)) - 1) ^ ((1 << self._remaining) - 1) + ) + v >>= self._remaining - bits + self._remaining -= bits + + return v + + def write(self, field_type, data, bits): + if self._remaining == 0: + self._remaining = field_type.size * 8 + self._type = field_type + + if self.endian != '>': + self._buffer |= data << (self._type.size * 8 - self._remaining) + else: + self._buffer |= data << (self._remaining - bits) + + self._remaining -= bits + + def flush(self): + self._type._write(self.stream, self._buffer) + self._type = None + self._remaining = 0 + self._buffer = 0 + + def reset(self): + self._type = None + self._buffer = 0 + self._remaining = 0 + + +class Field(object): + """Holds a structure field.""" + + def __init__(self, name, type_, bits=None, offset=None): + self.name = name + self.type = type_ + self.bits = bits + self.offset = offset + + def __repr__(self): + return ''.format(self.name, self.type) + + +class Array(BaseType): + """Implements a fixed or dynamically sized array type. + + Example: + When using the default C-style parser, the following syntax is supported: + + x[3] -> 3 -> static length. + x[] -> None -> null-terminated. + x[expr] -> expr -> dynamic length. + """ + + def __init__(self, cstruct, type_, count): + self.type = type_ + self.count = count + self.dynamic = isinstance(self.count, Expression) or self.count is None + + super(Array, self).__init__(cstruct) + + def _read(self, stream, context=None): + if self.count is None: + return self.type._read_0(stream) + + if self.dynamic: + count = self.count.evaluate(context) + else: + count = self.count + + return self.type._read_array(stream, max(0, count)) + + def _write(self, f, data): + if self.count is None: + return self.type._write_0(f, data) + + return self.type._write_array(f, data) + + def default(self): + if self.dynamic or self.count is None: + return [] + + return [self.type.default() for i in xrange(self.count)] + + def __repr__(self): + if self.count is None: + return '{0!r}[]'.format(self.type) + + return '{0!r}[{1}]'.format(self.type, self.count) + + def __len__(self): + if self.dynamic: + raise TypeError("Dynamic size") + + return len(self.type) * self.count + + +class PackedType(RawType): + """Implements a packed type that uses Python struct packing characters.""" + + def __init__(self, cstruct, name, size, packchar): + self.packchar = packchar + super(PackedType, self).__init__(cstruct, name, size) + + def _read(self, stream): + return self._read_array(stream, 1)[0] + + def _read_array(self, stream, count): + length = self.size * count + data = stream.read(length) + fmt = self.cstruct.endian + str(count) + self.packchar + if len(data) != length: + raise EOFError("Read %d bytes, but expected %d" % (len(data), length)) + + return list(struct.unpack(fmt, data)) + + def _read_0(self, stream): + r = [] + while True: + d = stream.read(self.size) + v = struct.unpack(self.cstruct.endian + self.packchar, d)[0] + + if v == 0: + break + + r.append(v) + + return r + + def _write(self, stream, data): + return self._write_array(stream, [data]) + + def _write_array(self, stream, data): + fmt = self.cstruct.endian + str(len(data)) + self.packchar + return stream.write(struct.pack(fmt, *data)) + + def _write_0(self, stream, data): + return self._write_array(stream, data + [0]) + + def default(self): + return 0 + + def default_array(self, count): + return [0] * count + + +class CharType(RawType): + """Implements a character type that can properly handle strings.""" + + def __init__(self, cstruct): + super(CharType, self).__init__(cstruct, 'char', 1) + + def _read(self, stream): + return stream.read(1) + + def _read_array(self, stream, count): + if count == 0: + return b'' + + return stream.read(count) + + def _read_0(self, stream): + r = [] + while True: + c = stream.read(1) + if c == b'': + raise EOFError() + + if c == b'\x00': + break + + r.append(c) + + return b''.join(r) + + def _write(self, stream, data): + if isinstance(data, int): + data = chr(data) + + if PY3 and isinstance(data, str): + data = data.encode('latin-1') + + return stream.write(data) + + def _write_array(self, stream, data): + return self._write(stream, data) + + def _write_0(self, stream, data): + return self._write(stream, data + b'\x00') + + def default(self): + return b'\x00' + + def default_array(self, count): + return b'\x00' * count + + +class WcharType(RawType): + """Implements a wide-character type.""" + + def __init__(self, cstruct): + super(WcharType, self).__init__(cstruct, 'wchar', 2) + + @property + def encoding(self): + if self.cstruct.endian == '<': + return 'utf-16-le' + elif self.cstruct.endian == '>': + return 'utf-16-be' + + def _read(self, stream): + return stream.read(2).decode(self.encoding) + + def _read_array(self, stream, count): + if count == 0: + return u'' + + data = stream.read(2 * count) + return data.decode(self.encoding) + + def _read_0(self, stream): + r = b'' + while True: + c = stream.read(2) + + if len(c) != 2: + raise EOFError() + + if c == b'\x00\x00': + break + + r += c + + return r.decode(self.encoding) + + def _write(self, stream, data): + return stream.write(data.encode(self.encoding)) + + def _write_array(self, stream, data): + return self._write(stream, data) + + def _write_0(self, stream, data): + return self._write(stream, data + u'\x00') + + def default(self): + return u'\x00' + + def default_array(self, count): + return u'\x00' * count + + +class BytesInteger(RawType): + """Implements an integer type that can span an arbitrary amount of bytes.""" + + def __init__(self, cstruct, name, size, signed): + self.signed = signed + super(BytesInteger, self).__init__(cstruct, name, size) + + @staticmethod + def parse(buf, size, count, signed, endian): + nums = [] + + for c in xrange(count): + num = 0 + data = buf[c * size:(c + 1) * size] + if endian == '<': + data = b''.join(data[i:i + 1] for i in reversed(xrange(len(data)))) + + ints = list(data) if PY3 else map(ord, data) + for i in ints: + num = (num << 8) | i + + if signed and num & 1 << (size * 8 - 1): + bias = 1 << (size * 8 - 1) + num -= bias * 2 + + nums.append(num) + + return nums + + @staticmethod + def pack(data, size, endian): + buf = [] + for i in data: + num = int(i) + if num < 0: + num += 1 << (size * 8) + + d = [b'\x00'] * size + i = size - 1 + + while i >= 0: + b = num & 255 + d[i] = bytes((b,)) if PY3 else chr(b) + num >>= 8 + i -= 1 + + if endian == '<': + d = b''.join(d[i:i + 1][0] for i in reversed(xrange(len(d)))) + else: + d = b''.join(d) + + buf.append(d) + + return b''.join(buf) + + def _read(self, stream): + return self.parse(stream.read(self.size * 1), self.size, 1, self.signed, self.cstruct.endian)[0] + + def _read_array(self, stream, count): + return self.parse(stream.read(self.size * count), self.size, count, self.signed, self.cstruct.endian) + + def _read_0(self, stream): + r = [] + while True: + v = self._read(stream) + if v == 0: + break + r.append(v) + + return r + + def _write(self, stream, data): + return stream.write(self.pack([data], self.size, self.cstruct.endian)) + + def _write_array(self, stream, data): + return stream.write(self.pack(data, self.size, self.cstruct.endian)) + + def _write_0(self, stream, data): + return self._write_array(stream, data + [0]) + + def default(self): + return 0 + + def default_array(self, count): + return [0] * count + + +class Enum(RawType): + """Implements an Enum type. + + Enums can be made using any type. The API for accessing enums and their + values is very similar to Python 3 native enums. + + Example: + When using the default C-style parser, the following syntax is supported: + + enum [: ] { + + }; + + For example, an enum that has A=1, B=5 and C=6 could be written like so: + + enum Test : uint16 { + A, B=5, C + }; + """ + + def __init__(self, cstruct, name, type_, values): + self.type = type_ + self.values = values + self.reverse = {} + + for k, v in values.items(): + self.reverse[v] = k + + super(Enum, self).__init__(cstruct, name, len(self.type)) + + def __call__(self, value): + return EnumInstance(self, value) + + def _read(self, stream): + v = self.type._read(stream) + return self(v) + + def _read_array(self, stream, count): + return list(map(self, self.type._read_array(stream, count))) + + def _read_0(self, stream): + return list(map(self, self.type._read_0(stream))) + + def _write(self, stream, data): + data = data.value if isinstance(data, EnumInstance) else data + return self.type._write(stream, data) + + def _write_array(self, stream, data): + data = [d.value if isinstance(d, EnumInstance) else d for d in data] + return self.type._write_array(stream, data) + + def _write_0(self, stream, data): + data = [d.value if isinstance(d, EnumInstance) else d for d in data] + return self.type._write_0(stream, data) + + def default(self): + return self(0) + + def __getitem__(self, attr): + if attr in self.values: + return self(self.values[attr]) + + raise KeyError(attr) + + def __getattr__(self, attr): + if attr in self.values: + return self(self.values[attr]) + + raise AttributeError(attr) + + def __contains__(self, attr): + return attr in self.values + + +class EnumInstance(object): + """Implements a value instance of an Enum""" + + def __init__(self, enum, value): + self.enum = enum + self.value = value + + @property + def name(self): + if self.value not in self.enum.reverse: + return '{}_{}'.format(self.enum.name, self.value) + return self.enum.reverse[self.value] + + def __eq__(self, value): + if isinstance(value, EnumInstance) and value.enum is not self.enum: + return False + + if hasattr(value, 'value'): + value = value.value + + return self.value == value + + def __ne__(self, value): + return self.__eq__(value) is False + + def __hash__(self): + return hash((self.enum, self.value)) + + def __str__(self): + return '{}.{}'.format(self.enum.name, self.name) + + def __repr__(self): + return '<{}.{}: {}>'.format(self.enum.name, self.name, self.value) + + +class Union(RawType): + def __init__(self, cstruct): + self.cstruct = cstruct + super(Union, self).__init__(cstruct) + + def _read(self, stream): + raise NotImplementedError() + + +class Pointer(RawType): + """Implements a pointer to some other type.""" + + def __init__(self, cstruct, target): + self.cstruct = cstruct + self.type = target + super(Pointer, self).__init__(cstruct) + + def _read(self, stream, ctx): + addr = self.cstruct.pointer(stream) + return PointerInstance(self.type, stream, addr, ctx) + + def __len__(self): + return len(self.cstruct.pointer) + + def __repr__(self): + return ''.format(self.type) + + +class VoidType(RawType): + """Implements a void type.""" + + def __init__(self): + super(VoidType, self).__init__(None, 'void', 0) + + def _read(self, stream): + return None + + +def ctypes(structure): + """Create ctypes structures from cstruct structures.""" + fields = [] + for field in structure.fields: + t = ctypes_type(field.type) + fields.append((field.name, t)) + + tt = type(structure.name, (_ctypes.Structure, ), {"_fields_": fields}) + return tt + + +def ctypes_type(t): + mapping = { + "I": _ctypes.c_ulong, + "i": _ctypes.c_long, + "b": _ctypes.c_int8, + } + + if isinstance(t, PackedType): + return mapping[t.packchar] + + if isinstance(t, CharType): + return _ctypes.c_char + + if isinstance(t, Array): + subtype = ctypes_type(t._type) + return subtype * t.count + + if isinstance(t, Pointer): + subtype = ctypes_type(t._target) + return ctypes.POINTER(subtype) + + raise NotImplementedError("Type not implemented: %s" % t.__class__.__name__) + + +class Compiler(object): + """Compiler for cstruct structures. Creates somewhat optimized parsing code.""" + + def __init__(self, cstruct): + self.cstruct = cstruct + + def compile(self, structure): + source = self.gen_struct_class(structure) + c = compile(source, '', 'exec') + + env = { + 'OrderedDict': OrderedDict, + 'Structure': Structure, + 'Instance': Instance, + 'Expression': Expression, + 'EnumInstance': EnumInstance, + 'PointerInstance': PointerInstance, + 'BytesInteger': BytesInteger, + 'BitBuffer': BitBuffer, + 'struct': struct, + 'xrange': xrange, + } + + exec(c, env) + sc = env[structure.name](self.cstruct, structure, source) + + return sc + + def gen_struct_class(self, structure): + blocks = [] + classes = [] + cur_block = [] + read_size = 0 + prev_was_bits = False + + for field in structure.fields: + ft = self.cstruct.resolve(field.type) + + if not isinstance( + ft, + ( + Structure, + Pointer, + Enum, + Array, + PackedType, + CharType, + WcharType, + BytesInteger, + ), + ): + raise CompilerError("Unsupported type for compiler: {}".format(ft)) + + if isinstance(ft, Structure) or ( + isinstance(ft, Array) and isinstance(ft.type, Structure) + ): + if cur_block: + blocks.append(self.gen_read_block(read_size, cur_block)) + + struct_read = 's = stream.tell()\n' + if isinstance(ft, Array): + num = ft.count + + if isinstance(num, Expression): + num = 'max(0, Expression(self.cstruct, "{expr}").evaluate(r))'.format( + expr=num.expr + ) + + struct_read += ( + 'r["{name}"] = []\n' + 'for _ in xrange({num}):\n' + ' r["{name}"].append(self.cstruct.{struct_name}._read(stream))\n'.format( + name=field.name, num=num, struct_name=ft.type.name + ) + ) + else: + struct_read += 'r["{name}"] = self.cstruct.{struct_name}._read(stream)\n'.format( + name=field.name, struct_name=ft.name + ) + + struct_read += 'sizes["{name}"] = stream.tell() - s'.format( + name=field.name + ) + blocks.append(struct_read) + read_size = 0 + cur_block = [] + continue + + if field.bits: + if cur_block: + blocks.append(self.gen_read_block(read_size, cur_block)) + + blocks.append( + 'r["{name}"] = bitreader.read(self.cstruct.{type_name}, {bits})'.format( + name=field.name, type_name=field.type.name, bits=field.bits + ) + ) + read_size = 0 + cur_block = [] + prev_was_bits = True + continue + elif prev_was_bits: + blocks.append('bitreader.reset()') + prev_was_bits = False + + try: + count = len(ft) + read_size += count + cur_block.append(field) + except Exception: + if cur_block: + blocks.append(self.gen_read_block(read_size, cur_block)) + blocks.append(self.gen_dynamic_block(field)) + read_size = 0 + cur_block = [] + + if len(cur_block): + blocks.append(self.gen_read_block(read_size, cur_block)) + + read_code = '\n\n'.join(blocks) + read_code = '\n'.join([' ' * 2 + line for line in read_code.split('\n')]) + + classes.append(COMPILE_TEMPL.format(name=structure.name, read_code=read_code)) + return '\n\n'.join(classes) + + def gen_read_block(self, size, block): + templ = ( + 'buf = stream.read({size})\n' + 'if len(buf) != {size}: raise EOFError()\n' + 'data = struct.unpack(self.cstruct.endian + "{{}}", buf)\n' + '{{}}'.format(size=size) + ) + readcode = [] + fmt = [] + + curtype = None + curcount = 0 + + buf_offset = 0 + data_offset = 0 + + for field in block: + ft = self.cstruct.resolve(field.type) + t = ft + count = 1 + data_count = 1 + read_slice = '' + + if isinstance(t, Enum): + t = t.type + elif isinstance(t, Pointer): + t = self.cstruct.pointer + + if isinstance(ft, Array): + count = t.count + data_count = count + t = t.type + + if isinstance(t, Enum): + t = t.type + elif isinstance(t, Pointer): + t = self.cstruct.pointer + + if isinstance(t, (CharType, WcharType, BytesInteger)): + read_slice = '{}:{}'.format( + buf_offset, buf_offset + (count * t.size) + ) + else: + read_slice = '{}:{}'.format(data_offset, data_offset + count) + elif isinstance(t, CharType): + read_slice = str(buf_offset) + elif isinstance(t, (WcharType, BytesInteger)): + read_slice = '{}:{}'.format(buf_offset, buf_offset + t.size) + else: + read_slice = str(data_offset) + + if not curtype: + if isinstance(t, PackedType): + curtype = t.packchar + else: + curtype = 'x' + + if isinstance(t, (PackedType, CharType, WcharType, BytesInteger, Enum)): + charcount = count + + if isinstance(t, (CharType, WcharType, BytesInteger)): + data_count = 0 + packchar = 'x' + charcount *= t.size + else: + packchar = t.packchar + + if curtype != packchar: + fmt.append('{}{}'.format(curcount, curtype)) + curcount = 0 + + curcount += charcount + curtype = packchar + + getter = '' + if isinstance(t, BytesInteger): + getter = 'BytesInteger.parse(buf[{slice}], {size}, {count}, {signed}, self.cstruct.endian){data_slice}'.format( + slice=read_slice, + size=t.size, + count=count, + signed=t.signed, + data_slice='[0]' if count == 1 else '', + ) + elif isinstance(t, (CharType, WcharType)): + getter = 'buf[{}]'.format(read_slice) + if isinstance(t, WcharType): + getter += ".decode('utf-16-le' if self.cstruct.endian == '<' else 'utf-16-be')" + else: + getter = 'data[{}]'.format(read_slice) + + if isinstance(ft, Enum): + getter = 'EnumInstance(self.cstruct.{type_name}, {getter})'.format( + type_name=ft.name, getter=getter + ) + elif isinstance(ft, Array) and isinstance(ft.type, Enum): + getter = '[EnumInstance(self.cstruct.{type_name}, d) for d in {getter}]'.format( + type_name=ft.type.name, getter=getter + ) + elif isinstance(ft, Pointer): + getter = 'PointerInstance(self.cstruct.{type_name}, stream, {getter}, r)'.format( + type_name=ft.type.name, getter=getter + ) + elif isinstance(ft, Array) and isinstance(ft.type, Pointer): + getter = '[PointerInstance(self.cstruct.{type_name}, stream, d, r) for d in {getter}]'.format( + type_name=ft.type.name, getter=getter + ) + elif isinstance(ft, Array) and isinstance(t, PackedType): + getter = 'list({})'.format(getter) + + readcode.append( + 'r["{name}"] = {getter}'.format(name=field.name, getter=getter) + ) + readcode.append( + 'sizes["{name}"] = {size}'.format(name=field.name, size=count * t.size) + ) + + data_offset += data_count + buf_offset += count * t.size + + if curcount: + fmt.append('{}{}'.format(curcount, curtype)) + + return templ.format(''.join(fmt), '\n'.join(readcode)) + + def gen_dynamic_block(self, field): + if not isinstance(field.type, Array): + raise CompilerError( + "Only Array can be dynamic, got {!r}".format(field.type) + ) + + t = field.type.type + reader = None + + if not field.type.count: # Null terminated + if isinstance(t, PackedType): + reader = ( + 't = []\nwhile True:\n' + ' d = stream.read({size})\n' + ' if len(d) != {size}: raise EOFError()\n' + ' v = struct.unpack(self.cstruct.endian + "{packchar}", d)[0]\n' + ' if v == 0: break\n' + ' t.append(v)'.format(size=t.size, packchar=t.packchar) + ) + + elif isinstance(t, (CharType, WcharType)): + reader = ( + 't = []\n' + 'while True:\n' + ' c = stream.read({size})\n' + ' if len(c) != {size}: raise EOFError()\n' + ' if c == b"{null}": break\n' + ' t.append(c)\nt = b"".join(t)'.format( + size=t.size, null='\\x00' * t.size + ) + ) + + if isinstance(t, WcharType): + reader += ".decode('utf-16-le' if self.cstruct.endian == '<' else 'utf-16-be')" + elif isinstance(t, BytesInteger): + reader = ( + 't = []\n' + 'while True:\n' + ' d = stream.read({size})\n' + ' if len(d) != {size}: raise EOFError()\n' + ' v = BytesInteger.parse(d, {size}, 1, {signed}, self.cstruct.endian)\n' + ' if v == 0: break\n' + ' t.append(v)'.format(size=t.size, signed=t.signed) + ) + + return '{reader}\nr["{name}"] = t\nsizes["{name}"] = len(t)'.format( + reader=reader, name=field.name + ) + else: + expr = field.type.count.expr + expr_read = ( + 'dynsize = max(0, Expression(self.cstruct, "{expr}").evaluate(r))\n' + 'buf = stream.read(dynsize * {type_size})\n' + 'if len(buf) != dynsize * {type_size}: raise EOFError()\n' + 'r["{name}"] = {{reader}}\n' + 'sizes["{name}"] = dynsize * {type_size}'.format( + expr=expr, name=field.name, type_size=t.size + ) + ) + + if isinstance(t, PackedType): + reader = 'list(struct.unpack(self.cstruct.endian + "{{:d}}{packchar}".format(dynsize), buf))'.format( + packchar=t.packchar, type_size=t.size + ) + elif isinstance(t, (CharType, WcharType)): + reader = 'buf' + if isinstance(t, WcharType): + reader += ".decode('utf-16-le' if self.cstruct.endian == '<' else 'utf-16-be')" + elif isinstance(t, BytesInteger): + reader = 'BytesInteger.parse(buf, {size}, dynsize, {signed}, self.cstruct.endian)'.format( + size=t.size, signed=t.signed + ) + + return expr_read.format(reader=reader, size=None) + + +def hexdump(s, palette=None, offset=0, prefix="", is_retstr=False): + """Hexdump some data. + + Args: + s: Bytes to hexdump. + palette: Colorize the hexdump using this color pattern. + offset: Byte offset of the hexdump. + prefix: Optional prefix. + """ + if palette: + palette = palette[::-1] + + remaining = 0 + active = None + retstr = "" + + for i in xrange(0, len(s), 16): + vals = "" + chars = [] + for j in xrange(16): + if not active and palette: + remaining, active = palette.pop() + vals += active + elif active and j == 0: + vals += active + + if i + j >= len(s): + vals += " " + else: + c = s[i + j] + c = chr(c) if PY3 else c + p = c if c in PRINTABLE else "." + + if active: + vals += "{:02x}".format(ord(c)) + chars.append(active + p + COLOR_NORMAL) + else: + vals += "{:02x}".format(ord(c)) + chars.append(p) + + remaining -= 1 + if remaining == 0: + active = None + + if palette is not None: + vals += COLOR_NORMAL + + if j == 15: + if palette is not None: + vals += COLOR_NORMAL + + vals += " " + + if j == 7: + vals += " " + + chars = "".join(chars) + line = "{}{:08x} {:48s} {}".format(prefix, offset + i, vals, chars) + if is_retstr: + retstr += line + "\n" + else: + print(line) + if is_retstr: + return retstr + + +def dumpstruct(t, data=None, offset=0, is_color = True, is_retstr=False): + """Dump a structure or parsed structure instance. + + Prints a colorized hexdump and parsed structure output. + + Args: + t: Structure or Instance to dump. + data: Bytes to parse the Structure on, if t is not a parsed Instance. + offset: Byte offset of the hexdump. + """ + if is_color: + colors = [ + (COLOR_RED, COLOR_BG_RED), + (COLOR_GREEN, COLOR_BG_GREEN), + (COLOR_YELLOW, COLOR_BG_YELLOW), + (COLOR_BLUE, COLOR_BG_BLUE), + (COLOR_PURPLE, COLOR_BG_PURPLE), + (COLOR_CYAN, COLOR_BG_CYAN), + (COLOR_WHITE, COLOR_BG_WHITE), + ] + else: + colors = [ + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ('', ''), + ] + + if isinstance(t, Instance): + g = t + t = t._type + data = g.dumps() + elif isinstance(t, Structure) and data: + g = t(data) + else: + raise ValueError("Invalid arguments") + + palette = [] + ci = 0 + out = "struct {}".format(t.name) + ":\n" + for field in g._type.fields: + fg, bg = colors[ci % len(colors)] + palette.append((g._size(field.name), bg)) + ci += 1 + + v = getattr(g, field.name) + if isinstance(v, str): + v = repr(v) + elif isinstance(v, int): + v = hex(v) + elif isinstance(v, list): + v = pprint.pformat(v) + if '\n' in v: + v = v.replace('\n', '\n{}'.format(' ' * (len(field.name) + 4))) + + out += "- {}{}{}: {}\n".format(fg, field.name, COLOR_NORMAL if is_color else '', v) + + if is_retstr: + retstr = "\n" + retstr += hexdump(data, palette if is_color else None, offset=offset, is_retstr=True) + retstr += "\n" + retstr += out + return retstr + print() + hexdump(data, palette if is_color else None, offset=offset) + print() + print(out) diff --git a/files/plugins/m_vmemd.dll b/files/plugins/m_vmemd.dll new file mode 100644 index 0000000000000000000000000000000000000000..2d484e8f96da421f35408d5f6f50965383c64ca7 GIT binary patch literal 12800 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 o{DunV^o8^uGPQ`}_8Pq}0a@e 0, procinfo['va-peb32'] > 0 + procstruct_cache_proc_wow64[pid] = result + return result + + + +def List(pid, path): + # + # List function - this module employs a dynamic list function - which makes + # it responsible for providing directory listings of its contents in a + # highly optimized way. It is very important that the List function is as + # speedy as possible - to avoid locking up the file system. + # + # First check the directory to be listed. Only the module root directory is + # allowed. If it's not the module root directory return None. + if path != 'procstruct': + return None + # Populate the 'common' files which are always in the module directory + # below. Both binary and hexdump/hexascii versions are populated. + result = { + 'eprocess.bin': {'size': procstruct_eprocess_size_bin, 'read': ReadEPROCESS_Binary, 'write': WriteEPROCESS_Binary}, + 'eprocess.txt': {'size': procstruct_eprocess_size_hex, 'read': ReadEPROCESS_Hexdump, 'write': None} + } + # Populate PEB (if it exists), it almost always do, but there may be + # some special processes like 'System', 'Registry' and so on that only + # exist in kernel space without a PEB... + fPeb64, fPeb32 = IsProcessPeb6432(pid) + if fPeb64: + result['peb.bin'] = {'size': procstruct_peb_size_bin, 'read': ReadPEB_Binary, 'write': WritePEB_Binary} + result['peb.txt'] = {'size': procstruct_peb_size_hex, 'read': ReadPEB_Hexdump, 'write': None} + # Optionally populate 32-bit PEBs into the directory listings if a 32-bit + # process is to be listed. + if fPeb32: + result['peb32.bin'] = {'size': procstruct_peb_size_bin, 'read': ReadPEB_Binary, 'write': WritePEB_Binary} + result['peb32.txt'] = {'size': procstruct_peb_size_hex, 'read': ReadPEB_Hexdump, 'write': None} + return result + + + +def Close(): + # Nothing to clean up here for this plugin -> do nothing! + pass + + + +def Initialize(os_target): + # Check that the operating system is 64-bit Windows. If it's not then raise + # an exception to terminate loading of this module. + if os_target != VMMPY_TARGET_WINDOWS_X64: + raise RuntimeError("Only x64 Windows is supported by the pym_procstruct module.") + # Calculate the size of the 'eprocess_size_hex' global variable. This is + # only done once - at module instantiation to speed up the list operation. + global procstruct_eprocess_size_hex + procstruct_eprocess_size_hex = len(VmmPy_UtilFillHexAscii(bytes(procstruct_eprocess_size_bin))) + # Calculate the size of the 'PEB page' + global procstruct_peb_size_hex + procstruct_peb_size_hex = len(VmmPy_UtilFillHexAscii(bytes(procstruct_peb_size_bin))) + # Register a directory with the VmmPyPlugin plugin manager. The directory + # is a non-root (i.e. a process) directory and have a custom List function. + VmmPyPlugin_FileRegisterDirectory(False, 'procstruct', List) diff --git a/files/python36/information.txt b/files/python36/information.txt new file mode 100644 index 0000000..0bb49d7 --- /dev/null +++ b/files/python36/information.txt @@ -0,0 +1,4 @@ +Put your Python 3.6 embedded for Windows 64-bit in this directory to enable Python functionality. +Download Python 3.6 "Windows x86-64 embeddable zip file" and unzip in this folder. +Python may be downloaded from: https://www.python.org/downloads/ +This is not required if Python 3.6 for Windows 64-bit is already on the path. diff --git a/files/vmm.dll b/files/vmm.dll new file mode 100644 index 0000000000000000000000000000000000000000..9a4a1c3b70d51f49b37a9106824246beb77bc621 GIT binary patch literal 101376 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%0A-cYt+Nbpwvg4 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 literal 0 HcmV?d00001 diff --git a/files/vmm.lib b/files/vmm.lib new file mode 100644 index 0000000000000000000000000000000000000000..92fa1493a7dc58e05611613c0a9160cdb0e47b74 GIT binary patch literal 9702 zcmb_hy>App6o0k}5c6dle}A4o_$oq(IU5HNWP=Sj`0R^qKtV|7!*{kAo`wt?Q?xO{D z0oSD+w4k0BoGzgS^#aSKCbXb_%!SdwkVlCYB1QwxJnEkSAJqHaqr^7&puT&YV0RxV z0j$JMCNrB#FDx!+re~I}F3zTx7Zx+OQz^=1=5Ni-MVa(7xE^o$#;y6AndzzJDMH^g zliqSFPPX9ulwZu3^Q9m1Ip}dVA25vueuQ()DdbI~TwZiU`udb{1mi_UEEg-;LMmUV z*jK|*UEEFIiyW=`ZA08 zY-p(=<97NocS=qrG<67hyZb$^&@i2( zW@@6Nbc|Z)IJMFtI!s5Xj_UVFQ-kW&t z<`@``4iW$4lhLI=nmmDhA(=%f$GA>`mu6WIbcvClCTQ?|rUGuu5WsWKNIng`8gj@Q z?+nT@u1le1daO#i-H{~OcOKUTl_3G1Y3=r87W}TiPVh5V1@7W1_9~% z3O^D6B3$kv(#?%G0!=X3RauocFZ0F9j{wGb`68Ojh&EIx(-r|E+!=KdNP3&sR6jVI zD->h`v|FVodEo-D`K&bNwj1aZehqaj%kX|@bNTxZ*GHE4D}959U&ISwSqRw7#L zDN=nHiQkpt*)%3f`5~Kdu|&9&D;LP}_$ADRt99qw(DrH3SWYRHv1YWS{(B9T$CPg_m`H6R60J6#RCpTaJ=htynUGz=|*T@ zq8NhHK5UV=m&e1h?aIaombgQRMdWSO=Vej^|Bz+uh(DyD`L~kTSOyP3nF`gN;3|5l zVq6^C3Lf!sLN}+lXi3X%KdPXKyLHTcVc3Hx;vu7ytLNXKg4a7?TPxFlNPjQ9U3pT#zPyktZ0iCEpV@my`p1=s5OigJ+4EQH(`17)ch644d?1tq8joL_PM be8Gk@sNjeqf4nc3G{ilY)eR{)YF76jG!!2g literal 0 HcmV?d00001 diff --git a/files/vmmdll.h b/files/vmmdll.h new file mode 100644 index 0000000..623a09b --- /dev/null +++ b/files/vmmdll.h @@ -0,0 +1,566 @@ +// vmmdll.h : header file to include in projects that use vmm.dll either as +// stand anlone projects or as native plugins to vmm.dll. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include + +#ifndef __VMMDLL_H__ +#define __VMMDLL_H__ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +/* +* RESERVED FUNCTION! DO NOT USE! +* Call other VMMDLL_Intialize functions to initialize VMM.DLL and the memory +* process file system. +*/ +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]); + +/* +* Initialize VMM.DLL from a memory dump file in raw format. VMM.DLL will be +* initialized in read-only mode. It's possible to optionally specify the page +* table base of the windows kernel (for full vmm features) or the page table +* base of a single 64-bit process in any x64 operating system. NB! usually it +* is not necessary to specify the PageTableBase - it will be auto-identified +* most often if the target is Windows. +* -- szFileName = the file name of the raw memory dump to use. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Intiailize VMM.DLL from a supported FPGA device over USB. VMM.DLL will be +* initialized in read/write mode upon success. Optionally it will be possible +* to specify the max physical address and the page table base of the kernel or +* process that should be investigated. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* -- szMaxPhysicalAddressOpt = max physical address of the target system as a +* hex string. Example: "0x8000000000". If zero value is given the max +* address will be auto-identified. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Initialize VMM.DLL from a the "Total Meltdown" CVE-2018-1038 vulnerability. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* initialized in read/write mode upon success. +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeTotalMeltdown(); + +/* +* Close an initialized instance of VMM.DLL and clean up all allocated resources +* including plugins, linked PCILeech.DLL and other memory resources. +* -- return = success/fail. +*/ +BOOL VMMDLL_Close(); + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +/* +* Options used together with the functions: VMMDLL_GetOption & VMMDLL_SetOption +* If VMM.DLL is chained with PCILEECH.DLL then required values will be passed +* along to PCILEECH.DLL when necessary. +* For more detailed information check the sources for individual device types. +*/ +#define VMMDLL_OPT_DEVICE_FPGA_PROBE_MAXPAGES 0x01 // RW +#define VMMDLL_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_RX 0x03 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_TX 0x04 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_READ 0x05 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE 0x06 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_WRITE 0x07 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_READ 0x08 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_RETRY_ON_ERROR 0x09 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DEVICE_ID 0x80 // R +#define VMMDLL_OPT_DEVICE_FPGA_FPGA_ID 0x81 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR 0x82 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR 0x83 // R + +#define VMMDLL_OPT_CORE_PRINTF_ENABLE 0x80000001 // RW +#define VMMDLL_OPT_CORE_VERBOSE 0x80000002 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R +#define VMMDLL_OPT_CORE_TARGET_SYSTEM 0x80000007 // R + +#define VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED 0x40000001 // R - 1/0 +#define VMMDLL_OPT_CONFIG_TICK_PERIOD 0x40000002 // RW - base tick period in ms +#define VMMDLL_OPT_CONFIG_READCACHE_TICKS 0x40000003 // RW - memory cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_TLBCACHE_TICKS 0x40000004 // RW - page table (tlb) cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL 0x40000005 // RW - process refresh (partial) period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL 0x40000006 // RW - process refresh (full) period (in ticks) +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR 0x40000007 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR 0x40000008 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION 0x40000009 // R +#define VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL 0x4000000A // RW - enable function call statistics (.status/statistics_fncall file) + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- pqwValue = pointer to ULONG64 to receive option value. +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- qwValue +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +// This is the core of the memory process file system. All implementation and +// analysis towards the file system is possible by using functionality below. +//----------------------------------------------------------------------------- + +#define VMMDLL_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMMDLL_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMMDLL_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMMDLL_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMMDLL_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +typedef struct tdVMMDLL_VFS_FILELIST { + VOID(*pfnAddFile) (_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved); + VOID(*pfnAddDirectory)(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved); + HANDLE h; +} VMMDLL_VFS_FILELIST, *PVMMDLL_VFS_FILELIST; + +/* +* Helper function macros for callbacks into the VMM_VFS_FILELIST structure. +*/ +#define VMMDLL_VfsList_AddFile(pFileList, szName, cb) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddFile(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, cb, NULL); } +#define VMMDLL_VfsList_AddDirectory(pFileList, szName) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddDirectory(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, NULL); } + +/* +* List a directory of files in the memory process file system. Directories and +* files will be listed by callbacks into functions supplied in the pFileList +* parameter. If information of an individual file is needed it's neccessary +* to list all files in its directory. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + +/* +* Read select parts of a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +* +*/ +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + +/* +* Write select parts to a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + +/* +* Utility functions for memory process file system read/write towards different +* underlying data representations. +*/ +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow); + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +// Function and structures to initialize and use the memory process file system +// plugin functionality. The plugin manager is started by a call to function: +// VMM_VfsInitializePlugins. Each built-in plugin and external plugin of which +// the DLL name matches m_*.dll will receive a call to its InitializeVmmPlugin +// function. The plugin/module may decide to call pfnPluginManager_Register to +// register plugins in the form of different names one or more times. +// Example of registration function in a plugin DLL below: +// 'VOID InitializeVmmPlugin(_In_ PVMM_PLUGIN_REGINFO pRegInfo)' +//----------------------------------------------------------------------------- + +/* +* Initialize all potential plugins, both built-in and external, that maps into +* the memory process file system. Please note that plugins are not loaded by +* default - they have to be explicitly loaded by calling this function. They +* will be unloaded on a general close of the vmm dll. +* -- return +*/ +BOOL VMMDLL_VfsInitializePlugins(); + +#define VMMDLL_PLUGIN_CONTEXT_MAGIC 0xc0ffee663df9301c +#define VMMDLL_PLUGIN_CONTEXT_VERSION 1 +#define VMMDLL_PLUGIN_REGINFO_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PLUGIN_REGINFO_VERSION 1 + +#define VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE 0x01 + +typedef struct tdVMMDLL_PLUGIN_CONTEXT { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD dwPID; + PHANDLE phModulePrivate; + PHANDLE phProcessPrivate; + PVOID pProcess; + LPSTR szModule; + LPSTR szPath; + PVOID pvReserved1; + PVOID pvReserved2; +} VMMDLL_PLUGIN_CONTEXT, *PVMMDLL_PLUGIN_CONTEXT; + +typedef struct tdVMMDLL_PLUGIN_REGINFO { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; + HMODULE hDLL; + HMODULE hReservedDll; // not for general use (only used for python). + BOOL(*pfnPluginManager_Register)(struct tdVMMDLL_PLUGIN_REGINFO *pPluginRegInfo); + PVOID pvReserved1; + PVOID pvReserved2; + // general plugin registration info to be filled out by the plugin below: + struct { + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + PVOID pvReserved1; + PVOID pvReserved2; + } reg_info; + // function plugin registration info to be filled out by the plugin below: + struct { + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + PVOID pvReserved1; + PVOID pvReserved2; + } reg_fn; +} VMMDLL_PLUGIN_REGINFO, *PVMMDLL_PLUGIN_REGINFO; + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +// Vmm core functaionlity such as read (and write) to both virtual and physical +// memory. NB! writing will only work if the target is supported - i.e. not a +// memory dump file... +// To read physical memory specify dwPID as (DWORD)-1 +//----------------------------------------------------------------------------- + +// FLAG used to supress the default read cache in calls to VMM_MemReadEx() +// which will lead to the read being fetched from the target system always. +// Cached page tables (used for translating virtual2physical) are still used. +#define VMMDLL_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMMDLL_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMMDLL_TARGET_UNKNOWN_X64 0x0001 +#define VMMDLL_TARGET_WINDOWS_X64 0x0002 + +typedef struct tdVMMDLL_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} VMMDLL_MEM_IO_SCATTER_HEADER, *PVMMDLL_MEM_IO_SCATTER_HEADER, **PPVMMDLL_MEM_IO_SCATTER_HEADER; + +/* +* Read memory in various non-contigious locations specified by the pointers to +* the items in the ppDMAs array. Result for each unit of work will be given +* individually. No upper limit of number of items to read, but no performance +* boost will be given if above hardware limit. Max size of each unit of work is +* one 4k page (4096 bytes). +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpDMAsRead = optional count of number of successfully read ppDMAs. +* -- flags = optional flags as given by VMM_FLAG_* +* -- return = the number of successfully read items. +*/ +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); + +/* +* Read a single 4096-byte page of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pbPage +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Read a contigious arbitrary amount of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious amount of memory and report the number of bytes read in pcbRead. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +* -- return = success/fail. NB! reads may report as success even if 0 bytes are +* read - it's recommended to verify pcbReadOpt parameter. +*/ +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + +/* +* Write a contigious arbitrary amount of memory. Please note some virtual memory +* such as pages of executables (such as DLLs) may be shared between different +* virtual memory over different processes. As an example a write to kernel32.dll +* in one process is likely to affect kernel32 in the whole system - in all +* processes. Heaps and Stacks and other memory are usually safe to write to. +* Please take care when writing to memory! +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Translate a virtual address to a physical address by walking the page tables +* of the specified process. +* -- dwPID +* -- qwVA +* -- pqwPA +* -- return = success/fail. +*/ +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +// Functionality below is mostly relating to Windows processes. +//----------------------------------------------------------------------------- + +/* +* Retrieve an active process given it's name. Please note that if multiple +* processes with the same name exists only one will be returned. If required to +* parse all processes with the same name please iterate over the PID list by +* calling VMMDLL_PidList together with VMMDLL_ProcessGetInformation. +* -- szProcName = process name (truncated max 15 chars) case insensitive. +* -- pdwPID = pointer that will receive PID on success. +* -- return +*/ +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + +/* +* List the PIDs in the system. +* -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. +* -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. +* -- return = success/fail. +*/ +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + +// flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define VMMDLL_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMMDLL_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMMDLL_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMMDLL_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +typedef struct tdVMMDLL_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMMDLL_MEMMAP_ENTRY, *PVMMDLL_MEMMAP_ENTRY; + +/* +* Retrieve memory map entries from the specified process. Memory map entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries bytes. +* If the pMemMapEntries is set to NULL the number of memory map entries will be +* given in the pcMemMapEntries parameter. +* -- dwPID +* -- pMemMapEntries = buffer of minimum length sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. +* -- pcMemMapEntries = pointer to number of memory map entries. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + +/* +* Retrieve a single memory map entry given a virtual address within that entrys +* range. +* -- dwPID +* -- pMemMapEntry +* -- va = virtual address in the memory map entry to retrieve. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules); + +typedef struct tdVMMDLL_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; +} VMMDLL_MODULEMAP_ENTRY, *PVMMDLL_MODULEMAP_ENTRY; + +/* +* Retrieve the module entries from the specified process. The module entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the +* pcModuleEntries is set to NULL the number of module entries will be given +* in the pcModuleEntries parameter. +* -- dwPID +* -- pModuleEntries = buffer of minimum length sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. +* -- pcModuleEntries = pointer to number of memory map entries. +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + +/* +* Retrieve a module (.exe or .dll or similar) given a module name. +* -- dwPID +* -- szModuleName +* -- pModuleEntry +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry); + +#define VMMDLL_PROCESS_INFORMATION_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PROCESS_INFORMATION_VERSION 1 + +typedef struct tdVMMDLL_PROCESS_INFORMATION { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; // as given by VMMDLL_TARGET_* + BOOL fUserOnly; // only user mode pages listed + DWORD dwPID; + DWORD dwState; + CHAR szName[16]; + ULONG64 paPML4; + ULONG64 paPML4_UserOpt; // may not exist + union { + struct { + ULONG64 vaEPROCESS; + ULONG64 vaPEB; + ULONG64 vaENTRY; + BOOL fWow64; + DWORD vaPEB32; // WoW64 only + } win; + } os; +} VMMDLL_PROCESS_INFORMATION, *PVMMDLL_PROCESS_INFORMATION; + +/* +* Retrieve various process information from a PID. Process information such as +* name, page directory bases and the process state may be retrieved. +* -- dwPID +* -- pProcessInformation = if null, size is given in *pcbProcessInfo +* -- pcbProcessInformation = size of pProcessInfo (in bytes) on entry and exit +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation); + +typedef struct tdVMMDLL_EAT_ENTRY { + ULONG64 vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMDLL_EAT_ENTRY, *PVMMDLL_EAT_ENTRY; + +typedef struct tdVMMDLL_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMDLL_IAT_ENTRY, *PVMMDLL_IAT_ENTRY; + +/* +* Retrieve information about: Data Directories, Sections, Export Address Table +* and Import Address Table (IAT). +* If the pData == NULL upon entry the number of entries of the pData array must +* have in order to be able to hold the data is returned. +* -- dwPID +* -- szModule +* -- pData +* -- cData +* -- pcData +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + + + +//----------------------------------------------------------------------------- +// VMM UTIL FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __VMMDLL_H__ */ diff --git a/files/vmmpy.py b/files/vmmpy.py new file mode 100644 index 0000000..dd9777e --- /dev/null +++ b/files/vmmpy.py @@ -0,0 +1,537 @@ +# vmmpy.py +# +# Provides a convenient python interface for the memory process file system +# virtual memory manager - vmm.dll / vmmpyc.pyc. +# +# Fast and convenient python access towards the native vmm.dll and in some +# cases linked pcileech.dll libraries. This wrapper also provides for code +# completion in some supported dev environments. +# +# https://github.com/ufrisk/ +# +# (c) Ulf Frisk, 2018 +# Author: Ulf Frisk, pcileech@frizk.net +# + +from vmmpyc import * + +#------------------------------------------------------------------------------ +# VmmPy CONSTANTS BELOW: +# NB! Only some unrelated contants are put here. Constants more closely related +# to functionality is put close to the functionality itself. +#------------------------------------------------------------------------------ + +# NTSTATUS values. (Used/Returned by Write file plugin callbacks). +VMMPY_STATUS_SUCCESS = 0x00000000 +VMMPY_STATUS_UNSUCCESSFUL = 0xC0000001 +VMMPY_STATUS_END_OF_FILE = 0xC0000011 +VMMPY_STATUS_FILE_INVALID = 0xC0000098 + +# TARGET SYSTEM values - used to determine if a plugin is supported or not for +# the current system that is being analyzed. +VMMPY_TARGET_UNKNOWN_X64 = 0x0001 +VMMPY_TARGET_WINDOWS_X64 = 0x0002 + +# EVENT values - received by the notify callback function for specific events +# occuring in the native plugin manager / vmm / memory process file system. +VMMPY_PLUGIN_EVENT_VERBOSITYCHANGE = 0x01 + +#------------------------------------------------------------------------------ +# VmmPy INITIALIZATION FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ + +def VmmPy_Close(): + """Close an initialized instance of VMM.DLL and clean up all allocated resources including plugins, linked PCILeech.dll and other memory resources. + + Keyword arguments: + N/A + + Example: + VmmPy_Close() --> True + """ + VMMPYC_Close() + + + +def VmmPy_InternalInitializeReserved(args, is_printf = True, is_verbose = False, is_verbose_extra = False, is_verbose_tlp = False, page_table_base = 0): + """INTERNAL USE ONLY - DO NOT CALL DIRECTLY! + """ + if page_table_base > 0: + args.append("-cr3") + args.append(str(page_table_base)) + if is_printf: + args.append("-vdll") + if is_verbose: + args.append("-v") + if is_verbose_extra: + args.append("-vv") + if is_verbose_tlp: + args.append("-vvv") + VMMPYC_InitializeReserved(args) + + + +def VmmPy_InitializeFile(file_name, is_printf = True, is_verbose = False, is_verbose_extra = False, is_verbose_tlp = False, page_table_base = 0): + """Initialize VmmPy and the Virtual Memory Manager VMM.DLL from a memory dump file. + + Keyword arguments: + file_name -- str: memory dump file to load. + is_printf -- bool: console output from vmm.dll is enabled. + is_verbose -- bool: verbose level. + is_verbose_extra -- bool: extra verbose level. + is_verbose_tlp -- bool: show FPGA TLPs or similar - super verbose! + page_table_base -- int: optional page directory base of the OS kernel or a x64 process. + + Example: + VmmPy_InitializeFile("c:\\temp\\dump.raw") + """ + args = ["", "-device", file_name] + VmmPy_InternalInitializeReserved(args, is_printf, is_verbose, is_verbose_extra, is_verbose_tlp, page_table_base) + + + +def VmmPy_InitializeTotalMeltdown(is_printf = True, is_verbose = False, is_verbose_extra = False, is_verbose_tlp = False, page_table_base = 0): + """Initialize VmmPy and the Virtual Memory Manager VMM.DLL from the "Total Meltdown" CVE-2018-1038 vulnerability. + NB! Read/Write mode will be enabled. + NB! Required a linked pcileech.dll + + Keyword arguments: + is_printf -- bool: console output from vmm.dll is enabled. + is_verbose -- bool: verbose level. + is_verbose_extra -- bool: extra verbose level. + is_verbose_tlp -- bool: show FPGA TLPs or similar - super verbose! + page_table_base -- int: optional page directory base of the OS kernel or a x64 process. + + Example: + VmmPy_InitializeFile("c:\\temp\\dump.raw") + """ + args = ["", "-device", "totalmeltdown"] + VmmPy_InternalInitializeReserved(args, is_printf, is_verbose, is_verbose_extra, is_verbose_tlp, page_table_base) + + + +def VmmPy_InitializeFPGA(is_printf = True, is_verbose = False, is_verbose_extra = False, is_verbose_tlp = False, page_table_base = 0): + """Initialize VmmPy and the Virtual Memory Manager VMM.DLL from a supported FPGA device over USB. + NB! Read/Write mode will be enabled. + NB! Required a linked pcileech.dll + + Keyword arguments: + is_printf -- bool: console output from vmm.dll is enabled. + is_verbose -- bool: verbose level. + is_verbose_extra -- bool: extra verbose level. + is_verbose_tlp -- bool: show FPGA TLPs or similar - super verbose! + page_table_base -- int: optional page directory base of the OS kernel or a x64 process. + + Example: + VmmPy_InitializeFile("c:\\temp\\dump.raw") + """ + args = ["", "-device", "fpga"] + VmmPy_InternalInitializeReserved(args, is_printf, is_verbose, is_verbose_extra, is_verbose_tlp, page_table_base) + + + +#------------------------------------------------------------------------------ +# VmmPy CONFIGURATION FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ + +VMMPY_OPT_DEVICE_FPGA_PROBE_MAXPAGES = 0x01 # RW +VMMPY_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT = 0x02 # RW +VMMPY_OPT_DEVICE_FPGA_MAX_SIZE_RX = 0x03 # RW +VMMPY_OPT_DEVICE_FPGA_MAX_SIZE_TX = 0x04 # RW +VMMPY_OPT_DEVICE_FPGA_DELAY_PROBE_READ = 0x05 # RW - uS +VMMPY_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE = 0x06 # RW - uS +VMMPY_OPT_DEVICE_FPGA_DELAY_WRITE = 0x07 # RW - uS +VMMPY_OPT_DEVICE_FPGA_DELAY_READ = 0x08 # RW - uS +VMMPY_OPT_DEVICE_FPGA_RETRY_ON_ERROR = 0x09 # RW +VMMPY_OPT_DEVICE_FPGA_DEVICE_ID = 0x80 # R +VMMPY_OPT_DEVICE_FPGA_FPGA_ID = 0x81 # R +VMMPY_OPT_DEVICE_FPGA_VERSION_MAJOR = 0x82 # R +VMMPY_OPT_DEVICE_FPGA_VERSION_MINOR = 0x83 # R + +VMMPY_OPT_CORE_PRINTF_ENABLE = 0x80000001 # RW +VMMPY_OPT_CORE_VERBOSE = 0x80000002 # RW +VMMPY_OPT_CORE_VERBOSE_EXTRA = 0x80000003 # RW +VMMPY_OPT_CORE_VERBOSE_EXTRA_TLP = 0x80000004 # RW +VMMPY_OPT_CORE_MAX_NATIVE_ADDRESS = 0x80000005 # R +VMMPY_OPT_CORE_MAX_NATIVE_IOSIZE = 0x80000006 # R +VMMPY_OPT_CORE_TARGET_SYSTEM = 0x80000007 # R + +VMMPY_OPT_CONFIG_IS_REFRESH_ENABLED = 0x40000001 # R - 1/0 +VMMPY_OPT_CONFIG_TICK_PERIOD = 0x40000002 # RW - base tick period in ms +VMMPY_OPT_CONFIG_READCACHE_TICKS = 0x40000003 # RW - memory cache validity period (in ticks) +VMMPY_OPT_CONFIG_TLBCACHE_TICKS = 0x40000004 # RW - page table (tlb) cache validity period (in ticks) +VMMPY_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL = 0x40000005 # RW - process refresh (partial) period (in ticks) +VMMPY_OPT_CONFIG_PROCCACHE_TICKS_TOTAL = 0x40000006 # RW - process refresh (full) period (in ticks) +VMMPY_OPT_CONFIG_VMM_VERSION_MAJOR = 0x40000007 # R +VMMPY_OPT_CONFIG_VMM_VERSION_MINOR = 0x40000008 # R +VMMPY_OPT_CONFIG_VMM_VERSION_REVISION = 0x40000009 # R +VMMPY_OPT_CONFIG_STATISTICS_FUNCTIONCALL = 0x4000000A # RW - enable function call statistics (.status/statistics_fncall file) + + +def VmmPy_ConfigGet(vmmpy_opt_id): + """Retrieve a configuration setting given a VMMPY_OPT_* option. + + Keyword arguments: + vmmpy_opt_id -- int: the configuration value to retrieve as defined by VMMPY_OPT_*. + return -- int: configuration value. (Fail: -1). + + Example: + VmmPy_ConfigGet(VMMPY_OPT_CORE_PRINTF_ENABLE) --> 1 + """ + return VMMPYC_ConfigGet(vmmpy_opt_id) + + + +def VmmPy_ConfigSet(vmmpy_opt_id, value): + """Set a configuration setting given a VMMPY_OPT_* option. + + Keyword arguments: + vmmpy_opt_id -- int: the configuration value to retrieve as defined by VMMPY_OPT_*. + value -- int: value to set. + + Example: + VmmPy_ConfigSet(VMMPY_OPT_CORE_PRINTF_ENABLE, 0) + """ + VMMPYC_ConfigSet(vmmpy_opt_id, value) + + + +def VmmPy_GetVersion(): + """Retrieve the Version of the core functionality in the VMM.DLL. + + Example: + VmmPy_GetVersion() -> 1.0.0 + """ + verMajor = VMMPYC_ConfigGet(VMMPY_OPT_CONFIG_VMM_VERSION_MAJOR) + verMinor = VMMPYC_ConfigGet(VMMPY_OPT_CONFIG_VMM_VERSION_MINOR) + verRevision = VMMPYC_ConfigGet(VMMPY_OPT_CONFIG_VMM_VERSION_REVISION) + return str(verMajor) + '.' + str(verMinor) + '.' + str(verRevision) + + + +#------------------------------------------------------------------------------ +# VmmPy MEMORY ACCESS FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ + +VMMPY_FLAG_NOCACHE = 0x0001 # do not use the data cache (force reading from memory acquisition device) +VMMPY_FLAG_ZEROPAD_ON_FAIL = 0x0002 # zero pad failed physical memory reads and report success if read within range of physical memory. + + + +def VmmPy_MemRead(pid, address, length, flags = 0): + """Read memory given a pid, a (64-bit) address and length. Return result as bytes. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. -1 when reading physical memory. + address -- int: the address to read. + length -- int: the number of bytes to read. + flags -- int: optional flags as specified by VMMPY_FLAG* constants. + return -- bytes: memory. + + Example: + VmmPy_MemRead(-1, 0x1000, 4) --> b'\x00\x01\x02\x03' + """ + return VMMPYC_MemRead(pid, address, length, flags) + + + +def VmmPy_MemReadScatter(pid, address_list, flags = 0): + """Read page (4kB) sized & aligned memory given a pid and a list of (64-bit) addresses. Return result in list of dict. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. -1 when reading physical memory. + address_list -- list: a list of page (4kB/0x1000) aligned addresses. + flags -- int: optional flags as specified by VMMPY_FLAG* constants. + return -- list: of dicts with the result. + + Example: + VmmPy_MemReadScatter(-1, [0x1000]) --> [{'addr': 4096, 'pa': 4096, 'data': b'\x00\x01\x02\x03\x04 ... ', 'size': 4096}] + """ + + return VMMPYC_MemReadScatter(pid, address_list, flags) + + + +def VmmPy_MemWrite(pid, address, bytes_data): + """Write memory given a pid, a (64-bit) address and length. No return. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. -1 when writing physical memory. + address -- int: the address to write. + bytes_data -- bytes: a bytes-like object. + + Example: + VmmPy_MemWrite(0x666, 0x1000, b'\x00\x01\x02\x03') + """ + VMMPYC_MemWrite(pid, address, bytes_data) + + + +def VmmPy_MemVirt2Phys(pid, address): + """Translate a virtual address (va) to a physical address given a pid and return the result. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + va -- int: the virtual address (va) to translate + return -- int: the physical address (pa). + + Example: + VmmPy_MemVirt2Phys(0x666, 0x00007ff74d5da000) --> 0x000000004d5da000 + """ + return VMMPYC_MemVirt2Phys(pid, address) + + + +#------------------------------------------------------------------------------ +# VmmPy GENERAL PROCESS / MEMORY MAP FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ + +def VmmPy_PidList(): + """Retrieve all process identifiers (pids) in the system and return them as a list. + + Keyword arguments: + return -- list: pids. + + Example: + VmmPy_PidList() --> [4, 76, 324, 392, 576, 588, ...] + """ + return sorted(VMMPYC_PidList()) + + + +def VmmPy_PidGetFromName(process_name): + """Retrieve a pid from a process_name and return it. + NB! if more processes do have the same name only one will be returned by + this function. If important to find all then use VmmPy_PidList() instead. + + Keyword arguments: + process_name -- str: name of a process to find. + return -- int: pid number. + + Example: + VmmPy_PidGetFromName() --> 4 + """ + return VMMPYC_PidGetFromName(process_name) + + + +def VmmPy_ProcessGetMemoryMap(pid, is_identify_modules = False): + """Retrieve the memory map for a specific pid. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + is_identify_modules -- bool: (optional) identify module names (slow). + return -- list: of dict of memory map entries. + + Example: + VmmPy_ProcessGetMemoryMap(4) --> [{'va': 2147352576, 'size': 4096, 'pages': 1, 'wow64': False, 'tag': '', 'flags-pte': 9223372036854775812, 'flags': 'srwx'}, ...] + """ + return VMMPYC_ProcessGetMemoryMap(pid, is_identify_modules) + + + +def VmmPy_ProcessGetMemoryMapEntry(pid, va, is_identify_modules = False): + """Retrieve a single memory map entry for a given pid and virtual address (va). + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + va -- int: a virtual address inside the entry to retrieve. + is_identify_modules -- bool: (optional) identify module names (slow). + return -- dict: of memory map entries. + + Example: + VmmPy_ProcessGetMemoryMapEntry(4, 0x7ffe0000) --> {'va': 2147352576, 'size': 4096, 'pages': 1, 'wow64': False, 'name': '', 'flags-pte': 9223372036854775812, 'flags': 'srwx'} + """ + return VMMPYC_ProcessGetMemoryMapEntry(pid, va, is_identify_modules) + + + +def VmmPy_ProcessGetModuleMap(pid): + """Retrieve the module map for a specific pid. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + return -- list: of dict of module map information entries. + + Example: + VmmPy_ProcessGetModuleMap(332) --> [{'va': 140718422491136, 'va-entry': 0, 'wow64': False, 'size': 1966080, 'name': 'ntdll.dll'}, ...] + """ + return VMMPYC_ProcessGetModuleMap(pid) + + + +def VmmPy_ProcessGetModuleFromName(pid, module_name): + """Retrieve the module map for a specific pid and module name. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + module_name -- bool: name of the module to retrieve. + return -- dict: of module information. + + Example: + VmmPy_ProcessGetModuleMap(332, "ntdll.dll") --> {'va': 140718422491136, 'va-entry': 0, 'wow64': False, 'size': 1966080, 'name': 'ntdll.dll'} + """ + return VMMPYC_ProcessGetModuleFromName(pid, module_name) + + + +def VmmPy_ProcessGetInformation(pid): + """Retrieve process information for a specific pid and return as dict. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + return -- dict: of process information. + + Example: + VmmPy_ProcessGetInformation(332) --> {'pid': 8796, 'pa-pml4': 5798625280, 'pa-pml4-user': 6237978624, 'state': 0, 'target': 2, 'usermode': True, 'name': 'cmd.exe', 'wow64': False, 'va-entry': 140700131683072, 'va-eprocess': 18446635809067693440, 'va-peb': 708313505792, 'va-peb32': 0} + """ + return VMMPYC_ProcessGetInformation(pid) + + + +def VmmPy_ProcessListInformation(): + """Retrieve process information for all pids and return as dict of dict. + + Keyword arguments: + return -- dict: dict of process information with pid as key. + + Example: + VmmPy_ProcessListInformation() --> {4: {...}, ..., 322: {'pid': 8796, 'pa-pml4': 5798625280, 'pa-pml4-user': 6237978624, 'state': 0, 'target': 2, 'usermode': True, 'name': 'cmd.exe', 'wow64': False, 'va-entry': 140700131683072, 'va-eprocess': 18446635809067693440, 'va-peb': 708313505792, 'va-peb32': 0} + """ + pids = VmmPy_PidList() + result = {} + for pid in pids: + result[pid] = VMMPYC_ProcessGetInformation(pid) + return result + + + +#------------------------------------------------------------------------------ +# VmmPy WINDOWS SPECIFIC PROCESS FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ + +def VmmPy_ProcessGetEAT(pid, module_name): + """Retrieve the export address table (EAT) for a specific pid and module name and return as list of dict. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + module_name -- str: name of the module to retrieve. + return -- list: of dict of EAT information. + + Example: + VmmPy_ProcessGetEAT(332, "ntdll.dll") --> [{'i': 0, 'va': 140718385196671, 'offset': 585343, 'fn': 'AcquireSRWLockExclusive'}, ... ] + """ + return VMMPYC_ProcessGetEAT(pid, module_name) + + + +def VmmPy_ProcessGetIAT(pid, module_name): + """Retrieve the import address table (IAT) for a specific pid and module name and return as list of dict. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + module_name -- str: name of the module to retrieve. + return -- list: of dict of IAT information. + + Example: + VmmPy_ProcessGetAT(332, "cmd.exe") --> [{'i': 0, 'va': 140718377374992, 'fn': 'setlocale', 'dll': 'msvcrt.dll'}, ... ] + """ + return VMMPYC_ProcessGetIAT(pid, module_name) + + + +def VmmPy_ProcessGetDirectories(pid, module_name): + """Retrieve the data directories for a specific pid and module name and return as list of dict. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + module_name -- str: name of the module to retrieve. + return -- list: of dict of data direcories information. + + Example: + VmmPy_ProcessGetDirectories(332, "cmd.exe") --> [{'i': 0, 'size': 0, 'offset': 0, 'name': 'EXPORT'}, ... ] + """ + return VMMPYC_ProcessGetDirectories(pid, module_name) + + + +def VmmPy_ProcessGetSections(pid, module_name): + """Retrieve the sections for a specific pid and module name and return as list of dict. + + Keyword arguments: + pid -- int: the process identifier (pid) when reading process virtual memory. + module_name -- str: name of the module to retrieve. + return -- list: of dict of section information. + + Example: + VmmPy_ProcessGetSections(332, "cmd.exe") --> [{'i': 0, 'Characteristics': 1610612768, 'misc-PhysicalAddress': 183592, 'misc-VirtualSize': 183592, 'Name': '.text', 'NumberOfLinenumbers': 0, 'NumberOfRelocations': 0, 'PointerToLinenumbers': 0, 'PointerToRawData': 1024, 'PointerToRelocations': 0, 'SizeOfRawData': 183808, 'VirtualAddress': 4096}, ... ] + """ + return VMMPYC_ProcessGetSections(pid, module_name) + + + +#------------------------------------------------------------------------------ +# VmmPy VFS (Virtual File System) FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ +def VmmPy_VfsList(path): + """Retrieve a Virtual File System directory listing a path and return it. + + Keyword arguments: + path -- str: the directory path. + return -- dict: of dict of file/directory names. + + Example: + VmmPy_VfsList("/") --> {'pmem' : {'size': 247078670, 'f_isdir': False}, ...} + """ + path = path.replace('/', '\\') + return VMMPYC_VfsList(path) + + + +def VmmPy_VfsRead(path_file, length, offset = 0): + """Read a Virtual File System file. + + Keyword arguments: + path_file -- str: the file path including the file name. + length -- int: the amount of bytes to read. + offset -- int: start reading from this offset. + return -- bytes: the read data. + + Example: + VmmPy_VfsRead("/pmem", 0x1000, 0x10000000) --> b'000032040234023400 ...' + """ + path_file = path_file.replace('/', '\\') + return VMMPYC_VfsRead(path_file, length, offset) + + + +def VmmPy_VfsWrite(path_file, bytes_data, offset = 0): + """Write to Virtual File System file. + + Keyword arguments: + path_file -- str: the file path including the file name. + bytes_data -- bytes: the data to write. + offset -- int: start writing from this offset. + + Example: + VmmPy_VfsWrite("/pmem", b'000000011122', 0x2000) + """ + path_file = path_file.replace('/', '\\') + VmmPy_VfsWrite(path_file, bytes_data, offset) + + +#------------------------------------------------------------------------------ +# VmmPy UTIL FUNCTIONALITY BELOW: +#------------------------------------------------------------------------------ + +def VmmPy_UtilFillHexAscii(data_bytes, cb_initial_offset = 0): + """Fill a human readable hex ascii memory dump string given a bytes object. + + Keyword arguments: + data_bytes -- bytes: binary data to convert. + cb_initial_offset -- int: offset, must be max 0x1000 and multiple of 0x10. + return -- str: human readable dump-data. + """ + return VMMPYC_UtilFillHexAscii(data_bytes, cb_initial_offset) + + diff --git a/files/vmmpy_example.py b/files/vmmpy_example.py new file mode 100644 index 0000000..4557950 --- /dev/null +++ b/files/vmmpy_example.py @@ -0,0 +1,428 @@ +# vmmpy_example.py +# +# Example showcase file displaying how it is possible to interface the Memory +# Process File System / Virtual Memory Manager - VMM.DLL / VmmPy / VmmPyC with +# user created python programs. +# +# Requirement: Memory Dump file from Windows 7 x64 or later with a logged in +# user that have the process 'explorer.exe' running. +# +# To start example run: +# from vmmpy_example import * +# VmmPy_Example("") +# where is the file name and path of a +# Windows dump file of a 64-bit Windows operating system - Windows 7 or later. +# +# To start example how to conveniently parse the PE header structs by using +# the dissect.cstruct library and VmmPy please run the example: +# from vmmpy_example import * +# VmmPy_Example_ParsePE() +# +# https://github.com/ufrisk/ +# +# (c) Ulf Frisk, 2018 +# Author: Ulf Frisk, pcileech@frizk.net +# + +from vmmpy import * +from io import BytesIO +from dissect import cstruct + + +# Examples: +# +# VmmPy_Example("c:\\temp\\win10.raw") +# VmmPy_Example_ParsePE("c:\\temp\\win10.raw") + +def VmmPy_Example(dump_file_name): + print("--------------------------------------------------------------------") + print("Welcome to the VmmPy Example showcase / test cases. This will demo ") + print("how it is possible to use VmmPy to access memory dump files in a ") + print("convenient way. Please ensure that the VmmPy requirements about the ") + print("python version (such as Python 3.6) is met before starting ... ") + + # INIITALIZE + print("--------------------------------------------------------------------") + print("Initialize VmmPy with the dump file specified. ") + input("Press Enter to continue...") + print("CALL: VmmPy_InitializeFile()") + VmmPy_InitializeFile(dump_file_name) + print("SUCCESS: VmmPy_InitializeFile()") + + # GET CONFIG + print("--------------------------------------------------------------------") + print("Retrieve configuration value for: VMMPY_OPT_CORE_MAX_NATIVE_ADDRESS.") + input("Press Enter to continue...") + print("CALL: VmmPy_ConfigGet()") + result = VmmPy_ConfigGet(VMMPY_OPT_CORE_MAX_NATIVE_ADDRESS) + print("SUCCESS: VmmPy_ConfigGet()") + print(result) + + # SET CONFIG + print("--------------------------------------------------------------------") + print("Set configuration value for: VMMPY_OPT_CORE_PRINTF_ENABLE. ") + input("Press Enter to continue...") + print("CALL: VmmPy_ConfigSet()") + VmmPy_ConfigSet(VMMPY_OPT_CORE_PRINTF_ENABLE, 1) + print("SUCCESS: VmmPy_ConfigSet()") + + # MEM READ + print("--------------------------------------------------------------------") + print("Read 0x100 bytes of memory from the physical address 0x1000 ") + input("Press Enter to continue...") + print("CALL: VmmPy_MemRead()") + result = VmmPy_MemRead(-1, 0x1000, 0x100) + print("SUCCESS: VmmPy_MemRead()") + print(result) + + # MEM READ + FillHexAscii + print("--------------------------------------------------------------------") + print("Read 0x100 bytes of memory from the physical address 0x1000 ") + input("Press Enter to continue...") + print("CALL: VMMPYC_UtilFillHexAscii(VmmPy_MemRead())") + result = VmmPy_UtilFillHexAscii(VmmPy_MemRead(-1, 0x1000, 0x100)) + print("SUCCESS: VMMPYC_UtilFillHexAscii(VmmPy_MemRead())") + print(result) + + # MEM READ SCATTER + print("--------------------------------------------------------------------") + print("Read 2 non-contigious (scatter) memory from the physical addresses: ") + print("0x1000 and 0x3000. ") + input("Press Enter to continue...") + print("CALL: VmmPy_MemReadScatter()") + result = VmmPy_MemReadScatter(-1, [0x1000, 0x3000]) + print("SUCCESS: VmmPy_MemReadScatter()") + print(result) + + # PID + print("--------------------------------------------------------------------") + print("Retrieve the process identifier pid for the process 'explorer.exe'. ") + input("Press Enter to continue...") + print("CALL: VmmPy_PidGetFromName()") + result = VmmPy_PidGetFromName("explorer.exe") + print("SUCCESS: VmmPy_PidGetFromName()") + print(result) + pid = result + + # PIDs + print("--------------------------------------------------------------------") + print("List the process identifier pids of the processes in the system. ") + input("Press Enter to continue...") + print("CALL: VmmPy_PidList()") + result = VMMPYC_PidList() + print("SUCCESS: VmmPy_PidList()") + print(result) + + # PROCESS INFORMATION GET + print("--------------------------------------------------------------------") + print("Get the process information about the earlier explorer.exe process. ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetInformation()") + result = VmmPy_ProcessGetInformation(pid) + print("SUCCESS: VmmPy_ProcessGetInformation()") + print(result) + + # PROCESS INFORMATION LIST + print("--------------------------------------------------------------------") + print("Get the process information for all process in a dict by pid. ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessListInformation()") + result = VmmPy_ProcessListInformation() + print("SUCCESS: VmmPy_ProcessListInformation()") + print(result) + + # MODULE INFORMATION + print("--------------------------------------------------------------------") + print("Get module information about the explorer.exe module in the process.") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetModuleFromName()") + result = VmmPy_ProcessGetModuleFromName(pid, "explorer.exe") + print("SUCCESS: VmmPy_ProcessGetModuleFromName()") + print(result) + va = result['va'] + + # MEM MAP + print("--------------------------------------------------------------------") + print("Get the memory map of 'explorer.exe' by walking the page table. ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetMemoryMap()") + result = VmmPy_ProcessGetMemoryMap(pid, True) + print("SUCCESS: VmmPy_ProcessGetMemoryMap()") + print(result) + + # MEM MAP ENTRY + print("--------------------------------------------------------------------") + print("Get the PE base of 'explorer.exe' in the 'explorer.exe' process. ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetMemoryMapEntry()") + result = VmmPy_ProcessGetMemoryMapEntry(pid, va, True) + print("SUCCESS: VmmPy_ProcessGetMemoryMapEntry()") + print(result) + + # MEM VIRTUAL2PHYSICAL + print("--------------------------------------------------------------------") + print("Get physical address of the PE virtual address of 'explorer.exe'. ") + input("Press Enter to continue...") + print("CALL: VmmPy_MemVirt2Phys()") + result = VmmPy_MemVirt2Phys(pid, va) + print("SUCCESS: VmmPy_MemVirt2Phys()") + print(result) + + # MEM READ + print("--------------------------------------------------------------------") + print("Read 0x100 bytes of memory from 'explorer.exe' PE base. ") + input("Press Enter to continue...") + print("CALL: VmmPy_MemRead()") + result = VmmPy_UtilFillHexAscii(VmmPy_MemRead(pid, va, 0x100)) + print("SUCCESS: VmmPy_MemRead()") + print(result) + + # PE EAT + print("--------------------------------------------------------------------") + print("Get the Export Address Table given 'explorer.exe'/'kernel32.dll' ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetEAT()") + result = VmmPy_ProcessGetEAT(pid, "kernel32.dll") + print("SUCCESS: VmmPy_ProcessGetEAT()") + print(result) + + # PE IAT + print("--------------------------------------------------------------------") + print("Get the Import Address Table given 'explorer.exe'/'kernel32.dll' ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetIAT()") + result = VmmPy_ProcessGetIAT(pid, "kernel32.dll") + print("SUCCESS: VmmPy_ProcessGetIAT()") + print(result) + + # PE DATA DIRECTORIES + print("--------------------------------------------------------------------") + print("Get the PE Data Directories from 'explorer.exe'/'kernel32.dll' ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetDirectories()") + result = VmmPy_ProcessGetDirectories(pid, "kernel32.dll") + print("SUCCESS: VmmPy_ProcessGetDirectories()") + print(result) + + # PE SECTIONS + print("--------------------------------------------------------------------") + print("Get the PE Data Directories from 'explorer.exe'/'kernel32.dll' ") + input("Press Enter to continue...") + print("CALL: VmmPy_ProcessGetSections()") + result = VmmPy_ProcessGetSections(pid, "kernel32.dll") + print("SUCCESS: VmmPy_ProcessGetSections()") + print(result) + + # VFS LIST / + print("--------------------------------------------------------------------") + print("Retrieve the file list of the virtual file system from the root path") + input("Press Enter to continue...") + print("CALL: VmmPy_VfsList()") + result = VmmPy_VfsList('/') + print("SUCCESS: VmmPy_VfsList()") + print(result) + + # VFS LIST /name + print("--------------------------------------------------------------------") + print("Retrieve the file list of the virtual file system from the name path") + input("Press Enter to continue...") + print("CALL: VmmPy_VfsList()") + result = VmmPy_VfsList('/name') + print("SUCCESS: VmmPy_VfsList()") + print(result) + + # VFS READ + print("--------------------------------------------------------------------") + print("Read from a file in the virtual file system (/pmem at offset 0x1000)") + input("Press Enter to continue...") + print("CALL: VmmPy_VfsRead()") + result = VmmPy_UtilFillHexAscii(VmmPy_VfsRead('/pmem', 0x100, 0x1000)) + print("SUCCESS: VmmPy_VfsRead()") + print(result) + + + +PE_STRUCT_DEFINITIONS = """ + #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 + #define IMAGE_SIZEOF_SHORT_NAME 8 + typedef struct _IMAGE_DOS_HEADER + { + WORD e_magic; + WORD e_cblp; + WORD e_cp; + WORD e_crlc; + WORD e_cparhdr; + WORD e_minalloc; + WORD e_maxalloc; + WORD e_ss; + WORD e_sp; + WORD e_csum; + WORD e_ip; + WORD e_cs; + WORD e_lfarlc; + WORD e_ovno; + WORD e_res[4]; + WORD e_oemid; + WORD e_oeminfo; + WORD e_res2[10]; + LONG e_lfanew; + } IMAGE_DOS_HEADER; + typedef struct _IMAGE_FILE_HEADER { + WORD Machine; + WORD NumberOfSections; + DWORD TimeDateStamp; + DWORD PointerToSymbolTable; + DWORD NumberOfSymbols; + WORD SizeOfOptionalHeader; + WORD Characteristics; + } IMAGE_FILE_HEADER; + typedef struct _IMAGE_DATA_DIRECTORY { + ULONG VirtualAddress; + ULONG Size; + } IMAGE_DATA_DIRECTORY; + typedef struct _IMAGE_OPTIONAL_HEADER { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } IMAGE_OPTIONAL_HEADER; + typedef struct _IMAGE_OPTIONAL_HEADER64 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + ULONGLONG ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + ULONGLONG SizeOfStackReserve; + ULONGLONG SizeOfStackCommit; + ULONGLONG SizeOfHeapReserve; + ULONGLONG SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; + } IMAGE_OPTIONAL_HEADER64; + typedef struct _IMAGE_SECTION_HEADER { + char Name[IMAGE_SIZEOF_SHORT_NAME]; + ULONG VirtualSize; + ULONG VirtualAddress; + ULONG SizeOfRawData; + ULONG PointerToRawData; + ULONG PointerToRelocations; + ULONG PointerToLinenumbers; + USHORT NumberOfRelocations; + USHORT NumberOfLinenumbers; + ULONG Characteristics; + } IMAGE_SECTION_HEADER; +""" + + + +def VmmPy_Example_ParsePE(dump_file_name): + # INIITALIZE + print("--------------------------------------------------------------------") + print("Initialize VmmPy with the dump file specified. ") + input("Press Enter to continue...") + print("CALL: VmmPy_InitializeFile()") + VmmPy_InitializeFile(dump_file_name) + print("SUCCESS: VmmPy_InitializeFile()") + + # + # EXAMPLE BELOW USE THE FOX-IT DISSECT CSTRUCT PYTHON MODULE TO PARSE THE + # THE PE HEADER OF 'ntdll.dll' in 'explorer.exe' + # + print("--------------------------------------------------------------------") + print("Parse the PE header of 'explorer.exe'/'ntdll.dll' by using a VmmPy ") + print("custom version of the dissect.cstruct parsing library from fox-it. ") + print("dissect.cstruct: https://github.com/fox-it/dissect.cstruct ") + input("Press Enter to continue...") + + # Call VmmPy to retrieve the actual 0x1000 page containing the PE header. + print("CALL: VmmPy*") + mz_pid = VmmPy_PidGetFromName('explorer.exe') + mz_va = VmmPy_ProcessGetModuleFromName(mz_pid, "ntdll.dll")['va'] + mz_bytes = VmmPy_MemRead(mz_pid, mz_va, 0x1000) + print("SUCCESS: VmmPy*") + + # Create a stream for convenience + mz_stream = BytesIO(mz_bytes) + + # Set up dissect.cstruct + print("INITIALIZING dissect.cstruct and parsing PE header structures ... ") + pestruct = cstruct.cstruct() + pestruct.load(PE_STRUCT_DEFINITIONS) + + # Load the MZ stream into dissect.struct. NB! loading mz_bytes will work as + # well but will not be as convenient since the 'file pointer' won't move on + # struct reads automatically... + struct_mz = pestruct.IMAGE_DOS_HEADER(mz_stream) + if struct_mz.e_magic != 0x5a4d: + print("MZ HEADER DOES NOT MATCH - ABORTING") + return + print(struct_mz) + print(cstruct.dumpstruct(struct_mz, None, 0, False, True)) + + # Seek towards the PE signature / magic value and check that it is correct. + mz_stream.seek(struct_mz.e_lfanew) + signature = pestruct.uint32(mz_stream) + if signature != 0x4550: + print("PE HEADER DOES NOT MATCH") + return + + # Parse and display the PE file_header struct. + struct_file_header = pestruct.IMAGE_FILE_HEADER(mz_stream) + print(struct_file_header) + print(cstruct.dumpstruct(struct_file_header, None, 0, False, True)) + + # Parse and display the PE struct_optional_header struct. + struct_optional_header = pestruct.IMAGE_OPTIONAL_HEADER64(mz_stream) if struct_file_header.Machine == 0x8664 else pestruct.IMAGE_OPTIONAL_HEADER(mz_stream) + print(struct_optional_header) + print(cstruct.dumpstruct(struct_optional_header, None, 0, False, True)) + + # Parse and display the PE sections. + struct_sections = [pestruct.IMAGE_SECTION_HEADER(mz_stream) for _ in range(struct_file_header.NumberOfSections)] + for struct_section in struct_sections: + print(cstruct.dumpstruct(struct_section, None, 0, False, True)) diff --git a/files/vmmpyc.pyd b/files/vmmpyc.pyd new file mode 100644 index 0000000000000000000000000000000000000000..1e4377ec0687d11a9bb31eda182002f60f8018a5 GIT binary patch literal 31744 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 O=f + 'dirs': + # file only attributes below: + 'size': + 'write': + 'read': +} + +The dicts are: VmmPy_RootDirectoryRoot and VmmPy_RootDirectoryProcess. + +Please note that the VmmPy_RootDirectoryProcess dict is shared amongst all +processes. If process independant file listings are required please use the +'list' function callback to generate dynamic directory listings per-process. + +Please also note that even though the directory listings are shared amongst +all processes it's still possible to differentiate on file contents via the +'read' and 'write' callback functions. +""" + + + +def VmmPyPlugin_InternalInitialize(): + """Internal Use Only! - initialization function. + """ + if 'VmmPyPlugin_IsInitialized' in globals(): + return + global VmmPyPlugin_IsInitialized + global VmmPyPlugin_RootDirectoryRoot + global VmmPyPlugin_RootDirectoryProcess + global VmmPyPlugin_OsTarget + VmmPyPlugin_IsInitialized = True + VmmPyPlugin_RootDirectoryRoot = {} + VmmPyPlugin_RootDirectoryProcess = {} + VmmPyPlugin_OsTarget = VmmPy_ConfigGet(VMMPY_OPT_CORE_TARGET_SYSTEM) + VmmPyPlugin_InternalSetVerbosity(); + VmmPyPlugin_InternalInitializePlugins() + VMMPYCC_CallbackRegister( + VmmPyPlugin_InternalCallback_List, + VmmPyPlugin_InternalCallback_Read, + VmmPyPlugin_InternalCallback_Write, + VmmPyPlugin_InternalCallback_Notify, + VmmPyPlugin_InternalCallback_Close) + + + +def VmmPyPlugin_InternalInitializePlugins(): + """Internal Use Only! - initialization function - Load Plugins. + """ + import os + import glob + import importlib + global VmmPyPlugin_PluginModules + path = os.path.dirname(__file__) + '/' + plugin_files = glob.glob(path + 'plugins/pym_*/pym_*.py', recursive=True) + plugin_names = set() + for f in plugin_files: + f_split = f.replace(path, '').replace('\\', '/').split('/') + plugin_names.add(f_split[0] + '.' + f_split[1]) + VmmPyPlugin_PluginModules = [] + for e in plugin_names: + try: + module = importlib.import_module(e) + module.Initialize(VmmPyPlugin_OsTarget) + VmmPyPlugin_PluginModules.append(module) + if VmmPyPlugin_fPrintV: + print("VmmPyPlugin: Loaded '" + e + "'") + except Exception as e2: + if VmmPyPlugin_fPrintV: + print("VmmPyPlugin: Failed to load '" + e + "'") + print("VmmPyPlugin_InternalInitializePlugins: Exception: " + str(e2)) + + + +def VmmPyPlugin_InternalCallback_List(pid, path): + """Internal Use Only! + For a given path return list of dicts containing info for each entry. + + Keyword arguments: + pid -- int: the pid (or None if root). + path -- str: the path to retrieve. + return -- list of dict containing the information. + """ + try: + dir_entry = VmmPyPlugin_RootDirectoryRoot if (pid == None) else VmmPyPlugin_RootDirectoryProcess + path_items = list(filter(None, path.split('/'))) + for e in path_items: + if not e in dir_entry: + return [] + if not 'list' in dir_entry[e]: + return [] + if dir_entry[e]['list'] != None: + dir_entry = dir_entry[e]['list'](pid, path) + break + else: + dir_entry = dir_entry[e]['dirs'] + if dir_entry == None: + return [] + result = [] + for k,v in dir_entry.items(): + result.append({'name': k, + 'size': v['size'] if 'size' in v else 0, + 'f_isdir': True if 'dirs' in v else False + }) + return result + except Exception as e: + if VmmPyPlugin_fPrintV: + print("VmmPyPlugin_InternalCallback_List: Exception: " + str(e)) + return [] + + + +def VmmPyPlugin_InternalCallback_Read(pid, path, bytes_length, bytes_offset): + """Internal Use Only! + Read bytes from a given path/file. + + Keyword arguments: + pid -- int: process identifier (PID), None for root. + path -- str: the path/file to read. + bytes_length -- int: number of bytes to read. + bytes_offset -- int: offset of bytes to read. + return -- bytes: the data read. + """ + try: + file_name, file_attr = VmmPyPlugin_FileRetrieve(pid, path) + if file_attr['read'] == None: + return b'' + if bytes_offset >= file_attr['size']: + return b'' + if bytes_length + bytes_offset > file_attr['size']: + bytes_length = file_attr['size'] - bytes_offset + return file_attr['read'](pid, file_name, file_attr, bytes_length, bytes_offset) + except Exception as e: + if VmmPyPlugin_fPrintV: + print("VmmPyPlugin_InternalCallback_Read: Exception: " + str(e)) + return None + + + +def VmmPyPlugin_InternalCallback_Write(pid, path, bytes_data, bytes_offset): + """Internal Use Only! + Write bytes to a given path/file. + + Keyword arguments: + pid -- int: process identifier (PID), None for root. + path -- str: the path/file to write. + bytes_data -- bytes: the bytes to write. + bytes_offset -- int: offset of bytes to write. + return -- int: VMMPY_STATUS (NTSTATUS) value of the write operation. + """ + try: + file_name, file_attr = VmmPyPlugin_FileRetrieve(pid, path) + bytes_length = len(bytes_data) + if file_attr['write'] == None: + return VMMPY_STATUS_END_OF_FILE + if bytes_offset >= file_attr['size']: + return VMMPY_STATUS_END_OF_FILE + if bytes_length + bytes_offset > file_attr['size']: + bytes_length = file_attr['size'] - bytes_offset + return file_attr['write'](pid, file_name, file_attr, bytes_data, bytes_offset) + except Exception as e: + if VmmPyPlugin_fPrintV: + print("VmmPyPlugin_InternalCallback_Write: Exception: " + str(e)) + return VMMPY_STATUS_FILE_INVALID + + + +def VmmPyPlugin_InternalSetVerbosity(): + """Internal Use Only! + Set verbosity level variables + """ + try: + global VmmPyPlugin_fPrint, VmmPyPlugin_fPrintV, VmmPyPlugin_fPrintVV, VmmPyPlugin_fPrintVVV + VmmPyPlugin_fPrint = VmmPy_ConfigGet(VMMPY_OPT_CORE_PRINTF_ENABLE) > 0 + VmmPyPlugin_fPrintV = VmmPyPlugin_fPrint and VmmPy_ConfigGet(VMMPY_OPT_CORE_VERBOSE) > 0 + VmmPyPlugin_fPrintVV = VmmPyPlugin_fPrint and VmmPy_ConfigGet(VMMPY_OPT_CORE_VERBOSE_EXTRA) > 0 + VmmPyPlugin_fPrintVVV = VmmPyPlugin_fPrint and VmmPy_ConfigGet(VMMPY_OPT_CORE_VERBOSE_EXTRA_TLP) > 0 + except Exception as e: + if VmmPyPlugin_fPrintV: + print("VmmPyPlugin_InternalSetVerbosity: Exception: " + str(e)) + + + +def VmmPyPlugin_InternalCallback_Notify(fEvent, bytesData): + """Internal Use Only! + Receive notify events from the native plugin manager. + + Keyword arguments: + fEvent -- int: the event id as given by VMMPY_PLUGIN_EVENT_* + bytesData -- bytes: any bytes object (or None) related to the event. + """ + if fEvent == VMMPY_PLUGIN_EVENT_VERBOSITYCHANGE: + VmmPyPlugin_InternalSetVerbosity() + + + +def VmmPyPlugin_InternalCallback_Close(): + """Internal Use Only! + Callback when closing down python interpreter. + """ + print("VmmPyPlugin_InternalCallback_Close") + return 0 + + + +def VmmPyPlugin_FileRegister(pid, path, size, fn_read_callback, fn_write_callback = None, is_overwrite = False): + """Register a file in the file listing database. + NB! Required directories are automatically created if possible. + + Keyword arguments: + pid -- int: process identifier (PID), None for root. + path -- str: the path including the file name to register. + size -- the file size. + fn_read_callback = callback function for read operation. + fn_write_callback = callback function for write operation. + is_overwrite -- overwrise allowed? + """ + dir_entry = VmmPyPlugin_RootDirectoryRoot if (pid == None) else VmmPyPlugin_RootDirectoryProcess + path_items = list(filter(None, path.split('/'))) + file = path_items.pop() + for path_item in path_items: + if not path_item in dir_entry: + dir_entry[path_item] = {'list': None, 'dirs': {}} + if dir_entry[path_item]['list'] != None: + raise RuntimeError('VmmPyPlugin_FileRegister: cannot add file entry to sub-directory with dynamic listing.') + dir_entry = dir_entry[path_item]['dirs'] + if not is_overwrite and file in dir_entry: + raise RuntimeError('VmmPyPlugin_FileRegister: cannot overwrite existing file without is_overwrite flag set.') + dir_entry[file] = {'size': size, 'read': fn_read_callback, 'write': fn_write_callback} + + + +def VmmPyPlugin_FileRegisterDirectory(pid, path, fn_list_callback = None, is_overwrite = False): + """Register a directory in the file listing database. + NB! Required directories are automatically created if possible. + + Keyword arguments: + pid -- int: process identifier (PID), None for root. + path -- the path including the file name to register. + fn_list_callback = callback function for dynamic directory listing. + is_overwrite -- overwrise allowed? + """ + dir_entry = VmmPyPlugin_RootDirectoryRoot if (pid == None) else VmmPyPlugin_RootDirectoryProcess + path_items = list(filter(None, path.split('/'))) + dir_to_reg = path_items.pop() + for path_item in path_items: + if not path_item in dir_entry: + dir_entry[path_item] = {'list': None, 'dirs': {}} + if dir_entry[path_item]['list'] != None: + raise RuntimeError('VmmPyPlugin_FileRegisterDirectory: cannot add directory entry to sub-directory with dynamic listing.') + dir_entry = dir_entry[path_item]['dirs'] + if not is_overwrite and dir_to_reg in dir_entry: + raise RuntimeError('VmmPyPlugin_FileRegisterDirectory: cannot overwrite existing directory without is_overwrite flag set.') + dir_entry[dir_to_reg] = {'list': fn_list_callback, 'dirs': {}} + + + +def VmmPyPlugin_FileUnregister(pid, path): + """Unregister a directory or file from the file listing database. + + Keyword arguments: + pid -- int: process identifier (PID), None for root. + path -- str: the path including the file name to register. + """ + dir_entry = VmmPyPlugin_RootDirectoryRoot if (pid == None) else VmmPyPlugin_RootDirectoryProcess + path_items = list(filter(None, path.split('/'))) + entry = path_items.pop() + for path_item in path_items: + if not path_item in dir_entry: + raise RuntimeError('VmmPyPlugin_FileUnregister: cannot remove non-existant directory/file.') + dir_entry = dir_entry[path_item]['dirs'] + if not entry in dir_entry: + raise RuntimeError('VmmPyPlugin_FileUnregister: cannot remove non-existant directory/file.') + del dir_entry[entry] + + + +def VmmPyPlugin_FileRetrieve(pid, path): + """Retrieve a file from the file listing database. + + Keyword arguments: + pid -- int: process identifier (PID), None for root. + path -- str: the path including the file name to register. + pid -- int: process identifier (optional) to be forwarded to any dynamic list functions. + return -- tuple: . + """ + dir_entry = VmmPyPlugin_RootDirectoryRoot if (pid == None) else VmmPyPlugin_RootDirectoryProcess + path_items = list(filter(None, path.split('/'))) + entry = path_items.pop() + dir_path = '/'.join(path_items) + for path_item in path_items: + if not path_item in dir_entry: + raise RuntimeError('VmmPyPlugin_FileRetrieve: not found.') + if dir_entry[path_item]['list'] != None: + dir_entry = dir_entry[path_item]['list'](pid, dir_path) + else: + dir_entry = dir_entry[path_item]['dirs'] + if entry in dir_entry: + return entry, dir_entry[entry] + raise RuntimeError('VmmPyPlugin_FileRetrieve: not found.') + + + +#------------------------------------------------------------------------------ +# Initialize the VmmPyPlugin system and register it with the native code VMM. +#------------------------------------------------------------------------------ + +try: + VmmPyPlugin_InternalInitialize() +except Exception as e: + print(str(e)) + diff --git a/m_vmemd/m_vmemd.c b/m_vmemd/m_vmemd.c new file mode 100644 index 0000000..45cae82 --- /dev/null +++ b/m_vmemd/m_vmemd.c @@ -0,0 +1,135 @@ +// m_vmemd.h : implementation related to the vmemd native plugin module for the +// memory process file system. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include +#include +#include "vmmdll.h" + +ULONG64 VMemD_GetBaseFromFileName(LPSTR sz) +{ + if((strlen(sz) < 18) || (sz[0] != '0') || (sz[1] != 'x')) { return (ULONG64)-1; } + return strtoull(sz, NULL, 16); +} + +/* +* Read : function as specified by the module manager. The module manager will +* call into this callback function whenever a read shall occur from a "file". +* -- ctx +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +*/ +NTSTATUS VMemD_Read(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset) +{ + BOOL result; + ULONG64 cbMax, vaBase; + VMMDLL_MEMMAP_ENTRY entry; + // read memory from "vmemd" directory file + vaBase = VMemD_GetBaseFromFileName(ctx->szPath); + if(vaBase & 0xfff) { return VMMDLL_STATUS_FILE_INVALID; } + result = VMMDLL_ProcessGetMemoryMapEntry(ctx->dwPID, &entry, vaBase, FALSE); + if(!result) { return VMMDLL_STATUS_FILE_INVALID; } + *pcbRead = 0; + if(entry.AddrBase + (entry.cPages << 12) <= vaBase + cbOffset) { return VMMDLL_STATUS_END_OF_FILE; } + cbMax = min((entry.AddrBase + (entry.cPages << 12)), (vaBase + cb + cbOffset)) - (vaBase - cbOffset); // min(entry_top_addr, request_top_addr) - request_start_addr + result = VMMDLL_MemReadEx(ctx->dwPID, vaBase + cbOffset, pb, (DWORD)min(cb, cbMax), pcbRead, 0); + return (result && *pcbRead) ? VMMDLL_STATUS_SUCCESS : VMMDLL_STATUS_END_OF_FILE; +} + +/* +* Write : function as specified by the module manager. The module manager will +* call into this callback function whenever a write shall occur from a "file". +* -- ctx +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMemD_Write(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset) +{ + BOOL result; + ULONG64 cbMax, vaBase; + VMMDLL_MEMMAP_ENTRY entry; + // write memory from "vmemd" directory file + vaBase = VMemD_GetBaseFromFileName(ctx->szPath); + if(vaBase & 0xfff) { return VMMDLL_STATUS_FILE_INVALID; } + result = VMMDLL_ProcessGetMemoryMapEntry(ctx->dwPID, &entry, vaBase, FALSE); + if(!result) { return VMMDLL_STATUS_FILE_INVALID; } + *pcbWrite = 0; + if(entry.AddrBase + (entry.cPages << 12) <= vaBase + cbOffset) { return VMMDLL_STATUS_END_OF_FILE; } + cbMax = min((entry.AddrBase + (entry.cPages << 12)), (vaBase + cb + cbOffset)) - (vaBase - cbOffset); // min(entry_top_addr, request_top_addr) - request_start_addr + VMMDLL_MemWrite(ctx->dwPID, vaBase + cbOffset, pb, (DWORD)min(cb, cbMax)); + *pcbWrite = cb; + return VMMDLL_STATUS_SUCCESS; +} + +/* +* List : function as specified by the module manager. The module manager will +* call into this callback function whenever a list directory shall occur from +* the given module. +* -- ctx +* -- pFileList +* -- return +*/ +BOOL VMemD_List(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList) +{ + BOOL result; + DWORD i; + ULONG64 cEntries = 0; + CHAR szBufferFileName[MAX_PATH]; + PVMMDLL_MEMMAP_ENTRY pMemMap; + if(ctx->szPath[0]) { + // only list in module root directory. + // not root directory == error for this module. + return FALSE; + } + // populate memory map directory + result = VMMDLL_ProcessGetMemoryMap(ctx->dwPID, NULL, &cEntries, FALSE); + if(!result) { return FALSE; } + pMemMap = (PVMMDLL_MEMMAP_ENTRY)LocalAlloc(0, cEntries * sizeof(VMMDLL_MEMMAP_ENTRY)); + if(!pMemMap) { return FALSE; } + result = VMMDLL_ProcessGetMemoryMap(ctx->dwPID, pMemMap, &cEntries, TRUE); + if(!result) { + LocalFree(pMemMap); + return FALSE; + } + for(i = 0; i < cEntries; i++) { + sprintf_s( + szBufferFileName, + MAX_PATH - 1, + "0x%016llx%s%s.vmem", + pMemMap[i].AddrBase, + pMemMap[i].szTag[0] ? "-" : "", + pMemMap[i].szTag[0] ? pMemMap[i].szTag : ""); + VMMDLL_VfsList_AddFile(pFileList, szBufferFileName, (pMemMap[i].cPages << 12)); + } + LocalFree(pMemMap); + return TRUE; +} + +/* +* Initialization function for the vmemd native plugin module. +* It's important that the function is exported in the DLL and that it is +* declared exactly as below. The plugin manager will call into this function +* after the DLL is loaded. The DLL then must fill the appropriate information +* into the supplied struct and call the pfnPluginManager_Register function to +* register itself with the plugin manager. +* -- pRegInfo +*/ +__declspec(dllexport) +VOID InitializeVmmPlugin(_In_ PVMMDLL_PLUGIN_REGINFO pRegInfo) +{ + if(0 == (pRegInfo->fTargetSystem & (VMMDLL_TARGET_UNKNOWN_X64 | VMMDLL_TARGET_WINDOWS_X64))) { return; } + strcpy_s(pRegInfo->reg_info.szModuleName, 32, "vmemd"); // module name - 'vmemd'. + pRegInfo->reg_info.fProcessModule = TRUE; // module shows in process directory. + pRegInfo->reg_fn.pfnList = VMemD_List; // List function supported. + pRegInfo->reg_fn.pfnRead = VMemD_Read; // Read function supported. + pRegInfo->reg_fn.pfnWrite = VMemD_Write; // Write function supported. + pRegInfo->pfnPluginManager_Register(pRegInfo); // Register with the plugin maanger. +} diff --git a/m_vmemd/m_vmemd.vcxproj b/m_vmemd/m_vmemd.vcxproj new file mode 100644 index 0000000..282717e --- /dev/null +++ b/m_vmemd/m_vmemd.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {41D93156-9DCB-4F19-BF02-993CEC361A81} + mvmemd + 8.1 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + $(SolutionDir)\files\plugins\ + $(SolutionDir)\files\temp\$(ProjectName)\ + + + + $(SolutionDir)\files\plugins\ + $(SolutionDir)\files\temp\$(ProjectName)\ + + + + Level3 + Disabled + true + true + + + $(SolutionDir)\files\vmm.lib + $(OutDir)\..\lib\$(TargetName).pdb + $(OutDir)\..\lib\$(TargetName).lib + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + UseLinkTimeCodeGeneration + $(SolutionDir)\files\vmm.lib + DebugFull + $(OutDir)\..\lib\$(TargetName).pdb + $(OutDir)\..\lib\$(TargetName).lib + + + + + + + + + + + + \ No newline at end of file diff --git a/m_vmemd/m_vmemd.vcxproj.filters b/m_vmemd/m_vmemd.vcxproj.filters new file mode 100644 index 0000000..7d1dc9d --- /dev/null +++ b/m_vmemd/m_vmemd.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {2fec9e35-00d4-4c85-9715-a9582e63f7df} + + + + + Header Files\vmm + + + + + Source Files + + + \ No newline at end of file diff --git a/m_vmemd/m_vmemd.vcxproj.user b/m_vmemd/m_vmemd.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/m_vmemd/m_vmemd.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/m_vmemd/vmmdll.h b/m_vmemd/vmmdll.h new file mode 100644 index 0000000..623a09b --- /dev/null +++ b/m_vmemd/vmmdll.h @@ -0,0 +1,566 @@ +// vmmdll.h : header file to include in projects that use vmm.dll either as +// stand anlone projects or as native plugins to vmm.dll. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include + +#ifndef __VMMDLL_H__ +#define __VMMDLL_H__ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +/* +* RESERVED FUNCTION! DO NOT USE! +* Call other VMMDLL_Intialize functions to initialize VMM.DLL and the memory +* process file system. +*/ +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]); + +/* +* Initialize VMM.DLL from a memory dump file in raw format. VMM.DLL will be +* initialized in read-only mode. It's possible to optionally specify the page +* table base of the windows kernel (for full vmm features) or the page table +* base of a single 64-bit process in any x64 operating system. NB! usually it +* is not necessary to specify the PageTableBase - it will be auto-identified +* most often if the target is Windows. +* -- szFileName = the file name of the raw memory dump to use. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Intiailize VMM.DLL from a supported FPGA device over USB. VMM.DLL will be +* initialized in read/write mode upon success. Optionally it will be possible +* to specify the max physical address and the page table base of the kernel or +* process that should be investigated. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* -- szMaxPhysicalAddressOpt = max physical address of the target system as a +* hex string. Example: "0x8000000000". If zero value is given the max +* address will be auto-identified. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Initialize VMM.DLL from a the "Total Meltdown" CVE-2018-1038 vulnerability. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* initialized in read/write mode upon success. +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeTotalMeltdown(); + +/* +* Close an initialized instance of VMM.DLL and clean up all allocated resources +* including plugins, linked PCILeech.DLL and other memory resources. +* -- return = success/fail. +*/ +BOOL VMMDLL_Close(); + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +/* +* Options used together with the functions: VMMDLL_GetOption & VMMDLL_SetOption +* If VMM.DLL is chained with PCILEECH.DLL then required values will be passed +* along to PCILEECH.DLL when necessary. +* For more detailed information check the sources for individual device types. +*/ +#define VMMDLL_OPT_DEVICE_FPGA_PROBE_MAXPAGES 0x01 // RW +#define VMMDLL_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_RX 0x03 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_TX 0x04 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_READ 0x05 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE 0x06 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_WRITE 0x07 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_READ 0x08 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_RETRY_ON_ERROR 0x09 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DEVICE_ID 0x80 // R +#define VMMDLL_OPT_DEVICE_FPGA_FPGA_ID 0x81 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR 0x82 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR 0x83 // R + +#define VMMDLL_OPT_CORE_PRINTF_ENABLE 0x80000001 // RW +#define VMMDLL_OPT_CORE_VERBOSE 0x80000002 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R +#define VMMDLL_OPT_CORE_TARGET_SYSTEM 0x80000007 // R + +#define VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED 0x40000001 // R - 1/0 +#define VMMDLL_OPT_CONFIG_TICK_PERIOD 0x40000002 // RW - base tick period in ms +#define VMMDLL_OPT_CONFIG_READCACHE_TICKS 0x40000003 // RW - memory cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_TLBCACHE_TICKS 0x40000004 // RW - page table (tlb) cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL 0x40000005 // RW - process refresh (partial) period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL 0x40000006 // RW - process refresh (full) period (in ticks) +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR 0x40000007 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR 0x40000008 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION 0x40000009 // R +#define VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL 0x4000000A // RW - enable function call statistics (.status/statistics_fncall file) + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- pqwValue = pointer to ULONG64 to receive option value. +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- qwValue +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +// This is the core of the memory process file system. All implementation and +// analysis towards the file system is possible by using functionality below. +//----------------------------------------------------------------------------- + +#define VMMDLL_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMMDLL_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMMDLL_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMMDLL_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMMDLL_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +typedef struct tdVMMDLL_VFS_FILELIST { + VOID(*pfnAddFile) (_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved); + VOID(*pfnAddDirectory)(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved); + HANDLE h; +} VMMDLL_VFS_FILELIST, *PVMMDLL_VFS_FILELIST; + +/* +* Helper function macros for callbacks into the VMM_VFS_FILELIST structure. +*/ +#define VMMDLL_VfsList_AddFile(pFileList, szName, cb) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddFile(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, cb, NULL); } +#define VMMDLL_VfsList_AddDirectory(pFileList, szName) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddDirectory(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, NULL); } + +/* +* List a directory of files in the memory process file system. Directories and +* files will be listed by callbacks into functions supplied in the pFileList +* parameter. If information of an individual file is needed it's neccessary +* to list all files in its directory. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + +/* +* Read select parts of a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +* +*/ +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + +/* +* Write select parts to a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + +/* +* Utility functions for memory process file system read/write towards different +* underlying data representations. +*/ +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow); + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +// Function and structures to initialize and use the memory process file system +// plugin functionality. The plugin manager is started by a call to function: +// VMM_VfsInitializePlugins. Each built-in plugin and external plugin of which +// the DLL name matches m_*.dll will receive a call to its InitializeVmmPlugin +// function. The plugin/module may decide to call pfnPluginManager_Register to +// register plugins in the form of different names one or more times. +// Example of registration function in a plugin DLL below: +// 'VOID InitializeVmmPlugin(_In_ PVMM_PLUGIN_REGINFO pRegInfo)' +//----------------------------------------------------------------------------- + +/* +* Initialize all potential plugins, both built-in and external, that maps into +* the memory process file system. Please note that plugins are not loaded by +* default - they have to be explicitly loaded by calling this function. They +* will be unloaded on a general close of the vmm dll. +* -- return +*/ +BOOL VMMDLL_VfsInitializePlugins(); + +#define VMMDLL_PLUGIN_CONTEXT_MAGIC 0xc0ffee663df9301c +#define VMMDLL_PLUGIN_CONTEXT_VERSION 1 +#define VMMDLL_PLUGIN_REGINFO_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PLUGIN_REGINFO_VERSION 1 + +#define VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE 0x01 + +typedef struct tdVMMDLL_PLUGIN_CONTEXT { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD dwPID; + PHANDLE phModulePrivate; + PHANDLE phProcessPrivate; + PVOID pProcess; + LPSTR szModule; + LPSTR szPath; + PVOID pvReserved1; + PVOID pvReserved2; +} VMMDLL_PLUGIN_CONTEXT, *PVMMDLL_PLUGIN_CONTEXT; + +typedef struct tdVMMDLL_PLUGIN_REGINFO { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; + HMODULE hDLL; + HMODULE hReservedDll; // not for general use (only used for python). + BOOL(*pfnPluginManager_Register)(struct tdVMMDLL_PLUGIN_REGINFO *pPluginRegInfo); + PVOID pvReserved1; + PVOID pvReserved2; + // general plugin registration info to be filled out by the plugin below: + struct { + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + PVOID pvReserved1; + PVOID pvReserved2; + } reg_info; + // function plugin registration info to be filled out by the plugin below: + struct { + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + PVOID pvReserved1; + PVOID pvReserved2; + } reg_fn; +} VMMDLL_PLUGIN_REGINFO, *PVMMDLL_PLUGIN_REGINFO; + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +// Vmm core functaionlity such as read (and write) to both virtual and physical +// memory. NB! writing will only work if the target is supported - i.e. not a +// memory dump file... +// To read physical memory specify dwPID as (DWORD)-1 +//----------------------------------------------------------------------------- + +// FLAG used to supress the default read cache in calls to VMM_MemReadEx() +// which will lead to the read being fetched from the target system always. +// Cached page tables (used for translating virtual2physical) are still used. +#define VMMDLL_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMMDLL_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMMDLL_TARGET_UNKNOWN_X64 0x0001 +#define VMMDLL_TARGET_WINDOWS_X64 0x0002 + +typedef struct tdVMMDLL_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} VMMDLL_MEM_IO_SCATTER_HEADER, *PVMMDLL_MEM_IO_SCATTER_HEADER, **PPVMMDLL_MEM_IO_SCATTER_HEADER; + +/* +* Read memory in various non-contigious locations specified by the pointers to +* the items in the ppDMAs array. Result for each unit of work will be given +* individually. No upper limit of number of items to read, but no performance +* boost will be given if above hardware limit. Max size of each unit of work is +* one 4k page (4096 bytes). +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpDMAsRead = optional count of number of successfully read ppDMAs. +* -- flags = optional flags as given by VMM_FLAG_* +* -- return = the number of successfully read items. +*/ +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); + +/* +* Read a single 4096-byte page of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pbPage +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Read a contigious arbitrary amount of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious amount of memory and report the number of bytes read in pcbRead. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +* -- return = success/fail. NB! reads may report as success even if 0 bytes are +* read - it's recommended to verify pcbReadOpt parameter. +*/ +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + +/* +* Write a contigious arbitrary amount of memory. Please note some virtual memory +* such as pages of executables (such as DLLs) may be shared between different +* virtual memory over different processes. As an example a write to kernel32.dll +* in one process is likely to affect kernel32 in the whole system - in all +* processes. Heaps and Stacks and other memory are usually safe to write to. +* Please take care when writing to memory! +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Translate a virtual address to a physical address by walking the page tables +* of the specified process. +* -- dwPID +* -- qwVA +* -- pqwPA +* -- return = success/fail. +*/ +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +// Functionality below is mostly relating to Windows processes. +//----------------------------------------------------------------------------- + +/* +* Retrieve an active process given it's name. Please note that if multiple +* processes with the same name exists only one will be returned. If required to +* parse all processes with the same name please iterate over the PID list by +* calling VMMDLL_PidList together with VMMDLL_ProcessGetInformation. +* -- szProcName = process name (truncated max 15 chars) case insensitive. +* -- pdwPID = pointer that will receive PID on success. +* -- return +*/ +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + +/* +* List the PIDs in the system. +* -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. +* -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. +* -- return = success/fail. +*/ +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + +// flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define VMMDLL_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMMDLL_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMMDLL_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMMDLL_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +typedef struct tdVMMDLL_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMMDLL_MEMMAP_ENTRY, *PVMMDLL_MEMMAP_ENTRY; + +/* +* Retrieve memory map entries from the specified process. Memory map entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries bytes. +* If the pMemMapEntries is set to NULL the number of memory map entries will be +* given in the pcMemMapEntries parameter. +* -- dwPID +* -- pMemMapEntries = buffer of minimum length sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. +* -- pcMemMapEntries = pointer to number of memory map entries. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + +/* +* Retrieve a single memory map entry given a virtual address within that entrys +* range. +* -- dwPID +* -- pMemMapEntry +* -- va = virtual address in the memory map entry to retrieve. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules); + +typedef struct tdVMMDLL_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; +} VMMDLL_MODULEMAP_ENTRY, *PVMMDLL_MODULEMAP_ENTRY; + +/* +* Retrieve the module entries from the specified process. The module entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the +* pcModuleEntries is set to NULL the number of module entries will be given +* in the pcModuleEntries parameter. +* -- dwPID +* -- pModuleEntries = buffer of minimum length sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. +* -- pcModuleEntries = pointer to number of memory map entries. +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + +/* +* Retrieve a module (.exe or .dll or similar) given a module name. +* -- dwPID +* -- szModuleName +* -- pModuleEntry +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry); + +#define VMMDLL_PROCESS_INFORMATION_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PROCESS_INFORMATION_VERSION 1 + +typedef struct tdVMMDLL_PROCESS_INFORMATION { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; // as given by VMMDLL_TARGET_* + BOOL fUserOnly; // only user mode pages listed + DWORD dwPID; + DWORD dwState; + CHAR szName[16]; + ULONG64 paPML4; + ULONG64 paPML4_UserOpt; // may not exist + union { + struct { + ULONG64 vaEPROCESS; + ULONG64 vaPEB; + ULONG64 vaENTRY; + BOOL fWow64; + DWORD vaPEB32; // WoW64 only + } win; + } os; +} VMMDLL_PROCESS_INFORMATION, *PVMMDLL_PROCESS_INFORMATION; + +/* +* Retrieve various process information from a PID. Process information such as +* name, page directory bases and the process state may be retrieved. +* -- dwPID +* -- pProcessInformation = if null, size is given in *pcbProcessInfo +* -- pcbProcessInformation = size of pProcessInfo (in bytes) on entry and exit +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation); + +typedef struct tdVMMDLL_EAT_ENTRY { + ULONG64 vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMDLL_EAT_ENTRY, *PVMMDLL_EAT_ENTRY; + +typedef struct tdVMMDLL_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMDLL_IAT_ENTRY, *PVMMDLL_IAT_ENTRY; + +/* +* Retrieve information about: Data Directories, Sections, Export Address Table +* and Import Address Table (IAT). +* If the pData == NULL upon entry the number of entries of the pData array must +* have in order to be able to hold the data is returned. +* -- dwPID +* -- szModule +* -- pData +* -- cData +* -- pcData +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + + + +//----------------------------------------------------------------------------- +// VMM UTIL FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __VMMDLL_H__ */ diff --git a/vmm/device.c b/vmm/device.c new file mode 100644 index 0000000..c349e12 --- /dev/null +++ b/vmm/device.c @@ -0,0 +1,164 @@ +// device.c : implementation related to memory acquisition devices. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "device.h" +#include "devicefile.h" +#include "devicepcileechdll.h" +#include "statistics.h" +#include "vmm.h" + +VOID DeviceReadScatterMEM(_Inout_ PPMEM_IO_SCATTER_HEADER ppDMAs, _In_ DWORD cpDMAs, _Out_opt_ PDWORD pcpDMAsRead) +{ + QWORD tmStart = Statistics_CallStart(); + ctxMain->dev.pfnReadScatterMEM( ppDMAs, cpDMAs, pcpDMAsRead); + Statistics_CallEnd(STATISTICS_ID_DeviceReadScatterMEM, tmStart); +} + +BOOL DeviceWriteMEM(_In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb) +{ + BOOL result; + QWORD tmStart = Statistics_CallStart(); + result = ctxMain->dev.pfnWriteMEM && ctxMain->dev.pfnWriteMEM(qwAddr, pb, cb); + Statistics_CallEnd(STATISTICS_ID_DeviceWriteMEM, tmStart); + return result; +} + +VOID DeviceClose() +{ + if(ctxMain->dev.pfnClose) { + ctxMain->dev.pfnClose(); + } +} + +/* +* Auto-identifies the maximum address by starting to try to read memory at 4GB +* and then by moving upwards. Reads should be minimized - but if "bad" hardware +* this may still (in very rare occurances) freeze the target computer if DMA +* device is used. Should only be called whenever needed - i.e. when the native +* device does not report a valid value in combination with the absence of user +* defined max address. +*/ +QWORD DeviceAutoIdentifyMaxAddress() +{ + DWORD i, cMEM; + QWORD qwCurrentAddress = 0x100000000, qwChunkSize = 0x100000000; + MEM_IO_SCATTER_HEADER pMEM[1], *ppMEM[1]; + BYTE pbDummy[0x1000]; + DWORD dwOFFSETS[] = { 0x0, 0x1000, 0x2000, 0x3000, 0x00010000, 0x00100000, 0x01000000, 0x10000000 }; + DWORD cOFFSETS = sizeof(dwOFFSETS) / sizeof(DWORD); + // 1: set up + ZeroMemory(pMEM, sizeof(MEM_IO_SCATTER_HEADER)); + pMEM->pb = pbDummy; + pMEM->cbMax = 0x1000; + *ppMEM = pMEM; + // 2: loop until fail on smallest chunk size (0x1000) + while(TRUE) { + for(i = 0; i < cOFFSETS; i++) { + pMEM->cb = 0; + pMEM->qwA = qwCurrentAddress + qwChunkSize + dwOFFSETS[i]; + DeviceReadScatterMEM(ppMEM, 1, &cMEM); + if(cMEM) { + qwCurrentAddress += qwChunkSize; + break; + } + } + if(cMEM) { continue; } + if(qwChunkSize == 0x1000) { + return qwCurrentAddress + ((qwCurrentAddress == 0x100000000) ? 0 : 0xfff); + } + qwChunkSize >>= 1; // half chunk size + } +} + +BOOL DeviceOpen() +{ + BOOL result; + if((0 == _stricmp("fpga", ctxMain->cfg.szDevTpOrFileName)) || (0 == _stricmp("totalmeltdown", ctxMain->cfg.szDevTpOrFileName))) { + result = DevicePCILeechDll_Open(ctxMain); + } else { + result = DeviceFile_Open(ctxMain); + } + if(result) { + if((ctxMain->cfg.paAddrMax == 0x0000ffffffffffff) && (ctxMain->dev.paAddrMaxNative == 0x0000ffffffffffff)) { + // probe for max address - if needed and not already user supplied + ctxMain->dev.paAddrMaxNative = DeviceAutoIdentifyMaxAddress(ctxMain); + } + ctxMain->cfg.paAddrMax = min(ctxMain->cfg.paAddrMax, ctxMain->dev.paAddrMaxNative); + } + return result; +} + +BOOL DeviceGetOption(_In_ QWORD fOption, _Out_ PQWORD pqwValue) +{ + return ctxMain->dev.pfnGetOption && ctxMain->dev.pfnGetOption(fOption, pqwValue); +} + +BOOL DeviceSetOption(_In_ QWORD fOption, _In_ QWORD qwValue) +{ + return ctxMain->dev.pfnSetOption && ctxMain->dev.pfnSetOption(fOption, qwValue); +} + +DWORD DeviceReadMEMEx_DoWork_Scatter(_In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _Inout_opt_ PPAGE_STATISTICS pPageStat) +{ + PBYTE pbBuffer; + PMEM_IO_SCATTER_HEADER pDMAs, *ppDMAs; + DWORD i, o, cDMAs, cDMAsRead; + cDMAs = cb >> 12; + pbBuffer = (PBYTE)LocalAlloc(LMEM_ZEROINIT, cDMAs * (sizeof(PMEM_IO_SCATTER_HEADER) + sizeof(MEM_IO_SCATTER_HEADER))); + if(!pbBuffer) { return 0; } + ppDMAs = (PMEM_IO_SCATTER_HEADER*)pbBuffer; + pDMAs = (PMEM_IO_SCATTER_HEADER)(pbBuffer + cDMAs * sizeof(PMEM_IO_SCATTER_HEADER)); + for(i = 0, o = 0; i < cDMAs; i++, o += 0x1000) { + ppDMAs[i] = pDMAs + i; + pDMAs[i].qwA = qwAddr + o; + pDMAs[i].cbMax = min(0x1000, cb - o); + pDMAs[i].pb = pb + o; + } + DeviceReadScatterMEM(ppDMAs, cDMAs, &cDMAsRead); + for(i = 0; i < cDMAs; i++) { + if(pDMAs[i].cb == 0x1000) { + PageStatUpdate(pPageStat, pDMAs[i].qwA + 0x1000, 1, 0); + } else { + PageStatUpdate(pPageStat, pDMAs[i].qwA + 0x1000, 0, 1); + ZeroMemory(pDMAs[i].pb, 0x1000); + } + } + LocalFree(pbBuffer); + return cDMAsRead << 12; +} + +DWORD DeviceReadMEMEx_DoWork(_In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _Inout_opt_ PPAGE_STATISTICS pPageStat, _In_ DWORD cbMaxSizeIo) +{ + DWORD cbRd, cbRdOff; + DWORD cbChunk, cChunkTotal, cChunkSuccess = 0; + DWORD i, cbSuccess = 0; + // calculate current chunk sizes + cbChunk = ~0xfff & min(cb, cbMaxSizeIo); + cbChunk = (cbChunk > 0x3000) ? cbChunk : 0x1000; + cChunkTotal = (cb / cbChunk) + ((cb % cbChunk) ? 1 : 0); + // try read memory + memset(pb, 0, cb); + for(i = 0; i < cChunkTotal; i++) { + cbRdOff = i * cbChunk; + cbRd = ((i == cChunkTotal - 1) && (cb % cbChunk)) ? (cb % cbChunk) : cbChunk; // (last chunk may be smaller) + cbSuccess += DeviceReadMEMEx_DoWork_Scatter(qwAddr + cbRdOff, pb + cbRdOff, cbRd, pPageStat); + } + return cbSuccess; +} + +DWORD DeviceReadMEMEx(_In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _Inout_opt_ PPAGE_STATISTICS pPageStat) +{ + BYTE pbWorkaround[4096]; + DWORD cbDataRead; + // read memory (with strange workaround for 1-page reads...) + if(cb != 0x1000) { + cbDataRead = DeviceReadMEMEx_DoWork(qwAddr, pb, cb, pPageStat, (DWORD)ctxMain->dev.qwMaxSizeMemIo); + } else { + // why is this working ??? if not here console is screwed up... (threading issue?) + cbDataRead = DeviceReadMEMEx_DoWork(qwAddr, pbWorkaround, 0x1000, pPageStat, (DWORD)ctxMain->dev.qwMaxSizeMemIo); + memcpy(pb, pbWorkaround, 0x1000); + } + return cbDataRead; +} diff --git a/vmm/device.h b/vmm/device.h new file mode 100644 index 0000000..7d39f86 --- /dev/null +++ b/vmm/device.h @@ -0,0 +1,72 @@ +// device.h : definitions related to memory acquisition devices. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __DEVICE_H__ +#define __DEVICE_H__ +#include "vmm.h" +#include "statistics.h" + +/* +* Open a connection to the target device. +* -- result +*/ +BOOL DeviceOpen(); + +/* +* Clean up various device related stuff and deallocate memory buffers. +*/ +VOID DeviceClose(); + +/* +* Read memory in various non-contigious locations specified by the items in the +* phDMAs array. Result for each unit of work will be given individually. No upper +* limit of number of items to read, but no performance boost will be given if +* above hardware limit. Max size of each unit of work is one 4k page (4096 bytes). +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpMEMsRead = optional count of number of successfully read ppMEMs. +*/ +VOID DeviceReadScatterMEM(_Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _Out_opt_ PDWORD pcpMEMsRead); + +/* +* Try read memory in a fairly optimal way considering device limits. The number +* of total successfully read bytes is returned. Failed reads will be zeroed out +* in the returned memory. +* -- qwAddr +* -- pb +* -- cb +* -- pPageStat = optional page statistics +* -- return = the number of bytes successfully read. +*/ +DWORD DeviceReadMEMEx(_In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _Inout_opt_ PPAGE_STATISTICS pPageStat); + +/* +* Write data to the target system using DMA. +* -- qwAddr +* -- pb +* -- cb +* -- return +*/ +BOOL DeviceWriteMEM(_In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb); + +/* +* Set a device specific option value. Please see individual device header files +* for a list of the possible device specific options. +* -- fOption +* -- pqwValue = pointer to QWORD to receive option value. +* -- return +*/ +BOOL DeviceGetOption(_In_ QWORD fOption, _Out_ PQWORD pqwValue); + +/* +* Set a device specific option value. Please see individual device header files +* for a list of the possible device specific options. +* -- fOption +* -- qwValue +* -- return +*/ +BOOL DeviceSetOption(_In_ QWORD fOption, _In_ QWORD qwValue); + +#endif /* __DEVICE_H__ */ diff --git a/vmm/devicefile.c b/vmm/devicefile.c new file mode 100644 index 0000000..1dfc063 --- /dev/null +++ b/vmm/devicefile.c @@ -0,0 +1,82 @@ +// devicefile.c : implementation related to file backed memory acquisition device. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "devicefile.h" +#include "util.h" +#include "vmm.h" + +typedef struct tdDEVICE_CONTEXT_FILE { + FILE *pFile; + QWORD cbFile; + LPSTR szFileName; +} DEVICE_CONTEXT_FILE, *PDEVICE_CONTEXT_FILE; + +VOID DeviceFile_ReadScatterMEM(_Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _Out_opt_ PDWORD pcMEMsRead) +{ + PDEVICE_CONTEXT_FILE ctxFile = (PDEVICE_CONTEXT_FILE)ctxMain->dev.hDevice; + DWORD i, cbToRead, c = 0; + PMEM_IO_SCATTER_HEADER pMEM; + for(i = 0; i < cpMEMs; i++) { + pMEM = ppMEMs[i]; + if(pMEM->qwA >= ctxFile->cbFile) { continue; } + cbToRead = (DWORD)min(pMEM->cb, ctxFile->cbFile - pMEM->qwA); + if(pMEM->qwA != _ftelli64(ctxFile->pFile)) { + if(_fseeki64(ctxFile->pFile, pMEM->qwA, SEEK_SET)) { continue; } + } + pMEM->cb = (DWORD)fread(pMEM->pb, 1, pMEM->cbMax, ctxFile->pFile); + if(ctxMain->cfg.fVerboseExtraTlp) { + vmmprintf( + "devicefile.c!DeviceFile_ReadScatterMEM: READ:\n" \ + " file='%s'\n" \ + " offset=%016llx req_len=%08x rsp_len=%08x\n", + ctxFile->szFileName, + pMEM->qwA, + pMEM->cbMax, + pMEM->cb + ); + Util_PrintHexAscii(pMEM->pb, pMEM->cb, 0); + } + c += (ppMEMs[i]->cb >= ppMEMs[i]->cbMax) ? 1 : 0; + } + if(pcMEMsRead) { + *pcMEMsRead = c; + } +} + +VOID DeviceFile_Close() +{ + PDEVICE_CONTEXT_FILE ctxFile = (PDEVICE_CONTEXT_FILE)ctxMain->dev.hDevice; + if(!ctxFile) { return; } + fclose(ctxFile->pFile); + LocalFree(ctxFile); + ctxMain->dev.hDevice = 0; +} + +BOOL DeviceFile_Open() +{ + PDEVICE_CONTEXT_FILE ctxFile; + ctxFile = (PDEVICE_CONTEXT_FILE)LocalAlloc(LMEM_ZEROINIT, sizeof(DEVICE_CONTEXT_FILE)); + if(!ctxFile) { return FALSE; } + // open backing file + if(fopen_s(&ctxFile->pFile, ctxMain->cfg.szDevTpOrFileName, "rb") || !ctxFile->pFile) { goto fail; } + if(_fseeki64(ctxFile->pFile, 0, SEEK_END)) { goto fail; } // seek to end of file + ctxFile->cbFile = _ftelli64(ctxFile->pFile); // get current file pointer + if(ctxFile->cbFile < 0x1000) { goto fail; } + ctxFile->szFileName = ctxMain->cfg.szDevTpOrFileName; + ctxMain->dev.hDevice = (HANDLE)ctxFile; + // set callback functions and fix up config + ctxMain->dev.tp = VMM_DEVICE_FILE; + ctxMain->dev.qwMaxSizeMemIo = 0x00100000; // 1MB + ctxMain->dev.paAddrMaxNative = ctxFile->cbFile; + ctxMain->dev.pfnClose = DeviceFile_Close; + ctxMain->dev.pfnReadScatterMEM = DeviceFile_ReadScatterMEM; + vmmprintfv("DEVICE: Successfully opened file: '%s'.\n", ctxMain->cfg.szDevTpOrFileName); + return TRUE; +fail: + if(ctxFile->pFile) { fclose(ctxFile->pFile); } + LocalFree(ctxFile); + printf("DEVICE: ERROR: Failed opening file: '%s'.\n", ctxMain->cfg.szDevTpOrFileName); + return FALSE; +} diff --git a/vmm/devicefile.h b/vmm/devicefile.h new file mode 100644 index 0000000..13cea1a --- /dev/null +++ b/vmm/devicefile.h @@ -0,0 +1,16 @@ +// devicefile.h : definitions related to file backed memory acquisition device. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __DEVICEFILE_H__ +#define __DEVICEFILE_H__ +#include "vmm.h" + +/* +* Open a "connection" to the file. +* -- result +*/ +BOOL DeviceFile_Open(); + +#endif /* __DEVICEFILE_H__ */ diff --git a/vmm/devicepcileechdll.c b/vmm/devicepcileechdll.c new file mode 100644 index 0000000..99e413e --- /dev/null +++ b/vmm/devicepcileechdll.c @@ -0,0 +1,149 @@ +// devicepcileechdll.c : implementation related to PCILeech DLL memory acquisition device. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "devicepcileechdll.h" +#include "pcileech_dll.h" +#include "vmm.h" + +typedef struct tdDEVICE_CONTEXT_PCILEECH_DLL { + HMODULE hPCILeechDll; + + LPSTR(*PCILeech_GetVersion)(); + BOOL(*PCILeech_InitializeInternalReserved)(_In_ DWORD argc, _In_ char* argv[]); + BOOL(*PCILeech_Close)(); + BOOL(*PCIleech_DeviceConfigGet)(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + BOOL(*PCILeech_DeviceConfigSet)(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + BOOL(*PCILeech_DeviceWriteMEM)(_In_ ULONG64 qwAddr, _In_ PBYTE pb, _In_ DWORD cb); + DWORD(*PCILeech_DeviceReadScatterMEM)(_Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs); + +} DEVICE_CONTEXT_PCILEECH_DLL, *PDEVICE_CONTEXT_PCILEECH_DLL; + +BOOL DevicePCILeechDll_WriteMEM(_In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb) +{ + PDEVICE_CONTEXT_PCILEECH_DLL ctxDll = (PDEVICE_CONTEXT_PCILEECH_DLL)ctxMain->dev.hDevice; + if(!ctxDll) { return FALSE; } + return ctxDll->PCILeech_DeviceWriteMEM(qwAddr, pb, cb); +} + +VOID DevicePCILeechDll_ReadScatterMEM(_Inout_ PPMEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _Out_opt_ PDWORD pcMEMsRead) +{ + DWORD cMEMsRead; + PDEVICE_CONTEXT_PCILEECH_DLL ctxDll = (PDEVICE_CONTEXT_PCILEECH_DLL)ctxMain->dev.hDevice; + if(!ctxDll) { return; } + cMEMsRead = ctxDll->PCILeech_DeviceReadScatterMEM(ppMEMs, cpMEMs); + if(pcMEMsRead) { + *pcMEMsRead = cMEMsRead; + } +} + +BOOL DevicePCILeechDll_GetOption(_In_ QWORD fOption, _Out_ PQWORD pqwValue) +{ + PDEVICE_CONTEXT_PCILEECH_DLL ctxDll = (PDEVICE_CONTEXT_PCILEECH_DLL)ctxMain->dev.hDevice; + if(!ctxDll) { return FALSE; } + return ctxDll->PCIleech_DeviceConfigGet(fOption, pqwValue); +} + +BOOL DevicePCILeechDll_SetOption(_In_ QWORD fOption, _In_ QWORD qwValue) +{ + PDEVICE_CONTEXT_PCILEECH_DLL ctxDll = (PDEVICE_CONTEXT_PCILEECH_DLL)ctxMain->dev.hDevice; + if(!ctxDll) { return FALSE; } + return ctxDll->PCILeech_DeviceConfigSet(fOption, qwValue); +} + +VOID DevicePCILeechDll_Close() +{ + PDEVICE_CONTEXT_PCILEECH_DLL ctxDll = (PDEVICE_CONTEXT_PCILEECH_DLL)ctxMain->dev.hDevice; + if(!ctxDll) { return; } + ctxDll->PCILeech_Close(); + FreeLibrary(ctxDll->hPCILeechDll); + LocalFree(ctxDll); + ZeroMemory(&ctxMain->dev, sizeof(ctxMain->dev)); +} + +BOOL DevicePCILeechDll_Open() +{ + BOOL result; + DWORD cDllParams = 0; + LPSTR szDllVersion, szDllParams[0x10]; + PDEVICE_CONTEXT_PCILEECH_DLL ctxDll; + ctxDll = (PDEVICE_CONTEXT_PCILEECH_DLL)LocalAlloc(LMEM_ZEROINIT, sizeof(DEVICE_CONTEXT_PCILEECH_DLL)); + if(!ctxDll) { return FALSE; } + // Load PCILeech DLL + ctxDll->hPCILeechDll = LoadLibraryA("pcileech.dll"); + if(!ctxDll->hPCILeechDll) { + vmmprintf("DEVICE: ERROR: Failed loading pcileech.dll - not found!\n"); + goto fail; + } + // Retrieve version number + ctxDll->PCILeech_GetVersion = (LPSTR(*)())GetProcAddress(ctxDll->hPCILeechDll, "PCILeech_GetVersion"); + if(!ctxDll->PCILeech_GetVersion) { + vmmprintf("DEVICE: ERROR: Failed loading pcileech.dll - version number inaccessible!\n"); + goto fail; + } + szDllVersion = ctxDll->PCILeech_GetVersion(); + if(strncmp(szDllVersion, "3.", 2)) { // major version = 3 required (exactly) + vmmprintf("DEVICE: ERROR: Failed loading pcileech.dll - version number (major) mismatch!\n"); + vmmprintf(" Please ensure most recent version of memprocfs.exe and pcileech.dll\n"); + goto fail; + } + if(atoi(szDllVersion + 2) < 6) { // minor version = 6 required (at minimum) + vmmprintf("DEVICE: ERROR: Failed loading pcileech.dll - version number (minor) mismatch!\n"); + vmmprintf(" Please ensure most recent version of memprocfs.exe and pcileech.dll\n"); + goto fail; + } + // Load required functions + ctxDll->PCILeech_InitializeInternalReserved = (BOOL(*)(DWORD,char**))GetProcAddress(ctxDll->hPCILeechDll, "PCILeech_InitializeInternalReserved"); + ctxDll->PCILeech_Close = (BOOL(*)())GetProcAddress(ctxDll->hPCILeechDll, "PCILeech_Close"); + ctxDll->PCIleech_DeviceConfigGet = (BOOL(*)(ULONG64, PULONG64))GetProcAddress(ctxDll->hPCILeechDll, "PCIleech_DeviceConfigGet"); + ctxDll->PCILeech_DeviceConfigSet = (BOOL(*)(ULONG64, ULONG64))GetProcAddress(ctxDll->hPCILeechDll, "PCILeech_DeviceConfigSet"); + ctxDll->PCILeech_DeviceWriteMEM = (BOOL(*)(ULONG64, PBYTE, DWORD))GetProcAddress(ctxDll->hPCILeechDll, "PCILeech_DeviceWriteMEM"); + ctxDll->PCILeech_DeviceReadScatterMEM = (DWORD(*)(PPMEM_IO_SCATTER_HEADER, DWORD))GetProcAddress(ctxDll->hPCILeechDll, "PCILeech_DeviceReadScatterMEM"); + if(!(ctxDll->PCILeech_InitializeInternalReserved && ctxDll->PCILeech_Close && ctxDll->PCIleech_DeviceConfigGet && + ctxDll->PCILeech_DeviceConfigSet && ctxDll->PCILeech_DeviceWriteMEM && ctxDll->PCILeech_DeviceReadScatterMEM)) + { + vmmprintf("DEVICE: ERROR: Failed loading pcileech.dll - missing function(s)!\n"); + goto fail; + } + // Initialize DLL: 1 - enable dll printf + ctxDll->PCILeech_DeviceConfigSet(PCILEECH_DEVICE_CORE_PRINTF_ENABLE, ctxMain->cfg.fVerboseDll ? 1 : 0); + // Initialize DLL: 2 - initialize context + szDllParams[cDllParams++] = ""; + szDllParams[cDllParams++] = "dll_library_use"; + if(ctxMain->cfg.fVerbose) { szDllParams[cDllParams++] = "-v"; } + if(ctxMain->cfg.fVerboseExtra) { szDllParams[cDllParams++] = "-vv"; } + if(ctxMain->cfg.fVerboseExtraTlp) { szDllParams[cDllParams++] = "-vvv"; } + szDllParams[cDllParams++] = "-device"; + szDllParams[cDllParams++] = ctxMain->cfg.szDevTpOrFileName; + if(!ctxDll->PCILeech_InitializeInternalReserved(cDllParams, szDllParams)) { + vmmprintf("DEVICE: ERROR: Failed initializing pcileech.dll!PCILeech_InitializeInternalReserved\n"); + goto fail; + } + // Initialize DLL: 3 - get max address and io size + result = ctxDll->PCIleech_DeviceConfigGet(PCILEECH_DEVICE_CORE_MAX_NATIVE_ADDRESS, &ctxMain->dev.paAddrMaxNative); + if(!result || (ctxMain->dev.paAddrMaxNative <= 0x00100000)) { + vmmprintf("DEVICE: ERROR: Failed initializing pcileech.dll - max physical memory too low - %016llx\n", ctxMain->dev.paAddrMaxNative); + goto fail; + } + result = ctxDll->PCIleech_DeviceConfigGet(PCILEECH_DEVICE_CORE_MAX_NATIVE_IOSIZE, &ctxMain->dev.qwMaxSizeMemIo); + if(!result || (ctxMain->dev.qwMaxSizeMemIo < 0x1000)) { + vmmprintf("DEVICE: ERROR: Failed initializing pcileech.dll - max iosize too low - %016llx\n", ctxMain->dev.qwMaxSizeMemIo); + goto fail; + } + // set callback functions and fix up config + ctxMain->dev.hDevice = (HANDLE)ctxDll; + ctxMain->dev.tp = VMM_DEVICE_PCILEECH_DLL; + ctxMain->dev.pfnClose = DevicePCILeechDll_Close; + ctxMain->dev.pfnGetOption = DevicePCILeechDll_GetOption; + ctxMain->dev.pfnSetOption = DevicePCILeechDll_SetOption; + ctxMain->dev.pfnWriteMEM = DevicePCILeechDll_WriteMEM; + ctxMain->dev.pfnReadScatterMEM = DevicePCILeechDll_ReadScatterMEM; + vmmprintfv("DEVICE: Successfully opened pcileech.dll\n"); + return TRUE; +fail: + if(ctxDll->hPCILeechDll) { FreeLibrary(ctxDll->hPCILeechDll); } + LocalFree(ctxDll); + ZeroMemory(&ctxMain->dev, sizeof(ctxMain->dev)); + return FALSE; +} diff --git a/vmm/devicepcileechdll.h b/vmm/devicepcileechdll.h new file mode 100644 index 0000000..0e38c9a --- /dev/null +++ b/vmm/devicepcileechdll.h @@ -0,0 +1,16 @@ +// devicepcileechdll.h : definitions related to PCILeech DLL memory acquisition device. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __DEVICEPCILEECHDLL_H__ +#define __DEVICEPCILEECHDLL_H__ +#include "vmm.h" + +/* +* Open a "connection" to the PCILeech DLL. +* -- result +*/ +BOOL DevicePCILeechDll_Open(); + +#endif /* __DEVICEPCILEECHDLL_H__ */ diff --git a/vmm/m_ldrmodules.c b/vmm/m_ldrmodules.c new file mode 100644 index 0000000..742f3f7 --- /dev/null +++ b/vmm/m_ldrmodules.c @@ -0,0 +1,265 @@ +// m_ldrmodules.c : implementation of the ldrmodules built-in module. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "m_ldrmodules.h" +#include "pluginmanager.h" +#include "vmm.h" +#include "vmmproc_windows.h" +#include "vmmvfs.h" +#include "util.h" + +#define LDRMODULES_CACHE_TP_EAT 1 +#define LDRMODULES_CACHE_TP_IAT 2 +#define LDRMODULES_NUM_CACHE 8 +typedef struct tdLDRMODULES_CACHE_ENTRY { + DWORD dwCounter; + DWORD dwPID; + CHAR szDll[MAX_PATH]; + DWORD tp; + DWORD cb; + PBYTE pb; +} LDRMODULES_CACHE_ENTRY, *PLDRMODULES_CACHE_ENTRY; + +/* +* CloseHandleModule : function as specified by the module manager. The module +* manager will call into this callback function whenever the module should be +* unloaded. Any private handle stored in phModulePrivate should be deallocated. +* -- phModulePrivate +*/ +VOID LdrModule_CloseHandleModule(_Inout_opt_ PHANDLE phModulePrivate) +{ + DWORD i; + PLDRMODULES_CACHE_ENTRY pCache; + if(!phModulePrivate || !*phModulePrivate) { return; } + pCache = (PLDRMODULES_CACHE_ENTRY)*phModulePrivate; + for(i = 0; i < LDRMODULES_NUM_CACHE; i++) { + if(pCache[i].pb) { LocalFree(pCache[i].pb); } + } + LocalFree(pCache); + *phModulePrivate = NULL; +} + +PLDRMODULES_CACHE_ENTRY LdrModule_GetCacheEntry(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPSTR szDll, _In_ DWORD tp) +{ + DWORD i, iMin = 0, iMax = 0, iEmpty = (DWORD)-1; + PLDRMODULES_CACHE_ENTRY e, pCache; + pCache = (PLDRMODULES_CACHE_ENTRY)*ctx->phModulePrivate; + // find existing cached item + for(i = 0; i < LDRMODULES_NUM_CACHE; i++) { + e = pCache + i; + if((e->dwPID == ctx->dwPID) && (e->tp == tp) && e->pb && !strcmp(e->szDll, szDll)) { + return e; + } + if(e->dwCounter < pCache[iMin].dwCounter) { iMin = i; } + if(e->dwCounter > pCache[iMax].dwCounter) { iMax = i; } + if(!e->pb) { iEmpty = i; } + } + // reserve and prepare new item + i = (iEmpty < LDRMODULES_NUM_CACHE) ? iEmpty : iMin; + e = pCache + i; + if(e->pb) { LocalFree(e->pb); } + ZeroMemory(e, sizeof(LDRMODULES_CACHE_ENTRY)); + e->dwCounter = pCache[iMax].dwCounter + 1; + e->dwPID = ctx->dwPID; + e->tp = tp; + strncpy_s(e->szDll, MAX_PATH, szDll, MAX_PATH); + return e; +} + +#define LDRMODULES_MAX_IATEAT 0x10000 + +PLDRMODULES_CACHE_ENTRY LdrModule_GetEAT(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ PVMM_MODULEMAP_ENTRY pModule) +{ + DWORD i, o, cEATs; + PVMMPROC_WINDOWS_EAT_ENTRY pEATs = NULL; + PLDRMODULES_CACHE_ENTRY pCacheEntry; + // 1: retrieve cache + pCacheEntry = LdrModule_GetCacheEntry(ctx, pModule->szName, LDRMODULES_CACHE_TP_EAT); + if(pCacheEntry->pb) { return pCacheEntry; } + // 2: retrieve exported functions + cEATs = LDRMODULES_MAX_IATEAT; + pEATs = LocalAlloc(0, LDRMODULES_MAX_IATEAT * sizeof(VMMPROC_WINDOWS_EAT_ENTRY)); + if(!pEATs) { goto fail; } + VmmProcWindows_PE_LoadEAT_DisplayBuffer(ctx->pProcess, pModule, pEATs, &cEATs); + if(!cEATs) { goto fail; } + // 3: fill "display buffer" + pCacheEntry->cb = cEATs * 64 + 1; + pCacheEntry->pb = LocalAlloc(0, pCacheEntry->cb); + if(!pCacheEntry->pb) { goto fail; } + for(i = 0, o = 0; i < cEATs; i++) { + o += snprintf( + pCacheEntry->pb + o, + pCacheEntry->cb - o, + "%04x %016llx %-40.40s \n", // 64 bytes (chars) / line (function) + (WORD)i, + pModule->BaseAddress + pEATs[i].vaFunctionOffset, + pEATs[i].szFunction + ); + } + pCacheEntry->cb = o; + LocalFree(pEATs); + return pCacheEntry; +fail: + LocalFree(pEATs); + return NULL; +} + +PLDRMODULES_CACHE_ENTRY LdrModule_GetIAT(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ PVMM_MODULEMAP_ENTRY pModule) +{ + DWORD i, o, cIATs; + PVMMPROC_WINDOWS_IAT_ENTRY pIATs = NULL; + PLDRMODULES_CACHE_ENTRY pCacheEntry; + // 1: retrieve cache + pCacheEntry = LdrModule_GetCacheEntry(ctx, pModule->szName, LDRMODULES_CACHE_TP_IAT); + if(pCacheEntry->pb) { return pCacheEntry; } + // 2: retrieve exported functions + cIATs = LDRMODULES_MAX_IATEAT; + pIATs = LocalAlloc(0, LDRMODULES_MAX_IATEAT * sizeof(VMMPROC_WINDOWS_IAT_ENTRY)); + if(!pIATs) { goto fail; } + VmmProcWindows_PE_LoadIAT_DisplayBuffer(ctx->pProcess, pModule, pIATs, &cIATs); + if(!cIATs) { goto fail; } + // 3: fill "display buffer" + pCacheEntry->cb = cIATs * 128 + 1; + pCacheEntry->pb = LocalAlloc(0, pCacheEntry->cb); + if(!pCacheEntry->pb) { goto fail; } + for(i = 0, o = 0; i < cIATs; i++) { + o += snprintf( + pCacheEntry->pb + o, + pCacheEntry->cb - o, + "%04x %016llx %-40.40s %-64.64s\n", // 128 bytes (chars) / line (function) + (WORD)i, + pIATs[i].vaFunction, + pIATs[i].szFunction, + pIATs[i].szModule + ); + } + pCacheEntry->cb = o; + LocalFree(pIATs); + return pCacheEntry; +fail: + LocalFree(pIATs); + return NULL; +} + +/* +* Read : function as specified by the module manager. The module manager will +* call into this callback function whenever a read shall occur from a "file". +* -- ctx +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +*/ +NTSTATUS LdrModules_Read(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + DWORD i, cbBuffer; + BYTE pbBuffer[0x800]; + CHAR _szBuf[MAX_PATH] = { 0 }; + LPSTR szPath1, szPath2; + PLDRMODULES_CACHE_ENTRY pCacheEntry; + PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; + Util_PathSplit2(ctx->szPath, _szBuf, &szPath1, &szPath2); + if(szPath1[0] && szPath2[0]) { + for(i = 0; i < pProcess->cModuleMap; i++) { + if(0 == strncmp(szPath1, pProcess->pModuleMap[i].szName, MAX_PATH)) { + if(!_stricmp(szPath2, "base")) { + return Util_VfsReadFile_FromQWORD(pProcess->pModuleMap[i].BaseAddress, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(szPath2, "entry")) { + return Util_VfsReadFile_FromQWORD(pProcess->pModuleMap[i].EntryPoint, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(szPath2, "size")) { + return Util_VfsReadFile_FromDWORD(pProcess->pModuleMap[i].SizeOfImage, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(szPath2, "directories")) { + VmmProcWindows_PE_DIRECTORY_DisplayBuffer(ctx->pProcess, pProcess->pModuleMap + i, pbBuffer, 0x400, &cbBuffer, NULL); + return Util_VfsReadFile_FromPBYTE(pbBuffer, cbBuffer, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(szPath2, "export")) { + pCacheEntry = LdrModule_GetEAT(ctx, pProcess->pModuleMap + i); + if(!pCacheEntry) { return VMMDLL_STATUS_FILE_INVALID; } + return Util_VfsReadFile_FromPBYTE(pCacheEntry->pb, pCacheEntry->cb, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(szPath2, "import")) { + pCacheEntry = LdrModule_GetIAT(ctx, pProcess->pModuleMap + i); + if(!pCacheEntry) { return VMMDLL_STATUS_FILE_INVALID; } + return Util_VfsReadFile_FromPBYTE(pCacheEntry->pb, pCacheEntry->cb, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(szPath2, "sections")) { + VmmProcWindows_PE_SECTION_DisplayBuffer(ctx->pProcess, pProcess->pModuleMap + i, pbBuffer, 0x800, &cbBuffer, NULL); + return Util_VfsReadFile_FromPBYTE(pbBuffer, cbBuffer, pb, cb, pcbRead, cbOffset); + } + return VMMDLL_STATUS_FILE_INVALID; + } + } + } + return VMMDLL_STATUS_FILE_INVALID; +} + +/* +* List : function as specified by the module manager. The module manager will +* call into this callback function whenever a list directory shall occur from +* the given module. +* -- ctx +* -- pFileList +* -- return +*/ +BOOL LdrModules_List(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList) +{ + DWORD i; + CHAR _szBuf[MAX_PATH] = { 0 }; + LPSTR szPath1, szPath2; + PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; + // modules root directory -> add directory per DLL + if(!ctx->szPath[0]) { + for(i = 0; i < pProcess->cModuleMap; i++) { + VMMDLL_VfsList_AddDirectory(pFileList, pProcess->pModuleMap[i].szName); + } + return TRUE; + } + // individual module directory -> list files + Util_PathSplit2(ctx->szPath, _szBuf, &szPath1, &szPath2); + if(!szPath2[0]) { + for(i = 0; i < pProcess->cModuleMap; i++) { + if(0 == strncmp(szPath1, pProcess->pModuleMap[i].szName, MAX_PATH)) { + VmmProcWindows_PE_SetSizeSectionIATEAT_DisplayBuffer(ctx->pProcess, pProcess->pModuleMap + i); + VMMDLL_VfsList_AddFile(pFileList, "base", 16); + VMMDLL_VfsList_AddFile(pFileList, "entry", 16); + VMMDLL_VfsList_AddFile(pFileList, "size", 8); + VMMDLL_VfsList_AddFile(pFileList, "directories", 864); + VMMDLL_VfsList_AddFile(pFileList, "export", pProcess->pModuleMap[i].cbDisplayBufferEAT); + VMMDLL_VfsList_AddFile(pFileList, "import", pProcess->pModuleMap[i].cbDisplayBufferIAT); + VMMDLL_VfsList_AddFile(pFileList, "sections", pProcess->pModuleMap[i].cbDisplayBufferSections); + return TRUE; + } + } + return FALSE; + } + return FALSE; +} + +/* +* Initialization function. The module manager shall call into this function +* when the module shall be initialized. If the module wish to initialize it +* shall call the supplied pfnPluginManager_Register function. +* NB! the module does not have to register itself - for example if the target +* operating system or architecture is unsupported. +* -- pPluginRegInfo +*/ +VOID M_LdrModules_Initialize(_Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo) +{ + PLDRMODULES_CACHE_ENTRY pCache; + if(0 == (pPluginRegInfo->fTargetSystem & VMM_TARGET_WINDOWS_X64)) { return; } + pCache = LocalAlloc(LMEM_ZEROINIT, LDRMODULES_NUM_CACHE * sizeof(LDRMODULES_CACHE_ENTRY)); + if(!pCache) { return; } + strcpy_s(pPluginRegInfo->reg_info.szModuleName, 32, "modules"); // module name + pPluginRegInfo->reg_info.fProcessModule = TRUE; // module shows in process directory + pPluginRegInfo->reg_info.hModulePrivate = pCache; // module private handle (for cache) + pPluginRegInfo->reg_fn.pfnList = LdrModules_List; // List function supported + pPluginRegInfo->reg_fn.pfnRead = LdrModules_Read; // Read function supported + pPluginRegInfo->reg_fn.pfnCloseHandleModule = LdrModule_CloseHandleModule; // Close module private handle supported + pPluginRegInfo->pfnPluginManager_Register(pPluginRegInfo); +} diff --git a/vmm/m_ldrmodules.h b/vmm/m_ldrmodules.h new file mode 100644 index 0000000..f411c0d --- /dev/null +++ b/vmm/m_ldrmodules.h @@ -0,0 +1,17 @@ +// m_ldrmodules.h : definitions related to the ldrmodules built-in module. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __M_LDRMODULES_H__ +#define __M_LDRMODULES_H__ +#include +#include "vmmdll.h" + +/* +* Initialization function for the built-in ldrmodules module. +* -- pPluginRegInfo +*/ +VOID M_LdrModules_Initialize(_Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo); + +#endif /* __M_LDRMODULES_H__ */ diff --git a/vmm/m_status.c b/vmm/m_status.c new file mode 100644 index 0000000..cb36b50 --- /dev/null +++ b/vmm/m_status.c @@ -0,0 +1,274 @@ +// m_status.c : implementation of the .status built-in module. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +/* +* The m_status module registers itself with the name '.status' with the plugin manager. +* +* The module showcases both a "root" "process" directory module as well as a +* stateless module. It neither holds state in its "global" HandleModule context +* nor in the per-process specific HandleProcess contexts. +* +* The module implements listing of directories as well as read and write. +* Read/Write happens, if allowed, to various configuration and status settings +* related to the VMM and Memory Process File System. +*/ + +#include "m_virt2phys.h" +#include "pluginmanager.h" +#include "util.h" +#include "vmm.h" +#include "vmmvfs.h" +#include "statistics.h" + +/* +* Read : function as specified by the module manager. The module manager will +* call into this callback function whenever a read shall occur from a "file". +* -- ctx +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +*/ +NTSTATUS MStatus_Read(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; + DWORD cchBuffer; + CHAR szBuffer[0x400]; + DWORD cbCallStatistics = 0; + PBYTE pbCallStatistics = NULL; + NTSTATUS nt; + // "PROCESS" + if(pProcess) { + if(!_stricmp(ctx->szPath, "cache_file_enable")) { + return Util_VfsReadFile_FromBOOL(!pProcess->fFileCacheDisabled, pb, cb, pcbRead, cbOffset); + } + } + // "ROOT" + if(!pProcess) { + if(!_stricmp(ctx->szPath, "config_cache_enable")) { + return Util_VfsReadFile_FromBOOL(!(ctxVmm->flags & VMM_FLAG_NOCACHE), pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "config_statistics_fncall")) { + return Util_VfsReadFile_FromBOOL(Statistics_CallGetEnabled(), pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "config_refresh_enable")) { + return Util_VfsReadFile_FromBOOL(ctxVmm->ThreadProcCache.fEnabled, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "config_refresh_tick_period_ms")) { + return Util_VfsReadFile_FromDWORD(ctxVmm->ThreadProcCache.cMs_TickPeriod, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "config_refresh_read")) { + return Util_VfsReadFile_FromDWORD(ctxVmm->ThreadProcCache.cTick_Phys, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "config_refresh_tlb")) { + return Util_VfsReadFile_FromDWORD(ctxVmm->ThreadProcCache.cTick_TLB, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "config_refresh_proc_partial")) { + return Util_VfsReadFile_FromDWORD(ctxVmm->ThreadProcCache.cTick_ProcPartial, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "config_refresh_proc_total")) { + return Util_VfsReadFile_FromDWORD(ctxVmm->ThreadProcCache.cTick_ProcTotal, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "statistics")) { + cchBuffer = snprintf(szBuffer, 0x400, + "VMM STATISTICS (4kB PAGES / COUNTS - HEXADECIMAL)\n" \ + "===================================================\n" \ + "PHYSICAL MEMORY READ CACHE HIT: %16llx\n" \ + "PHYSICAL MEMORY READ RETRIEVED: %16llx\n" \ + "PHYSICAL MEMORY READ FAILED: %16llx\n" \ + "PHYSICAL MEMORY WRITE: %16llx\n" \ + "TLB CACHE HIT: %16llx\n" \ + "TLB RETRIEVED: %16llx\n" \ + "TLB FAILED: %16llx\n" \ + "PHYSICAL MEMORY REFRESH: %16llx\n" \ + "TLB MEMORY REFRESH: %16llx\n" \ + "PROCESS PARTIAL REFRESH: %16llx\n" \ + "PROCESS FULL REFRESH: %16llx\n", + ctxVmm->stat.cPhysCacheHit, ctxVmm->stat.cPhysReadSuccess, ctxVmm->stat.cPhysReadFail, + ctxVmm->stat.cPhysWrite, + ctxVmm->stat.cTlbCacheHit, ctxVmm->stat.cTlbReadSuccess, ctxVmm->stat.cTlbReadFail, + ctxVmm->stat.cRefreshPhys, ctxVmm->stat.cRefreshTlb, ctxVmm->stat.cRefreshProcessPartial, ctxVmm->stat.cRefreshProcessFull + ); + return Util_VfsReadFile_FromPBYTE(szBuffer, cchBuffer, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "statistics_fncall")) { + Statistics_CallToString(NULL, 0, &cbCallStatistics); + pbCallStatistics = LocalAlloc(0, cbCallStatistics); + if(!pbCallStatistics) { return VMMDLL_STATUS_FILE_INVALID; } + Statistics_CallToString(pbCallStatistics, cbCallStatistics, &cbCallStatistics); + nt = Util_VfsReadFile_FromPBYTE(pbCallStatistics, cbCallStatistics, pb, cb, pcbRead, cbOffset); + LocalFree(pbCallStatistics); + return nt; + } + if(!_stricmp(ctx->szPath, "config_printf_enable")) { + return Util_VfsReadFile_FromBOOL(ctxMain->cfg.fVerboseDll, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "config_printf_v")) { + return Util_VfsReadFile_FromBOOL(ctxMain->cfg.fVerbose, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "config_printf_vv")) { + return Util_VfsReadFile_FromBOOL(ctxMain->cfg.fVerboseExtra, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "config_printf_vvv")) { + return Util_VfsReadFile_FromBOOL(ctxMain->cfg.fVerboseExtraTlp, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(ctx->szPath, "native_max_address")) { + return Util_VfsReadFile_FromQWORD(ctxMain->dev.paAddrMaxNative, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "native_max_iosize")) { + return Util_VfsReadFile_FromQWORD(ctxMain->dev.qwMaxSizeMemIo, pb, cb, pcbRead, cbOffset, FALSE); + } + } + return VMMDLL_STATUS_FILE_INVALID; +} + +NTSTATUS MStatus_Write_NotifyVerbosityChange(_In_ NTSTATUS nt) +{ + if(nt == VMMDLL_STATUS_SUCCESS) { + PluginManager_Notify(VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE, NULL, 0); + } + return nt; +} + +/* +* Write : function as specified by the module manager. The module manager will +* call into this callback function whenever a write shall occur from a "file". +* -- ctx +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS MStatus_Write(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset) +{ + PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; + NTSTATUS nt; + BOOL fEnable = FALSE; + // "PROCESS" + if(pProcess) { + if(!_stricmp(ctx->szPath, "cache_file_enable")) { + if((cbOffset == 0) && (cb > 0)) { + if(((PCHAR)pb)[0] == '1') { pProcess->fFileCacheDisabled = FALSE; } + if(((PCHAR)pb)[0] == '0') { pProcess->fFileCacheDisabled = TRUE; } + } + *pcbWrite = cb; + return VMMDLL_STATUS_SUCCESS; + } + } + // "ROOT" + if(!pProcess) { + if(!_stricmp(ctx->szPath, "config_cache_enable")) { + nt = Util_VfsWriteFile_BOOL(&fEnable, pb, cb, pcbWrite, cbOffset); + if(nt == VMMDLL_STATUS_SUCCESS) { + ctxVmm->flags &= ~VMM_FLAG_NOCACHE; + ctxVmm->flags |= fEnable ? 0 : VMM_FLAG_NOCACHE; + } + return nt; + } + if(!_stricmp(ctx->szPath, "config_statistics_fncall")) { + nt = Util_VfsWriteFile_BOOL(&fEnable, pb, cb, pcbWrite, cbOffset); + if(nt == VMMDLL_STATUS_SUCCESS) { + Statistics_CallSetEnabled(fEnable); + } + return nt; + } + if(!_stricmp(ctx->szPath, "config_refresh_tick_period_ms")) { + return Util_VfsWriteFile_DWORD(&ctxVmm->ThreadProcCache.cMs_TickPeriod, pb, cb, pcbWrite, cbOffset, 50); + } + if(!_stricmp(ctx->szPath, "config_refresh_read")) { + return Util_VfsWriteFile_DWORD(&ctxVmm->ThreadProcCache.cTick_Phys, pb, cb, pcbWrite, cbOffset, 1); + } + if(!_stricmp(ctx->szPath, "config_refresh_tlb")) { + return Util_VfsWriteFile_DWORD(&ctxVmm->ThreadProcCache.cTick_TLB, pb, cb, pcbWrite, cbOffset, 1); + } + if(!_stricmp(ctx->szPath, "config_refresh_proc_partial")) { + return Util_VfsWriteFile_DWORD(&ctxVmm->ThreadProcCache.cTick_ProcPartial, pb, cb, pcbWrite, cbOffset, 1); + } + if(!_stricmp(ctx->szPath, "config_refresh_proc_total")) { + return Util_VfsWriteFile_DWORD(&ctxVmm->ThreadProcCache.cTick_ProcTotal, pb, cb, pcbWrite, cbOffset, 1); + } + if(!_stricmp(ctx->szPath, "config_printf_enable")) { + return MStatus_Write_NotifyVerbosityChange( + Util_VfsWriteFile_BOOL(&ctxMain->cfg.fVerboseDll, pb, cb, pcbWrite, cbOffset)); + } + if(!_stricmp(ctx->szPath, "config_printf_v")) { + return MStatus_Write_NotifyVerbosityChange( + Util_VfsWriteFile_BOOL(&ctxMain->cfg.fVerbose, pb, cb, pcbWrite, cbOffset)); + } + if(!_stricmp(ctx->szPath, "config_printf_vv")) { + return MStatus_Write_NotifyVerbosityChange( + Util_VfsWriteFile_BOOL(&ctxMain->cfg.fVerboseExtra, pb, cb, pcbWrite, cbOffset)); + } + if(!_stricmp(ctx->szPath, "config_printf_vvv")) { + return MStatus_Write_NotifyVerbosityChange( + Util_VfsWriteFile_BOOL(&ctxMain->cfg.fVerboseExtraTlp, pb, cb, pcbWrite, cbOffset)); + } + } + return VMMDLL_STATUS_FILE_INVALID; +} + +/* +* List : function as specified by the module manager. The module manager will +* call into this callback function whenever a list directory shall occur from +* the given module. +* -- ctx +* -- pFileList +* -- return +*/ +BOOL MStatus_List(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList) +{ + DWORD cbCallStatistics = 0; + PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; + // not module root directory -> fail! + if(ctx->szPath[0]) { return FALSE; } + // "root" view + if(!ctx->pProcess) { + VMMDLL_VfsList_AddFile(pFileList, "config_cache_enable", 1); + VMMDLL_VfsList_AddFile(pFileList, "config_statistics_fncall", 1); + VMMDLL_VfsList_AddFile(pFileList, "config_refresh_enable", 1); + VMMDLL_VfsList_AddFile(pFileList, "config_refresh_tick_period_ms", 8); + VMMDLL_VfsList_AddFile(pFileList, "config_refresh_read", 8); + VMMDLL_VfsList_AddFile(pFileList, "config_refresh_tlb", 8); + VMMDLL_VfsList_AddFile(pFileList, "config_refresh_proc_partial", 8); + VMMDLL_VfsList_AddFile(pFileList, "config_refresh_proc_total", 8); + VMMDLL_VfsList_AddFile(pFileList, "statistics", 0x283); + VMMDLL_VfsList_AddFile(pFileList, "config_printf_enable", 1); + VMMDLL_VfsList_AddFile(pFileList, "config_printf_v", 1); + VMMDLL_VfsList_AddFile(pFileList, "config_printf_vv", 1); + VMMDLL_VfsList_AddFile(pFileList, "config_printf_vvv", 1); + VMMDLL_VfsList_AddFile(pFileList, "native_max_address", 16); + VMMDLL_VfsList_AddFile(pFileList, "native_max_iosize", 16); + Statistics_CallToString(NULL, 0, &cbCallStatistics); + VMMDLL_VfsList_AddFile(pFileList, "statistics_fncall", cbCallStatistics); + } + // "process" view + if(pProcess) { + VMMDLL_VfsList_AddFile(pFileList, "cache_file_enable", 1); + } + return TRUE; +} + +/* +* Initialization function. The module manager shall call into this function +* when the module shall be initialized. If the module wish to initialize it +* shall call the supplied pfnPluginManager_Register function. +* NB! the module does not have to register itself - for example if the target +* operating system or architecture is unsupported. +* -- pPluginRegInfo +*/ +VOID M_Status_Initialize(_Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo) +{ + if(0 == (pPluginRegInfo->fTargetSystem & (VMM_TARGET_UNKNOWN_X64 | VMM_TARGET_WINDOWS_X64))) { return; } + strcpy_s(pPluginRegInfo->reg_info.szModuleName, 32, ".status"); // module name + pPluginRegInfo->reg_info.fRootModule = TRUE; // module shows in root directory + pPluginRegInfo->reg_info.fProcessModule = TRUE; // module shows in process directory + pPluginRegInfo->reg_fn.pfnList = MStatus_List; // List function supported + pPluginRegInfo->reg_fn.pfnRead = MStatus_Read; // Read function supported + pPluginRegInfo->reg_fn.pfnWrite = MStatus_Write; // Write function supported + pPluginRegInfo->pfnPluginManager_Register(pPluginRegInfo); +} diff --git a/vmm/m_status.h b/vmm/m_status.h new file mode 100644 index 0000000..7ee9324 --- /dev/null +++ b/vmm/m_status.h @@ -0,0 +1,17 @@ +// m_status.h : definitions related to the .status built-in module. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __M_STATUS_H__ +#define __M_STATUS_H__ +#include +#include "vmmdll.h" + +/* +* Initialization function for the built-in virt2phys module. +* -- pPluginRegInfo +*/ +VOID M_Status_Initialize(_Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo); + +#endif /* __M_STATUS_H__ */ diff --git a/vmm/m_virt2phys.c b/vmm/m_virt2phys.c new file mode 100644 index 0000000..9dda45e --- /dev/null +++ b/vmm/m_virt2phys.c @@ -0,0 +1,215 @@ +// m_virt2phys.c : implementation of the virt2phys built-in module. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "m_virt2phys.h" +#include "pluginmanager.h" +#include "util.h" +#include "vmm.h" +#include "vmmvfs.h" + +/* +* Virt2Phys_GetContext is an internal function which retrieves (or allocates) +* a pointer to a VMM_VIRT2PHYS_INFORMATION structure. If it does not exist and +* no "Dummy" is supplied a new VMM_VIRT2PHYS_INFORMATION is allocated & stored +* in the handle pointed by phProcessPrivate. This may later be free'd when the +* module manager calls the Virt2Phys_CloseHandleProcess function. +* -- phProcessPrivate +* -- pDummy +* -- return +*/ +PVMM_VIRT2PHYS_INFORMATION Virt2Phys_GetContext(_Inout_opt_ PHANDLE phProcessPrivate, _Inout_opt_ PVMM_VIRT2PHYS_INFORMATION pDummy) +{ + if(*phProcessPrivate) { + return (PVMM_VIRT2PHYS_INFORMATION)*phProcessPrivate; + } + if(pDummy) { + ZeroMemory(pDummy, sizeof(VMM_VIRT2PHYS_INFORMATION)); + return pDummy; + } + *phProcessPrivate = (HANDLE)LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_VIRT2PHYS_INFORMATION)); + return (PVMM_VIRT2PHYS_INFORMATION)*phProcessPrivate; +} + +/* +* Read : function as specified by the module manager. The module manager will +* call into this callback function whenever a read shall occur from a "file". +* -- ctx +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +*/ +NTSTATUS Virt2Phys_Read(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + BYTE iPML = 0; + DWORD cbBuffer; + PBYTE pbSourceData; + BYTE pbBuffer[0x1000]; + VMM_VIRT2PHYS_INFORMATION Virt2PhysInfo_Dummy, *pVirt2PhysInfo; + pVirt2PhysInfo = Virt2Phys_GetContext(ctx->phProcessPrivate, &Virt2PhysInfo_Dummy); + if(!_stricmp(ctx->szPath, "virt")) { + return Util_VfsReadFile_FromQWORD(pVirt2PhysInfo->va, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "phys")) { + return Util_VfsReadFile_FromQWORD(pVirt2PhysInfo->pas[0], pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(ctx->szPath, "map")) { + cbBuffer = snprintf( + pbBuffer, + 0x1000, + "PML4 %016llx +%03x %016llx\n" \ + "PDPT %016llx +%03x %016llx\n" \ + "PD %016llx +%03x %016llx\n" \ + "PT %016llx +%03x %016llx\n" \ + "PAGE %016llx\n", + pVirt2PhysInfo->pas[4], pVirt2PhysInfo->iPTEs[4] << 3, pVirt2PhysInfo->PTEs[4], + pVirt2PhysInfo->pas[3], pVirt2PhysInfo->iPTEs[3] << 3, pVirt2PhysInfo->PTEs[3], + pVirt2PhysInfo->pas[2], pVirt2PhysInfo->iPTEs[2] << 3, pVirt2PhysInfo->PTEs[2], + pVirt2PhysInfo->pas[1], pVirt2PhysInfo->iPTEs[1] << 3, pVirt2PhysInfo->PTEs[1], + pVirt2PhysInfo->pas[0] + ); + return Util_VfsReadFile_FromPBYTE(pbBuffer, cbBuffer, pb, cb, pcbRead, cbOffset); + } + // "page table" or data page + if(!_stricmp(ctx->szPath, "pt_pml4")) { iPML = 4; } + if(!_stricmp(ctx->szPath, "pt_pdpt")) { iPML = 3; } + if(!_stricmp(ctx->szPath, "pt_pd")) { iPML = 2; } + if(!_stricmp(ctx->szPath, "pt_pt")) { iPML = 1; } + ZeroMemory(pbBuffer, 0x1000); + pbSourceData = pbBuffer; + if(iPML && (pVirt2PhysInfo->pas[iPML] & ~0xfff)) { + pbSourceData = VmmTlbGetPageTable(pVirt2PhysInfo->pas[iPML] & ~0xfff, FALSE); + } + if(!_stricmp(ctx->szPath, "page") && (pVirt2PhysInfo->pas[0] & ~0xfff)) { + VmmReadPhysicalPage(pVirt2PhysInfo->pas[0] & ~0xfff, pbBuffer); + } + if(iPML || !_stricmp(ctx->szPath, "page")) { + return Util_VfsReadFile_FromPBYTE(pbSourceData, 0x1000, pb, cb, pcbRead, cbOffset); + } + return VMMDLL_STATUS_FILE_INVALID; +} + +/* +* Write to the "virt" virtual file - a new virtual address is written. Triggers +* an update of the cached virtual to physical translation information. +* -- ctx +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS Virt2Phys_WriteVA(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset) +{ + PVMM_PROCESS pProcess = (PVMM_PROCESS)ctx->pProcess; + BYTE pbBuffer[17]; + PVMM_VIRT2PHYS_INFORMATION pVirt2PhysInfo; + if(cbOffset < 16) { + pVirt2PhysInfo = Virt2Phys_GetContext(ctx->phProcessPrivate, NULL); + *pcbWrite = cb; + snprintf(pbBuffer, 17, "%016llx", pVirt2PhysInfo->va); + cb = (DWORD)min(16 - cbOffset, cb); + memcpy(pbBuffer + cbOffset, pb, cb); + pbBuffer[16] = 0; + pVirt2PhysInfo->va = strtoull(pbBuffer, NULL, 16); + VmmVirt2PhysGetInformation(pProcess, pVirt2PhysInfo); + } else { + *pcbWrite = 0; + } + return VMMDLL_STATUS_SUCCESS; +} + +/* +* Write : function as specified by the module manager. The module manager will +* call into this callback function whenever a write shall occur from a "file". +* -- ctx +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS Virt2Phys_Write(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset) +{ + DWORD i; + VMM_VIRT2PHYS_INFORMATION Virt2PhysInfo_Dummy, *pVirt2PhysInfo; + if(!_stricmp(ctx->szPath, "virt")) { + return Virt2Phys_WriteVA(ctx, pb, cb, pcbWrite, cbOffset); + } + pVirt2PhysInfo = Virt2Phys_GetContext(ctx->phProcessPrivate, &Virt2PhysInfo_Dummy); + i = 0xff; + if(!_stricmp(ctx->szPath, "pt_pml4")) { i = 4; } + if(!_stricmp(ctx->szPath, "pt_pdpt")) { i = 3; } + if(!_stricmp(ctx->szPath, "pt_pd")) { i = 2; } + if(!_stricmp(ctx->szPath, "pt_pt")) { i = 1; } + if(!_stricmp(ctx->szPath, "page")) { i = 0; } + if(i > 4) { return VMMDLL_STATUS_FILE_INVALID; } + if(pVirt2PhysInfo->pas[i] < 0x1000) { return VMMDLL_STATUS_FILE_INVALID; } + if(cbOffset > 0x1000) { return VMMDLL_STATUS_END_OF_FILE; } + *pcbWrite = (DWORD)min(cb, 0x1000 - cbOffset); + VmmWritePhysical(pVirt2PhysInfo->pas[i] + cbOffset, pb, *pcbWrite); + return *pcbWrite ? VMMDLL_STATUS_SUCCESS : VMMDLL_STATUS_END_OF_FILE; +} + +/* +* List : function as specified by the module manager. The module manager will +* call into this callback function whenever a list directory shall occur from +* the given module. +* -- ctx +* -- pFileList +* -- return +*/ +BOOL Virt2Phys_List(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList) +{ + if(ctx->szPath[0]) { + // only list in module root directory. + // not root directory == error for this module. + return FALSE; + } + VMMDLL_VfsList_AddFile(pFileList, "virt", 16); + VMMDLL_VfsList_AddFile(pFileList, "phys", 16); + VMMDLL_VfsList_AddFile(pFileList, "map", 198); + VMMDLL_VfsList_AddFile(pFileList, "page", 0x1000); + VMMDLL_VfsList_AddFile(pFileList, "pt_pml4", 0x1000); + VMMDLL_VfsList_AddFile(pFileList, "pt_pdpt", 0x1000); + VMMDLL_VfsList_AddFile(pFileList, "pt_pd", 0x1000); + VMMDLL_VfsList_AddFile(pFileList, "pt_pt", 0x1000); + return TRUE; +} + +/* +* CloseHandleProcess : function as specified by the module manager. The module +* manager will call into this callback function whenever a process specific +* handle shall be closed. Please note that only the memory pointed to by the +* parameter phProcessPrivate shall be free'd. +* -- ctx +* -- phModulePrivate +* -- phProcessPrivate = memory to be free'd. +*/ +VOID Virt2Phys_CloseHandleProcess(_In_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate) +{ + LocalFree(*phProcessPrivate); +} + +/* +* Initialization function. The module manager shall call into this function +* when the module shall be initialized. If the module wish to initialize it +* shall call the supplied pfnPluginManager_Register function. +* NB! the module does not have to register itself - for example if the target +* operating system or architecture is unsupported. +* -- pPluginRegInfo +*/ +VOID M_Virt2Phys_Initialize(_Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo) +{ + if(0 == (pPluginRegInfo->fTargetSystem & (VMM_TARGET_UNKNOWN_X64 | VMM_TARGET_WINDOWS_X64))) { return; } + strcpy_s(pPluginRegInfo->reg_info.szModuleName, 32, "virt2phys"); // module name + pPluginRegInfo->reg_info.fProcessModule = TRUE; // module shows in process directory + pPluginRegInfo->reg_fn.pfnList = Virt2Phys_List; // List function supported + pPluginRegInfo->reg_fn.pfnRead = Virt2Phys_Read; // Read function supported + pPluginRegInfo->reg_fn.pfnWrite = Virt2Phys_Write; // Write function supported + pPluginRegInfo->reg_fn.pfnCloseHandleProcess = Virt2Phys_CloseHandleProcess; // Close process module private handle supported + pPluginRegInfo->pfnPluginManager_Register(pPluginRegInfo); +} diff --git a/vmm/m_virt2phys.h b/vmm/m_virt2phys.h new file mode 100644 index 0000000..d59ba40 --- /dev/null +++ b/vmm/m_virt2phys.h @@ -0,0 +1,17 @@ +// m_virt2phys.h : definitions related to the virt2phys built-in module. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __M_VIRT2PHYS_H__ +#define __M_VIRT2PHYS_H__ +#include +#include "vmmdll.h" + +/* +* Initialization function for the built-in virt2phys module. +* -- pPluginRegInfo +*/ +VOID M_Virt2Phys_Initialize(_Inout_ PVMMDLL_PLUGIN_REGINFO pPluginRegInfo); + +#endif /* __M_VIRT2PHYS_H__ */ diff --git a/vmm/pcileech_dll.h b/vmm/pcileech_dll.h new file mode 100644 index 0000000..130659f --- /dev/null +++ b/vmm/pcileech_dll.h @@ -0,0 +1,398 @@ +// pcileech_dll.h : defines related to dynamic link library (dll) functionality. +// Please use together with pcileech.dll in other projects. +// Please see pcileech_dll_example.c for integration examples. +// +// Version 3.5 +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#ifndef __PCILEECH_DLL_H__ +#define __PCILEECH_DLL_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + /* + * Retrieve the current version of PCILeech. + * -- return = the current version of PCILeech in ascii text. + */ + LPSTR PCILeech_GetVersion(); + + /* + * Initialize PCILeech from a memory dump file in raw format. PCILeech will be + * initialized in read-only mode. It's possible to optionally specify the page + * table base of the windows kernel (for full vmm features) or the page table + * base of a single 64-bit process in any x64 operating system. NB! usually it + * is not necessary to specify the PageTableBase - it will be auto-identified + * most often if the target is Windows. + * -- szFileName = the file name of the raw memory dump to use. + * -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process + * as hex string. NB! this is usally not required. Example: "0x1ab000". + * -- return = success/fail. + */ + BOOL PCILeech_InitializeFromFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + + /* + * Intiailize PCILeech from a supported FPGA device over USB. PCIleech will be + * initialized in read/write mode upon success. Optionally it will be possible + * to specify the max physical address and the page table base of the kernel or + * process that should be investigated. + * -- szMaxPhysicalAddressOpt = max physical address of the target system as a + * hex string. Example: "0x8000000000". + * -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process + * as hex string. NB! this is usally not required. Example: "0x1ab000". + * -- return = success/fail. + */ + BOOL PCILeech_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + + /* + * Intiailize PCILeech from a the "Total Meltdown" CVE-2018-1038 vulnerability. + * initialized in read/write mode upon success. + * -- return = success/fail. + */ + BOOL PCILeech_InitializeTotalMeltdown(); + + /* + * Initialize PCIleech from a USB3380 device. The functionality of a USB3380 + * device is limited to DMA in the 32-bit physical address space below 4GB. + * As such the USB3380 cannot be used together with the VMM functionality. + * Furthermore; if undreadable memory such as memory holes or PCIe space is to + * be read the USB3380 device will freeze and require a complete power cycle. + * Despite limitations the USB330 will work together with the functions: + * PCILeech_DeviceReadMEM and PCILeech_DeviceWriteMEM. + */ + BOOL PCILeech_InitializeUSB3380(); + + /* + * Shut down the PCILeech functionality and clean up allocated resources. This + * should always be called before the DLL is unloaded (unless upon process exit) + * to ensure resources are cleaned up. + */ + BOOL PCILeech_Close(); + + /* + * For internal use only - do not use! + */ + BOOL PCILeech_InitializeInternalReserved(_In_ DWORD argc, _In_ char* argv[]); + + /* + * Device specific options used together with the PCILeech_DeviceGetOption and + * PCILeech_DeviceSetOption functions. For more detailed information check the + * sources for the individual device types. + */ +#define PCILEECH_DEVICE_OPT_FPGA_PROBE_MAXPAGES 0x01 // RW +#define PCILEECH_DEVICE_OPT_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define PCILEECH_DEVICE_OPT_FPGA_MAX_SIZE_RX 0x03 // RW +#define PCILEECH_DEVICE_OPT_FPGA_MAX_SIZE_TX 0x04 // RW +#define PCILEECH_DEVICE_OPT_FPGA_DELAY_PROBE_READ 0x05 // RW uS +#define PCILEECH_DEVICE_OPT_FPGA_DELAY_PROBE_WRITE 0x06 // RW uS +#define PCILEECH_DEVICE_OPT_FPGA_DELAY_WRITE 0x07 // RW uS +#define PCILEECH_DEVICE_OPT_FPGA_DELAY_READ 0x08 // RW uS +#define PCILEECH_DEVICE_OPT_FPGA_RETRY_ON_ERROR 0x09 // RW +#define PCILEECH_DEVICE_OPT_FPGA_DEVICE_ID 0x80 // R +#define PCILEECH_DEVICE_OPT_FPGA_FPGA_ID 0x81 // R +#define PCILEECH_DEVICE_OPT_FPGA_VERSION_MAJOR 0x82 // R +#define PCILEECH_DEVICE_OPT_FPGA_VERSION_MINOR 0x83 // R + +#define PCILEECH_DEVICE_CORE_PRINTF_ENABLE 0x80000001 // RW +#define PCILEECH_DEVICE_CORE_VERBOSE 0x80000002 // RW +#define PCILEECH_DEVICE_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define PCILEECH_DEVICE_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define PCILEECH_DEVICE_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define PCILEECH_DEVICE_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R + + /* + * Set a device specific option value. Please see defines PCILEECH_DEVICE_OPT_* + * for information about valid option values. Please note that option values + * may overlap between different device types with different meanings. + * -- fOption + * -- pqwValue = pointer to ULONG64 to receive option value. + * -- return = success/fail. + */ + BOOL PCIleech_DeviceConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + + /* + * Set a device specific option value. Please see defines PCILEECH_DEVICE_OPT_* + * for information about valid option values. Please note that option values + * may overlap between different device types with different meanings. + * -- fOption + * -- qwValue + * -- return = success/fail. + */ + BOOL PCILeech_DeviceConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + /* + * Write target physical memory. Minimum granularity: byte. + * -- qwAddr = the physical address to write to in the target system. + * -- pb = bytes to write + * -- cb = number of bytes to write. + * -- return = success/fail. + */ + BOOL PCILeech_DeviceWriteMEM(_In_ ULONG64 qwAddr, _In_ PBYTE pb, _In_ DWORD cb); + + /* + * Read target physical memory. Minimum granularity: page (4kB). + * -- qwAddr = physical address in target system to read. + * -- pb = pre-allocated buffer to place result in. + * -- cb = length of data to read, must not be larger than pb. + * -- return = success/fail. + */ + BOOL PCILeech_DeviceReadMEM(_In_ ULONG64 qwAddr, _Out_ PBYTE pb, _In_ DWORD cb); + + typedef struct tdPCILEECH_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + } PCILEECH_MEM_IO_SCATTER_HEADER, *PPCILEECH_MEM_IO_SCATTER_HEADER, **PPPCILEECH_MEM_IO_SCATTER_HEADER; + + /* + * Read memory in various non-contigious locations specified by the pointers to + * the items in the ppDMAs array. Result for each unit of work will be given + * individually. No upper limit of number of items to read, but no performance + * boost will be given if above hardware limit. Max size of each unit of work is + * one 4k page (4096 bytes). + * -- ppMEMs = array of scatter read headers. + * -- cpMEMs = count of ppDMAs. + * -- pcpDMAsRead = optional count of number of successfully read ppDMAs. + * -- return = the number of successfully read items. + */ + DWORD PCILeech_DeviceReadScatterMEM(_Inout_ PPPCILEECH_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs); + + /* + * Initialize the Virtual Memory Manager (VMM). This will try to auto-identify + * the operating system, parse and enumerate its processes in order to make it + * available through the PCILeech_VMM* functions. + * If auto-identifying of a Windows sytem fails please try initialize PCILeech + * with the address of the kernel page table in the szPageTableBaseOpt parameter + * in a call to the PCIleech_Initialize* functions. The page table base may be + * obtained (sometimes) from pcileech.exe by running the identify command. + * -- return = success/fail. + */ + BOOL PCILeech_VmmInitialize(); + + /* + * Close the Virtual Memory Manager (VMM) and clean up resources. This must be + * done before PCILeech_VmmInitialize() is called again. This is not necessary + * if PCILeech is completely closed by a call to PCILeech_Close() - which will + * take care of all necessary cleanup activitities. + */ + BOOL PCILeech_VmmClose(); + +#define PCILEECH_VMM_CONFIG_IS_REFRESH_ENABLED 1 // read-only, 1/0 +#define PCILEECH_VMM_CONFIG_TICK_PERIOD 2 // read-write, base tick period in ms +#define PCILEECH_VMM_CONFIG_READCACHE_TICKS 3 // read-write, memory cache validity period (in ticks) +#define PCILEECH_VMM_CONFIG_TLBCACHE_TICKS 4 // read-write, page table (tlb) cache validity period (in ticks) +#define PCILEECH_VMM_CONFIG_PROCCACHE_TICKS_PARTIAL 5 // read-write, process refresh (partial) period (in ticks) +#define PCILEECH_VMM_CONFIG_PROCCACHE_TICKS_TOTAL 6 // read-write, process refresh (full) period (in ticks) + + /* + * Retrieve a configuration value from the PCILeech DLL. + * -- dwConfigOption = configuration option as specified by PCILEECH_VMM_CONFIG* + * -- pdwConfigValue = pointer to DWORD that will receive configuration value. + * -- return = success/fail. + */ + BOOL PCILeech_VmmConfigGet(_In_ DWORD dwConfigOption, _Out_ PDWORD pdwConfigValue); + + /* + * Set a configuration value in the PCILeech DLL. Note that not all values are + * possible to set - some are read-only. Setting a read-only value will result + * in fuction returning failure. + * -- dwConfigOption = configuration option as specified by PCILEECH_VMM_CONFIG* + * -- dwConfigValue = configuration value to set. + * -- return = success/fail. + */ + BOOL PCILeech_VmmConfigSet(_In_ DWORD dwConfigOption, _In_ DWORD dwConfigValue); + + // FLAG used to supress the default read cache in calls to PCILeech_VmmReadEx() + // which will lead to the read being fetched from the target system always. + // Cached page tables (used for translating virtual2physical) are still used. +#define VMM_FLAG_NOCACHE 0x0001 + + /* + * Read a single 4096-byte page of virtual memory. + * -- dwPID + * -- qwVA + * -- pbPage + * -- return = success/fail (depending if all requested bytes are read or not). + */ + BOOL PCILeech_VmmReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + + /* + * Read a virtually contigious arbitrary amount of memory. + * -- dwPID + * -- qwVA + * -- pb + * -- cb + * -- return = success/fail (depending if all requested bytes are read or not). + */ + BOOL PCILeech_VmmRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + + /* + * Read a virtually contigious amount of memory and report the number of bytes + * read in pcbRead. + * -- dwPID + * -- qwVA + * -- pb + * -- cb + * -- pcbRead + * -- flags = flags as in VMM_FLAG_* + * -- return = success/fail. NB! reads may report as success even if 0 bytes are + * read - it's recommended to verify pcbReadOpt parameter. + */ + BOOL PCILeech_VmmReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + + /* + * Write a virtually contigious arbitrary amount of memory. Please note some + * virtual memory - such as pages of executables (such as DLLs) may be shared + * between different virtual memory over different processes. As an example a + * write to kernel32.dll in one process is likely to affect kernel32 in the + * whole system - in all processes. Heaps and Stacks and other memory are + * usually safe to write to. Please take care when writing to memory! + * -- dwPID + * -- qwVA + * -- pb + * -- cb + * -- return = TRUE on success, FALSE on partial or zero write. + */ + BOOL PCILeech_VmmWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + + /* + * Translate a virtual address to a physical address by walking the page tables + * of the specified process. + * -- dwPID + * -- qwVA + * -- pqwPA + * -- return = success/fail. + */ + BOOL PCILeech_VmmVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + // flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define PCIEECH_VMM_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define PCIEECH_VMM_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define PCIEECH_VMM_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define PCIEECH_VMM_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + + typedef struct tdPCILEECH_VMM_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szName[32]; + } PCILEECH_VMM_MEMMAP_ENTRY, *PPCILEECH_VMM_MEMMAP_ENTRY; + + /* + * Retrieve memory map entries from the specified process. Memory map entries + * are copied into the user supplied buffer that must be at least of size: + * sizeof(PCILEECH_VMM_MEMMAP_ENTRY)*pcMemMapEntries bytes. + * If the pMemMapEntries is set to NULL the number of memory map entries will be + * given in the pcMemMapEntries parameter. + * -- dwPID + * -- pMemMapEntries = buffer of minimum length sizeof(PCILEECH_VMM_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. + * -- pcMemMapEntries = pointer to number of memory map entries. + * -- fIdentifyModules = try identify modules as well (= slower) + * -- return = success/fail. + */ + BOOL PCILeech_VmmProcessGetMemoryMap(_In_ DWORD dwPID, _Out_ PPCILEECH_VMM_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + + typedef struct tdPCILEECH_VMM_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; + } PCILEECH_VMM_MODULEMAP_ENTRY, *PPCILEECH_VMM_MODULEMAP_ENTRY; + + /* + * Retrieve the module entries from the specified process. The module entries + * are copied into the user supplied buffer that must be at least of size: + * sizeof(PCILEECH_VMM_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the + * pcModuleEntries is set to NULL the number of module entries will be given + * in the pcModuleEntries parameter. + * -- dwPID + * -- pModuleEntries = buffer of minimum length sizeof(PCILEECH_VMM_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. + * -- pcModuleEntries = pointer to number of memory map entries. + * -- return = success/fail. + */ + BOOL PCILeech_VmmProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PPCILEECH_VMM_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + + /* + * Retrieve a module (.exe or .dll or similar) given a module name. + * -- dwPID + * -- szModuleName + * -- pModuleEntry + * -- return = success/fail. + */ + BOOL PCILeech_VmmProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PPCILEECH_VMM_MODULEMAP_ENTRY pModuleEntry); + + /* + * Retrieve an active process given it's name. Please note that if multiple + * processes with the same name exists only one will be returned. If required to + * parse all processes with the same name please iterate over the PID list by + * calling PCILeech_VmmProcessListPIDs together with PCIleech_VmmProcessInfo. + * -- szProcName = process name (truncated max 15 chars) case insensitive. + * -- pdwPID = pointer that will receive PID on success. + * -- return + */ + BOOL PCILeech_VmmProcessGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + + /* + * List the PIDs in the system. + * -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. + * -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. + * -- return = success/fail. + */ + BOOL PCILeech_VmmProcessListPIDs(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + + /* + * Retrieve various process information from a PID. Process information such as + * name, page directory bases and the process state may be retrieved. Parameters, + * except for dwPID are optional. + * -- dwPID + * -- szNameOpt + * -- pqwPageDirectoryBaseOpt + * -- pqwPageDirectoryBaseUserOpt + * -- pdwStateOpt + * -- return = success/fail. + */ + BOOL PCIleech_VmmProcessInfo(_In_ DWORD dwPID, _Out_opt_ CHAR szNameOpt[16], _Out_opt_ PULONG64 pqwPageDirectoryBaseOpt, _Out_opt_ PULONG64 pqwPageDirectoryBaseUserOpt, _Out_opt_ PDWORD pdwStateOpt); + + typedef struct tdPCILEECH_VMM_EAT_ENTRY { + DWORD vaFunctionOffset; + CHAR szFunction[40]; + } PCILEECH_VMM_EAT_ENTRY, *PPCILEECH_VMM_EAT_ENTRY; + + typedef struct tdPCILEECH_VMM_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; + } PCILEECH_VMM_IAT_ENTRY, *PPCILEECH_VMM_IAT_ENTRY; + + /* + * Retrieve information about: Data Directories, Sections, Export Address Table + * and Import Address Table (IAT). + * If the pData == NULL upon entry the number of entries of the pData array must + * have in order to be able to hold the data is returned. + * -- dwPID + * -- szModule + * -- pData + * -- cData + * -- pcData + * -- return = success/fail. + */ + BOOL PCIleech_VmmProcess_GetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + BOOL PCIleech_VmmProcess_GetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); + BOOL PCIleech_VmmProcess_GetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PPCILEECH_VMM_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + BOOL PCIleech_VmmProcess_GetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PPCILEECH_VMM_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __PCILEECH_DLL_H__ */ diff --git a/vmm/pluginmanager.c b/vmm/pluginmanager.c new file mode 100644 index 0000000..b592dde --- /dev/null +++ b/vmm/pluginmanager.c @@ -0,0 +1,422 @@ +// pluginmanager.h : implementation of the plugin manager for memory process file system plugins. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "pluginmanager.h" +#include "statistics.h" +#include "util.h" +#include "vmm.h" +#include "vmmdll.h" +#include "m_ldrmodules.h" +#include "m_status.h" +#include "m_virt2phys.h" + +// +// This file contains functionality related to keeping track of plugins, both +// internal built-in ones and loadable plugins in the form of compliant DLLs. +// +// The functionality and data structures are at this moment single-threaded in +// this implementation and should be protected by a lock (ctxVmm->MasterLock). +// +// Core module calls are: List, Read, Write. +// Other module calls are: Notify and Close. +// +// In general, a pointer to a stored-away module specific handle is given in +// every call together with a plugin/process specific pointer to a handle. +// The plugin/process specific handle is stored per module, per PID. +// +// A pProcess struct (if applicable) and a PID is also given in each call +// together with the module name and path. +// + +// ---------------------------------------------------------------------------- +// MODULES CORE FUNCTIONALITY - DEFINES BELOW: +// ---------------------------------------------------------------------------- +#define PLUGIN_CONFIG_NUM_PROCESS_CACHE 23 + +typedef struct tdVMMM_PROCESS_CACHE_ENTRY { + struct tdVMMM_PROCESS_CACHE_ENTRY *FLink; + HANDLE hModuleProcPrivate; + DWORD dwPID; +} VMMM_PROCESS_CACHE_ENTRY, *PVMMM_PROCESS_CACHE_ENTRY; + +typedef struct tdPLUGIN_LISTENTRY { + struct tdPLUGIN_LISTENTRY *FLink; + HMODULE hDLL; + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + VMMM_PROCESS_CACHE_ENTRY ModuleProcCache[PLUGIN_CONFIG_NUM_PROCESS_CACHE]; +} PLUGIN_LISTENTRY, *PPLUGIN_LISTENTRY; + +// ---------------------------------------------------------------------------- +// MODULES CORE FUNCTIONALITY - IMPLEMENTATION BELOW: +// ---------------------------------------------------------------------------- +PHANDLE PluginManager_ProcCacheGet(_Inout_ PPLUGIN_LISTENTRY pModule, _In_ DWORD dwPID) +{ + PVMMM_PROCESS_CACHE_ENTRY e; + e = &pModule->ModuleProcCache[dwPID % PLUGIN_CONFIG_NUM_PROCESS_CACHE]; + while(e) { + if(!e->hModuleProcPrivate || (e->dwPID == dwPID)) { + e->dwPID = dwPID; + return &e->hModuleProcPrivate; + } + if(!e->FLink) { + e->FLink = LocalAlloc(LMEM_ZEROINIT, sizeof(VMMM_PROCESS_CACHE_ENTRY)); + } + e = e->FLink; + } + return NULL; +} + +VOID PluginManager_ContextInitialize(_Inout_ PVMMDLL_PLUGIN_CONTEXT ctx, PPLUGIN_LISTENTRY pModule, _In_opt_ PVMM_PROCESS pProcess, _In_ LPSTR szPath, _In_ BOOL fDll) +{ + ctx->magic = VMMDLL_PLUGIN_CONTEXT_MAGIC; + ctx->wVersion = VMMDLL_PLUGIN_CONTEXT_VERSION; + ctx->wSize = sizeof(VMMDLL_PLUGIN_CONTEXT); + ctx->dwPID = (pProcess ? pProcess->dwPID : (DWORD)-1); + ctx->phModulePrivate = &pModule->hModulePrivate; + ctx->pProcess = fDll ? NULL : pProcess; + ctx->phProcessPrivate = pProcess ? PluginManager_ProcCacheGet(pModule, ctx->dwPID) : NULL; + ctx->szModule = pModule->szModuleName; + ctx->szPath = szPath; +} + +VOID PluginManager_ListAll(_In_opt_ PVMM_PROCESS pProcess, _Inout_ PHANDLE pFileList) +{ + PPLUGIN_LISTENTRY pModule = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList; + while(pModule) { + if((pProcess && pModule->fProcessModule) || (!pProcess && pModule->fRootModule)) { + VMMDLL_VfsList_AddDirectory(pFileList, pModule->szModuleName); + } + pModule = pModule->FLink; + } +} + +BOOL PluginManager_List(_In_opt_ PVMM_PROCESS pProcess, _In_ LPSTR szModule, _In_ LPSTR szPath, _Inout_ PHANDLE pFileList) +{ + QWORD tmStart = Statistics_CallStart(); + BOOL result; + VMMDLL_PLUGIN_CONTEXT ctx; + PPLUGIN_LISTENTRY pModule = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList; + while(pModule) { + if(!((pProcess && pModule->fProcessModule) || (!pProcess && pModule->fRootModule))) { + pModule = pModule->FLink; + continue; + } + if(!_stricmp(szModule, pModule->szModuleName)) { + if(pModule->pfnList) { + if(pModule->hDLL) { + PluginManager_ContextInitialize(&ctx, pModule, pProcess, (szPath ? szPath : ""), TRUE); + VmmLockRelease(); + result = pModule->pfnList(&ctx, pFileList); + VmmLockAcquire(); + Statistics_CallEnd(STATISTICS_ID_PluginManager_List, tmStart); + return result; + } else { + PluginManager_ContextInitialize(&ctx, pModule, pProcess, (szPath ? szPath : ""), FALSE); + Statistics_CallEnd(STATISTICS_ID_PluginManager_List, tmStart); + return pModule->pfnList(&ctx, pFileList); + } + } + } + pModule = pModule->FLink; + } + Statistics_CallEnd(STATISTICS_ID_PluginManager_List, tmStart); + return FALSE; +} + +NTSTATUS PluginManager_Read(_In_opt_ PVMM_PROCESS pProcess, _In_ LPSTR szModule, _In_ LPSTR szPath, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + QWORD tmStart = Statistics_CallStart(); + NTSTATUS nt; + VMMDLL_PLUGIN_CONTEXT ctx; + PPLUGIN_LISTENTRY pModule = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList; + while(pModule) { + if(!((pProcess && pModule->fProcessModule) || (!pProcess && pModule->fRootModule))) { + pModule = pModule->FLink; + continue; + } + if(!_stricmp(szModule, pModule->szModuleName)) { + if(pModule->pfnRead) { + if(pModule->hDLL) { + PluginManager_ContextInitialize(&ctx, pModule, pProcess, (szPath ? szPath : ""), TRUE); + VmmLockRelease(); + nt = pModule->pfnRead(&ctx, pb, cb, pcbRead, cbOffset); + VmmLockAcquire(); + Statistics_CallEnd(STATISTICS_ID_PluginManager_Read, tmStart); + return nt; + } else { + PluginManager_ContextInitialize(&ctx, pModule, pProcess, (szPath ? szPath : ""), FALSE); + Statistics_CallEnd(STATISTICS_ID_PluginManager_Read, tmStart); + return pModule->pfnRead(&ctx, pb, cb, pcbRead, cbOffset); + } + } + } + pModule = pModule->FLink; + } + Statistics_CallEnd(STATISTICS_ID_PluginManager_Read, tmStart); + return VMMDLL_STATUS_FILE_INVALID; +} + +NTSTATUS PluginManager_Write(_In_opt_ PVMM_PROCESS pProcess, _In_ LPSTR szModule, _In_ LPSTR szPath, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset) +{ + QWORD tmStart = Statistics_CallStart(); + NTSTATUS nt; + VMMDLL_PLUGIN_CONTEXT ctx; + PPLUGIN_LISTENTRY pModule = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList; + while(pModule) { + if(!((pProcess && pModule->fProcessModule) || (!pProcess && pModule->fRootModule))) { + pModule = pModule->FLink; + continue; + } + if(!_stricmp(szModule, pModule->szModuleName)) { + if(pModule->hDLL) { + PluginManager_ContextInitialize(&ctx, pModule, pProcess, (szPath ? szPath : ""), TRUE); + VmmLockRelease(); + nt = pModule->pfnWrite(&ctx, pb, cb, pcbWrite, cbOffset); + VmmLockAcquire(); + Statistics_CallEnd(STATISTICS_ID_PluginManager_Write, tmStart); + return nt; + } else { + PluginManager_ContextInitialize(&ctx, pModule, pProcess, (szPath ? szPath : ""), FALSE); + Statistics_CallEnd(STATISTICS_ID_PluginManager_Write, tmStart); + return pModule->pfnWrite(&ctx, pb, cb, pcbWrite, cbOffset); + } + } + pModule = pModule->FLink; + } + Statistics_CallEnd(STATISTICS_ID_PluginManager_Write, tmStart); + return VMMDLL_STATUS_FILE_INVALID; +} + +BOOL PluginManager_Notify(_In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent) +{ + QWORD tmStart = Statistics_CallStart(); + PPLUGIN_LISTENTRY pModule = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList; + while(pModule) { + if(pModule->pfnNotify) { + if(pModule->hDLL) { + VmmLockRelease(); + pModule->pfnNotify(&pModule->hModulePrivate, fEvent, pvEvent, cbEvent); + VmmLockAcquire(); + } else { + pModule->pfnNotify(&pModule->hModulePrivate, fEvent, pvEvent, cbEvent); + } + } + pModule = pModule->FLink; + } + Statistics_CallEnd(STATISTICS_ID_PluginManager_Notify, tmStart); + return TRUE; +} + +BOOL PluginManager_ModuleExists(_In_opt_ HMODULE hDLL, _In_opt_ LPSTR szModule) { + PPLUGIN_LISTENTRY pModule = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList; + while(pModule) { + if(hDLL && (hDLL == pModule->hDLL)) { return TRUE; } + if(szModule && !_stricmp(szModule, pModule->szModuleName)) { return TRUE; } + pModule = pModule->FLink; + } + return FALSE; +} + +BOOL PluginManager_Register(_In_ PVMMDLL_PLUGIN_REGINFO pRegInfo) +{ + const LPSTR RESERVED_NAMES[] = { "name", "pid", "pmem", "map", "pml4", "vmem", "pml4-user", "win-eprocess", "win-entry", "win-peb", "win-peb32", "win-modules" }; + PPLUGIN_LISTENTRY pModule; + DWORD i; + // 1: tests if module is valid + if(!pRegInfo || (pRegInfo->magic != VMMDLL_PLUGIN_REGINFO_MAGIC) || (pRegInfo->wVersion > VMMDLL_PLUGIN_REGINFO_VERSION)) { return FALSE; } + if(!pRegInfo->reg_fn.pfnList || !pRegInfo->reg_info.szModuleName[0] || (strlen(pRegInfo->reg_info.szModuleName) > 31)) { return FALSE; } + if(PluginManager_ModuleExists(NULL, pRegInfo->reg_info.szModuleName)) { return FALSE; } + pModule = (PPLUGIN_LISTENTRY)LocalAlloc(LMEM_ZEROINIT, sizeof(PLUGIN_LISTENTRY)); + if(!pModule) { return FALSE; } + if(!pRegInfo->reg_info.fRootModule && !pRegInfo->reg_info.fProcessModule) { return FALSE; } + for(i = 0; i < (sizeof(RESERVED_NAMES) / sizeof(LPSTR)); i++) { + if(!strcmp(pRegInfo->reg_info.szModuleName, RESERVED_NAMES[i])) { return FALSE; } + } + // 2: register module + pModule->hDLL = pRegInfo->hDLL; + pModule->hModulePrivate = pRegInfo->reg_info.hModulePrivate; + strncpy_s(pModule->szModuleName, 32, pRegInfo->reg_info.szModuleName, 32); + pModule->fRootModule = pRegInfo->reg_info.fRootModule; + pModule->fProcessModule = pRegInfo->reg_info.fProcessModule; + pModule->pfnList = pRegInfo->reg_fn.pfnList; + pModule->pfnRead = pRegInfo->reg_fn.pfnRead; + pModule->pfnWrite = pRegInfo->reg_fn.pfnWrite; + pModule->pfnNotify = pRegInfo->reg_fn.pfnNotify; + pModule->pfnCloseHandleModule = pRegInfo->reg_fn.pfnCloseHandleModule; + pModule->pfnCloseHandleProcess = pRegInfo->reg_fn.pfnCloseHandleProcess; + vmmprintfv("PluginManager: Loaded %s module '%s'.\n", (pModule->hDLL ? "native" : "built-in"), pModule->szModuleName); + pModule->FLink = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList; + ctxVmm->pVmmVfsModuleList = pModule; + return TRUE; +} + +VOID PluginManager_Close() +{ + DWORD i; + PPLUGIN_LISTENTRY pm; + PVMMM_PROCESS_CACHE_ENTRY pe; + while((pm = (PPLUGIN_LISTENTRY)ctxVmm->pVmmVfsModuleList)) { + // 1: Detach current module list entry from list + ctxVmm->pVmmVfsModuleList = pm->FLink; + // 2: Close process specific module handles + if(pm->pfnCloseHandleProcess) { + for(i = 0; i < PLUGIN_CONFIG_NUM_PROCESS_CACHE; i++) { + while((pe = pm->ModuleProcCache[i].FLink)) { + pm->ModuleProcCache[i].FLink = pe->FLink; + if(pe->hModuleProcPrivate) { pm->pfnCloseHandleProcess(pm->hModulePrivate, pe->hModuleProcPrivate); } + LocalFree(pe); + } + if(pm->ModuleProcCache[i].hModuleProcPrivate) { pm->pfnCloseHandleProcess(pm->hModulePrivate, pm->ModuleProcCache[i].hModuleProcPrivate); } + } + } + // 3: Close module specific handle + if(pm->pfnCloseHandleModule && pm->hModulePrivate) { pm->pfnCloseHandleModule(pm->hModulePrivate); } + // 4: FreeLibrary (if last module belonging to specific Library) + if(pm->hDLL && !PluginManager_ModuleExists(pm->hDLL, NULL)) { FreeLibrary(pm->hDLL); } + // 5: LocalFree this ListEntry + LocalFree(pm); + } +} + +VOID PluginManager_Initialize_RegInfoInit(_Inout_ PVMMDLL_PLUGIN_REGINFO pRI, _In_opt_ HMODULE hDLL) +{ + ZeroMemory(pRI, sizeof(VMMDLL_PLUGIN_REGINFO)); + pRI->magic = VMMDLL_PLUGIN_REGINFO_MAGIC; + pRI->wVersion = VMMDLL_PLUGIN_REGINFO_VERSION; + pRI->wSize = sizeof(VMMDLL_PLUGIN_REGINFO); + pRI->hDLL = hDLL; + pRI->fTargetSystem = ctxVmm->fTargetSystem; + pRI->pfnPluginManager_Register = PluginManager_Register; +} + +VOID PluginManager_Initialize_Python() +{ + VMMDLL_PLUGIN_REGINFO ri; + CHAR szPythonPath[MAX_PATH]; + HMODULE hDllPython = NULL, hDllPyPlugin = NULL; + VOID(*pfnInitializeVmmPlugin)(_In_ PVMMDLL_PLUGIN_REGINFO pRegInfo); + // 1: Locate Python by trying user-defined path + if(ctxMain->cfg.szPythonPath[0]) { + ZeroMemory(szPythonPath, MAX_PATH); + strcpy_s(szPythonPath, MAX_PATH, ctxMain->cfg.szPythonPath); + strcat_s(szPythonPath, MAX_PATH, "\\python36.dll"); + hDllPython = LoadLibraryExA(szPythonPath, 0, LOAD_WITH_ALTERED_SEARCH_PATH); + if(!hDllPython) { + ZeroMemory(ctxMain->cfg.szPythonPath, MAX_PATH); + vmmprintf("PluginManager: Python initialization failed. Python 3.6 not found on user specified path.\n"); + return; + } + } + // 2: Try locate Python by checking the python36 sub-directory relative to the current executable (.exe). + if(0 == ctxMain->cfg.szPythonPath[0]) { + ZeroMemory(szPythonPath, MAX_PATH); + Util_GetPathDll(szPythonPath, NULL); + strcat_s(szPythonPath, MAX_PATH, "python36\\python36.dll"); + hDllPython = LoadLibraryExA(szPythonPath, 0, LOAD_WITH_ALTERED_SEARCH_PATH); + if(hDllPython) { + Util_GetPathDll(ctxMain->cfg.szPythonPath, NULL); + strcat_s(ctxMain->cfg.szPythonPath, MAX_PATH, "python36\\"); + } + } + // 3: Try locate Python by loading from the current path. + if(0 == ctxMain->cfg.szPythonPath[0]) { + hDllPython = LoadLibraryA("python36.dll"); + if(hDllPython) { + Util_GetPathDll(ctxMain->cfg.szPythonPath, hDllPython); + } + } + // 4: Python is not found? + if(0 == ctxMain->cfg.szPythonPath[0]) { + vmmprintf("PluginManager: Python initialization failed. Python 3.6 not found.\n"); + goto fail; + } + // 5: process 'special status' python plugin manager. + hDllPyPlugin = LoadLibraryA("vmmpycplugin.dll"); + if(!hDllPyPlugin) { + vmmprintf("PluginManager: Python plugin manager failed to load.\n"); + goto fail; + } + pfnInitializeVmmPlugin = (VOID(*)(PVMMDLL_PLUGIN_REGINFO))GetProcAddress(hDllPyPlugin, "InitializeVmmPlugin"); + if(!pfnInitializeVmmPlugin) { + vmmprintf("PluginManager: Python plugin manager failed to load due to corrupt DLL.\n"); + goto fail; + } + PluginManager_Initialize_RegInfoInit(&ri, hDllPyPlugin); + ri.hReservedDll = hDllPython; + pfnInitializeVmmPlugin(&ri); + if(!PluginManager_ModuleExists(hDllPyPlugin, NULL)) { + vmmprintf("PluginManager: Python plugin manager failed to load due to internal error.\n"); + return; + } + vmmprintfv("PluginManager: Python plugin loaded.\n"); + if(hDllPython) { FreeLibrary(hDllPython); } + return; +fail: + if(hDllPyPlugin) { FreeLibrary(hDllPyPlugin); } + if(hDllPython) { FreeLibrary(hDllPython); } +} + +BOOL PluginManager_Initialize() +{ + VMMDLL_PLUGIN_REGINFO ri; + CHAR szPath[MAX_PATH]; + DWORD cchPathBase; + HANDLE hFindFile; + WIN32_FIND_DATAA FindData; + HMODULE hDLL; + VOID(*pfnInitializeVmmPlugin)(_In_ PVMMDLL_PLUGIN_REGINFO pRegInfo); + if(ctxVmm->pVmmVfsModuleList) { return FALSE; } // already initialized + ZeroMemory(&ri, sizeof(VMMDLL_PLUGIN_REGINFO)); + // 1: process built-in modules + PluginManager_Initialize_RegInfoInit(&ri, NULL); + M_Virt2Phys_Initialize(&ri); + PluginManager_Initialize_RegInfoInit(&ri, NULL); + M_LdrModules_Initialize(&ri); + PluginManager_Initialize_RegInfoInit(&ri, NULL); + M_Status_Initialize(&ri); + // 2: process dll modules + Util_GetPathDll(szPath, NULL); + cchPathBase = (DWORD)strnlen(szPath, MAX_PATH - 1); + strcat_s(szPath, MAX_PATH, "plugins\\m_*.dll"); + hFindFile = FindFirstFileA(szPath, &FindData); + if(hFindFile != INVALID_HANDLE_VALUE) { + do { + szPath[cchPathBase] = '\0'; + strcat_s(szPath, MAX_PATH, "plugins\\"); + strcat_s(szPath, MAX_PATH, FindData.cFileName); + hDLL = LoadLibraryExA(szPath, 0, 0); + if(!hDLL) { + vmmprintfvv("PluginManager: FAIL: Load DLL: '%s' - missing dependencies?\n", FindData.cFileName); + continue; + } + vmmprintfvv("PluginManager: Load DLL: '%s'\n", FindData.cFileName); + pfnInitializeVmmPlugin = (VOID(*)(PVMMDLL_PLUGIN_REGINFO))GetProcAddress(hDLL, "InitializeVmmPlugin"); + if(!pfnInitializeVmmPlugin) { + vmmprintfvv("PluginManager: UnLoad DLL: '%s' - Plugin Entry Point not found.\n", FindData.cFileName); + FreeLibrary(hDLL); + continue; + } + PluginManager_Initialize_RegInfoInit(&ri, hDLL); + pfnInitializeVmmPlugin(&ri); + if(!PluginManager_ModuleExists(hDLL, NULL)) { + vmmprintfvv("PluginManager: UnLoad DLL: '%s' - not registered with plugin manager.\n", FindData.cFileName); + FreeLibrary(hDLL); + continue; + } + } while(FindNextFileA(hFindFile, &FindData)); + } + // 3: process 'special status' python plugin manager. + PluginManager_Initialize_Python(); + return TRUE; +} diff --git a/vmm/pluginmanager.h b/vmm/pluginmanager.h new file mode 100644 index 0000000..184a4cd --- /dev/null +++ b/vmm/pluginmanager.h @@ -0,0 +1,76 @@ +// pluginmanager.h : definitions for the plugin manager for memory process file system plugins. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __PLUGINMANAGER_H__ +#define __PLUGINMANAGER_H__ + +#include +#include "vmm.h" + +/* +* Initialize built-in and external modules. +*/ +BOOL PluginManager_Initialize(); + +/* +* Close built-in and external modules, free their resources and unload loaded +* DLLs from memory. +*/ +VOID PluginManager_Close(); + +/* +* Enumerate modules in a process directory (insert directories of module names). +* -- pProcess +* -- pFileList +*/ +VOID PluginManager_ListAll(_In_opt_ PVMM_PROCESS pProcess, _Inout_ PHANDLE pFileList); + +/* +* Send a List command down the module chain to the appropriate module. +* -- pProcess +* -- szModule +* -- szPath +* -- pFileList +* -- return +*/ +BOOL PluginManager_List(_In_opt_ PVMM_PROCESS pProcess, _In_ LPSTR szModule, _In_ LPSTR szPath, _Inout_ PHANDLE pFileList); + +/* +* Send a Read command down the module chain to the appropriate module. +* -- pProcess +* -- szModule +* -- szPath +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +*/ +NTSTATUS PluginManager_Read(_In_opt_ PVMM_PROCESS pProcess, _In_ LPSTR szModule, _In_ LPSTR szPath, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset); + +/* +* Send a Write command down the module chain to the appropriate module. +* -- pProcess +* -- szModule +* -- szPath +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS PluginManager_Write(_In_opt_ PVMM_PROCESS pProcess, _In_ LPSTR szModule, _In_ LPSTR szPath, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset); + +/* +* Send a notification event to plugins that registered to receive notifications. +* Officially supported events are listed in vmmdll.h!VMMDLL_PLUGIN_EVENT_* +* -- fEvent = the event to send. +* -- pvEvent = optional binary object related to the event. +* -- cbEvent = length in bytes of pvEvent (if any). +* -- return = (always return TRUE). +*/ +BOOL PluginManager_Notify(_In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + +#endif /* __PLUGINMANAGER_H__ */ diff --git a/vmm/statistics.c b/vmm/statistics.c new file mode 100644 index 0000000..dd43eb6 --- /dev/null +++ b/vmm/statistics.c @@ -0,0 +1,308 @@ +// statistics.c : implementation of statistics related functionality. +// +// (c) Ulf Frisk, 2016-2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "statistics.h" +#include "vmm.h" + +// ---------------------------------------------------------------------------- +// PAGE READ STATISTICAL FUNCTIONALITY BELOW: +// ---------------------------------------------------------------------------- + +VOID _PageStatPrintMemMap(_Inout_ PPAGE_STATISTICS ps) +{ + BOOL fIsLinePrinted = FALSE; + QWORD i, qwAddrBase, qwAddrEnd; + if(!ps->i.fIsFirstPrintCompleted) { + vmmprintf(" Memory Map: \n START END #PAGES \n"); + } + if(!ps->i.MemMapIdx && !ps->i.MemMap[0]) { + vmmprintf(" \n \n"); + return; + } + if(ps->i.MemMapPrintCommitIdx >= PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 4) { + vmmprintf(" Maximum number of memory map entries reached. \n \n"); + return; + } + qwAddrBase = ps->i.qwAddrBase + ps->i.MemMapPrintCommitPages * 0x1000; + for(i = ps->i.MemMapPrintCommitIdx; i < PAGE_STATISTICS_MEM_MAP_MAX_ENTRY; i++) { + if(!ps->i.MemMap[i] && i == 0) { + continue; + } + if(!ps->i.MemMap[i] || (i == PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 1)) { + break; + } + qwAddrEnd = qwAddrBase + 0x1000 * (QWORD)ps->i.MemMap[i]; + if((i % 2) == 0) { + fIsLinePrinted = TRUE; + vmmprintf( + " %016llx - %016llx %08x \n", + qwAddrBase, + qwAddrEnd - 1, + ps->i.MemMap[i]); + if(i >= ps->i.MemMapPrintCommitIdx + 2) { + ps->i.MemMapPrintCommitPages += ps->i.MemMap[ps->i.MemMapPrintCommitIdx++]; + ps->i.MemMapPrintCommitPages += ps->i.MemMap[ps->i.MemMapPrintCommitIdx++]; + + } + } + qwAddrBase = qwAddrEnd; + } + if(!fIsLinePrinted) { // print extra line for formatting reasons. + vmmprintf(" (No memory successfully read yet) \n"); + } + vmmprintf(" \n"); +} + +VOID _PageStatShowUpdate(_Inout_ PPAGE_STATISTICS ps) +{ + if(0 == ps->cPageTotal) { return; } + QWORD qwPercentTotal = ((ps->cPageSuccess + ps->cPageFail) * 100) / ps->cPageTotal; + QWORD qwPercentSuccess = (ps->cPageSuccess * 200 + 1) / (ps->cPageTotal * 2); + QWORD qwPercentFail = (ps->cPageFail * 200 + 1) / (ps->cPageTotal * 2); + QWORD qwTickCountElapsed = GetTickCount64() - ps->i.qwTickCountStart; + QWORD qwSpeed = ((ps->cPageSuccess + ps->cPageFail) * 4) / (1 + (qwTickCountElapsed / 1000)); + HANDLE hConsole; + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + BOOL isMBs = qwSpeed >= 1024; + if(ps->i.fIsFirstPrintCompleted) { +#ifdef WIN32 + hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + GetConsoleScreenBufferInfo(hConsole, &consoleInfo); + consoleInfo.dwCursorPosition.Y -= ps->i.fMemMap ? 9 : 7; + SetConsoleCursorPosition(hConsole, consoleInfo.dwCursorPosition); +#endif /* WIN32 */ +#if defined(LINUX) || defined(ANDROID) + vmmprintf(ps->i.fMemMap ? "\033[9A" : "\033[7A"); // move cursor up 7/9 positions +#endif /* LINUX || ANDROID */ + } + if(ps->i.fMemMap) { + _PageStatPrintMemMap(ps); + } + if(ps->cPageTotal < 0x0000000fffffffff) { + vmmprintf( + " Current Action: %s \n" \ + " Access Mode: %s \n" \ + " Progress: %llu / %llu (%llu%%) \n" \ + " Speed: %llu %s \n" \ + " Address: 0x%016llX \n" \ + " Pages read: %llu / %llu (%llu%%) \n" \ + " Pages failed: %llu (%llu%%) \n", + ps->szAction, + ps->fKMD ? "KMD (kernel module assisted DMA)" : "DMA (hardware only) ", + (ps->cPageSuccess + ps->cPageFail) / 256, + ps->cPageTotal / 256, + qwPercentTotal, + (isMBs ? qwSpeed >> 10 : qwSpeed), + (isMBs ? "MB/s" : "kB/s"), + ps->qwAddr, + ps->cPageSuccess, + ps->cPageTotal, + qwPercentSuccess, + ps->cPageFail, + qwPercentFail); + } else { + vmmprintf( + " Current Action: %s \n" \ + " Access Mode: %s \n" \ + " Progress: %llu / (unknown) \n" \ + " Speed: %llu %s \n" \ + " Address: 0x%016llX \n" \ + " Pages read: %llu \n" \ + " Pages failed: %llu \n", + ps->szAction, + ps->fKMD ? "KMD (kernel module assisted DMA)" : "DMA (hardware only) ", + (ps->cPageSuccess + ps->cPageFail) / 256, + (isMBs ? qwSpeed >> 10 : qwSpeed), + (isMBs ? "MB/s" : "kB/s"), + ps->qwAddr, + ps->cPageSuccess, + ps->cPageFail); + } + ps->i.fIsFirstPrintCompleted = TRUE; +} + +VOID _PageStatThreadLoop(_In_ PPAGE_STATISTICS ps) +{ + while(!ps->i.fThreadExit) { + Sleep(100); + if(ps->i.fUpdate) { + ps->i.fUpdate = FALSE; + _PageStatShowUpdate(ps); + } + } + ExitThread(0); +} + +VOID PageStatClose(_Inout_ PPAGE_STATISTICS ps) +{ + BOOL status; + DWORD dwExitCode; + ps->i.fUpdate = TRUE; + ps->i.fThreadExit = TRUE; + while((status = GetExitCodeThread(ps->i.hThread, &dwExitCode)) && STILL_ACTIVE == dwExitCode) { + SwitchToThread(); + } + if(!status) { + Sleep(200); + } +} + +VOID PageStatInitialize(_Inout_ PPAGE_STATISTICS ps, _In_ QWORD qwAddrBase, _In_ QWORD qwAddrMax, _In_ LPSTR szAction, _In_ BOOL fKMD, _In_ BOOL fMemMap) +{ + memset(ps, 0, sizeof(PAGE_STATISTICS)); + ps->qwAddr = qwAddrBase; + ps->cPageTotal = (qwAddrMax - qwAddrBase + 1) / 4096; + ps->szAction = szAction; + ps->fKMD = fKMD; + ps->i.fMemMap = fMemMap; + ps->i.qwAddrBase = qwAddrBase; + ps->i.qwTickCountStart = GetTickCount64(); + ps->i.hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)_PageStatThreadLoop, ps, 0, NULL); +} + +VOID PageStatUpdate(_Inout_opt_ PPAGE_STATISTICS ps, _In_ QWORD qwAddr, _In_ QWORD cPageSuccessAdd, _In_ QWORD cPageFailAdd) +{ + if(!ps) { return; } + ps->qwAddr = qwAddr; + ps->cPageSuccess += cPageSuccessAdd; + ps->cPageFail += cPageFailAdd; + // add to memory map, even == success, odd = fail. + if(ps->i.MemMapIdx < PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 2) { + if(cPageSuccessAdd) { + if(ps->i.MemMapIdx % 2 == 1) { + ps->i.MemMapIdx++; + } + ps->i.MemMap[ps->i.MemMapIdx] += (DWORD)cPageSuccessAdd; + } + if(cPageFailAdd) { + if(ps->i.MemMapIdx % 2 == 0) { + ps->i.MemMapIdx++; + } + ps->i.MemMap[ps->i.MemMapIdx] += (DWORD)cPageFailAdd; + } + } + ps->i.fUpdate = TRUE; +} + +// ---------------------------------------------------------------------------- +// FUNCTION CALL STATISTICAL FUNCTIONALITY BELOW: +// ---------------------------------------------------------------------------- + +const LPSTR NAMES_VMM_STATISTICS_CALL[] = { + "INITIALIZE", + "VMMDLL_VfsList", + "VMMDLL_VfsRead", + "VMMDLL_VfsWrite", + "VMMDLL_VfsInitializePlugins", + "VMMDLL_MemReadEx", + "VMMDLL_MemWrite", + "VMMDLL_MemVirt2Phys", + "VMMDLL_PidList", + "VMMDLL_PidGetFromName", + "VMMDLL_ProcessGetInformation", + "VMMDLL_ProcessGetMemoryMap", + "VMMDLL_ProcessGetMemoryMapEntry", + "VMMDLL_ProcessGetModuleMap", + "VMMDLL_ProcessGetModuleFromName", + "VMMDLL_ProcessGetDirectories", + "VMMDLL_ProcessGetSections", + "VMMDLL_ProcessGetEAT", + "VMMDLL_ProcessGetIAT", + "PluginManager_List", + "PluginManager_Read", + "PluginManager_Write", + "PluginManager_Notify", + "DeviceReadScatterMEM", + "DeviceWriteMEM" +}; + +typedef struct tdCALLSTAT { + QWORD c; + QWORD tm; +} CALLSTAT, *PCALLSTAT; + +VOID Statistics_CallSetEnabled(_In_ BOOL fEnabled) +{ + if(fEnabled && ctxMain->pvStatistics) { return; } + if(!fEnabled && !ctxMain->pvStatistics) { return; } + if(fEnabled) { + ctxMain->pvStatistics = LocalAlloc(LMEM_ZEROINIT, (STATISTICS_ID_MAX + 1) * sizeof(CALLSTAT)); + } else { + LocalFree(ctxMain->pvStatistics); + ctxMain->pvStatistics = NULL; + } +} + +BOOL Statistics_CallGetEnabled() +{ + return ctxMain->pvStatistics != NULL; +} + +QWORD Statistics_CallStart() +{ + QWORD tmNow; + if(!ctxMain->pvStatistics) { return 0; } + QueryPerformanceCounter((PLARGE_INTEGER)&tmNow); + return tmNow; +} + +VOID Statistics_CallEnd(_In_ DWORD fId, QWORD tmCallStart) +{ + QWORD tmNow; + PCALLSTAT pStat; + if(!ctxMain->pvStatistics) { return; } + if(fId > STATISTICS_ID_MAX) { return; } + if(tmCallStart == 0) { return; } + pStat = ((PCALLSTAT)ctxMain->pvStatistics) + fId; + pStat->c++; + QueryPerformanceCounter((PLARGE_INTEGER)&tmNow); + pStat->tm += tmNow - tmCallStart; +} + +VOID Statistics_CallToString(_In_opt_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb) +{ + QWORD qwFreq, uS; + DWORD i, o = 0; + PCALLSTAT pStat; + if(!pb) { + *pcb = 71 * (STATISTICS_ID_MAX + 5); + return; + } + QueryPerformanceFrequency((PLARGE_INTEGER)&qwFreq); + o += snprintf( + pb + o, + cb - o, + "FUNCTION CALL STATISTICS: \n" \ + "VALUES IN DECIMAL, TIME IN MICROSECONDS uS, STATISTICS = %s \n" \ + "FUNCTION CALL NAME CALLS TIME AVG TIME TOTAL\n" \ + "======================================================================\n", + ctxMain->pvStatistics ? "ENABLED " : "DISABLED" + ); + for(i = 0; i <= STATISTICS_ID_MAX; i++) { + if(ctxMain->pvStatistics) { + pStat = ((PCALLSTAT)ctxMain->pvStatistics) + i; + if(pStat->c) { + uS = (pStat->tm * 1000000ULL) / qwFreq; + o += snprintf( + pb + o, + cb - o, + "%-32.32s %8i %8i %16lli\n", + NAMES_VMM_STATISTICS_CALL[i], + (DWORD)pStat->c, + (DWORD)(uS / pStat->c), + uS + ); + continue; + } + } + o += snprintf( + pb + o, + cb - o, + "%-32.32s %8i %8i %16lli\n", + NAMES_VMM_STATISTICS_CALL[i], + 0, 0, 0ULL); + } + *pcb = o; +} diff --git a/vmm/statistics.h b/vmm/statistics.h new file mode 100644 index 0000000..a46b8cf --- /dev/null +++ b/vmm/statistics.h @@ -0,0 +1,100 @@ +// statistics.h : definitions of statistics related functionality. +// +// (c) Ulf Frisk, 2016-2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __STATISTICS_H__ +#define __STATISTICS_H__ +#include "vmm.h" + +#define PAGE_STATISTICS_MEM_MAP_MAX_ENTRY 4096 + +typedef struct tdPageStatistics { + QWORD qwAddr; + QWORD cPageTotal; + QWORD cPageSuccess; + QWORD cPageFail; + BOOL fKMD; + LPSTR szAction; + struct _InternalUseOnly { + BOOL fUpdate; + BOOL fThreadExit; + BOOL fMemMap; + BOOL fIsFirstPrintCompleted; + HANDLE hThread; + WORD wConsoleCursorPosition; + QWORD qwTickCountStart; + QWORD qwAddrBase; + QWORD MemMapIdx; + QWORD MemMapPrintCommitIdx; + QWORD MemMapPrintCommitPages; + DWORD MemMap[PAGE_STATISTICS_MEM_MAP_MAX_ENTRY]; + } i; +} PAGE_STATISTICS, *PPAGE_STATISTICS; + +/* +* Initialize the page statistics. This will also start displaying the page statistics +* on the screen asynchronously. PageStatClose must be called to stop this. +* -- ps = ptr to the PAGE_STATISTICS struct to initialize. +* -- qwAddrBase = the base address that the statistics will be based upon. +* -- qwAddrMax = the maximum address. +* -- szAction = the text shown as action. +* -- fKMD = is KMD mode. +* -- fPageMap = display read memory map when PageStatClose is called. +*/ +VOID PageStatInitialize(_Inout_ PPAGE_STATISTICS ps, _In_ QWORD qwAddrBase, _In_ QWORD qwAddrMax, _In_ LPSTR szAction, _In_ BOOL fKMD, _In_ BOOL fMemMap); + +/* +* Do one last update of the on-screen page statistics, display the read memory map if +* previously set in PageStatInitialize and stop the on-screen updates. +* -- ps = ptr to the PAGE_STATISTICS struct to stop using. +*/ +VOID PageStatClose(_Inout_ PPAGE_STATISTICS ps); + +/* +* Update the page statistics with the current address and with successfully and failed +* pages. Should not be called before PageStatInitialize and not after PageStatClose. +* This function must be used if the memory map should be shown; otherwise it's possible +* to alter the PPAGE_STATISTICS struct members directly. +* -- ps = pointer to page statistics struct (optional). +* -- qwAddr = new address (after completed operation). +* -- cPageSuccessAdd = number of successfully read pages. +* -- cPageFailAdd = number of pages that failed. +*/ +VOID PageStatUpdate(_Inout_opt_ PPAGE_STATISTICS ps, _In_ QWORD qwAddr, _In_ QWORD cPageSuccessAdd, _In_ QWORD cPageFailAdd); + +#define STATISTICS_ID_INITIALIZE 0x00 +#define STATISTICS_ID_VMMDLL_VfsList 0x01 +#define STATISTICS_ID_VMMDLL_VfsRead 0x02 +#define STATISTICS_ID_VMMDLL_VfsWrite 0x03 +#define STATISTICS_ID_VMMDLL_VfsInitializePlugins 0x04 +#define STATISTICS_ID_VMMDLL_MemReadEx 0x05 +#define STATISTICS_ID_VMMDLL_MemWrite 0x06 +#define STATISTICS_ID_VMMDLL_MemVirt2Phys 0x07 +#define STATISTICS_ID_VMMDLL_PidList 0x08 +#define STATISTICS_ID_VMMDLL_PidGetFromName 0x09 +#define STATISTICS_ID_VMMDLL_ProcessGetInformation 0x0a +#define STATISTICS_ID_VMMDLL_ProcessGetMemoryMap 0x0b +#define STATISTICS_ID_VMMDLL_ProcessGetMemoryMapEntry 0x0c +#define STATISTICS_ID_VMMDLL_ProcessGetModuleMap 0x0d +#define STATISTICS_ID_VMMDLL_ProcessGetModuleFromName 0x0e +#define STATISTICS_ID_VMMDLL_ProcessGetDirectories 0x0f +#define STATISTICS_ID_VMMDLL_ProcessGetSections 0x10 +#define STATISTICS_ID_VMMDLL_ProcessGetEAT 0x11 +#define STATISTICS_ID_VMMDLL_ProcessGetIAT 0x12 +#define STATISTICS_ID_PluginManager_List 0x13 +#define STATISTICS_ID_PluginManager_Read 0x14 +#define STATISTICS_ID_PluginManager_Write 0x15 +#define STATISTICS_ID_PluginManager_Notify 0x16 +#define STATISTICS_ID_DeviceReadScatterMEM 0x17 +#define STATISTICS_ID_DeviceWriteMEM 0x18 +#define STATISTICS_ID_MAX 0x18 +#define STATISTICS_ID_NOLOG 0xffffffff + +VOID Statistics_CallSetEnabled(_In_ BOOL fEnabled); +BOOL Statistics_CallGetEnabled(); +QWORD Statistics_CallStart(); +VOID Statistics_CallEnd(_In_ DWORD fId, QWORD tmCallStart); +VOID Statistics_CallToString(_In_opt_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb); + +#endif /* __STATISTICS_H__ */ diff --git a/vmm/util.c b/vmm/util.c new file mode 100644 index 0000000..145846b --- /dev/null +++ b/vmm/util.c @@ -0,0 +1,199 @@ +// util.c : implementation of various utility functions. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "util.h" + +QWORD Util_GetNumeric(_In_ LPSTR sz) +{ + if((strlen(sz) > 1) && (sz[0] == '0') && ((sz[1] == 'x') || (sz[1] == 'X'))) { + return strtoull(sz, NULL, 16); // Hex (starts with 0x) + } else { + return strtoull(sz, NULL, 10); // Not Hex -> try Decimal + } +} + +#define Util_2HexChar(x) (((((x) & 0xf) <= 9) ? '0' : ('a' - 10)) + ((x) & 0xf)) + +BOOL Util_FillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_opt_ LPSTR sz, _Inout_ PDWORD pcsz) +{ + DWORD i, j, o = 0, szMax, iMod; + // checks + if((cbInitialOffset > cb) || (cbInitialOffset > 0x1000) || (cbInitialOffset & 0xf)) { return FALSE; } + *pcsz = szMax = cb * 5 + 80; + if(cb > szMax) { return FALSE; } + if(!sz) { return TRUE; } + // fill buffer with bytes + for(i = cbInitialOffset; i < cb + ((cb % 16) ? (16 - cb % 16) : 0); i++) + { + // address + if(0 == i % 16) { + iMod = i % 0x10000; + sz[o++] = Util_2HexChar(iMod >> 12); + sz[o++] = Util_2HexChar(iMod >> 8); + sz[o++] = Util_2HexChar(iMod >> 4); + sz[o++] = Util_2HexChar(iMod); + sz[o++] = ' '; + sz[o++] = ' '; + sz[o++] = ' '; + sz[o++] = ' '; + } else if(0 == i % 8) { + sz[o++] = ' '; + } + // hex + if(i < cb) { + sz[o++] = Util_2HexChar(pb[i] >> 4); + sz[o++] = Util_2HexChar(pb[i]); + sz[o++] = ' '; + } else { + sz[o++] = ' '; + sz[o++] = ' '; + sz[o++] = ' '; + } + // ascii + if(15 == i % 16) { + sz[o++] = ' '; + sz[o++] = ' '; + for(j = i - 15; j <= i; j++) { + if(j >= cb) { + sz[o++] = ' '; + } else { + sz[o++] = (isprint(pb[j]) ? (CHAR)pb[j] : '.'); + } + } + sz[o++] = '\n'; + } + } + sz[o++] = 0; + return TRUE; +} + +VOID Util_PrintHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset) +{ + DWORD szMax; + LPSTR sz; + if(cb > 0x10000) { + vmmprintf("Large output. Only displaying first 65kB.\n"); + cb = 0x10000 - cbInitialOffset; + } + Util_FillHexAscii(pb, cb, cbInitialOffset, NULL, &szMax); + if(!(sz = LocalAlloc(0, szMax))) { return; } + Util_FillHexAscii(pb, cb, cbInitialOffset, sz, &szMax); + vmmprintf(sz); + LocalFree(sz); +} + +VOID Util_PathSplit2(_In_ LPSTR sz, _Inout_ CHAR _szBuf[MAX_PATH], _Out_ LPSTR *psz1, _Out_ LPSTR *psz2) +{ + DWORD i; + strcpy_s(_szBuf, MAX_PATH, sz); + *psz1 = _szBuf; + for(i = 0; i < MAX_PATH; i++) { + if('\0' == _szBuf[i]) { + *psz2 = _szBuf + i; + return; + } + if('\\' == _szBuf[i]) { + _szBuf[i] = '\0'; + *psz2 = _szBuf + i + 1; + return; + } + } +} + +VOID Util_PathSplit2_WCHAR(_In_ LPWSTR wsz, _Inout_ CHAR _szBuf[MAX_PATH], _Out_ LPSTR *psz1, _Out_ LPSTR *psz2) +{ + DWORD i; + for(i = 0; i < MAX_PATH; i++) { + _szBuf[i] = (CHAR)wsz[i]; + if(!_szBuf[i]) { break; } + } + _szBuf[i] = 0; + *psz1 = _szBuf; + for(i = 0; i < MAX_PATH; i++) { + if('\0' == _szBuf[i]) { + *psz2 = _szBuf + i; + return; + } + if('\\' == _szBuf[i]) { + _szBuf[i] = '\0'; + *psz2 = _szBuf + i + 1; + return; + } + } +} + +VOID Util_GetPathDll(_Out_ CHAR szPath[MAX_PATH], _In_opt_ HMODULE hModule) +{ + SIZE_T i; + GetModuleFileNameA(hModule, szPath, MAX_PATH - 4); + for(i = strlen(szPath) - 1; i > 0; i--) { + if(szPath[i] == '/' || szPath[i] == '\\') { + szPath[i + 1] = '\0'; + return; + } + } +} + +#define UTIL_NTSTATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define UTIL_NTSTATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) + +NTSTATUS Util_VfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ QWORD cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + if(cbOffset > cbFile) { return UTIL_NTSTATUS_END_OF_FILE; } + *pcbRead = (DWORD)min(cb, cbFile - cbOffset); + memcpy(pb, pbFile + cbOffset, *pcbRead); + return *pcbRead ? UTIL_NTSTATUS_SUCCESS : UTIL_NTSTATUS_END_OF_FILE; +} + +NTSTATUS Util_VfsReadFile_FromQWORD(_In_ QWORD qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset, _In_ BOOL fPrefix) +{ + BYTE pbBuffer[32]; + DWORD cbBuffer; + cbBuffer = snprintf(pbBuffer, 32, (fPrefix ? "0x%016llx" : "%016llx"), qwValue); + return Util_VfsReadFile_FromPBYTE(pbBuffer, cbBuffer, pb, cb, pcbRead, cbOffset); +} + +NTSTATUS Util_VfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset, _In_ BOOL fPrefix) +{ + BYTE pbBuffer[32]; + DWORD cbBuffer; + cbBuffer = snprintf(pbBuffer, 32, (fPrefix ? "0x%08x" : "%08x"), dwValue); + return Util_VfsReadFile_FromPBYTE(pbBuffer, cbBuffer, pb, cb, pcbRead, cbOffset); +} + +NTSTATUS Util_VfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + BYTE pbBuffer[1]; + pbBuffer[0] = fValue ? '1' : '0'; + return Util_VfsReadFile_FromPBYTE(pbBuffer, 1, pb, cb, pcbRead, cbOffset); +} + +NTSTATUS Util_VfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset) +{ + CHAR ch; + if((cb > 0) && (cbOffset == 0)) { + ch = *(PCHAR)pb; + *pfTarget = (ch == 0 || ch == '0') ? FALSE : TRUE; + } + *pcbWrite = cb; + return UTIL_NTSTATUS_SUCCESS; +} + +NTSTATUS Util_VfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset, _In_ DWORD dwMinAllow) +{ + DWORD dw; + BYTE pbBuffer[9]; + if(cbOffset < 8) { + snprintf(pbBuffer, 9, "%08x", *pdwTarget); + cb = (DWORD)min(8 - cbOffset, cb); + memcpy(pbBuffer + cbOffset, pb, cb); + pbBuffer[8] = 0; + dw = strtoul(pbBuffer, NULL, 16); + dw = max(dw, dwMinAllow); + *pdwTarget = dw; + } + *pcbWrite = cb; + return UTIL_NTSTATUS_SUCCESS; +} diff --git a/vmm/util.h b/vmm/util.h new file mode 100644 index 0000000..213c4f9 --- /dev/null +++ b/vmm/util.h @@ -0,0 +1,74 @@ +// util.h : definitions of various utility functions. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __UTIL_H__ +#define __UTIL_H__ +#include "vmm.h" + +/* +* Parse a string returning the QWORD representing the string. The string may +* consist of a decimal or hexadecimal integer string. Hexadecimals must begin +* with 0x. +* -- sz +* -- return +*/ +QWORD Util_GetNumeric(_In_ LPSTR sz); + +/* +* Print a maximum of 8192 bytes of binary data as hexascii on the screen. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +*/ +VOID Util_PrintHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset); + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL Util_FillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_opt_ LPSTR sz, _Inout_ PDWORD pcsz); + +/* +* Split a "path" string into two at the first '\' character. If no 2nd string +* is not found then it's returned as null character '\0' (i.e. not as NULL). +* -- sz = the original string to split (of maximum MAX_PATH length) +* -- _szBuf = MAX_PATH sized buffer that will be overwritten and used throughout the lifetime of psz1/psz2 outputs. +* -- psz1 +* -- psz2 +*/ +VOID Util_PathSplit2(_In_ LPSTR sz, _Inout_ CHAR _szBuf[MAX_PATH], _Out_ LPSTR *psz1, _Out_ LPSTR *psz2); + +/* +* Split a "path" string into two at the first '\' character. If no 2nd string +* is not found then it's returned as null character '\0' (i.e. not as NULL). +* -- wsz = the original string to split (of maximum MAX_PATH length) +* -- _szBuf = MAX_PATH sized buffer that will be overwritten and used throughout the lifetime of psz1/psz2 outputs. +* -- psz1 +* -- psz2 +*/ +VOID Util_PathSplit2_WCHAR(_In_ LPWSTR wsz, _Inout_ CHAR _szBuf[MAX_PATH], _Out_ LPSTR *psz1, _Out_ LPSTR *psz2); + +/* +* Return the path of the specified hModule (DLL) - ending with a backslash, or current Executable. +* -- szPath +* -- hModule = Optional, HMODULE handle for path to DLL, NULL for path to EXE. +*/ +VOID Util_GetPathDll(_Out_ CHAR szPath[MAX_PATH], _In_opt_ HMODULE hModule); + +/* +* Utility functions for read/write towards different underlying data representations. +*/ +NTSTATUS Util_VfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ QWORD cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset); +NTSTATUS Util_VfsReadFile_FromQWORD(_In_ QWORD qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset, _In_ BOOL fPrefix); +NTSTATUS Util_VfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset, _In_ BOOL fPrefix); +NTSTATUS Util_VfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset); +NTSTATUS Util_VfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset); +NTSTATUS Util_VfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset, _In_ DWORD dwMinAllow); + +#endif /* __UTIL_H__ */ diff --git a/vmm/vmm.c b/vmm/vmm.c new file mode 100644 index 0000000..b4588c0 --- /dev/null +++ b/vmm/vmm.c @@ -0,0 +1,1089 @@ +// vmm.c : implementation of functions related to virtual memory management support. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include "vmm.h" +#include "vmmproc.h" +#include "pluginmanager.h" +#include "device.h" +#include "util.h" + +// ---------------------------------------------------------------------------- +// MASTER LOCK FUNCTIONALITY BELOW: +// ---------------------------------------------------------------------------- + +VOID VmmLockAcquire() +{ + EnterCriticalSection(&ctxVmm->MasterLock); +} + +VOID VmmLockRelease() +{ + LeaveCriticalSection(&ctxVmm->MasterLock); +} + +// ---------------------------------------------------------------------------- +// INTERNAL VMMU FUNCTIONALITY: PAGE TABLES. +// ---------------------------------------------------------------------------- + +VOID VmmCacheClose(_In_ PVMM_CACHE_TABLE t) +{ + if(!t) { return; } + LocalFree(t->S); + LocalFree(t); +} + +PVMM_CACHE_TABLE VmmCacheInitialize(_In_ QWORD cEntries) +{ + QWORD i; + PVMM_CACHE_TABLE t; + t = LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_CACHE_TABLE)); + if(!t) { return NULL; } + t->S = LocalAlloc(LMEM_ZEROINIT, cEntries * sizeof(VMM_CACHE_ENTRY)); + if(!t->S) { + LocalFree(t); + return NULL; + } + for(i = 0; i < cEntries; i++) { + t->S[i].qwMAGIC = VMM_CACHE_ENTRY_MAGIC; + t->S[i].h.cbMax = 0x1000; + t->S[i].h.pb = t->S[i].pb; + if(i > 0) { + t->S[i].AgeBLink = &t->S[i - 1]; + } + if(i < cEntries - 1) { + t->S[i].AgeFLink = &t->S[i + 1]; + } + } + t->AgeFLink = &t->S[0]; + t->AgeBLink = &t->S[cEntries - 1]; + return t; +} + +PMEM_IO_SCATTER_HEADER VmmCacheGet(_In_ PVMM_CACHE_TABLE t, _In_ QWORD qwA) +{ + PVMM_CACHE_ENTRY e; + WORD h; + h = (qwA >> 12) % VMM_CACHE_TABLESIZE; + e = t->M[h]; + while(e) { + if(e->h.qwA == qwA) { + if(e->AgeBLink) { + // disconnect from age list + if(e->AgeFLink) { + e->AgeFLink->AgeBLink = e->AgeBLink; + } else { + t->AgeBLink = e->AgeBLink; + } + e->AgeBLink->AgeFLink = e->AgeFLink; + // put entry at front in age list + e->AgeFLink = t->AgeFLink; + e->AgeFLink->AgeBLink = e; + e->AgeBLink = NULL; + t->AgeFLink = e; + } + return &e->h; + } + e = e->FLink; + } + return NULL; +} + +VOID VmmCachePut(_Inout_ PVMM_CACHE_TABLE t, _In_ PVMM_CACHE_ENTRY e) +{ + WORD h; + if(e->qwMAGIC != VMM_CACHE_ENTRY_MAGIC) { + vmmprintf("VMM: WARN: vmm.c!VmmCachePut: BAD ITEM PUT INTO CACHE - SHOULD NOT HAPPEN!\n"); + } + if(e->h.cb == 0x1000) { // valid + // calculate bucket hash and insert + h = (e->h.qwA >> 12) % VMM_CACHE_TABLESIZE; + if(t->M[h]) { + // previous entry exists - insert new at front of list + t->M[h]->BLink = e; + e->FLink = t->M[h]; + } + t->M[h] = e; + // put entry at front in age list + e->AgeFLink = t->AgeFLink; + e->AgeFLink->AgeBLink = e; + e->AgeBLink = NULL; + t->AgeFLink = e; + } else { + // invalid, put entry at last in age list + e->AgeBLink = t->AgeBLink; + e->AgeBLink->AgeFLink = e; + e->AgeFLink = NULL; + t->AgeBLink = e; + } +} + +PVMM_CACHE_ENTRY VmmCacheReserve(_Inout_ PVMM_CACHE_TABLE t) +{ + PVMM_CACHE_ENTRY e; + WORD h; + // retrieve and disconnect entry from age list + e = t->AgeBLink; + e->AgeBLink->AgeFLink = NULL; + t->AgeBLink = e->AgeBLink; + // disconnect entry from hash table. since most aged item is retrieved this + // should always be last in any potential hash table bucket list. + if(e->BLink) { + e->BLink->FLink = NULL; + } + h = (e->h.qwA >> 12) % VMM_CACHE_TABLESIZE; + if(t->M[h] == e) { + t->M[h] = NULL; + } + // null list links and return item + e->FLink = NULL; + e->FLink = NULL; + e->AgeFLink = NULL; + e->AgeBLink = NULL; + e->tm = 0; + e->h.cb = 0; + e->h.qwA = 0; + return e; +} + +/* +* Invalidate a cache entry (if exists) +*/ +VOID VmmCacheInvalidate_2(_Inout_ PVMM_CACHE_TABLE t, _In_ QWORD pa) +{ + WORD h; + PVMM_CACHE_ENTRY e; + h = (pa >> 12) % VMM_CACHE_TABLESIZE; + // invalidate all items in h bucket while letting them remain in age list + e = t->M[h]; + t->M[h] = NULL; + while(e) { + if(e->BLink) { + e->BLink->FLink = NULL; + e->BLink = NULL; + } + e = e->FLink; + } +} + +VOID VmmCacheInvalidate(_In_ QWORD pa) +{ + VmmCacheInvalidate_2(ctxVmm->ptTLB, pa); + VmmCacheInvalidate_2(ctxVmm->ptPHYS, pa); +} + + +VOID VmmCacheClear(_In_ BOOL fTLB, _In_ BOOL fPHYS) +{ + if(fTLB && ctxVmm->ptTLB) { + VmmCacheClose(ctxVmm->ptTLB); + ctxVmm->ptTLB = VmmCacheInitialize(VMM_CACHE_TLB_ENTRIES); + } + if(fPHYS && ctxVmm->ptPHYS) { + VmmCacheClose(ctxVmm->ptPHYS); + ctxVmm->ptPHYS = VmmCacheInitialize(VMM_CACHE_PHYS_ENTRIES); + } +} + +PMEM_IO_SCATTER_HEADER VmmCacheGet_FromDeviceOnMiss(_In_ PVMM_CACHE_TABLE t, _In_ QWORD qwA) +{ + PVMM_CACHE_ENTRY pe; + PMEM_IO_SCATTER_HEADER pMEM; + pMEM = VmmCacheGet(t, qwA); + if(pMEM) { return pMEM; } + pe = VmmCacheReserve(t); + pMEM = &pe->h; + pMEM->qwA = qwA; + DeviceReadScatterMEM(&pMEM, 1, NULL); + VmmCachePut(t, pe); + return (pMEM->cb == 0x1000) ? pMEM : NULL; +} + +/* +* Tries to verify that a loaded page table is correct. If just a bit strange +* bytes/ptes supplied in pb will be altered to look better. +*/ +BOOL VmmTlbPageTableVerify(_Inout_ PBYTE pb, _In_ QWORD pa, _In_ BOOL fSelfRefReq) +{ + DWORD i; + QWORD *ptes, c = 0, pte; + BOOL fSelfRef = FALSE; + if(!pb) { return FALSE; } + ptes = (PQWORD)pb; + for(i = 0; i < 512; i++) { + pte = *(ptes + i); + if((pte & 0x01) && ((0x000fffffffffffff & pte) > ctxMain->cfg.paAddrMax)) { + // A bad PTE, or memory allocated above the physical address max + // limit. This may be just trash in the page table in which case + // we clear this faulty entry. If too may bad PTEs are found this + // is most probably not a page table - zero it out but let it + // remain in cache to prevent performance degrading reloads... + if(ctxVmm) { + vmmprintfvv("VMM: vmm.c!VmmTlbPageTableVerify: BAD PTE %016llx at PA: %016llx i: %i\n", *(ptes + i), pa, i); + } + *(ptes + i) = (QWORD)0; + c++; + if(c > 16) { break; } + } + if(pa == (0x0000fffffffff000 & pte)) { + fSelfRef = TRUE; + } + } + if((c > 16) || (fSelfRefReq && !fSelfRef)) { + if(ctxVmm) { + vmmprintfvv("VMM: vmm.c!VmmTlbPageTableVerify: BAD PT PAGE at PA: %016llx\n", pa); + } + ZeroMemory(pb, 4096); + return FALSE; + } + return TRUE; +} + +PBYTE VmmTlbGetPageTable(_In_ QWORD qwPA, _In_ BOOL fCacheOnly) +{ + BOOL result; + PMEM_IO_SCATTER_HEADER pDMA; + pDMA = VmmCacheGet(ctxVmm->ptTLB, qwPA); + if(pDMA) { + ctxVmm->stat.cTlbCacheHit++; + return pDMA->pb; + } + if(fCacheOnly) { return NULL; } + pDMA = VmmCacheGet_FromDeviceOnMiss(ctxVmm->ptTLB, qwPA); + if(!pDMA) { + ctxVmm->stat.cTlbReadFail++; + return NULL; + } + ctxVmm->stat.cTlbReadSuccess++; + result = VmmTlbPageTableVerify(pDMA->pb, pDMA->qwA, FALSE); + if(!result) { return NULL; } + return pDMA->pb; +} + +PVMM_PROCESS VmmProcessGetEx(_In_ PVMM_PROCESS_TABLE pt, _In_ DWORD dwPID) +{ + DWORD i, iStart; + i = iStart = dwPID % VMM_PROCESSTABLE_ENTRIES_MAX; + while(TRUE) { + if(!pt->M[i]) { return NULL; } + if(pt->M[i]->dwPID == dwPID) { + return pt->M[i]; + } + if(++i == VMM_PROCESSTABLE_ENTRIES_MAX) { i = 0; } + if(i == iStart) { return NULL; } + } +} + +PVMM_PROCESS VmmProcessGet(_In_ DWORD dwPID) +{ + return VmmProcessGetEx(ctxVmm->ptPROC, dwPID); +} + +PVMM_PROCESS VmmProcessCreateEntry(_In_ DWORD dwPID, _In_ DWORD dwState, _In_ QWORD paPML4, _In_ QWORD paPML4_UserOpt, _In_ CHAR szName[16], _In_ BOOL fUserOnly, _In_ BOOL fSpiderPageTableDone) +{ + QWORD i, iStart, cEmpty = 0, cValid = 0; + PVMM_PROCESS pNewProcess; + PBYTE pbPML4; + // 1: Sanity check PML4 + pbPML4 = VmmTlbGetPageTable(paPML4, FALSE); + if(!pbPML4) { return NULL; } + if(!VmmTlbPageTableVerify(pbPML4, paPML4, (ctxVmm->fTargetSystem & VMM_TARGET_WINDOWS_X64))) { return NULL; } + // 2: Allocate new PID table (if not already existing) + if(ctxVmm->ptPROC->ptNew == NULL) { + if(!(ctxVmm->ptPROC->ptNew = LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_PROCESS_TABLE)))) { return NULL; } + } + // 3: Prepare existing item, or create new item, for new PID + pNewProcess = VmmProcessGetEx(ctxVmm->ptPROC, dwPID); + if(!pNewProcess) { + if(!(pNewProcess = LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_PROCESS)))) { return NULL; } + } + memcpy(pNewProcess->szName, szName, 16); + pNewProcess->dwPID = dwPID; + pNewProcess->dwState = dwState; + pNewProcess->paPML4 = paPML4; + pNewProcess->paPML4_UserOpt = paPML4_UserOpt; + pNewProcess->fUserOnly = fUserOnly; + pNewProcess->fSpiderPageTableDone = pNewProcess->fSpiderPageTableDone || fSpiderPageTableDone; + pNewProcess->_i_fMigrated = TRUE; + // 4: Install new PID + i = iStart = dwPID % VMM_PROCESSTABLE_ENTRIES_MAX; + while(TRUE) { + if(!ctxVmm->ptPROC->ptNew->M[i]) { + ctxVmm->ptPROC->ptNew->M[i] = pNewProcess; + ctxVmm->ptPROC->ptNew->iFLinkM[i] = ctxVmm->ptPROC->ptNew->iFLink; + ctxVmm->ptPROC->ptNew->iFLink = (WORD)i; + ctxVmm->ptPROC->ptNew->c++; + return pNewProcess; + } + if(++i == VMM_PROCESSTABLE_ENTRIES_MAX) { i = 0; } + if(i == iStart) { return NULL; } + } +} + +VOID VmmProcessCloseTable(_In_ PVMM_PROCESS_TABLE pt, _In_ BOOL fForceFreeAll) +{ + PVMM_PROCESS pProcess; + WORD i, iProcess; + if(!pt) { return; } + VmmProcessCloseTable(pt->ptNew, fForceFreeAll); + iProcess = pt->iFLink; + pProcess = pt->M[iProcess]; + while(pProcess) { + if(fForceFreeAll || !pProcess->_i_fMigrated) { + LocalFree(pProcess->pMemMap); + LocalFree(pProcess->pModuleMap); + LocalFree(pProcess->pbMemMapDisplayCache); + for(i = 0; i < VMM_PROCESS_OS_ALLOC_PTR_MAX; i++) { + LocalFree(pProcess->os.unk.pvReserved[i]); + } + LocalFree(pProcess); + } + iProcess = pt->iFLinkM[iProcess]; + pProcess = pt->M[iProcess]; + if(!pProcess || iProcess == pt->iFLink) { break; } + } + LocalFree(pt); +} + +BOOL VmmProcessCreateTable() +{ + if(ctxVmm->ptPROC) { + VmmProcessCloseTable(ctxVmm->ptPROC, TRUE); + } + ctxVmm->ptPROC = (PVMM_PROCESS_TABLE)LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_PROCESS_TABLE)); + return (ctxVmm->ptPROC != NULL); +} + +VOID VmmProcessCreateFinish() +{ + WORD iProcess; + PVMM_PROCESS pProcess; + PVMM_PROCESS_TABLE pt, ptOld; + ptOld = ctxVmm->ptPROC; + pt = ctxVmm->ptPROC = ptOld->ptNew; + if(!pt) { return; } + ptOld->ptNew = NULL; + // close old table and free memory + VmmProcessCloseTable(ptOld, FALSE); + // set migrated to false for all entries in new table + iProcess = pt->iFLink; + pProcess = pt->M[iProcess]; + while(pProcess) { + pProcess->_i_fMigrated = FALSE; + iProcess = pt->iFLinkM[iProcess]; + pProcess = pt->M[iProcess]; + if(!pProcess || (iProcess == pt->iFLink)) { break; } + } +} + +VOID VmmProcessListPIDs(_Out_ PDWORD pPIDs, _Inout_ PSIZE_T pcPIDs) +{ + DWORD i = 0; + WORD iProcess; + PVMM_PROCESS pProcess; + PVMM_PROCESS_TABLE pt = ctxVmm->ptPROC; + if(!pPIDs) { + *pcPIDs = pt->c; + return; + } + if(*pcPIDs < pt->c) { + *pcPIDs = 0; + return; + } + // copy all PIDs + iProcess = pt->iFLink; + pProcess = pt->M[iProcess]; + while(pProcess) { + *(pPIDs + i) = pProcess->dwPID; + i++; + iProcess = pt->iFLinkM[iProcess]; + pProcess = pt->M[iProcess]; + if(!pProcess || (iProcess == pt->iFLink)) { break; } + } + *pcPIDs = i; +} + +#define VMM_TLB_SIZE_STAGEBUF 0x200 + +typedef struct tdVMM_TLB_SPIDER_STAGE_INTERNAL { + QWORD c; + PMEM_IO_SCATTER_HEADER ppDMAs[VMM_TLB_SIZE_STAGEBUF]; + PVMM_CACHE_ENTRY ppEntrys[VMM_TLB_SIZE_STAGEBUF]; +} VMM_TLB_SPIDER_STAGE_INTERNAL, *PVMM_TLB_SPIDER_STAGE_INTERNAL; + +VOID VmmTlbSpider_ReadToCache(PVMM_TLB_SPIDER_STAGE_INTERNAL pTlbSpiderStage) +{ + QWORD i; + DeviceReadScatterMEM(pTlbSpiderStage->ppDMAs, (DWORD)pTlbSpiderStage->c, NULL); + for(i = 0; i < pTlbSpiderStage->c; i++) { + VmmTlbPageTableVerify(pTlbSpiderStage->ppEntrys[i]->h.pb, pTlbSpiderStage->ppEntrys[i]->h.qwA, FALSE); + VmmCachePut(ctxVmm->ptTLB, pTlbSpiderStage->ppEntrys[i]); + } + pTlbSpiderStage->c = 0; +} + +BOOL VmmTlbSpider_Stage(_In_ QWORD qwPA, _In_ QWORD qwPML, _In_ BOOL fUserOnly, PVMM_TLB_SPIDER_STAGE_INTERNAL pTlbSpiderStage) +{ + BOOL fSpiderComplete = TRUE; + PMEM_IO_SCATTER_HEADER pt; + QWORD i, pe; + // 1: retrieve from cache, add to staging if not found + pt = VmmCacheGet(ctxVmm->ptTLB, qwPA); + if(!pt) { + pTlbSpiderStage->ppEntrys[pTlbSpiderStage->c] = VmmCacheReserve(ctxVmm->ptTLB); + pTlbSpiderStage->ppDMAs[pTlbSpiderStage->c] = &pTlbSpiderStage->ppEntrys[pTlbSpiderStage->c]->h; + pTlbSpiderStage->ppDMAs[pTlbSpiderStage->c]->qwA = qwPA; + pTlbSpiderStage->c++; + if(pTlbSpiderStage->c == VMM_TLB_SIZE_STAGEBUF) { + VmmTlbSpider_ReadToCache( pTlbSpiderStage); + } + return FALSE; + } + // 2: walk trough all entries for PML4, PDPT, PD + if(qwPML == 1) { return TRUE; } + for(i = 0; i < 0x1000; i += 8) { + pe = *(PQWORD)(pt->pb + i); + if(!(pe & 0x01)) { continue; } // not valid + if(pe & 0x80) { continue; } // not valid ptr to (PDPT || PD || PT) + if(fUserOnly && !(pe & 0x04)) { continue; } // supervisor page when fUserOnly -> not valid + fSpiderComplete = VmmTlbSpider_Stage(pe & 0x0000fffffffff000, qwPML - 1, fUserOnly, pTlbSpiderStage) && fSpiderComplete; + } + return fSpiderComplete; +} + +/* +* Iterate over PML4, PTPT, PD (3 times in total) to first stage uncached pages +* and then commit them to the cache. +*/ +VOID VmmTlbSpider(_In_ QWORD qwPML4, _In_ BOOL fUserOnly) +{ + BOOL result; + QWORD i = 0; + PVMM_TLB_SPIDER_STAGE_INTERNAL pTlbSpiderStage; + if(!(pTlbSpiderStage = (PVMM_TLB_SPIDER_STAGE_INTERNAL)LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_TLB_SPIDER_STAGE_INTERNAL)))) { return; } + while(TRUE) { + i++; + result = VmmTlbSpider_Stage(qwPML4, 4, fUserOnly, pTlbSpiderStage); + if(pTlbSpiderStage->c) { + VmmTlbSpider_ReadToCache(pTlbSpiderStage); + } + if(result || (i == 3)) { + LocalFree(pTlbSpiderStage); + return; + } + } +} + +const QWORD VMM_PAGETABLEMAP_PML_REGION_SIZE[5] = { 0, 12, 21, 30, 39 }; + +VOID VmmMapInitialize_Index(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVABase, _In_ QWORD qwPML, _In_ QWORD PTEs[512], _In_ BOOL fSupervisorPML, _In_ QWORD paMax) +{ + PBYTE pbNextPageTable; + QWORD i, pte, qwVA, qwNextVA, qwNextPA = 0; + BOOL fUserOnly, fNextSupervisorPML; + QWORD cMemMap = pProcess->cMemMap; + PVMM_MEMMAP_ENTRY pMemMap = pProcess->pMemMap; + PVMM_MEMMAP_ENTRY pMemMapEntry = pMemMap + cMemMap - 1; + fUserOnly = pProcess->fUserOnly; + for(i = 0; i < 512; i++) { + pte = PTEs[i]; + if(!(pte & 0x01)) { continue; } + qwNextPA = pte & 0x0000fffffffff000; + if(qwNextPA > paMax) { continue; } + if(fSupervisorPML) { pte = pte & 0xfffffffffffffffb; } + if(fUserOnly && !(pte & 0x04)) { continue; } + qwVA = qwVABase + (i << VMM_PAGETABLEMAP_PML_REGION_SIZE[qwPML]); + // maps page + if((qwPML == 1) || (pte & 0x80) /* PS */) { + if(qwPML == 4) { continue; } // not supported - PML4 cannot map page directly + if((cMemMap == 0) || + (pMemMapEntry->fPage != (pte & VMM_MEMMAP_FLAG_PAGE_MASK)) || + (qwVA != pMemMapEntry->AddrBase + (pMemMapEntry->cPages << 12))) + { + if(cMemMap + 1 >= VMM_MEMMAP_ENTRIES_MAX) { return; } + pMemMapEntry = pProcess->pMemMap + cMemMap; + pMemMapEntry->AddrBase = qwVA; + pMemMapEntry->fPage = pte & VMM_MEMMAP_FLAG_PAGE_MASK; + pMemMapEntry->cPages = 1ULL << (VMM_PAGETABLEMAP_PML_REGION_SIZE[qwPML] - 12); + pProcess->cMemMap++; + cMemMap++; + continue; + } + pMemMapEntry->cPages += 1ULL << (VMM_PAGETABLEMAP_PML_REGION_SIZE[qwPML] - 12); + continue; + } + // maps page table (PDPT, PD, PT) + qwNextVA = qwVA; + pbNextPageTable = VmmTlbGetPageTable(qwNextPA, FALSE); + if(!pbNextPageTable) { continue; } + fNextSupervisorPML = !(pte & 0x04); + VmmMapInitialize_Index( pProcess, qwNextVA, qwPML - 1, (PQWORD)pbNextPageTable, fNextSupervisorPML, paMax); + cMemMap = pProcess->cMemMap; + pMemMapEntry = pProcess->pMemMap + cMemMap - 1; + } +} + +VOID VmmMapInitialize(_In_ PVMM_PROCESS pProcess) +{ + QWORD i, cMemMap; + PBYTE pbPML4; + pProcess->cbMemMapDisplayCache = 0; + LocalFree(pProcess->pbMemMapDisplayCache); + pProcess->pbMemMapDisplayCache = NULL; + LocalFree(pProcess->pMemMap); + pProcess->cMemMap = 0; + pProcess->pMemMap = (PVMM_MEMMAP_ENTRY)LocalAlloc(LMEM_ZEROINIT, VMM_MEMMAP_ENTRIES_MAX * sizeof(VMM_MEMMAP_ENTRY)); + if(!pProcess->pMemMap) { return; } + pbPML4 = VmmTlbGetPageTable(pProcess->paPML4, FALSE); + if(!pbPML4) { return; } + VmmMapInitialize_Index(pProcess, 0, 4, (PQWORD)pbPML4, FALSE, ctxMain->cfg.paAddrMax); + cMemMap = pProcess->cMemMap; + for(i = 0; i < cMemMap; i++) { // fixup sign extension for kernel addresses + if(pProcess->pMemMap[i].AddrBase & 0x0000800000000000) { + pProcess->pMemMap[i].AddrBase |= 0xffff000000000000; + } + } +} + +/* +* Map a tag into the sorted memory map in O(log2) operations. Supply only one +* of szTag or wszTag. +* -- pProcess +* -- vaBase +* -- vaLimit = limit == vaBase + size (== top address in range +1) +* -- szTag +* -- wszTag +*/ +VOID VmmMapTag(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaBase, _In_ QWORD vaLimit, _In_opt_ LPSTR szTag, _In_opt_ LPWSTR wszTag, _In_opt_ BOOL fWoW64) +{ + PVMM_MEMMAP_ENTRY pMap; + QWORD i, lvl, cMap; + pMap = pProcess->pMemMap; + cMap = pProcess->cMemMap; + if(!pMap || !cMap) { return; } + // 1: locate base + lvl = 1; + i = cMap >> lvl; + while(TRUE) { + lvl++; + if((cMap >> lvl) == 0) { + break; + } + if(pMap[i].AddrBase > vaBase) { + i -= (cMap >> lvl); + } else { + i += (cMap >> lvl); + } + } + // 2: scan back if needed + while(i && (pMap[i].AddrBase > vaBase)) { + i--; + } + // 3: fill in tag + while((i < cMap) && (pMap[i].AddrBase + (pMap[i].cPages << 12) <= vaLimit)) { + if(pMap[i].AddrBase >= vaBase) { + pMap[i].fWoW64 = fWoW64; + if(wszTag) { + snprintf(pMap[i].szTag, 31, "%S", wszTag); + } + if(szTag) { + snprintf(pMap[i].szTag, 31, "%s", szTag); + } + } + i++; + } +} + +VOID VmmMapDisplayBufferGenerate(_In_ PVMM_PROCESS pProcess) +{ + DWORD i, o = 0; + PBYTE pbBuffer; + if(!pProcess->cMemMap || !pProcess->pMemMap) { return; } + pProcess->cbMemMapDisplayCache = 0; + LocalFree(pProcess->pbMemMapDisplayCache); + pProcess->pbMemMapDisplayCache = NULL; + pbBuffer = LocalAlloc(LMEM_ZEROINIT, 89 * pProcess->cMemMap); + if(!pbBuffer) { return; } + for(i = 0; i < pProcess->cMemMap; i++) { + o += snprintf( + pbBuffer + o, + 89, + "%04x %8x %016llx-%016llx %sr%s%s%s%s\n", + i, + (DWORD)pProcess->pMemMap[i].cPages, + pProcess->pMemMap[i].AddrBase, + pProcess->pMemMap[i].AddrBase + (pProcess->pMemMap[i].cPages << 12) - 1, + pProcess->pMemMap[i].fPage & VMM_MEMMAP_FLAG_PAGE_NS ? "-" : "s", + pProcess->pMemMap[i].fPage & VMM_MEMMAP_FLAG_PAGE_W ? "w" : "-", + pProcess->pMemMap[i].fPage & VMM_MEMMAP_FLAG_PAGE_NX ? "-" : "x", + pProcess->pMemMap[i].szTag[0] ? (pProcess->pMemMap[i].fWoW64 ? " 32 " : " ") : "", + pProcess->pMemMap[i].szTag + ); + } + pProcess->pbMemMapDisplayCache = LocalAlloc(0, o); + if(!pProcess->pbMemMapDisplayCache) { goto fail; } + memcpy(pProcess->pbMemMapDisplayCache, pbBuffer, o); + pProcess->cbMemMapDisplayCache = o; +fail: + LocalFree(pbBuffer); +} + +PVMM_MEMMAP_ENTRY VmmMapGetEntry(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVA) +{ + QWORD i, ce; + PVMM_MEMMAP_ENTRY pe; + if(!pProcess->pMemMap) { return NULL; } + ce = pProcess->cMemMap; + for(i = 0; i < ce; i++) { + pe = pProcess->pMemMap + i; + if((pe->AddrBase >= qwVA) && (qwVA <= pe->AddrBase + (pe->cPages << 12))) { + return pe; + } + } + return NULL; +} + +_Success_(return) +BOOL VmmVirt2PhysEx(_In_ BOOL fUserOnly, _In_ QWORD va, _In_ QWORD iPML, _In_ QWORD PTEs[512], _Out_ PQWORD ppa) +{ + QWORD pte, i, qwMask; + PBYTE pbNextPageTable; + i = 0x1ff & (va >> VMM_PAGETABLEMAP_PML_REGION_SIZE[iPML]); + pte = PTEs[i]; + if(!(pte & 0x01)) { return FALSE; } // NOT VALID + if(fUserOnly && !(pte & 0x04)) { return FALSE; } // SUPERVISOR PAGE & USER MODE REQ + if(pte & 0x000f000000000000) { return FALSE; } // RESERVED + if((iPML == 1) || (pte & 0x80) /* PS */) { + if(iPML == 4) { return FALSE; } // NO SUPPORT IN PML4 + qwMask = 0xffffffffffffffff << VMM_PAGETABLEMAP_PML_REGION_SIZE[iPML]; + *ppa = pte & 0x0000fffffffff000 & qwMask; // MASK AWAY BITS FOR 4kB/2MB/1GB PAGES + qwMask = qwMask ^ 0xffffffffffffffff; + *ppa = *ppa | (qwMask & va); // FILL LOWER ADDRESS BITS + return TRUE; + } + pbNextPageTable = VmmTlbGetPageTable(pte & 0x0000fffffffff000, FALSE); + if(!pbNextPageTable) { return FALSE; } + return VmmVirt2PhysEx(fUserOnly, va, iPML - 1, (PQWORD)pbNextPageTable, ppa); +} + +_Success_(return) +BOOL VmmVirt2Phys(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Out_ PQWORD pqwPA) +{ + PBYTE pbPML4 = VmmTlbGetPageTable(pProcess->paPML4, FALSE); + if(!pbPML4) { return FALSE; } + *pqwPA = 0; + return VmmVirt2PhysEx(pProcess->fUserOnly, qwVA, 4, (PQWORD)pbPML4, pqwPA); +} + +VOID VmmVirt2PhysGetInformation_DoWork(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_VIRT2PHYS_INFORMATION pVirt2PhysInfo, _In_ QWORD qwPML, _In_ QWORD PTEs[512]) +{ + QWORD pte, i, qwMask; + PBYTE pbNextPageTable; + i = 0x1ff & (pVirt2PhysInfo->va >> VMM_PAGETABLEMAP_PML_REGION_SIZE[qwPML]); + pte = PTEs[i]; + pVirt2PhysInfo->iPTEs[qwPML] = (WORD)i; + pVirt2PhysInfo->PTEs[qwPML] = pte; + if(!(pte & 0x01)) { return; } // NOT VALID + if(pProcess->fUserOnly && !(pte & 0x04)) { return; } // SUPERVISOR PAGE & USER MODE REQ + if(pte & 0x000f000000000000) { return; } // RESERVED + if((qwPML == 1) || (pte & 0x80) /* PS */) { + if(qwPML == 4) { return; } // NO SUPPORT IN PML4 + qwMask = 0xffffffffffffffff << VMM_PAGETABLEMAP_PML_REGION_SIZE[qwPML]; + pVirt2PhysInfo->pas[0] = pte & 0x0000fffffffff000 & qwMask; // MASK AWAY BITS FOR 4kB/2MB/1GB PAGES + qwMask = qwMask ^ 0xffffffffffffffff; + pVirt2PhysInfo->pas[0] = pVirt2PhysInfo->pas[0] | (qwMask & pVirt2PhysInfo->va); // FILL LOWER ADDRESS BITS + return; + } + if(!(pbNextPageTable = VmmTlbGetPageTable(pte & 0x0000fffffffff000, FALSE))) { return; } + pVirt2PhysInfo->pas[qwPML - 1] = pte & 0x0000fffffffff000; + VmmVirt2PhysGetInformation_DoWork(pProcess, pVirt2PhysInfo, qwPML - 1, (PQWORD)pbNextPageTable); +} + +VOID VmmVirt2PhysGetInformation(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_VIRT2PHYS_INFORMATION pVirt2PhysInfo) +{ + QWORD va; + PBYTE pbPML4; + va = pVirt2PhysInfo->va; + ZeroMemory(pVirt2PhysInfo, sizeof(VMM_VIRT2PHYS_INFORMATION)); + pVirt2PhysInfo->va = va; + pVirt2PhysInfo->pas[4] = pProcess->paPML4; + if(!(pbPML4 = VmmTlbGetPageTable(pProcess->paPML4, FALSE))) { return; } + VmmVirt2PhysGetInformation_DoWork(pProcess, pVirt2PhysInfo, 4, (PQWORD)pbPML4); +} + +// ---------------------------------------------------------------------------- +// INTERNAL VMMU FUNCTIONALITY: VIRTUAL MEMORY ACCESS. +// ---------------------------------------------------------------------------- + +VOID VmmWriteScatterVirtual(_In_ PVMM_PROCESS pProcess, _Inout_ PPMEM_IO_SCATTER_HEADER ppDMAsVirt, _In_ DWORD cpDMAsVirt) +{ + BOOL result; + QWORD i, qwPA; + PMEM_IO_SCATTER_HEADER pMEM_Virt; + // loop over the items, this may not be very efficient compared to a true + // scatter write, but since underlying hardware implementation does not + // support it yet this will be fine ... + if(ctxVmm->fReadOnly) { return; } + for(i = 0; i < cpDMAsVirt; i++) { + pMEM_Virt = ppDMAsVirt[i]; + pMEM_Virt->cb = 0; + result = VmmVirt2Phys(pProcess, pMEM_Virt->qwA, &qwPA); + if(!result) { continue; } + ctxVmm->stat.cPhysWrite++; + result = DeviceWriteMEM(qwPA, pMEM_Virt->pb, pMEM_Virt->cbMax); + if(result) { + pMEM_Virt->cb = pMEM_Virt->cbMax; + VmmCacheInvalidate(qwPA & ~0xfff); + } + } +} + +VOID VmmWriteScatterPhysical(_Inout_ PPMEM_IO_SCATTER_HEADER ppDMAsPhys, _In_ DWORD cpDMAsPhys) +{ + BOOL result; + QWORD i; + PMEM_IO_SCATTER_HEADER pMEM_Phys; + // loop over the items, this may not be very efficient compared to a true + // scatter write, but since underlying hardware implementation does not + // support it yet this will be fine ... + if(ctxVmm->fReadOnly) { return; } + for(i = 0; i < cpDMAsPhys; i++) { + pMEM_Phys = ppDMAsPhys[i]; + ctxVmm->stat.cPhysWrite++; + result = DeviceWriteMEM(pMEM_Phys->qwA, pMEM_Phys->pb, pMEM_Phys->cbMax); + if(result) { + pMEM_Phys->cb = pMEM_Phys->cbMax; + VmmCacheInvalidate(pMEM_Phys->qwA & ~0xfff); + } + } +} + +BOOL VmmWritePhysical(_In_ QWORD pa, _Out_ PBYTE pb, _In_ DWORD cb) +{ + QWORD paPage; + // 1: invalidate any physical pages from cache + paPage = pa & ~0xfff; + do { + ctxVmm->stat.cPhysWrite++; + VmmCacheInvalidate(paPage); + paPage += 0x1000; + } while(paPage < pa + cb); + // 2: perform write + return DeviceWriteMEM(pa, pb, cb); +} + +BOOL VmmReadPhysicalPage(_In_ QWORD qwPA, _Inout_bytecount_(4096) PBYTE pbPage) +{ + PMEM_IO_SCATTER_HEADER pMEM_Phys; + PVMM_CACHE_ENTRY pMEMPhysCacheEntry; + DWORD cReadMEMs = 0; + qwPA &= ~0xfff; + pMEM_Phys = VmmCacheGet(ctxVmm->ptPHYS, qwPA); + if(pMEM_Phys) { + memcpy(pbPage, pMEM_Phys->pb, 0x1000); + return TRUE; + } + pMEMPhysCacheEntry = VmmCacheReserve(ctxVmm->ptPHYS); + pMEM_Phys = &pMEMPhysCacheEntry->h; + pMEM_Phys->cb = 0; + pMEM_Phys->qwA = qwPA; + DeviceReadScatterMEM(&pMEM_Phys, 1, &cReadMEMs); + VmmCachePut(ctxVmm->ptPHYS, pMEMPhysCacheEntry); + if(cReadMEMs) { + memcpy(pbPage, pMEM_Phys->pb, 0x1000); + return TRUE; + } + ZeroMemory(pbPage, 0x1000); + return FALSE; +} + +VOID VmmReadScatterPhysical( _Inout_ PPMEM_IO_SCATTER_HEADER ppMEMsPhys, _In_ DWORD cpMEMsPhys, _In_ QWORD flags) +{ + DWORD i, j, cPagesPerScatterRead, cMEMsPhysCache; + PVMM_CACHE_ENTRY ppMEMsPhysCacheEntry[0x48]; + PMEM_IO_SCATTER_HEADER ppMEMsPhysCacheIo[0x48]; + PMEM_IO_SCATTER_HEADER pMEM_Src, pMEM_Dst; + // 2.1: retrieve data loop - read strategy: non-cached read + if(VMM_FLAG_NOCACHE & (flags | ctxVmm->flags)) { + DeviceReadScatterMEM(ppMEMsPhys, cpMEMsPhys, NULL); + return; + } + // 2.2: retrieve data loop - read strategy: cached read (standard/preferred) + cPagesPerScatterRead = min(0x48, ((ctxMain->dev.qwMaxSizeMemIo & ~0xfff) >> 12)); + cMEMsPhysCache = 0; + for(i = 0; i < cpMEMsPhys; i++) { + // retrieve from cache (if found) + pMEM_Src = VmmCacheGet(ctxVmm->ptPHYS, ppMEMsPhys[i]->qwA); + if(pMEM_Src) { + // in cache - copy data into requester and set as completed! + ppMEMsPhys[i]->cb = 0x1000; + memcpy(ppMEMsPhys[i]->pb, pMEM_Src->pb, 0x1000); + ctxVmm->stat.cPhysCacheHit++; + } else { + // not in cache - add to requesting queue + ppMEMsPhysCacheEntry[cMEMsPhysCache] = VmmCacheReserve(ctxVmm->ptPHYS); + ppMEMsPhysCacheIo[cMEMsPhysCache] = &ppMEMsPhysCacheEntry[cMEMsPhysCache]->h; + ppMEMsPhysCacheIo[cMEMsPhysCache]->cb = 0; + ppMEMsPhysCacheIo[cMEMsPhysCache]->qwA = ppMEMsPhys[i]->qwA; + ppMEMsPhysCacheIo[cMEMsPhysCache]->pvReserved1 = (PVOID)ppMEMsPhys[i]; + cMEMsPhysCache++; + } + // physical read if requesting queue is full or if this is last + if(cMEMsPhysCache && ((cMEMsPhysCache == cPagesPerScatterRead) || (i == cpMEMsPhys - 1))) { + // SPECULATIVE FUTURE READ IF NEGLIGIBLE PERFORMANCE LOSS + while(cMEMsPhysCache < min(0x18, cPagesPerScatterRead)) { + ppMEMsPhysCacheEntry[cMEMsPhysCache] = VmmCacheReserve(ctxVmm->ptPHYS); + ppMEMsPhysCacheIo[cMEMsPhysCache] = &ppMEMsPhysCacheEntry[cMEMsPhysCache]->h; + ppMEMsPhysCacheIo[cMEMsPhysCache]->cb = 0; + ppMEMsPhysCacheIo[cMEMsPhysCache]->qwA = (QWORD)ppMEMsPhysCacheIo[cMEMsPhysCache - 1]->qwA + 0x1000; + ppMEMsPhysCacheIo[cMEMsPhysCache]->pvReserved1 = NULL; + cMEMsPhysCache++; + } + // physical memory access + DeviceReadScatterMEM(ppMEMsPhysCacheIo, cMEMsPhysCache, NULL); + for(j = 0; j < cMEMsPhysCache; j++) { + VmmCachePut(ctxVmm->ptPHYS, ppMEMsPhysCacheEntry[j]); + pMEM_Src = &ppMEMsPhysCacheEntry[j]->h; + pMEM_Dst = (PMEM_IO_SCATTER_HEADER)pMEM_Src->pvReserved1; + if(pMEM_Dst) { + if(pMEM_Src->cb) { + pMEM_Dst->cb = pMEM_Src->cb; + memcpy(pMEM_Dst->pb, pMEM_Src->pb, 0x1000); + } else if((flags & VMM_FLAG_ZEROPAD_ON_FAIL) && (pMEM_Src->qwA < ctxMain->cfg.paAddrMax)) { + pMEM_Dst->cb = 0x1000; + ZeroMemory(pMEM_Dst->pb, 0x1000); + } else { + pMEM_Dst->cb = 0; + } + } + if(pMEM_Src->cb == 0x1000) { + ctxVmm->stat.cPhysReadSuccess++; + } else { + ctxVmm->stat.cPhysReadFail++; + } + } + cMEMsPhysCache = 0; + } + } +} + +VOID VmmReadScatterVirtual(_In_ PVMM_PROCESS pProcess, _Inout_ PPMEM_IO_SCATTER_HEADER ppMEMsVirt, _In_ DWORD cpMEMsVirt, _In_ QWORD flags) +{ + DWORD i = 0, iVA, iPA; + QWORD qwPA; + MEM_IO_SCATTER_HEADER oMEMsPhys[0x48]; + PMEM_IO_SCATTER_HEADER pIoPA, pIoVA, ppMEMsPhys[0x48]; + // if chunk is larger than threshold (0x48) - split it into workitems + if(cpMEMsVirt > 0x48) { + while(i < cpMEMsVirt) { + VmmReadScatterVirtual(pProcess, ppMEMsVirt + i, min(0x48, cpMEMsVirt - i), flags); + i += 0x48; + } + return; + } + // 1: translate virt2phys + for(iVA = 0, iPA = 0; iVA < cpMEMsVirt; iVA++) { + pIoVA = ppMEMsVirt[iVA]; + if(VmmVirt2Phys(pProcess, pIoVA->qwA, &qwPA)) { + pIoPA = ppMEMsPhys[iPA] = &oMEMsPhys[iPA]; + iPA++; + pIoPA->qwA = qwPA; + pIoPA->cbMax = 0x1000; + pIoPA->cb = 0; + pIoPA->pb = pIoVA->pb; + pIoPA->pvReserved1 = (PVOID)pIoVA; + } else { + pIoVA->cb = 0; + } + } + VmmReadScatterPhysical(ppMEMsPhys, iPA, flags); + while(iPA > 0) { + iPA--; + ((PMEM_IO_SCATTER_HEADER)ppMEMsPhys[iPA]->pvReserved1)->cb = ppMEMsPhys[iPA]->cb; + } +} + +// ---------------------------------------------------------------------------- +// PUBLICALLY VISIBLE FUNCTIONALITY RELATED TO VMMU. +// ---------------------------------------------------------------------------- + +VOID VmmClose() +{ + if(!ctxVmm) { return; } + if(ctxVmm->pVmmVfsModuleList) { PluginManager_Close(); } + if(ctxVmm->ThreadProcCache.fEnabled) { + ctxVmm->ThreadProcCache.fEnabled = FALSE; + while(ctxVmm->ThreadProcCache.hThread) { + SwitchToThread(); + } + } + VmmProcessCloseTable(ctxVmm->ptPROC, TRUE); + VmmCacheClose(ctxVmm->ptTLB); + VmmCacheClose(ctxVmm->ptPHYS); + DeleteCriticalSection(&ctxVmm->MasterLock); + LocalFree(ctxVmm); + ctxVmm = NULL; +} + +VOID VmmWriteEx(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Out_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbWrite) +{ + DWORD i = 0, oVA = 0, cbWrite = 0, cbP, cDMAs; + PBYTE pbBuffer; + PMEM_IO_SCATTER_HEADER pDMAs, *ppDMAs; + if(pcbWrite) { *pcbWrite = 0; } + // allocate + cDMAs = (DWORD)(((qwVA & 0xfff) + cb + 0xfff) >> 12); + pbBuffer = (PBYTE)LocalAlloc(LMEM_ZEROINIT, cDMAs * (sizeof(MEM_IO_SCATTER_HEADER) + sizeof(PMEM_IO_SCATTER_HEADER))); + if(!pbBuffer) { return; } + pDMAs = (PMEM_IO_SCATTER_HEADER)pbBuffer; + ppDMAs = (PPMEM_IO_SCATTER_HEADER)(pbBuffer + cDMAs * sizeof(MEM_IO_SCATTER_HEADER)); + // prepare pages + while(oVA < cb) { + ppDMAs[i] = &pDMAs[i]; + pDMAs[i].qwA = qwVA + oVA; + cbP = 0x1000 - ((qwVA + oVA) & 0xfff); + cbP = min(cbP, cb - oVA); + pDMAs[i].cbMax = cbP; + pDMAs[i].pb = pb + oVA; + oVA += cbP; + i++; + } + // write and count result + if(pProcess) { + VmmWriteScatterVirtual(pProcess, ppDMAs, cDMAs); + } else { + VmmWriteScatterPhysical(ppDMAs, cDMAs); + } + if(pcbWrite) { + for(i = 0; i < cDMAs; i++) { + cbWrite += pDMAs[i].cb; + } + *pcbWrite = cbWrite; + } + LocalFree(pbBuffer); +} + +BOOL VmmWrite(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Out_ PBYTE pb, _In_ DWORD cb) +{ + DWORD cbWrite; + VmmWriteEx(pProcess, qwVA, pb, cb, &cbWrite); + return (cbWrite == cb); +} + +VOID VmmReadEx(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ QWORD flags) +{ + DWORD cbP, cDMAs, cbRead = 0; + PBYTE pbBuffer; + PMEM_IO_SCATTER_HEADER pDMAs, *ppDMAs; + QWORD i, oVA; + if(pcbReadOpt) { *pcbReadOpt = 0; } + if(!cb) { return; } + cDMAs = (DWORD)(((qwVA & 0xfff) + cb + 0xfff) >> 12); + pbBuffer = (PBYTE)LocalAlloc(LMEM_ZEROINIT, 0x2000 + cDMAs * (sizeof(MEM_IO_SCATTER_HEADER) + sizeof(PMEM_IO_SCATTER_HEADER))); + if(!pbBuffer) { return; } + pDMAs = (PMEM_IO_SCATTER_HEADER)(pbBuffer + 0x2000); + ppDMAs = (PPMEM_IO_SCATTER_HEADER)(pbBuffer + 0x2000 + cDMAs * sizeof(MEM_IO_SCATTER_HEADER)); + oVA = qwVA & 0xfff; + // prepare "middle" pages + for(i = 0; i < cDMAs; i++) { + ppDMAs[i] = &pDMAs[i]; + pDMAs[i].qwA = qwVA - oVA + (i << 12); + pDMAs[i].cbMax = 0x1000; + pDMAs[i].pb = pb - oVA + (i << 12); + } + // fixup "first/last" pages + pDMAs[0].pb = pbBuffer; + if(cDMAs > 1) { + pDMAs[cDMAs - 1].pb = pbBuffer + 0x1000; + } + // Read VMM and handle result + if(pProcess) { + VmmReadScatterVirtual(pProcess, ppDMAs, cDMAs, flags); + } else { + VmmReadScatterPhysical(ppDMAs, cDMAs, flags); + } + for(i = 0; i < cDMAs; i++) { + if(pDMAs[i].cb == 0x1000) { + cbRead += 0x1000; + } else { + ZeroMemory(pDMAs[i].pb, 0x1000); + } + } + cbRead -= (pDMAs[0].cb == 0x1000) ? 0x1000 : 0; // adjust byte count for first page (if needed) + cbRead -= ((cDMAs > 1) && (pDMAs[cDMAs - 1].cb == 0x1000)) ? 0x1000 : 0; // adjust byte count for last page (if needed) + // Handle first page + cbP = (DWORD)min(cb, 0x1000 - oVA); + if(pDMAs[0].cb == 0x1000) { + memcpy(pb, pDMAs[0].pb + oVA, cbP); + cbRead += cbP; + } else { + ZeroMemory(pb, cbP); + } + // Handle last page + if(cDMAs > 1) { + cbP = (((qwVA + cb) & 0xfff) ? ((qwVA + cb) & 0xfff) : 0x1000); + if(pDMAs[cDMAs - 1].cb == 0x1000) { + memcpy(pb + ((QWORD)cDMAs << 12) - oVA - 0x1000, pDMAs[cDMAs - 1].pb, cbP); + cbRead += cbP; + } else { + ZeroMemory(pb + ((QWORD)cDMAs << 12) - oVA - 0x1000, cbP); + } + } + if(pcbReadOpt) { *pcbReadOpt = cbRead; } + LocalFree(pbBuffer); +} + +BOOL VmmReadString_Unicode2Ansi(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Out_ LPSTR sz, _In_ DWORD cch) +{ + DWORD i = 0; + BOOL result; + WCHAR wsz[0x1000]; + if(cch > 0x1000) { return FALSE; } + result = VmmRead(pProcess, qwVA, (PBYTE)wsz, cch << 1); + if(!result) { return FALSE; } + for(i = 0; i < cch; i++) { + sz[i] = ((WORD)wsz[i] <= 0xff) ? (CHAR)wsz[i] : '?'; + if(sz[i] == 0) { return TRUE; } + } + return TRUE; +} + +BOOL VmmRead(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwA, _Out_ PBYTE pb, _In_ DWORD cb) +{ + DWORD cbRead; + VmmReadEx(pProcess, qwA, pb, cb, &cbRead, 0); + return (cbRead == cb); +} + +BOOL VmmReadPage(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwA, _Inout_bytecount_(4096) PBYTE pbPage) +{ + DWORD cb; + VmmReadEx(pProcess, qwA, pbPage, 0x1000, &cb, 0); + return cb == 0x1000; +} + +BOOL VmmInitialize() +{ + // 1: allocate & initialize + if(ctxVmm) { VmmClose(); } + ctxVmm = (PVMM_CONTEXT)LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_CONTEXT)); + if(!ctxVmm) { goto fail; } + // 2: CACHE INIT: Process Table + VmmProcessCreateTable(); + if(!ctxVmm->ptPROC) { goto fail; } + // 3: CACHE INIT: Translation Lookaside Buffer (TLB) Cache Table + ctxVmm->ptTLB = VmmCacheInitialize(VMM_CACHE_TLB_ENTRIES); + if(!ctxVmm->ptTLB) { goto fail; } + // 3: CACHE INIT: Physical Memory Cache Table + ctxVmm->ptPHYS = VmmCacheInitialize(VMM_CACHE_PHYS_ENTRIES); + if(!ctxVmm->ptPHYS) { goto fail; } + // 4: OTHER INIT: + ctxVmm->fReadOnly = (ctxMain->dev.tp == VMM_DEVICE_FILE); + InitializeCriticalSection(&ctxVmm->MasterLock); + return TRUE; +fail: + VmmClose(); + return FALSE; +} diff --git a/vmm/vmm.h b/vmm/vmm.h new file mode 100644 index 0000000..b78f6d1 --- /dev/null +++ b/vmm/vmm.h @@ -0,0 +1,505 @@ +// vmm.h : definitions related to virtual memory management support. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __VMM_H__ +#define __VMM_H__ +#include +#include + +typedef unsigned __int64 QWORD, *PQWORD; + +typedef struct tdMEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} MEM_IO_SCATTER_HEADER, *PMEM_IO_SCATTER_HEADER, **PPMEM_IO_SCATTER_HEADER; + +// ---------------------------------------------------------------------------- +// VMM configuration constants and struct definitions below: +// ---------------------------------------------------------------------------- + +#define VMM_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMM_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMM_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMM_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMM_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +#define VMM_PROCESSTABLE_ENTRIES_MAX 0x4000 +#define VMM_PROCESS_OS_ALLOC_PTR_MAX 0x4 // max number of operating system specific pointers that must be free'd +#define VMM_MEMMAP_ENTRIES_MAX 0x4000 + +#define VMM_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMM_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMM_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMM_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +#define VMM_CACHE_TABLESIZE 0x4011 // (not even # to prevent clogging at specific table 'hash' buckets) +#define VMM_CACHE_TLB_ENTRIES 0x4000 // -> 64MB of cached data +#define VMM_CACHE_PHYS_ENTRIES 0x4000 // -> 64MB of cached data + +#define VMM_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMM_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMM_TARGET_UNKNOWN_X64 0x0001 +#define VMM_TARGET_WINDOWS_X64 0x0002 + +#define VMM_VERSION_MAJOR 1 +#define VMM_VERSION_MINOR 0 +#define VMM_VERSION_REVISION 0 + +typedef struct tdVMM_MEMMAP_ENTRY { + QWORD AddrBase; + QWORD cPages; + QWORD fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMM_MEMMAP_ENTRY, *PVMM_MEMMAP_ENTRY; + +typedef struct tdVMM_MODULEMAP_ENTRY { + QWORD BaseAddress; + QWORD EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; + // # of entries in EAT / IAT (lazy loaded due to performance reasons) + BOOL fLoadedEAT; + DWORD cbDisplayBufferEAT; + BOOL fLoadedIAT; + DWORD cbDisplayBufferIAT; + DWORD cbDisplayBufferSections; +} VMM_MODULEMAP_ENTRY, *PVMM_MODULEMAP_ENTRY; + +typedef struct tdVMM_VIRT2PHYS_INFORMATION { + QWORD va; + QWORD pas[5]; // physical addresses of pagetable[PML]/page[0] + QWORD PTEs[5]; // PTEs[PML] + WORD iPTEs[5]; // Index of PTE in page table +} VMM_VIRT2PHYS_INFORMATION, *PVMM_VIRT2PHYS_INFORMATION; + +typedef struct tdVMM_PROCESS { + DWORD dwPID; + DWORD dwState; // state of process, 0 = running + QWORD paPML4; + QWORD paPML4_UserOpt; + CHAR szName[16]; + BOOL _i_fMigrated; + BOOL fUserOnly; + BOOL fSpiderPageTableDone; + BOOL fFileCacheDisabled; + // memmap related pointers (free must be called separately) + QWORD cMemMap; + PVMM_MEMMAP_ENTRY pMemMap; + PBYTE pbMemMapDisplayCache; + QWORD cbMemMapDisplayCache; + // module map (free must be called separately) + QWORD cModuleMap; + PVMM_MODULEMAP_ENTRY pModuleMap; + union { + struct { + PVOID pvReserved[VMM_PROCESS_OS_ALLOC_PTR_MAX]; // os-specific buffer to be allocated if needed (free'd by VmmClose) + } unk; + struct { + PBYTE pbLdrModulesDisplayCache; + PVOID pbReserved[VMM_PROCESS_OS_ALLOC_PTR_MAX - 1]; + DWORD cbLdrModulesDisplayCache; + QWORD vaEPROCESS; + QWORD vaPEB; + DWORD vaPEB32; // WoW64 only + QWORD vaENTRY; + BOOL fWow64; + } win; + } os; +} VMM_PROCESS, *PVMM_PROCESS; + +typedef struct tdVMM_PROCESS_TABLE { + SIZE_T c; + WORD iFLink; + WORD iFLinkM[VMM_PROCESSTABLE_ENTRIES_MAX]; + PVMM_PROCESS M[VMM_PROCESSTABLE_ENTRIES_MAX]; + struct tdVMM_PROCESS_TABLE *ptNew; +} VMM_PROCESS_TABLE, *PVMM_PROCESS_TABLE; + +#define VMM_CACHE_ENTRY_MAGIC 0x29d50298c4921034 + +typedef struct tdVMM_CACHE_ENTRY { + QWORD qwMAGIC; + struct tdVMM_CACHE_ENTRY *FLink; + struct tdVMM_CACHE_ENTRY *BLink; + struct tdVMM_CACHE_ENTRY *AgeFLink; + struct tdVMM_CACHE_ENTRY *AgeBLink; + QWORD tm; + MEM_IO_SCATTER_HEADER h; + BYTE pb[0x1000]; +} VMM_CACHE_ENTRY, *PVMM_CACHE_ENTRY, **PPVMM_CACHE_ENTRY; + +typedef struct tdVMM_CACHE_TABLE { + PVMM_CACHE_ENTRY M[VMM_CACHE_TABLESIZE]; + PVMM_CACHE_ENTRY AgeFLink; + PVMM_CACHE_ENTRY AgeBLink; + PVMM_CACHE_ENTRY S; +} VMM_CACHE_TABLE, *PVMM_CACHE_TABLE; + +// ---------------------------------------------------------------------------- +// VMM general constants and struct definitions below: +// ---------------------------------------------------------------------------- + +typedef struct tdVmmConfig { + CHAR szMountPoint[1]; + CHAR szDevTpOrFileName[MAX_PATH]; + CHAR szPythonPath[MAX_PATH]; + QWORD paCR3; + QWORD paAddrMax; + // flags below + BOOL fCommandIdentify; + BOOL fVerboseDll; + BOOL fVerbose; + BOOL fVerboseExtra; + BOOL fVerboseExtraTlp; +} VMMCONFIG, *PVMMCONFIG; + +typedef enum tdMPFS_DEVICE_TYPE { + VMM_DEVICE_NA, + VMM_DEVICE_FILE, + VMM_DEVICE_PCILEECH_DLL, +} MPFS_DEVICE_TYPE; + +typedef struct tdVmmDeviceConfig { + HANDLE hDevice; + QWORD paAddrMaxNative; + QWORD qwMaxSizeMemIo; + MPFS_DEVICE_TYPE tp; + VOID(*pfnReadScatterMEM)(_Inout_ PPMEM_IO_SCATTER_HEADER ppDMAs, _In_ DWORD cpDMAs, _Out_opt_ PDWORD pcpDMAsRead); + BOOL(*pfnWriteMEM)(_In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb); + VOID(*pfnClose)(); + BOOL(*pfnGetOption)(_In_ QWORD fOption, _Out_ PQWORD pqwValue); + BOOL(*pfnSetOption)(_In_ QWORD fOption, _In_ QWORD qwValue); +} VMMDEVICE_CONFIG, *PVMMDEVICE_CONFIG; + +typedef struct tdVMM_STATISTICS { + QWORD cPhysCacheHit; + QWORD cPhysReadSuccess; + QWORD cPhysReadFail; + QWORD cPhysWrite; + QWORD cTlbCacheHit; + QWORD cTlbReadSuccess; + QWORD cTlbReadFail; + QWORD cRefreshPhys; + QWORD cRefreshTlb; + QWORD cRefreshProcessPartial; + QWORD cRefreshProcessFull; +} VMM_STATISTICS, *PVMM_STATISTICS; + +typedef struct tdVMM_CONTEXT { + CRITICAL_SECTION MasterLock; + PVMM_PROCESS_TABLE ptPROC; + PVMM_CACHE_TABLE ptTLB; + PVMM_CACHE_TABLE ptPHYS; + BOOL fReadOnly; + // os specific below: + DWORD fTargetSystem; + DWORD flags; + struct { + BOOL fEnabled; + HANDLE hThread; + DWORD cMs_TickPeriod; + DWORD cTick_Phys; + DWORD cTick_TLB; + DWORD cTick_ProcPartial; + DWORD cTick_ProcTotal; + } ThreadProcCache; + VMM_STATISTICS stat; + PVOID pVmmVfsModuleList; +} VMM_CONTEXT, *PVMM_CONTEXT; + +typedef struct tdVMM_MAIN_CONTEXT { + VMMCONFIG cfg; + VMMDEVICE_CONFIG dev; + PVOID pvStatistics; +} VMM_MAIN_CONTEXT, *PVMM_MAIN_CONTEXT; + +// ---------------------------------------------------------------------------- +// VMM global variables below: +// ---------------------------------------------------------------------------- + +PVMM_CONTEXT ctxVmm; +PVMM_MAIN_CONTEXT ctxMain; + +#define vmmprintf(format, ...) { if(ctxMain->cfg.fVerboseDll) { printf(format, ##__VA_ARGS__); } } +#define vmmprintfv(format, ...) { if(ctxMain->cfg.fVerbose) { printf(format, ##__VA_ARGS__); } } +#define vmmprintfvv(format, ...) { if(ctxMain->cfg.fVerboseExtra) { printf(format, ##__VA_ARGS__); } } +#define vmmprintfvvv(format, ...) { if(ctxMain->cfg.fVerboseExtraTlp) { printf(format, ##__VA_ARGS__); } } + +// ---------------------------------------------------------------------------- +// VMM function definitions below: +// ---------------------------------------------------------------------------- + +/* +* Acquire the VMM master lock. Required if interoperating with the VMM from a +* function that has not already acquired the lock. Lock must be relased in a +* fairly short amount of time in order for the VMM to continue working. +* !!! MUST NEVER BE ACQUIRED FOR LENGTHY AMOUNT OF TIMES !!! +*/ +VOID VmmLockAcquire(); + +/* +* Release VMM master lock that has previously been acquired by VmmLockAcquire. +*/ +VOID VmmLockRelease(); + +/* +* Write a virtually contigious arbitrary amount of memory. +* -- pProcess +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VmmWrite(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Write physical memory and clear any VMM caches that may contain data. +* -- pa +* -- pb +* -- cb +* -- return +*/ +BOOL VmmWritePhysical(_In_ QWORD pa, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a virtually contigious arbitrary amount of memory containing cch number of +* unicode characters and convert them into ansi characters. Characters > 0xff are +* converted into '?'. +* -- pProcess +* -- qwVA +* -- sz +* -- cch +* -- return +*/ +BOOL VmmReadString_Unicode2Ansi(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Out_ LPSTR sz, _In_ DWORD cch); + +/* +* Read a contigious arbitrary amount of memory, virtual or physical. +* Virtual memory is read if a process is specified in pProcess parameter. +* Physical memory is read if NULL is specified in pProcess parameter. +* -- pProcess +* -- qwVA = NULL=='physical memory read', PTR=='virtual memory read' +* -- pb +* -- cb +* -- return +*/ +BOOL VmmRead(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious arbitrary amount of memory, physical or virtual, and report +* the number of bytes read in pcbRead. +* Virtual memory is read if a process is specified in pProcess. +* Physical memory is read if NULL is specified in pProcess. +* -- pProcess = NULL=='physical memory read', PTR=='virtual memory read' +* -- qwA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +*/ +VOID VmmReadEx(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ QWORD flags); + +/* +* Read a single 4096-byte page of memory, virtual or physical. +* Virtual memory is read if a process is specified in pProcess. +* Physical memory is read if NULL is specified in pProcess. +* -- pProcess = NULL=='physical memory read', PTR=='virtual memory read' +* -- qwA +* -- pbPage +* -- return +*/ +BOOL VmmReadPage(_In_opt_ PVMM_PROCESS pProcess, _In_ QWORD qwA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Scatter read virtual memory. Non contiguous 4096-byte pages. +* -- pProcess +* -- ppMEMsVirt +* -- cpMEMsVirt +* -- flags = flags as in VMM_FLAG_*, [VMM_FLAG_NOCACHE for supression of data (not tlb) caching] +*/ +VOID VmmReadScatterVirtual(_In_ PVMM_PROCESS pProcess, _Inout_ PPMEM_IO_SCATTER_HEADER ppMEMsVirt, _In_ DWORD cpMEMsVirt, _In_ QWORD flags); + +/* +* Scatter read physical memory. Non contiguous 4096-byte pages. +* -- ppMEMsPhys +* -- cpMEMsPhys +* -- flags = flags as in VMM_FLAG_*, [VMM_FLAG_NOCACHE for supression of caching] +*/ +VOID VmmReadScatterPhysical(_Inout_ PPMEM_IO_SCATTER_HEADER ppMEMsPhys, _In_ DWORD cpMEMsPhys, _In_ QWORD flags); + +/* +* Read a single 4096-byte page of physical memory. +* -- qwPA +* -- pbPage +* -- return +*/ +BOOL VmmReadPhysicalPage(_In_ QWORD qwPA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Translate a virtual address to a physical address by walking the page tables. +* -- pProcess +* -- qwVA +* -- pqwPA +* -- return +*/ +_Success_(return) +BOOL VmmVirt2Phys(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVA, _Out_ PQWORD pqwPA); + +/* +* Translate a virtual address to a physical address given some extra parameters as +* as compared to the standard recommended function VmmVirt2Phys. +* -- fUserOnly +* -- va +* -- iPML +* -- PTEs +* -- ppa +* -- return +*/ +_Success_(return) +BOOL VmmVirt2PhysEx(_In_ BOOL fUserOnly, _In_ QWORD va, _In_ QWORD iPML, _In_ QWORD PTEs[512], _Out_ PQWORD ppa); + +/* +* Spider the TLB (page table cache) to load all page table pages into the cache. +* This is done to speed up various subsequent virtual memory accesses. +* NB! pages may fall out of the cache if it's in heavy use or doe to timing. +* -- qwPML4 = physical adderss of the Page Mapping Level 4 table to spider. +* -- fUserOnly = only spider user-mode (ring3) pages, no kernel pages. +*/ +VOID VmmTlbSpider(_In_ QWORD qwPML4, _In_ BOOL fUserOnly); + +/* +* Try verify that a supplied page table in pb is valid by analyzing it. +* -- pb = 0x1000 bytes containing the page table page. +* -- pa = physical address if the page table page. +* -- fSelfRefReq = is a self referential entry required to be in the map? (PML4 for Windows). +*/ +BOOL VmmTlbPageTableVerify(_Inout_ PBYTE pb, _In_ QWORD pa, _In_ BOOL fSelfRefReq); + +/* +* Retrieve a page table (0x1000 bytes) via the TLB cache. +* -- qwPA +* -- fCacheOnly = if set do not make a request to underlying device if not in cache. +* -- return +*/ +PBYTE VmmTlbGetPageTable(_In_ QWORD qwPA, _In_ BOOL fCacheOnly); + +/* +* Retrieve information of the virtual2physical address translation for the +* supplied process. The Virtual address must be supplied in pVirt2PhysInfo upon +* entry. +* -- pProcess +* -- pVirt2PhysInfo +*/ +VOID VmmVirt2PhysGetInformation(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_VIRT2PHYS_INFORMATION pVirt2PhysInfo); + +/* +* Initialize the memory map for a specific process. This may take some time +* especially for kernel/system processes. +* -- pProcess +*/ +VOID VmmMapInitialize(_In_ PVMM_PROCESS pProcess); + +/* +* Map a tag into the sorted memory map in O(log2) operations. Supply only one +* of szTag or wszTag. Tags are usually module/dll name. +* -- pProcess +* -- vaBase +* -- vaLimit = limit == vaBase + size (== top address in range +1) +* -- szTag +* -- wszTag +* -- fWoW64 +*/ +VOID VmmMapTag(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaBase, _In_ QWORD vaLimit, _In_opt_ LPSTR szTag, _In_opt_ LPWSTR wszTag, _In_opt_ BOOL fWoW64); + +/* +* Retrieve a memory map entry info given a specific address. +* -- pProcess +* -- qwVA +* -- return = the memory map entry or NULL if not found. +*/ +PVMM_MEMMAP_ENTRY VmmMapGetEntry(_In_ PVMM_PROCESS pProcess, _In_ QWORD qwVA); + +/* +* Generate the human-readable text byte-buffer representing an already existing +* memory map in the process. This memory map must have been initialized with a +* separate call to VmmMapInitialize. +* -- pProcess +*/ +VOID VmmMapDisplayBufferGenerate(_In_ PVMM_PROCESS pProcess); + + +/* +* Create or re-create the entire process table. This will clean the complete and +* all existing processes will be cleared. +* -- return +*/ +BOOL VmmProcessCreateTable(); + +/* +* Retrieve an existing process given a process id (PID). +* -- dwPID +* -- return = a process struct, or NULL if not found. +*/ +PVMM_PROCESS VmmProcessGet(_In_ DWORD dwPID); + +/* +* Create a new process item. New process items are created in a separate data +* structure and won't become visible to the "Process" functions until after the +* VmmProcessCreateFinish have been called. +*/ +PVMM_PROCESS VmmProcessCreateEntry(_In_ DWORD dwPID, _In_ DWORD dwState, _In_ QWORD paPML4, _In_ QWORD paPML4_UserOpt, _In_ CHAR szName[16], _In_ BOOL fUserOnly, _In_ BOOL fSpiderPageTableDone); + +/* +* Activate the pending, not yet active, processes added by VmmProcessCreateEntry. +* This will also clear any previous processes. +*/ +VOID VmmProcessCreateFinish(); + +/* +* List the PIDs and put them into the supplied table. +* -- pPIDs = user allocated DWORD array to receive result, or NULL. +* -- pcPIDs = ptr to number of DWORDs in pPIDs on entry - number of PIDs in system on exit. +*/ +VOID VmmProcessListPIDs(_Out_ PDWORD pPIDs, _Inout_ PSIZE_T pcPIDs); + +/* +* Clear the specified cache from all entries. +* -- fTLB +* -- fPHYS +*/ +VOID VmmCacheClear( _In_ BOOL fTLB, _In_ BOOL fPHYS); + +/* +* Invalidate cache entries belonging to a specific physical address. +* -- pa +*/ +VOID VmmCacheInvalidate( _In_ QWORD pa); + +/* +* Initialize a new VMM context. This must always be done before calling any +* other VMM functions. An alternative way to do this is to call the function: +* VmmProcInitialize. +* -- return +*/ +BOOL VmmInitialize(); + +/* +* Close and clean up the VMM context inside the PCILeech context, if existing. +*/ +VOID VmmClose(); + +#endif /* __VMM_H__ */ diff --git a/vmm/vmm.vcxproj b/vmm/vmm.vcxproj new file mode 100644 index 0000000..e5cdf6d --- /dev/null +++ b/vmm/vmm.vcxproj @@ -0,0 +1,133 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {18BEBDF1-73F2-4ABB-BDBE-507ADAB32494} + vmm + 8.1 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + .dll + + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + .dll + + + + Level3 + Disabled + true + true + + + vmmdll.def + + + $(OutDir)\lib\$(TargetName).pdb + $(OutDir)\lib\$(TargetName).lib + + + copy "$(OutDir)\lib\vmm.lib" "$(OutDir)\vmm.lib" /y + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + vmmdll.def + UseLinkTimeCodeGeneration + $(OutDir)\lib\$(TargetName).pdb + $(OutDir)\lib\$(TargetName).lib + + + copy "$(OutDir)\lib\vmm.lib" "$(OutDir)\vmm.lib" /y + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vmm/vmm.vcxproj.filters b/vmm/vmm.vcxproj.filters new file mode 100644 index 0000000..c8fa8aa --- /dev/null +++ b/vmm/vmm.vcxproj.filters @@ -0,0 +1,113 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {bc9b7cdb-d6b1-4bb5-9c1a-6f105979cac4} + + + + + Resource Files + + + + + Header Files\pcileech + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/vmm/vmm.vcxproj.user b/vmm/vmm.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/vmm/vmm.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/vmm/vmmdll.c b/vmm/vmmdll.c new file mode 100644 index 0000000..b524fb5 --- /dev/null +++ b/vmm/vmmdll.c @@ -0,0 +1,866 @@ +// vmmdll.h : implementation of core dynamic link library (dll) functionality +// of the virtual memory manager (VMM) for The Memory Process File System. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include "vmmdll.h" +#include "device.h" +#include "pluginmanager.h" +#include "util.h" +#include "vmm.h" +#include "vmmproc.h" +#include "vmmproc_windows.h" +#include "vmmvfs.h" + +// ---------------------------------------------------------------------------- +// Synchronization macro below. The VMM isn't thread safe so it's important to +// serialize access to it over the VMM MasterLock. This master lock is shared +// with internal VMM housekeeping functionality. +// ---------------------------------------------------------------------------- + +#define CALL_SYNCHRONIZED_IMPLEMENTATION_VMM(id, fn) { \ + QWORD tm; \ + BOOL result; \ + if(!ctxVmm) { return FALSE; } \ + tm = Statistics_CallStart(); \ + VmmLockAcquire(); \ + result = fn; \ + VmmLockRelease(); \ + Statistics_CallEnd(id, tm); \ + return result; \ +} + +#define CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_NTSTATUS(id, fn) { \ + QWORD tm; \ + NTSTATUS nt; \ + if(!ctxVmm) { return ((NTSTATUS)0xC0000001L); } /* UNSUCCESSFUL */ \ + tm = Statistics_CallStart(); \ + VmmLockAcquire(); \ + nt = fn; \ + VmmLockRelease(); \ + Statistics_CallEnd(id, tm); \ + return nt; \ +} + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +BOOL VmmDll_ConfigIntialize(_In_ DWORD argc, _In_ char* argv[]) +{ + char* argv2[3]; + CHAR chMountMount = '\0'; + DWORD i = 0; + if((argc == 2) && argv[1][0] && (argv[1][0] != '-')) { + // click to open -> only 1 argument ... + argv2[0] = argv[0]; + argv2[1] = "-device"; + argv2[2] = argv[1]; + return VmmDll_ConfigIntialize(3, argv2); + } + while(i < argc) { + if(0 == _stricmp(argv[i], "")) { + i++; + continue; + } else if(0 == _stricmp(argv[i], "-vdll")) { + ctxMain->cfg.fVerboseDll = TRUE; + i++; + continue; + } else if(0 == _stricmp(argv[i], "-v")) { + ctxMain->cfg.fVerbose = TRUE; + i++; + continue; + } else if(0 == _stricmp(argv[i], "-vv")) { + ctxMain->cfg.fVerboseExtra = TRUE; + i++; + continue; + } else if(0 == _stricmp(argv[i], "-vvv")) { + ctxMain->cfg.fVerboseExtraTlp = TRUE; + i++; + continue; + } else if(0 == _stricmp(argv[i], "-identify")) { + ctxMain->cfg.fCommandIdentify = TRUE; + i++; + continue; + } else if(i + 1 >= argc) { + return FALSE; + } else if(0 == strcmp(argv[i], "-cr3")) { + ctxMain->cfg.paCR3 = Util_GetNumeric(argv[i + 1]); + i += 2; + continue; + } else if(0 == strcmp(argv[i], "-max")) { + ctxMain->cfg.paAddrMax = Util_GetNumeric(argv[i + 1]); + i += 2; + continue; + } else if(0 == strcmp(argv[i], "-device")) { + strcpy_s(ctxMain->cfg.szDevTpOrFileName, MAX_PATH, argv[i + 1]); + i += 2; + continue; + } else if(0 == strcmp(argv[i], "-pythonpath")) { + strcpy_s(ctxMain->cfg.szPythonPath, MAX_PATH, argv[i + 1]); + i += 2; + continue; + } else if(0 == strcmp(argv[i], "-mount")) { + chMountMount = argv[i + 1][0]; + i += 2; + continue; + } else { + return FALSE; + } + } + if((chMountMount > 'A' && chMountMount < 'Z') || (chMountMount > 'a' && chMountMount < 'z')) { + ctxMain->cfg.szMountPoint[0] = chMountMount; + } else { + ctxMain->cfg.szMountPoint[0] = 'M'; + } + if(ctxMain->cfg.paAddrMax == 0) { ctxMain->cfg.paAddrMax = 0x0000ffffffffffff; } + if(ctxMain->cfg.paAddrMax < 0x00100000) { return FALSE; } + ctxMain->cfg.fVerbose = ctxMain->cfg.fVerbose && ctxMain->cfg.fVerboseDll; + ctxMain->cfg.fVerboseExtra = ctxMain->cfg.fVerboseExtra && ctxMain->cfg.fVerboseDll; + ctxMain->cfg.fVerboseExtraTlp = ctxMain->cfg.fVerboseExtraTlp && ctxMain->cfg.fVerboseDll; + return (ctxMain->cfg.szDevTpOrFileName[0] != 0); +} + +VOID VmmDll_PrintHelp() +{ + vmmprintf( + " \n" \ + " THE MEMORY PROCESS FILE SYSTEM v%i.%i.%i COMMAND LINE REFERENCE: \n" \ + " The Memory Process File System may be used in stand-alone mode with support \n" \ + " for memory dump files or together with PCILeech if pcileech.dll is placed in \n" \ + " the application directory. For information about PCILeech and requirements \n" \ + " please consult the separate PCILeech documenation. \n" \ + " ----- \n" \ + " The Memory Process File System (c) 2018 Ulf Frisk \n" \ + " License: GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 \n" \ + " Contact information: pcileech@frizk.net \n" \ + " The Memory Process File System: https://github.com/ufrisk/MemProcFS \n" \ + " PCILeech: https://github.com/ufrisk/pcileech \n" \ + " ----- \n" \ + " The recommended way to use the Memory Process File System is to specify the \n" \ + " memory acquisition device in the -device option and possibly more options. \n" \ + " Example 1: MemProcFS.exe -device c:\\temp\\memdump-win10x64.pmem \n" \ + " Example 2: MemProcFS.exe -device c:\\temp\\memdump-win10x64.pmem -v -vv \n" \ + " Example 3: MemProcFS.exe -device FPGA \n" \ + " The Memory Process File System may also be started the memory dump file name \n" \ + " as the only option. This allows to make file extensions associated so that \n" \ + " they may be opened by double-clicking on them. This mode allows no options. \n" \ + " Example 4: MemProcFS.exe c:\\dumps\\memdump-win7x64.pmem \n" \ + " ----- \n" \ + " Valid options: \n" \ + " -device: select memory acquisition device or raw memory dump file to use. \n" \ + " Valid options: , FPGA, TOTALMELTDOWN \n" \ + " = memory dump file name optionally including path.\n" \ + " Below acquisition devices require pcileech.dll and are not built-in: \n" \ + " TOTALMELTDOWN = use CVE-2018-1038 (vulnerable windows 7 only) \n" \ + " FPGA = use PCILeech PCIe DMA hardware memory acquisition device. \n" \ + " -v : verbose option. Additional information is displayed in the output. \n" \ + " Option has no value. Example: -v \n" \ + " -vv : extra verbose option. More detailed additional information is shown \n" \ + " in output. Option has no value. Example: -vv \n" \ + " -vvv : super verbose option. Show all data transferred such as PCIe TLPs. \n" \ + " Option has no value. Example: -vvv \n" \ + " -cr3 : base address of kernel/process page table (PML4) / CR3 CPU register. \n" \ + " -max : memory max address, valid range: 0x0 .. 0xffffffffffffffff \n" \ + " default: auto-detect (max supported by device / target system). \n" \ + " -pythonpath : specify the path to a python 3.6 installation for Windows. \n" \ + " The path given should be to the directory that contain: python36.dll \n" \ + " Example: -pythonpath \"C:\\Program Files\\Python36\" \n" \ + " -mount : drive letter to mount The Memory Process File system at. \n" \ + " default: M Example: -mount Q \n" \ + " -identify : scan memory for the operating system and the kernel page table. \n" \ + " This may help if the default auto-detect is not working. \n" \ + " Option has no value. Example: -identify \n" \ + " \n", + VMM_VERSION_MAJOR, VMM_VERSION_MINOR, VMM_VERSION_REVISION + ); +} + +VOID VmmDll_FreeContext() +{ + if(ctxVmm) { + VmmClose(); + } + if(ctxMain) { + Statistics_CallSetEnabled(FALSE); + DeviceClose(); + LocalFree(ctxMain); + ctxMain = NULL; + } +} + +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]) +{ + ctxMain = LocalAlloc(LMEM_ZEROINIT, sizeof(VMM_MAIN_CONTEXT)); + if(!ctxMain) { + return FALSE; + } + // initialize configuration + if(!VmmDll_ConfigIntialize((DWORD)argc, argv)) { + VmmDll_PrintHelp(); + VmmDll_FreeContext(); + return FALSE; + } + // ctxMain.cfg context is inintialized from here onwards - vmmprintf is working! + if(!DeviceOpen()) { + vmmprintf("MemProcFS: Failed to connect to memory acquisition device.\n"); + VmmDll_FreeContext(); + return FALSE; + } + // ctxMain.dev context is initialized from here onwards - device functionality is working! + if(ctxMain->cfg.fCommandIdentify) { + // if identify option is supplied try scan for page directory base... + VmmProcIdentify(); + } + if(!VmmProcInitialize()) { + vmmprintf("MOUNT: INFO: PROC file system not mounted.\n"); + VmmDll_FreeContext(); + return FALSE; + } + // ctxVmm context is initialized from here onwards - vmm functionality is working! + return TRUE; +} + +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt) +{ + return VMMDLL_InitializeReserved(5, (LPSTR[]) { "", "-device", szFileName, "-cr3", (szPageTableBaseOpt ? szPageTableBaseOpt : "0") }); +} + +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt) +{ + return VMMDLL_InitializeReserved(7, (LPSTR[]) { "", "-device", "fpga", "-cr3", (szPageTableBaseOpt ? szPageTableBaseOpt : "0", "-max", szMaxPhysicalAddressOpt) }); +} + +BOOL VMMDLL_InitializeTotalMeltdown() +{ + return VMMDLL_InitializeReserved(3, (LPSTR[]) { "", "-device", "totalmeltdown" }); +} + +BOOL VMMDLL_Close() +{ + VmmDll_FreeContext(); + return TRUE; +} + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +//----------------------------------------------------------------------------- + +BOOL VMMDLL_ConfigGet_VmmCore(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue) +{ + switch(fOption) { + case VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED: + *pqwValue = ctxVmm->ThreadProcCache.fEnabled ? 1 : 0; + break; + case VMMDLL_OPT_CONFIG_TICK_PERIOD: + *pqwValue = ctxVmm->ThreadProcCache.cMs_TickPeriod; + break; + case VMMDLL_OPT_CONFIG_READCACHE_TICKS: + *pqwValue = ctxVmm->ThreadProcCache.cTick_Phys; + break; + case VMMDLL_OPT_CONFIG_TLBCACHE_TICKS: + *pqwValue = ctxVmm->ThreadProcCache.cTick_TLB; + break; + case VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL: + *pqwValue = ctxVmm->ThreadProcCache.cTick_ProcPartial; + break; + case VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL: + *pqwValue = ctxVmm->ThreadProcCache.cTick_ProcTotal; + break; + case VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL: + *pqwValue = Statistics_CallGetEnabled() ? 1 : 0; + return TRUE; + default: + return FALSE; + } + return TRUE; +} + +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue) +{ + if(!pqwValue) { return FALSE; } + if(fOption & 0x40000000) { + if(fOption == VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR) { + *pqwValue = VMM_VERSION_MAJOR; + return TRUE; + } else if(fOption == VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR) { + *pqwValue = VMM_VERSION_MINOR; + return TRUE; + } else if(fOption == VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION) { + *pqwValue = VMM_VERSION_REVISION; + return TRUE; + } + } + if(!ctxVmm) { return FALSE; } + // core options affecting only vmm.dll + if(fOption & 0x40000000) { + return VMMDLL_ConfigGet_VmmCore(fOption, pqwValue); + } + // core options affecting both vmm.dll and pcileech.dll + if(fOption & 0x80000000) { + switch(fOption) { + case VMMDLL_OPT_CORE_PRINTF_ENABLE: + *pqwValue = ctxMain->cfg.fVerboseDll ? 1 : 0; + return TRUE; + case VMMDLL_OPT_CORE_VERBOSE: + *pqwValue = ctxMain->cfg.fVerbose ? 1 : 0; + return TRUE; + case VMMDLL_OPT_CORE_VERBOSE_EXTRA: + *pqwValue = ctxMain->cfg.fVerboseExtra ? 1 : 0; + return TRUE; + case VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP: + *pqwValue = ctxMain->cfg.fVerboseExtraTlp ? 1 : 0; + return TRUE; + case VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS: + *pqwValue = ctxMain->dev.paAddrMaxNative; + return TRUE; + case VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE: + *pqwValue = ctxMain->dev.qwMaxSizeMemIo; + return TRUE; + case VMMDLL_OPT_CORE_TARGET_SYSTEM: + *pqwValue = ctxVmm->fTargetSystem; + return TRUE; + default: + return FALSE; + } + } + // non-recognized option - possibly a device option to pass along to pcileech.dll + return DeviceGetOption(fOption, pqwValue); +} + +BOOL VMMDLL_ConfigSet_VmmCore(_In_ ULONG64 fOption, _In_ ULONG64 qwValue) +{ + switch(fOption) { + case VMMDLL_OPT_CONFIG_TICK_PERIOD: + ctxVmm->ThreadProcCache.cMs_TickPeriod = (DWORD)qwValue; + break; + case VMMDLL_OPT_CONFIG_READCACHE_TICKS: + ctxVmm->ThreadProcCache.cTick_Phys = (DWORD)qwValue; + break; + case VMMDLL_OPT_CONFIG_TLBCACHE_TICKS: + ctxVmm->ThreadProcCache.cTick_TLB = (DWORD)qwValue; + break; + case VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL: + ctxVmm->ThreadProcCache.cTick_ProcPartial = (DWORD)qwValue; + break; + case VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL: + ctxVmm->ThreadProcCache.cTick_ProcTotal = (DWORD)qwValue; + break; + case VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL: + Statistics_CallSetEnabled(qwValue ? TRUE : FALSE); + return TRUE; + default: + return FALSE; + } + return TRUE; +} + +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue) +{ + if(!ctxVmm) { return FALSE; } + // core options affecting only vmm.dll + if(fOption & 0x40000000) { + return VMMDLL_ConfigSet_VmmCore(fOption, qwValue); + } + // core options affecting both vmm.dll and pcileech.dll + if(fOption & 0x80000000) { + DeviceSetOption(fOption, qwValue); // also set option in pcileech.dll (if existing). + switch(fOption) { + case VMMDLL_OPT_CORE_PRINTF_ENABLE: + ctxMain->cfg.fVerboseDll = qwValue ? TRUE : FALSE; + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_NOLOG, + PluginManager_Notify(VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE, NULL, 0)) + return TRUE; + case VMMDLL_OPT_CORE_VERBOSE: + ctxMain->cfg.fVerbose = qwValue ? TRUE : FALSE; + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_NOLOG, + PluginManager_Notify(VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE, NULL, 0)) + return TRUE; + case VMMDLL_OPT_CORE_VERBOSE_EXTRA: + ctxMain->cfg.fVerboseExtra = qwValue ? TRUE : FALSE; + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_NOLOG, + PluginManager_Notify(VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE, NULL, 0)) + return TRUE; + case VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP: + ctxMain->cfg.fVerboseExtraTlp = qwValue ? TRUE : FALSE; + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_NOLOG, + PluginManager_Notify(VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE, NULL, 0)) + return TRUE; + default: + return FALSE; + } + } + // non-recognized option - possibly a device option to pass along to pcileech.dll + return DeviceSetOption(fOption, qwValue); +} + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_VfsList, + VmmVfs_List(wcsPath, (PHANDLE)pFileList)) +} + +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_NTSTATUS( + STATISTICS_ID_VMMDLL_VfsRead, + VmmVfs_Read(wcsFileName, pb, cb, pcbRead, cbOffset)) +} + +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM_NTSTATUS( + STATISTICS_ID_VMMDLL_VfsWrite, + VmmVfs_Write(wcsFileName, pb, cb, pcbWrite, cbOffset)) +} + +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset) +{ + return Util_VfsReadFile_FromPBYTE(pbFile, cbFile, pb, cb, pcbRead, cbOffset); +} + +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix) +{ + return Util_VfsReadFile_FromQWORD(qwValue, pb, cb, pcbRead, cbOffset, fPrefix); +} + +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix) +{ + return Util_VfsReadFile_FromDWORD(dwValue, pb, cb, pcbRead, cbOffset, fPrefix); +} + +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset) +{ + return Util_VfsReadFile_FromBOOL(fValue, pb, cb, pcbRead, cbOffset); +} + +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset) +{ + return Util_VfsWriteFile_BOOL(pfTarget, pb, cb, pcbWrite, cbOffset); +} + +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow) +{ + return Util_VfsWriteFile_DWORD(pdwTarget, pb, cb, pcbWrite, cbOffset, dwMinAllow); +} + + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +BOOL VMMDLL_VfsInitializePlugins() +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_VfsInitializePlugins, + PluginManager_Initialize()) +} + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags) +{ + DWORD i, cMEMs; + PVMM_PROCESS pProcess = NULL; + if(!ctxVmm) { return 0; } + VmmLockAcquire(); + if(dwPID == -1) { + VmmReadScatterPhysical((PPMEM_IO_SCATTER_HEADER)ppMEMs, cpMEMs, flags); + } else { + pProcess = VmmProcessGet(dwPID); + if(!pProcess) { + VmmLockRelease(); + return FALSE; + } + VmmReadScatterVirtual(pProcess, (PPMEM_IO_SCATTER_HEADER)ppMEMs, cpMEMs, flags); + } + for(i = 0, cMEMs = 0; i < cpMEMs; i++) { + if(ppMEMs[i]->cb == ppMEMs[i]->cbMax) { + cMEMs++; + } + } + VmmLockRelease(); + return cMEMs; +} + +BOOL VMMDLL_MemReadEx_Impl(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags) +{ + PVMM_PROCESS pProcess = NULL; + if(dwPID != -1) { + pProcess = VmmProcessGet(dwPID); + if(!pProcess) { return FALSE; } + } + VmmReadEx(pProcess, qwVA, pb, cb, pcbReadOpt, flags); + return TRUE; +} + +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_MemReadEx, + VMMDLL_MemReadEx_Impl(dwPID, qwVA, pb, cb, pcbReadOpt, flags)) +} + +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb) +{ + DWORD dwRead; + return VMMDLL_MemReadEx(dwPID, qwVA, pb, cb, &dwRead, 0) && (dwRead == cb); +} + +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage) +{ + DWORD dwRead; + return VMMDLL_MemReadEx(dwPID, qwVA, pbPage, 4096, &dwRead, 0) && (dwRead == 4096); +} + +BOOL VMMDLL_MemWrite_Impl(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb) +{ + PVMM_PROCESS pProcess = NULL; + if(dwPID != -1) { + pProcess = VmmProcessGet(dwPID); + if(!pProcess) { return FALSE; } + } + return VmmWrite(pProcess, qwVA, pb, cb); +} + +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_MemWrite, + VMMDLL_MemWrite_Impl(dwPID, qwVA, pb, cb)) +} + +BOOL VMMDLL_MemVirt2Phys_Impl(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA) +{ + PVMM_PROCESS pProcess = VmmProcessGet(dwPID); + if(!pProcess) { return FALSE; } + return VmmVirt2Phys(pProcess, qwVA, pqwPA); +} + +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_MemVirt2Phys, + VMMDLL_MemVirt2Phys_Impl(dwPID, qwVA, pqwPA)) +} + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +BOOL VMMDLL_ProcessGetMemoryMap_Impl(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules) +{ + PVMM_PROCESS pProcess = VmmProcessGet(dwPID); + if(!pProcess) { return FALSE; } + if(!pProcess->pMemMap || !pProcess->cMemMap) { + if(!pProcess->fSpiderPageTableDone) { + VmmTlbSpider(pProcess->paPML4, pProcess->fUserOnly); + pProcess->fSpiderPageTableDone = TRUE; + } + VmmMapInitialize(pProcess); + if(fIdentifyModules) { + VmmProc_InitializeModuleNames(pProcess); + } + } + if(!pMemMapEntries) { + *pcMemMapEntries = pProcess->cMemMap; + } else { + if(!pProcess->pMemMap || (*pcMemMapEntries < pProcess->cMemMap)) { return FALSE; } + memcpy(pMemMapEntries, pProcess->pMemMap, sizeof(VMMDLL_MEMMAP_ENTRY) * pProcess->cMemMap); + *pcMemMapEntries = pProcess->cMemMap; + } + return TRUE; +} + +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetMemoryMap, + VMMDLL_ProcessGetMemoryMap_Impl(dwPID, pMemMapEntries, pcMemMapEntries, fIdentifyModules)) +} + +BOOL VMMDLL_ProcessGetMemoryMapEntry_Impl(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules) +{ + PVMM_PROCESS pProcess = VmmProcessGet(dwPID); + PVMM_MEMMAP_ENTRY e; + if(!pProcess) { return FALSE; } + if(fIdentifyModules) { + VmmMapInitialize(pProcess); + VmmProc_InitializeModuleNames(pProcess); + } + if(!pProcess->pMemMap) { + VmmMapInitialize(pProcess); + } + e = VmmMapGetEntry(pProcess, va); + if(!e) { return FALSE; } + memcpy(pMemMapEntry, e, sizeof(VMM_MEMMAP_ENTRY)); + return TRUE; +} + +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetMemoryMapEntry, + VMMDLL_ProcessGetMemoryMapEntry_Impl(dwPID, pMemMapEntry, va, fIdentifyModules)) +} + +BOOL VMMDLL_ProcessGetModuleMap_Impl(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries) +{ + ULONG64 i; + PVMM_PROCESS pProcess = VmmProcessGet(dwPID); + if(!pProcess) { return FALSE; } + if(!pcModuleEntries) { return FALSE; } + if(!pProcess->pModuleMap || !pProcess->cModuleMap) { + if(!pProcess->fSpiderPageTableDone) { + VmmTlbSpider(pProcess->paPML4, pProcess->fUserOnly); + pProcess->fSpiderPageTableDone = TRUE; + } + VmmProc_InitializeModuleNames(pProcess); + } + if(!pModuleEntries) { + *pcModuleEntries = pProcess->cModuleMap; + } else { + if(!pProcess->pModuleMap || (*pcModuleEntries < pProcess->cModuleMap)) { return FALSE; } + for(i = 0; i < pProcess->cModuleMap; i++) { + memcpy(pModuleEntries + i, pProcess->pModuleMap + i, sizeof(VMMDLL_MODULEMAP_ENTRY)); + } + *pcModuleEntries = pProcess->cModuleMap; + } + return TRUE; +} + +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetModuleMap, + VMMDLL_ProcessGetModuleMap_Impl(dwPID, pModuleEntries, pcModuleEntries)) +} + +BOOL VMMDLL_ProcessGetModuleFromName_Impl(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry) +{ + BOOL result; + ULONG64 i, cModuleEntries; + PVMMDLL_MODULEMAP_ENTRY pModuleEntries = NULL; + result = VMMDLL_ProcessGetModuleMap_Impl(dwPID, NULL, &cModuleEntries); + if(!result || !cModuleEntries) { return FALSE; } + pModuleEntries = (PVMMDLL_MODULEMAP_ENTRY)LocalAlloc(0, sizeof(VMMDLL_MODULEMAP_ENTRY) * cModuleEntries); + if(!pModuleEntries) { return FALSE; } + result = VMMDLL_ProcessGetModuleMap_Impl(dwPID, pModuleEntries, &cModuleEntries); + if(result && cModuleEntries) { + for(i = 0; i < cModuleEntries; i++) { + if(!_strnicmp(szModuleName, pModuleEntries[i].szName, 31)) { + memcpy(pModuleEntry, pModuleEntries + i, sizeof(VMMDLL_MODULEMAP_ENTRY)); + LocalFree(pModuleEntries); + return TRUE; + } + } + } + LocalFree(pModuleEntries); + return FALSE; +} + +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetModuleFromName, + VMMDLL_ProcessGetModuleFromName_Impl(dwPID, szModuleName, pModuleEntry)) +} + +BOOL VMMDLL_PidList_Impl(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs) +{ + VmmProcessListPIDs(pPIDs, pcPIDs); + return TRUE; +} + +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_PidList, + VMMDLL_PidList_Impl(pPIDs, pcPIDs)) +} + +BOOL VMMDLL_PidGetFromName_Impl(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID) +{ + DWORD i, pdwPIDs[1024]; + SIZE_T cPIDs = 1024; + PVMM_PROCESS pProcess; + VmmProcessListPIDs(pdwPIDs, &cPIDs); + for(i = 0; i < cPIDs; i++) { + pProcess = VmmProcessGet(pdwPIDs[i]); + if(!pProcess) { return FALSE; } + if(_strnicmp(szProcName, pProcess->szName, 15)) { continue; } + if(pProcess->dwState) { continue; } + *pdwPID = pdwPIDs[i]; + return TRUE; + } + return FALSE; +} + +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_PidGetFromName, + VMMDLL_PidGetFromName_Impl(szProcName, pdwPID)) +} + +BOOL VMMDLL_ProcessGetInformation_Impl(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pInfo, _In_ PSIZE_T pcbProcessInfo) +{ + PVMM_PROCESS pProcess; + if(!pcbProcessInfo) { return FALSE; } + if(!pInfo) { + *pcbProcessInfo = sizeof(VMMDLL_PROCESS_INFORMATION); + return TRUE; + } + if(*pcbProcessInfo < sizeof(VMMDLL_PROCESS_INFORMATION)) { return FALSE; } + if(pInfo->magic != VMMDLL_PROCESS_INFORMATION_MAGIC) { return FALSE; } + if(pInfo->wVersion != VMMDLL_PROCESS_INFORMATION_VERSION) { return FALSE; } + if(!(pProcess = VmmProcessGet(dwPID))) { return FALSE; } + ZeroMemory(pInfo, sizeof(VMMDLL_PROCESS_INFORMATION_MAGIC)); + // set general parameters + pInfo->wVersion = VMMDLL_PROCESS_INFORMATION_VERSION; + pInfo->wSize = sizeof(VMMDLL_PROCESS_INFORMATION); + pInfo->fTargetSystem = VMMDLL_TARGET_UNKNOWN_X64; + pInfo->fUserOnly = pProcess->fUserOnly; + pInfo->dwPID = dwPID; + pInfo->dwState = pProcess->dwState; + pInfo->paPML4 = pProcess->paPML4; + pInfo->paPML4_UserOpt = pProcess->paPML4_UserOpt; + memcpy(pInfo->szName, pProcess->szName, sizeof(pInfo->szName)); + // set windows specific parameters + if(ctxVmm->fTargetSystem == VMM_TARGET_WINDOWS_X64) { + pInfo->fTargetSystem = VMMDLL_TARGET_WINDOWS_X64; + pInfo->os.win.fWow64 = pProcess->os.win.fWow64; + pInfo->os.win.vaENTRY = pProcess->os.win.vaENTRY; + pInfo->os.win.vaEPROCESS = pProcess->os.win.vaEPROCESS; + pInfo->os.win.vaPEB = pProcess->os.win.vaPEB; + pInfo->os.win.vaPEB32 = pProcess->os.win.vaPEB32; + } + return TRUE; +} + +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetInformation, + VMMDLL_ProcessGetInformation_Impl(dwPID, pProcessInformation, pcbProcessInformation)) +} + +BOOL VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl( + _In_ DWORD dwPID, + _In_ LPSTR szModule, + _In_ DWORD cData, + _Out_ PDWORD pcData, + _Out_opt_ PIMAGE_DATA_DIRECTORY pDataDirectory, + _Out_opt_ PIMAGE_SECTION_HEADER pSections, + _Out_opt_ PVMMDLL_EAT_ENTRY pEAT, + _Out_opt_ PVOID pIAT, + BOOL _In_ fDataDirectory, + BOOL _In_ fSections, + BOOL _In_ fEAT, + BOOL _In_ fIAT +) +{ + DWORD i; + PVMM_MODULEMAP_ENTRY pModule = NULL; + PVMM_PROCESS pProcess = VmmProcessGet(dwPID); + if(!pProcess) { return FALSE; } + // genereate module map (if required) + if(!pProcess->pModuleMap || !pProcess->cModuleMap) { + if(!pProcess->fSpiderPageTableDone) { + VmmTlbSpider(pProcess->paPML4, pProcess->fUserOnly); + pProcess->fSpiderPageTableDone = TRUE; + } + VmmProc_InitializeModuleNames(pProcess); + if(!pProcess->pModuleMap || !pProcess->cModuleMap) { return FALSE; } + } + // fetch requested module + for(i = 0; i < pProcess->cModuleMap; i++) { + if(!_stricmp(pProcess->pModuleMap[i].szName, szModule)) { + pModule = &pProcess->pModuleMap[i]; + } + } + if(!pModule) { return FALSE; } + // data directories + if(fDataDirectory) { + if(!pDataDirectory) { *pcData = 16; return TRUE; } + if(cData < 16) { return FALSE; } + VmmProcWindows_PE_DIRECTORY_DisplayBuffer(pProcess, pModule, NULL, 0, NULL, pDataDirectory); + *pcData = 16; + return TRUE; + } + // sections + if(fSections) { + i = VmmProcWindows_PE_GetNumberOfSection(pProcess, pModule, NULL, FALSE); + if(!pSections) { *pcData = i; return TRUE; } + if(cData < i) { return FALSE; } + VmmProcWindows_PE_SECTION_DisplayBuffer(pProcess, pModule, NULL, 0, NULL, pSections); + *pcData = i; + return TRUE; + } + // export address table (EAT) + if(fEAT) { + i = VmmProcWindows_PE_GetNumberOfEAT(pProcess, pModule, NULL, FALSE); + if(!pEAT) { *pcData = i; return TRUE; } + if(cData < i) { return FALSE; } + VmmProcWindows_PE_LoadEAT_DisplayBuffer(pProcess, pModule, (PVMMPROC_WINDOWS_EAT_ENTRY)pEAT, &cData); + *pcData = cData; + return TRUE; + } + // import address table (IAT) + if(fIAT) { + i = VmmProcWindows_PE_GetNumberOfIAT(pProcess, pModule, NULL, FALSE); + if(!pIAT) { *pcData = i; return TRUE; } + if(cData < i) { return FALSE; } + VmmProcWindows_PE_LoadIAT_DisplayBuffer(pProcess, pModule, (PVMMPROC_WINDOWS_IAT_ENTRY)pIAT, &cData); + *pcData = cData; + return TRUE; + } + return FALSE; +} + +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetDirectories, + VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl(dwPID, szModule, cData, pcData, pData, NULL, NULL, NULL, TRUE, FALSE, FALSE, FALSE)) +} + +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetSections, + VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl(dwPID, szModule, cData, pcData, NULL, pData, NULL, NULL, FALSE, TRUE, FALSE, FALSE)) +} + +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetEAT, + VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl(dwPID, szModule, cData, pcData, NULL, NULL, pData, NULL, FALSE, FALSE, TRUE, FALSE)) +} + +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData) +{ + CALL_SYNCHRONIZED_IMPLEMENTATION_VMM( + STATISTICS_ID_VMMDLL_ProcessGetIAT, + VMMDLL_ProcessGet_Directories_Sections_IAT_EAT_Impl(dwPID, szModule, cData, pcData, NULL, NULL, NULL, pData, FALSE, FALSE, FALSE, TRUE)) +} + +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz) +{ + return Util_FillHexAscii(pb, cb, cbInitialOffset, sz, pcsz); +} diff --git a/vmm/vmmdll.def b/vmm/vmmdll.def new file mode 100644 index 0000000..8649344 --- /dev/null +++ b/vmm/vmmdll.def @@ -0,0 +1,45 @@ +LIBRARY VMM +EXPORTS + VMMDLL_InitializeReserved + VMMDLL_InitializeFile + VMMDLL_InitializeFPGA + VMMDLL_InitializeTotalMeltdown + VMMDLL_Close + + VMMDLL_ConfigGet + VMMDLL_ConfigSet + + VMMDLL_VfsList + VMMDLL_VfsRead + VMMDLL_VfsWrite + + VMMDLL_UtilVfsReadFile_FromPBYTE + VMMDLL_UtilVfsReadFile_FromQWORD + VMMDLL_UtilVfsReadFile_FromDWORD + VMMDLL_UtilVfsReadFile_FromBOOL + VMMDLL_UtilVfsWriteFile_BOOL + VMMDLL_UtilVfsWriteFile_DWORD + + VMMDLL_VfsInitializePlugins + + VMMDLL_MemReadScatter + VMMDLL_MemReadPage + VMMDLL_MemRead + VMMDLL_MemReadEx + VMMDLL_MemWrite + VMMDLL_MemVirt2Phys + + VMMDLL_PidList + VMMDLL_PidGetFromName + VMMDLL_ProcessGetMemoryMap + VMMDLL_ProcessGetMemoryMapEntry + VMMDLL_ProcessGetModuleMap + VMMDLL_ProcessGetModuleFromName + VMMDLL_ProcessGetInformation + + VMMDLL_ProcessGetDirectories + VMMDLL_ProcessGetSections + VMMDLL_ProcessGetEAT + VMMDLL_ProcessGetIAT + + VMMDLL_UtilFillHexAscii diff --git a/vmm/vmmdll.h b/vmm/vmmdll.h new file mode 100644 index 0000000..623a09b --- /dev/null +++ b/vmm/vmmdll.h @@ -0,0 +1,566 @@ +// vmmdll.h : header file to include in projects that use vmm.dll either as +// stand anlone projects or as native plugins to vmm.dll. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include + +#ifndef __VMMDLL_H__ +#define __VMMDLL_H__ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +/* +* RESERVED FUNCTION! DO NOT USE! +* Call other VMMDLL_Intialize functions to initialize VMM.DLL and the memory +* process file system. +*/ +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]); + +/* +* Initialize VMM.DLL from a memory dump file in raw format. VMM.DLL will be +* initialized in read-only mode. It's possible to optionally specify the page +* table base of the windows kernel (for full vmm features) or the page table +* base of a single 64-bit process in any x64 operating system. NB! usually it +* is not necessary to specify the PageTableBase - it will be auto-identified +* most often if the target is Windows. +* -- szFileName = the file name of the raw memory dump to use. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Intiailize VMM.DLL from a supported FPGA device over USB. VMM.DLL will be +* initialized in read/write mode upon success. Optionally it will be possible +* to specify the max physical address and the page table base of the kernel or +* process that should be investigated. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* -- szMaxPhysicalAddressOpt = max physical address of the target system as a +* hex string. Example: "0x8000000000". If zero value is given the max +* address will be auto-identified. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Initialize VMM.DLL from a the "Total Meltdown" CVE-2018-1038 vulnerability. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* initialized in read/write mode upon success. +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeTotalMeltdown(); + +/* +* Close an initialized instance of VMM.DLL and clean up all allocated resources +* including plugins, linked PCILeech.DLL and other memory resources. +* -- return = success/fail. +*/ +BOOL VMMDLL_Close(); + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +/* +* Options used together with the functions: VMMDLL_GetOption & VMMDLL_SetOption +* If VMM.DLL is chained with PCILEECH.DLL then required values will be passed +* along to PCILEECH.DLL when necessary. +* For more detailed information check the sources for individual device types. +*/ +#define VMMDLL_OPT_DEVICE_FPGA_PROBE_MAXPAGES 0x01 // RW +#define VMMDLL_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_RX 0x03 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_TX 0x04 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_READ 0x05 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE 0x06 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_WRITE 0x07 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_READ 0x08 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_RETRY_ON_ERROR 0x09 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DEVICE_ID 0x80 // R +#define VMMDLL_OPT_DEVICE_FPGA_FPGA_ID 0x81 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR 0x82 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR 0x83 // R + +#define VMMDLL_OPT_CORE_PRINTF_ENABLE 0x80000001 // RW +#define VMMDLL_OPT_CORE_VERBOSE 0x80000002 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R +#define VMMDLL_OPT_CORE_TARGET_SYSTEM 0x80000007 // R + +#define VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED 0x40000001 // R - 1/0 +#define VMMDLL_OPT_CONFIG_TICK_PERIOD 0x40000002 // RW - base tick period in ms +#define VMMDLL_OPT_CONFIG_READCACHE_TICKS 0x40000003 // RW - memory cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_TLBCACHE_TICKS 0x40000004 // RW - page table (tlb) cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL 0x40000005 // RW - process refresh (partial) period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL 0x40000006 // RW - process refresh (full) period (in ticks) +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR 0x40000007 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR 0x40000008 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION 0x40000009 // R +#define VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL 0x4000000A // RW - enable function call statistics (.status/statistics_fncall file) + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- pqwValue = pointer to ULONG64 to receive option value. +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- qwValue +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +// This is the core of the memory process file system. All implementation and +// analysis towards the file system is possible by using functionality below. +//----------------------------------------------------------------------------- + +#define VMMDLL_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMMDLL_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMMDLL_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMMDLL_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMMDLL_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +typedef struct tdVMMDLL_VFS_FILELIST { + VOID(*pfnAddFile) (_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved); + VOID(*pfnAddDirectory)(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved); + HANDLE h; +} VMMDLL_VFS_FILELIST, *PVMMDLL_VFS_FILELIST; + +/* +* Helper function macros for callbacks into the VMM_VFS_FILELIST structure. +*/ +#define VMMDLL_VfsList_AddFile(pFileList, szName, cb) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddFile(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, cb, NULL); } +#define VMMDLL_VfsList_AddDirectory(pFileList, szName) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddDirectory(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, NULL); } + +/* +* List a directory of files in the memory process file system. Directories and +* files will be listed by callbacks into functions supplied in the pFileList +* parameter. If information of an individual file is needed it's neccessary +* to list all files in its directory. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + +/* +* Read select parts of a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +* +*/ +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + +/* +* Write select parts to a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + +/* +* Utility functions for memory process file system read/write towards different +* underlying data representations. +*/ +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow); + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +// Function and structures to initialize and use the memory process file system +// plugin functionality. The plugin manager is started by a call to function: +// VMM_VfsInitializePlugins. Each built-in plugin and external plugin of which +// the DLL name matches m_*.dll will receive a call to its InitializeVmmPlugin +// function. The plugin/module may decide to call pfnPluginManager_Register to +// register plugins in the form of different names one or more times. +// Example of registration function in a plugin DLL below: +// 'VOID InitializeVmmPlugin(_In_ PVMM_PLUGIN_REGINFO pRegInfo)' +//----------------------------------------------------------------------------- + +/* +* Initialize all potential plugins, both built-in and external, that maps into +* the memory process file system. Please note that plugins are not loaded by +* default - they have to be explicitly loaded by calling this function. They +* will be unloaded on a general close of the vmm dll. +* -- return +*/ +BOOL VMMDLL_VfsInitializePlugins(); + +#define VMMDLL_PLUGIN_CONTEXT_MAGIC 0xc0ffee663df9301c +#define VMMDLL_PLUGIN_CONTEXT_VERSION 1 +#define VMMDLL_PLUGIN_REGINFO_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PLUGIN_REGINFO_VERSION 1 + +#define VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE 0x01 + +typedef struct tdVMMDLL_PLUGIN_CONTEXT { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD dwPID; + PHANDLE phModulePrivate; + PHANDLE phProcessPrivate; + PVOID pProcess; + LPSTR szModule; + LPSTR szPath; + PVOID pvReserved1; + PVOID pvReserved2; +} VMMDLL_PLUGIN_CONTEXT, *PVMMDLL_PLUGIN_CONTEXT; + +typedef struct tdVMMDLL_PLUGIN_REGINFO { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; + HMODULE hDLL; + HMODULE hReservedDll; // not for general use (only used for python). + BOOL(*pfnPluginManager_Register)(struct tdVMMDLL_PLUGIN_REGINFO *pPluginRegInfo); + PVOID pvReserved1; + PVOID pvReserved2; + // general plugin registration info to be filled out by the plugin below: + struct { + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + PVOID pvReserved1; + PVOID pvReserved2; + } reg_info; + // function plugin registration info to be filled out by the plugin below: + struct { + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + PVOID pvReserved1; + PVOID pvReserved2; + } reg_fn; +} VMMDLL_PLUGIN_REGINFO, *PVMMDLL_PLUGIN_REGINFO; + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +// Vmm core functaionlity such as read (and write) to both virtual and physical +// memory. NB! writing will only work if the target is supported - i.e. not a +// memory dump file... +// To read physical memory specify dwPID as (DWORD)-1 +//----------------------------------------------------------------------------- + +// FLAG used to supress the default read cache in calls to VMM_MemReadEx() +// which will lead to the read being fetched from the target system always. +// Cached page tables (used for translating virtual2physical) are still used. +#define VMMDLL_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMMDLL_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMMDLL_TARGET_UNKNOWN_X64 0x0001 +#define VMMDLL_TARGET_WINDOWS_X64 0x0002 + +typedef struct tdVMMDLL_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} VMMDLL_MEM_IO_SCATTER_HEADER, *PVMMDLL_MEM_IO_SCATTER_HEADER, **PPVMMDLL_MEM_IO_SCATTER_HEADER; + +/* +* Read memory in various non-contigious locations specified by the pointers to +* the items in the ppDMAs array. Result for each unit of work will be given +* individually. No upper limit of number of items to read, but no performance +* boost will be given if above hardware limit. Max size of each unit of work is +* one 4k page (4096 bytes). +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpDMAsRead = optional count of number of successfully read ppDMAs. +* -- flags = optional flags as given by VMM_FLAG_* +* -- return = the number of successfully read items. +*/ +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); + +/* +* Read a single 4096-byte page of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pbPage +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Read a contigious arbitrary amount of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious amount of memory and report the number of bytes read in pcbRead. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +* -- return = success/fail. NB! reads may report as success even if 0 bytes are +* read - it's recommended to verify pcbReadOpt parameter. +*/ +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + +/* +* Write a contigious arbitrary amount of memory. Please note some virtual memory +* such as pages of executables (such as DLLs) may be shared between different +* virtual memory over different processes. As an example a write to kernel32.dll +* in one process is likely to affect kernel32 in the whole system - in all +* processes. Heaps and Stacks and other memory are usually safe to write to. +* Please take care when writing to memory! +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Translate a virtual address to a physical address by walking the page tables +* of the specified process. +* -- dwPID +* -- qwVA +* -- pqwPA +* -- return = success/fail. +*/ +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +// Functionality below is mostly relating to Windows processes. +//----------------------------------------------------------------------------- + +/* +* Retrieve an active process given it's name. Please note that if multiple +* processes with the same name exists only one will be returned. If required to +* parse all processes with the same name please iterate over the PID list by +* calling VMMDLL_PidList together with VMMDLL_ProcessGetInformation. +* -- szProcName = process name (truncated max 15 chars) case insensitive. +* -- pdwPID = pointer that will receive PID on success. +* -- return +*/ +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + +/* +* List the PIDs in the system. +* -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. +* -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. +* -- return = success/fail. +*/ +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + +// flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define VMMDLL_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMMDLL_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMMDLL_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMMDLL_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +typedef struct tdVMMDLL_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMMDLL_MEMMAP_ENTRY, *PVMMDLL_MEMMAP_ENTRY; + +/* +* Retrieve memory map entries from the specified process. Memory map entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries bytes. +* If the pMemMapEntries is set to NULL the number of memory map entries will be +* given in the pcMemMapEntries parameter. +* -- dwPID +* -- pMemMapEntries = buffer of minimum length sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. +* -- pcMemMapEntries = pointer to number of memory map entries. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + +/* +* Retrieve a single memory map entry given a virtual address within that entrys +* range. +* -- dwPID +* -- pMemMapEntry +* -- va = virtual address in the memory map entry to retrieve. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules); + +typedef struct tdVMMDLL_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; +} VMMDLL_MODULEMAP_ENTRY, *PVMMDLL_MODULEMAP_ENTRY; + +/* +* Retrieve the module entries from the specified process. The module entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the +* pcModuleEntries is set to NULL the number of module entries will be given +* in the pcModuleEntries parameter. +* -- dwPID +* -- pModuleEntries = buffer of minimum length sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. +* -- pcModuleEntries = pointer to number of memory map entries. +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + +/* +* Retrieve a module (.exe or .dll or similar) given a module name. +* -- dwPID +* -- szModuleName +* -- pModuleEntry +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry); + +#define VMMDLL_PROCESS_INFORMATION_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PROCESS_INFORMATION_VERSION 1 + +typedef struct tdVMMDLL_PROCESS_INFORMATION { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; // as given by VMMDLL_TARGET_* + BOOL fUserOnly; // only user mode pages listed + DWORD dwPID; + DWORD dwState; + CHAR szName[16]; + ULONG64 paPML4; + ULONG64 paPML4_UserOpt; // may not exist + union { + struct { + ULONG64 vaEPROCESS; + ULONG64 vaPEB; + ULONG64 vaENTRY; + BOOL fWow64; + DWORD vaPEB32; // WoW64 only + } win; + } os; +} VMMDLL_PROCESS_INFORMATION, *PVMMDLL_PROCESS_INFORMATION; + +/* +* Retrieve various process information from a PID. Process information such as +* name, page directory bases and the process state may be retrieved. +* -- dwPID +* -- pProcessInformation = if null, size is given in *pcbProcessInfo +* -- pcbProcessInformation = size of pProcessInfo (in bytes) on entry and exit +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation); + +typedef struct tdVMMDLL_EAT_ENTRY { + ULONG64 vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMDLL_EAT_ENTRY, *PVMMDLL_EAT_ENTRY; + +typedef struct tdVMMDLL_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMDLL_IAT_ENTRY, *PVMMDLL_IAT_ENTRY; + +/* +* Retrieve information about: Data Directories, Sections, Export Address Table +* and Import Address Table (IAT). +* If the pData == NULL upon entry the number of entries of the pData array must +* have in order to be able to hold the data is returned. +* -- dwPID +* -- szModule +* -- pData +* -- cData +* -- pcData +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + + + +//----------------------------------------------------------------------------- +// VMM UTIL FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __VMMDLL_H__ */ diff --git a/vmm/vmmproc.c b/vmm/vmmproc.c new file mode 100644 index 0000000..ee32c92 --- /dev/null +++ b/vmm/vmmproc.c @@ -0,0 +1,364 @@ +// vfsproc.c : implementation of functions related to operating system and process parsing of virtual memory. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include "vmmproc.h" +#include "vmmproc_windows.h" +#include "device.h" +#include "statistics.h" +#include "util.h" + +// ---------------------------------------------------------------------------- +// GENERIC PROCESS RELATED FUNCTIONALITY BELOW: +// ---------------------------------------------------------------------------- + +/* +* see VmmProcPHYS_ScanWindowsKernel_LargePages for more information! +* Scan a page table hierarchy between virtual addresses between vaMin and vaMax +* for the first occurence of large 2MB pages. This is usually the ntoskrnl.exe +* if the OS is Windows. Ntoskrnl.exe is loaded between the virtual addresses: +* 0xFFFFF80000000000-0xFFFFF803FFFFFFFF +* -- ctxVmm, +* -- paTable = set to: physical address of PML4 +* -- vaBase = set to 0 +* -- vaMin = 0xFFFFF80000000000 (if windows kernel) +* -- vaMax = 0xFFFFF803FFFFFFFF (if windows kernel) +* -- cPML = set to 4 +* -- pvaBase +* -- pcbSize +*/ +VOID VmmProcPHYS_ScanWindowsKernel_LargePages_PageTableWalk(_In_ QWORD paTable, _In_ QWORD vaBase, _In_ QWORD vaMin, _In_ QWORD vaMax, _In_ BYTE cPML, _Inout_ PQWORD pvaBase, _Inout_ PQWORD pcbSize) +{ + const QWORD PML_REGION_SIZE[5] = { 0, 12, 21, 30, 39 }; + QWORD i, pte, *ptes, vaCurrent, vaSizeRegion; + ptes = (PQWORD)VmmTlbGetPageTable(paTable, FALSE); + if(!ptes) { return; } + if(cPML == 4) { + *pvaBase = 0; + *pcbSize = 0; + if(!VmmTlbPageTableVerify((PBYTE)ptes, paTable, TRUE)) { return; } + vaBase = 0; + } + for(i = 0; i < 512; i++) { + // address in range + vaSizeRegion = 1ULL << PML_REGION_SIZE[cPML]; + vaCurrent = vaBase + (i << PML_REGION_SIZE[cPML]); + vaCurrent |= (vaCurrent & 0x0000800000000000) ? 0xffff000000000000 : 0; // sign extend + if(*pvaBase && (vaCurrent >(*pvaBase + *pcbSize))) { return; } + if(vaCurrent < vaMin) { continue; } + if(vaCurrent > vaMax) { return; } + // check PTEs + pte = ptes[i]; + if(!(pte & 0x01)) { continue; } // NOT VALID + if(cPML == 2) { + if(!(pte & 0x80)) { continue; } + if(!*pvaBase) { *pvaBase = vaCurrent; } + *pcbSize += 0x200000; + continue; + } else { + if(pte & 0x80) { continue; } // PS = 1 + VmmProcPHYS_ScanWindowsKernel_LargePages_PageTableWalk(pte & 0x0000fffffffff000, vaCurrent, vaMin, vaMax, cPML - 1, pvaBase, pcbSize); + } + } +} + +/* +* Sometimes the PageDirectoryBase (PML4) is known, but the kernel location may +* be unknown. This functions walks the page table in the area in which ntorkrnl +* is loaded (0xFFFFF80000000000-0xFFFFF803FFFFFFFF) looking for 2MB large pages +* If an area in 2MB pages are found it is scanned for the ntoskrnl.exe base. +* -- paPML4 +* -- return = virtual address of ntoskrnl.exe base if successful, otherwise 0. +*/ +QWORD VmmProcPHYS_ScanWindowsKernel_LargePages(_In_ QWORD paPML4) +{ + PBYTE pbBuffer; + QWORD p, o, vaCurrentMin, vaBase, cbSize; + PVMM_PROCESS pSystemProcess = NULL; + BOOL fINITKDBG, fPOOLCODE; + vaCurrentMin = 0xFFFFF80000000000; // base of windows kernel possible location + while(TRUE) { + VmmProcPHYS_ScanWindowsKernel_LargePages_PageTableWalk(paPML4, 0, vaCurrentMin, 0xFFFFF803FFFFFFFF, 4, &vaBase, &cbSize); + if(!vaBase) { return 0; } + vaCurrentMin = vaBase + cbSize; + if(cbSize <= 0x00400000) { continue; } // too small + if(cbSize >= 0x01000000) { continue; } // too big + if(!pSystemProcess) { + pSystemProcess = VmmProcessCreateEntry(4, 0, paPML4, 0, "System", FALSE, FALSE); + if(!pSystemProcess) { return 0; } + VmmProcessCreateFinish(); + } + // try locate ntoskrnl.exe base inside suggested area + pbBuffer = (PBYTE)LocalAlloc(0, cbSize); + if(!pbBuffer) { return 0; } + VmmReadEx(pSystemProcess, vaBase, pbBuffer, (DWORD)cbSize, NULL, 0); + for(p = 0; p < cbSize; p += 0x1000) { + if(*(PWORD)(pbBuffer + p) != 0x5a4d) { continue; } + // check if module header contains INITKDBG and POOLCODE + fINITKDBG = FALSE; + fPOOLCODE = FALSE; + for(o = 0; o < 0x1000; o += 8) { + if(*(PQWORD)(pbBuffer + p + o) == 0x4742444B54494E49) { // INITKDBG + fINITKDBG = TRUE; + } + if(*(PQWORD)(pbBuffer + p + o) == 0x45444F434C4F4F50) { // POOLCODE + fPOOLCODE = TRUE; + } + if(fINITKDBG && fPOOLCODE) { + LocalFree(pbBuffer); + return vaBase + p; + } + } + } + LocalFree(pbBuffer); + } +} + +// ---------------------------------------------------------------------------- +// GENERIC PROCESS RELATED FUNCTIONALITY BELOW: +// ---------------------------------------------------------------------------- + +/* +* Try initialize from user supplied CR3/PML4 supplied in parameter at startup. +* -- ctx +* -- return +*/ +BOOL VmmProcUserCR3TryInitialize() +{ + PVMM_PROCESS pProcess; + pProcess = VmmProcessCreateEntry(0, 0, ctxMain->cfg.paCR3, 0, "unknown_process", FALSE, TRUE); + VmmProcessCreateFinish(); + if(!pProcess) { + vmmprintfv("VmmProc: FAIL: Initialization of Process failed from user-defined CR3 %016llx. #4.\n", ctxMain->cfg.paCR3); + return FALSE; + } + VmmTlbSpider(pProcess->paPML4, FALSE); + ctxVmm->fTargetSystem = VMM_TARGET_UNKNOWN_X64; + return TRUE; +} + +#define VMMPROC_UPDATERTHREAD_PERIOD 100 +#define VMMPROC_UPDATERTHREAD_PHYSCACHE (500 / VMMPROC_UPDATERTHREAD_PERIOD) // 0.5s +#define VMMPROC_UPDATERTHREAD_TLB (5 * 1000 / VMMPROC_UPDATERTHREAD_PERIOD) // 5s +#define VMMPROC_UPDATERTHREAD_PROC_REFRESHLIST (5 * 1000 / VMMPROC_UPDATERTHREAD_PERIOD) // 5s +#define VMMPROC_UPDATERTHREAD_PROC_REFRESHTOTAL (15 * 1000 / VMMPROC_UPDATERTHREAD_PERIOD) // 15s + +DWORD VmmProcCacheUpdaterThread() +{ + QWORD i = 0; + BOOL fPHYS, fTLB, fProcList, fProcTotal; + PVMM_PROCESS pSystemProcess; + QWORD paSystemPML4, vaSystemEPROCESS; + vmmprintfv("VmmProc: Start periodic cache flushing.\n"); + ctxVmm->ThreadProcCache.cMs_TickPeriod = VMMPROC_UPDATERTHREAD_PERIOD; + ctxVmm->ThreadProcCache.cTick_Phys = VMMPROC_UPDATERTHREAD_PHYSCACHE; + ctxVmm->ThreadProcCache.cTick_TLB = VMMPROC_UPDATERTHREAD_TLB; + ctxVmm->ThreadProcCache.cTick_ProcPartial = VMMPROC_UPDATERTHREAD_PROC_REFRESHLIST; + ctxVmm->ThreadProcCache.cTick_ProcTotal = VMMPROC_UPDATERTHREAD_PROC_REFRESHTOTAL; + while(ctxVmm->ThreadProcCache.fEnabled) { + Sleep(ctxVmm->ThreadProcCache.cMs_TickPeriod); + i++; + fTLB = !(i % ctxVmm->ThreadProcCache.cTick_TLB); + fPHYS = !(i % ctxVmm->ThreadProcCache.cTick_Phys); + fProcTotal = !(i % ctxVmm->ThreadProcCache.cTick_ProcTotal); + fProcList = !(i % ctxVmm->ThreadProcCache.cTick_ProcPartial) && !fProcTotal; + EnterCriticalSection(&ctxVmm->MasterLock); + // PHYS / TLB cache clear + if(fPHYS || fTLB) { + VmmCacheClear(fTLB, fPHYS); + ctxVmm->stat.cRefreshPhys += fPHYS ? 1 : 0; + ctxVmm->stat.cRefreshTlb += fTLB ? 1 : 0; + } + // refresh proc list + if(fProcList) { + ctxVmm->stat.cRefreshProcessPartial++; + // Windows OS + if(ctxVmm->fTargetSystem & VMM_TARGET_WINDOWS_X64) { + pSystemProcess = VmmProcessGet(4); + if(pSystemProcess) { + VmmProcWindows_EnumerateEPROCESS(pSystemProcess); + vmmprintfvv("VmmProc: vmmproc.c!VmmProcCacheUpdaterThread FlushProcessList\n"); + } + } + } + // total refresh of entire proc cache + if(fProcTotal) { + ctxVmm->stat.cRefreshProcessFull++; + // Windows OS + if(ctxVmm->fTargetSystem & VMM_TARGET_WINDOWS_X64) { + pSystemProcess = VmmProcessGet(4); + if(pSystemProcess) { + paSystemPML4 = pSystemProcess->paPML4; + vaSystemEPROCESS = pSystemProcess->os.win.vaEPROCESS; + // spider TLB and set up initial system process and enumerate EPROCESS + VmmTlbSpider(paSystemPML4, FALSE); + pSystemProcess = VmmProcessCreateEntry(4, 0, paSystemPML4, 0, "System", FALSE, TRUE); + if(!pSystemProcess) { + vmmprintf("VmmProc: Failed to refresh memory process file system - aborting.\n"); + VmmProcessCreateFinish(); + ctxVmm->ThreadProcCache.fEnabled = FALSE; + LeaveCriticalSection(&ctxVmm->MasterLock); + goto fail; + } + pSystemProcess->os.win.vaEPROCESS = vaSystemEPROCESS; + VmmProcWindows_EnumerateEPROCESS(pSystemProcess); + vmmprintfvv("VmmProc: vmmproc.c!VmmProcCacheUpdaterThread FlushProcessListAndBuffers\n"); + } + } + // Single user-defined X64 process + if(ctxVmm->fTargetSystem & VMM_TARGET_UNKNOWN_X64) { + VmmProcessCreateTable(); + VmmProcUserCR3TryInitialize(ctxVmm); + } + } + LeaveCriticalSection(&ctxVmm->MasterLock); + } +fail: + vmmprintfv("VmmProc: Exit periodic cache flushing.\n"); + ctxVmm->ThreadProcCache.hThread = NULL; + return 0; +} + +VOID VmmProc_InitializeModuleNames(_In_ PVMM_PROCESS pProcess) +{ + if(ctxVmm->fTargetSystem & VMM_TARGET_WINDOWS_X64) { + VmmProcWindows_InitializeModuleNames(pProcess); + } +} + +BOOL VmmProcInitialize() +{ + BOOL result; + QWORD vaKernelBase; + if(!VmmInitialize()) { return FALSE; } + // user supplied a CR3 - use it! + if(ctxMain->cfg.paCR3) { + // if VmmProcPHYS_ScanWindowsKernel_LargePages returns a value this is a + // Windows system - initialize it, otherwise initialize the generic x64 + // single process more basic mode. + result = FALSE; + vaKernelBase = VmmProcPHYS_ScanWindowsKernel_LargePages(ctxMain->cfg.paCR3); + if(vaKernelBase) { + result = VmmProcWindows_TryInitialize(ctxMain->cfg.paCR3, vaKernelBase); + } + if(!vaKernelBase) { + result = VmmProcUserCR3TryInitialize(ctxVmm); + if(!result) { + VmmInitialize(); // re-initialize VMM to clear state + } + } + if(!result) { + result = VmmProcUserCR3TryInitialize(ctxVmm); + } + } else { + // no page directory was found, so try initialize it by looking if the + // "low stub" exists on a Windows sytem and use it. Otherwise fail. + result = VmmProcWindows_TryInitialize(0, 0); + if(!result) { + vmmprintf( + "VmmProc: Unable to auto-identify operating system for PROC file system mount. \n" \ + " Please specify PageDirectoryBase (CR3/PML4) in the -cr3 option if value\n" \ + " is known. If unknown it may be recoverable with command 'identify'. \n"); + } + } + // set up cache mainenance in the form of a separate worker thread in case + // the backend is a writeable device (FPGA). File devices are read-only so + // far so full caching is enabled since they are considered to be read-only. + if(result && !ctxVmm->fReadOnly) { + ctxVmm->ThreadProcCache.fEnabled = TRUE; + ctxVmm->ThreadProcCache.hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)VmmProcCacheUpdaterThread, ctxVmm, 0, NULL); + if(!ctxVmm->ThreadProcCache.hThread) { ctxVmm->ThreadProcCache.fEnabled = FALSE; } + } + return result; +} + +// ---------------------------------------------------------------------------- +// SCAN/SEARCH TO IDENTIFY IMAGE: +// - Currently Windows PageDirectoryBase/CR3/PML4 detection is supported only +// ---------------------------------------------------------------------------- + +_Success_(return) +BOOL VmmProcPHYS_VerifyWindowsEPROCESS(_In_ PBYTE pb, _In_ QWORD cb, _In_ QWORD cbOffset, _Out_ PQWORD ppaPML4) +{ + QWORD i; + if(cb < cbOffset + 8) { return FALSE; } + if((cb & 0x07) || (cb < 0x500) || (cbOffset < 0x500)) { return FALSE; } + if(*(PQWORD)(pb + cbOffset) != 0x00006D6574737953) { return FALSE; } // not matching System00 + if(*(PQWORD)(pb + cbOffset + 8) & 0x00ffffffffffffff) { return FALSE; } // not matching 0000000 + // maybe we have EPROCESS struct here, scan back to see if we can find + // 4 kernel addresses in a row and a potential PML4 after that and zero + // DWORD before that. (EPROCESS HDR). + for(i = cbOffset; i > cbOffset - 0x500; i -= 8) { + if((*(PQWORD)(pb + i - 0x00) & 0xfffff00000000000)) { continue; }; // DirectoryTableBase + if(!*(PQWORD)(pb + i - 0x00)) { continue; }; // DirectoryTableBase + if((*(PQWORD)(pb + i - 0x08) & 0xffff800000000000) != 0xffff800000000000) { continue; }; // PTR + if((*(PQWORD)(pb + i - 0x10) & 0xffff800000000000) != 0xffff800000000000) { continue; }; // PTR + if((*(PQWORD)(pb + i - 0x18) & 0xffff800000000000) != 0xffff800000000000) { continue; }; // PTR + if((*(PQWORD)(pb + i - 0x20) & 0xffff800000000000) != 0xffff800000000000) { continue; }; // PTR + if((*(PDWORD)(pb + i - 0x24) != 0x00000000)) { continue; }; // SignalState + *ppaPML4 = *(PQWORD)(pb + i - 0x00) & ~0xfff; + return TRUE; + } + return FALSE; +} + +_Success_(return) +BOOL VmmProcPHYS_ScanForKernel(_Out_ PQWORD ppaPML4, _In_ QWORD paBase, _In_ QWORD paMax, _In_ LPSTR szDescription) +{ + QWORD o, i, paCurrent; + PAGE_STATISTICS pageStat; + PBYTE pbBuffer8M; + BOOL result; + // initialize / allocate memory + if(!(pbBuffer8M = LocalAlloc(0, 0x800000))) { return FALSE; } + ZeroMemory(&pageStat, sizeof(PAGE_STATISTICS)); + paCurrent = paBase; + PageStatInitialize(&pageStat, paCurrent, paMax, szDescription, FALSE, FALSE); + // loop kmd-find + for(; paCurrent < paMax; paCurrent += 0x00800000) { + if(!DeviceReadMEMEx(paCurrent, pbBuffer8M, 0x00800000, &pageStat)) { continue; } + for(o = 0; o < 0x00800000; o += 0x1000) { + // Scan for windows EPROCESS (to get DirectoryBase/PML4) + for(i = 0; i < 0x1000; i += 8) { + if(*(PQWORD)(pbBuffer8M + o + i) == 0x00006D6574737953) { + result = VmmProcPHYS_VerifyWindowsEPROCESS(pbBuffer8M, 0x00800000, o + i, ppaPML4); + if(result) { + pageStat.szAction = "Windows System PageDirectoryBase/PML4 located"; + LocalFree(pbBuffer8M); + PageStatClose(&pageStat); + return TRUE; + } + } + } + } + } + LocalFree(pbBuffer8M); + PageStatClose(&pageStat); + *ppaPML4 = 0; + return FALSE; +} + +BOOL VmmProcIdentify() +{ + QWORD paPML4; + BOOL result = FALSE; + vmmprintf( + "IDENTIFY: Scanning to identify target operating system and page directories...\n" + " Currently supported oprerating systems:\n" + " - Windows (64-bit).\n"); + if(ctxMain->cfg.paAddrMax > 0x100000000) { + result = VmmProcPHYS_ScanForKernel(&paPML4, 0x100000000, ctxMain->cfg.paAddrMax, "Scanning 4GB+ to Identify (1/2) ..."); + } + if(!result) { + result = VmmProcPHYS_ScanForKernel(&paPML4, 0x01000000, 0x100000000, "Scanning 0-4GB to Identify (2/2) ..."); + } + if(result) { + vmmprintf("IDENTIFY: Succeeded: Windows System page directory base is located at: 0x%llx\n", paPML4); + ctxMain->cfg.paCR3 = paPML4; + return TRUE; + } + vmmprintf("IDENTIFY: Failed. No fully supported operating system detected.\n"); + return FALSE; +} diff --git a/vmm/vmmproc.h b/vmm/vmmproc.h new file mode 100644 index 0000000..2ed1f55 --- /dev/null +++ b/vmm/vmmproc.h @@ -0,0 +1,33 @@ +// vmmproc.h : definitions related to operating system and process parsing of virtual memory +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __VMMPROC_H__ +#define __VMMPROC_H__ +#include "vmm.h" + +/* +* Load operating system dependant module names, such as parsed from PE or ELF +* into the proper display caches, and also into the memory map. +*/ +VOID VmmProc_InitializeModuleNames(_In_ PVMM_PROCESS pProcess); + +/* +* Tries to automatically identify the operating system given by the supplied +* memory device (fpga hardware or file). If an operating system is successfully +* identified a VMM_CONTEXT will be created and stored within the PCILEECH_CONTEXT. +* If the VMM fails to identify an operating system FALSE is returned. +* -- return +*/ +BOOL VmmProcInitialize(); + +/* +* Scans the memory for supported operating system structures, such as Windows +* page directory bases and update the ctxMain.cfg with the correct value upon +* success. +* -- return +*/ +BOOL VmmProcIdentify(); + +#endif /* __VMMPROC_H__ */ diff --git a/vmm/vmmproc_windows.c b/vmm/vmmproc_windows.c new file mode 100644 index 0000000..49b3674 --- /dev/null +++ b/vmm/vmmproc_windows.c @@ -0,0 +1,1009 @@ +// vmmproc_windows.h : implementation related to operating system and process +// parsing of virtual memory. Windows related features only. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include "vmmproc_windows.h" +#include "device.h" +#include "util.h" +#include + +// ---------------------------------------------------------------------------- +// WINDOWS SPECIFIC PROCESS RELATED FUNCTIONALITY BELOW: +// GENERAL FUNCTIONALITY +// ---------------------------------------------------------------------------- + +PIMAGE_NT_HEADERS VmmProcWindows_GetVerifyHeaderPE(_In_ PVMM_PROCESS pProcess, _In_opt_ QWORD vaModule, _Inout_ PBYTE pbModuleHeader, _Out_ PBOOL pfHdr32) +{ + PIMAGE_DOS_HEADER dosHeader; + PIMAGE_NT_HEADERS ntHeader; + *pfHdr32 = FALSE; + if(vaModule) { + if(!VmmReadPage(pProcess, vaModule, pbModuleHeader)) { return NULL; } + } + dosHeader = (PIMAGE_DOS_HEADER)pbModuleHeader; // dos header. + if(!dosHeader || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) { return NULL; } + if(dosHeader->e_lfanew > 0x800) { return NULL; } + ntHeader = (PIMAGE_NT_HEADERS)(pbModuleHeader + dosHeader->e_lfanew); // nt header + if(!ntHeader || ntHeader->Signature != IMAGE_NT_SIGNATURE) { return NULL; } + if((ntHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) && (ntHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)) { return NULL; } + *pfHdr32 = (ntHeader->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC); + return ntHeader; +} + +// ---------------------------------------------------------------------------- +// WINDOWS SPECIFIC PROCESS RELATED FUNCTIONALITY BELOW: +// IMPORT/EXPORT DIRECTORY PARSING +// ---------------------------------------------------------------------------- + +VOID VmmProcWindows_PE_SECTION_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_opt_ PBYTE pbDisplayBufferOpt, _In_ DWORD cbDisplayBufferMax, _Out_ PDWORD pcbDisplayBuffer, _Out_opt_ PIMAGE_SECTION_HEADER pSectionsOpt) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 ntHeader64; + PIMAGE_NT_HEADERS32 ntHeader32; + BOOL fHdr32; + DWORD i; + PIMAGE_SECTION_HEADER pSectionBase; + if(pcbDisplayBuffer) { *pcbDisplayBuffer = 0; } + if(!(ntHeader64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { return; } + ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; + pSectionBase = fHdr32 ? + (PIMAGE_SECTION_HEADER)((QWORD)ntHeader32 + sizeof(IMAGE_NT_HEADERS32)) : + (PIMAGE_SECTION_HEADER)((QWORD)ntHeader64 + sizeof(IMAGE_NT_HEADERS64)); + if(pbDisplayBufferOpt) { + for(i = 0; i < (DWORD)min(32, ntHeader64->FileHeader.NumberOfSections); i++) { + // 52 byte per line (indluding newline) + *pcbDisplayBuffer += snprintf( + pbDisplayBufferOpt + *pcbDisplayBuffer, + cbDisplayBufferMax - *pcbDisplayBuffer, + "%02x %-8.8s %016llx %08x %08x %c%c%c\n", + i, + pSectionBase[i].Name, + pModule->BaseAddress + pSectionBase[i].VirtualAddress, + pSectionBase[i].VirtualAddress, + pSectionBase[i].Misc.VirtualSize, + (pSectionBase[i].Characteristics & IMAGE_SCN_MEM_READ) ? 'r' : '-', + (pSectionBase[i].Characteristics & IMAGE_SCN_MEM_WRITE) ? 'w' : '-', + (pSectionBase[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) ? 'x' : '-' + ); + } + } + if(pSectionsOpt) { + memcpy(pSectionsOpt, pSectionBase, ntHeader64->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)); + } +} + +VOID VmmProcWindows_PE_DIRECTORY_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_opt_ PBYTE pbDisplayBufferOpt, _In_ DWORD cbDisplayBufferMax, _Out_ PDWORD pcbDisplayBuffer, _Out_opt_ PIMAGE_DATA_DIRECTORY pDataDirectoryOpt) +{ + LPCSTR DIRECTORIES[16] = { "EXPORT", "IMPORT", "RESOURCE", "EXCEPTION", "SECURITY", "BASERELOC", "DEBUG", "ARCHITECTURE", "GLOBALPTR", "TLS", "LOAD_CONFIG", "BOUND_IMPORT", "IAT", "DELAY_IMPORT", "COM_DESCRIPTOR", "RESERVED" }; + BYTE i, pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 ntHeader64; + PIMAGE_NT_HEADERS32 ntHeader32; + PIMAGE_DATA_DIRECTORY pDataDirectoryBase; + BOOL fHdr32; + if(pcbDisplayBuffer) { *pcbDisplayBuffer = 0; } + if(!(ntHeader64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { return; } + ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; + pDataDirectoryBase = fHdr32 ? ntHeader32->OptionalHeader.DataDirectory : ntHeader64->OptionalHeader.DataDirectory; + if(pbDisplayBufferOpt) { + for(i = 0; i < 16; i++) { + if(pbDisplayBufferOpt) { + *pcbDisplayBuffer += snprintf( + pbDisplayBufferOpt + *pcbDisplayBuffer, + cbDisplayBufferMax - *pcbDisplayBuffer, + "%x %-16.16s %016llx %08x %08x\n", + i, + DIRECTORIES[i], + pModule->BaseAddress + pDataDirectoryBase[i].VirtualAddress, + pDataDirectoryBase[i].VirtualAddress, + pDataDirectoryBase[i].Size + ); + } + } + } + if(pDataDirectoryOpt) { + memcpy(pDataDirectoryOpt, pDataDirectoryBase, 16 * sizeof(IMAGE_DATA_DIRECTORY)); + } +} + + +VOID VmmProcWindows_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_ PVMMPROC_WINDOWS_EAT_ENTRY pEATs, _Inout_ PDWORD pcEATs) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 ntHeader64; + PIMAGE_NT_HEADERS32 ntHeader32; + QWORD oExportDirectory, cbExportDirectory; + PBYTE pbExportDirectory = NULL; + PIMAGE_EXPORT_DIRECTORY pExportDirectory; + QWORD oNameOrdinal, ooName, oName, oFunction; + WORD wOrdinalFnIdx; + DWORD vaFunctionOffset; + BOOL fHdr32; + DWORD i, cEATs = *pcEATs; + *pcEATs = 0; + // load both 32/64 bit ntHeader (only one will be valid) + if(!(ntHeader64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { goto cleanup; } + ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; + // Load Export Address Table (EAT) + oExportDirectory = fHdr32 ? + ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress : + ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + cbExportDirectory = fHdr32 ? + ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size : + ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + if(!oExportDirectory || !cbExportDirectory || cbExportDirectory > 0x01000000) { goto cleanup; } + if(!(pbExportDirectory = LocalAlloc(0, cbExportDirectory))) { goto cleanup; } + if(!VmmRead(pProcess, pModule->BaseAddress + oExportDirectory, pbExportDirectory, (DWORD)cbExportDirectory)) { goto cleanup; } + // Walk exported functions + pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)pbExportDirectory; + for(i = 0; i < pExportDirectory->NumberOfNames && i < cEATs; i++) { + // + oNameOrdinal = pExportDirectory->AddressOfNameOrdinals + (i << 1); + if((oNameOrdinal - sizeof(WORD) - oExportDirectory) > cbExportDirectory) { continue; } + wOrdinalFnIdx = *(PWORD)(pbExportDirectory - oExportDirectory + oNameOrdinal); + // + ooName = pExportDirectory->AddressOfNames + (i << 2); + if((ooName - sizeof(DWORD) - oExportDirectory) > cbExportDirectory) { continue; } + oName = *(PDWORD)(pbExportDirectory - oExportDirectory + ooName); + if((oName - 2 - oExportDirectory) > cbExportDirectory) { continue; } + // + oFunction = pExportDirectory->AddressOfFunctions + (wOrdinalFnIdx << 2); + if((oFunction - sizeof(DWORD) - oExportDirectory) > cbExportDirectory) { continue; } + vaFunctionOffset = *(PDWORD)(pbExportDirectory - oExportDirectory + oFunction); + // store into caller supplied info struct + pEATs[i].vaFunctionOffset = vaFunctionOffset; + pEATs[i].vaFunction = pModule->BaseAddress + vaFunctionOffset; + strncpy_s(pEATs[i].szFunction, 40, (LPSTR)(pbExportDirectory - oExportDirectory + oName), _TRUNCATE); + } + *pcEATs = i; +cleanup: + LocalFree(pbExportDirectory); +} + +VOID VmmProcWindows_PE_LoadIAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_ PVMMPROC_WINDOWS_IAT_ENTRY pIATs, _Inout_ PDWORD pcIATs) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 ntHeader64; + PIMAGE_NT_HEADERS32 ntHeader32; + QWORD oImportDirectory; + PIMAGE_IMPORT_DESCRIPTOR pIID; + PQWORD pIAT64, pHNA64; + PDWORD pIAT32, pHNA32; + PBYTE pbModule; + DWORD cbModule, cbRead; + BOOL fHdr32, fFnName; + DWORD c, i, j, cIATs = *pcIATs; + *pcIATs = 0; + // Load the module + if(pModule->SizeOfImage > 0x01000000) { return; } + cbModule = pModule->SizeOfImage; + if(!(pbModule = LocalAlloc(LMEM_ZEROINIT, cbModule))) { return; } + VmmReadEx(pProcess, pModule->BaseAddress, pbModule, cbModule, &cbRead, 0); + if(cbRead <= 0x2000) { goto cleanup; } + // load both 32/64 bit ntHeader (only one will be valid) + if(!(ntHeader64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { goto cleanup; } + ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; + oImportDirectory = fHdr32 ? + ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress : + ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + if(!oImportDirectory || (oImportDirectory >= cbModule)) { goto cleanup; } + // Walk imported modules / functions + pIID = (PIMAGE_IMPORT_DESCRIPTOR)(pbModule + oImportDirectory); + i = 0, c = 0; + while((oImportDirectory + (i + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR) < cbModule) && pIID[i].FirstThunk) { + if(c >= cIATs) { break; } + if(pIID[i].Name > cbModule - 64) { i++; continue; } + if(fHdr32) { + // 32-bit PE + j = 0; + pIAT32 = (PDWORD)(pbModule + pIID[i].FirstThunk); + pHNA32 = (PDWORD)(pbModule + pIID[i].OriginalFirstThunk); + while(TRUE) { + if(c >= cIATs) { break; } + if((QWORD)(pIAT32 + j) + sizeof(DWORD) - (QWORD)pbModule > cbModule) { break; } + if((QWORD)(pHNA32 + j) + sizeof(DWORD) - (QWORD)pbModule > cbModule) { break; } + if(!pIAT32[j]) { break; } + if(!pHNA32[j]) { break; } + fFnName = (pHNA32[j] < cbModule - 40); + // store into caller supplied info struct + pIATs[c].vaFunction = pIAT32[j]; + strncpy_s(pIATs[c].szFunction, 40, (fFnName ? (LPSTR)(pbModule + pHNA32[j] + 2) : ""), _TRUNCATE); + strncpy_s(pIATs[c].szModule, 64, (LPSTR)(pbModule + pIID[i].Name), _TRUNCATE); + c++; + j++; + } + } else { + // 64-bit PE + j = 0; + pIAT64 = (PQWORD)(pbModule + pIID[i].FirstThunk); + pHNA64 = (PQWORD)(pbModule + pIID[i].OriginalFirstThunk); + while(TRUE) { + if(c >= cIATs) { break; } + if((QWORD)(pIAT64 + j) + sizeof(QWORD) - (QWORD)pbModule > cbModule) { break; } + if((QWORD)(pHNA64 + j) + sizeof(QWORD) - (QWORD)pbModule > cbModule) { break; } + if(!pIAT64[j]) { break; } + if(!pHNA64[j]) { break; } + fFnName = (pHNA64[j] < cbModule - 40); + // store into caller supplied info struct + pIATs[c].vaFunction = pIAT64[j]; + strncpy_s(pIATs[c].szFunction, 40, (fFnName ? (LPSTR)(pbModule + pHNA64[j] + 2) : ""), _TRUNCATE); + strncpy_s(pIATs[c].szModule, 64, (LPSTR)(pbModule + pIID[i].Name), _TRUNCATE); + c++; + j++; + } + } + i++; + } + *pcIATs = c; +cleanup: + LocalFree(pbModule); +} + +WORD VmmProcWindows_PE_GetNumberOfSection(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _In_opt_ PIMAGE_NT_HEADERS pbModuleHeaderOpt, _In_opt_ BOOL fHdr32) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 pNtHeader64; + PIMAGE_NT_HEADERS32 pNtHeader32; + // load both 32/64 bit ntHeader unless already supplied in parameter (only one of 32/64 bit hdr will be valid) + if(!(pNtHeader64 = pbModuleHeaderOpt ? pbModuleHeaderOpt : VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { return 0; } + pNtHeader32 = (PIMAGE_NT_HEADERS32)pNtHeader64; + // retrieve number of sections + return fHdr32 ? pNtHeader32->FileHeader.NumberOfSections : pNtHeader64->FileHeader.NumberOfSections; +} + +DWORD VmmProcWindows_PE_GetNumberOfIAT(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _In_opt_ PIMAGE_NT_HEADERS pbModuleHeaderOpt, _In_opt_ BOOL fHdr32) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 pNtHeader64; + PIMAGE_NT_HEADERS32 pNtHeader32; + DWORD cbImportDirectory, cbImportAddressTable, cIatEntries, cModules; + // load both 32/64 bit ntHeader unless already supplied in parameter (only one of 32/64 bit hdr will be valid) + if(!(pNtHeader64 = pbModuleHeaderOpt ? pbModuleHeaderOpt : VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { return 0; } + pNtHeader32 = (PIMAGE_NT_HEADERS32)pNtHeader64; + // Calculate the number of functions in the import address table (IAT). + // Number of functions = # IAT entries - # Imported modules + cbImportDirectory = fHdr32 ? + pNtHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size : + pNtHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; + cbImportAddressTable = fHdr32 ? + pNtHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size : + pNtHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size; + cIatEntries = cbImportAddressTable / (fHdr32 ? sizeof(DWORD) : sizeof(QWORD)); + cModules = cbImportDirectory / sizeof(IMAGE_IMPORT_DESCRIPTOR); + return cIatEntries - cModules; +} + +DWORD VmmProcWindows_PE_GetNumberOfEAT(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _In_opt_ PIMAGE_NT_HEADERS pbModuleHeaderOpt, _In_opt_ BOOL fHdr32) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 pNtHeader64; + PIMAGE_NT_HEADERS32 pNtHeader32; + QWORD va, vaExportDirectory; + IMAGE_EXPORT_DIRECTORY hdrExportDirectory; + // load both 32/64 bit ntHeader unless already supplied in parameter (only one of 32/64 bit hdr will be valid) + if(!(pNtHeader64 = pbModuleHeaderOpt ? pbModuleHeaderOpt : VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { return 0; } + pNtHeader32 = (PIMAGE_NT_HEADERS32)pNtHeader64; + // Calculate the number of functions in the export address table (EAT). + va = fHdr32 ? + pNtHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress : + pNtHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + vaExportDirectory = va ? pModule->BaseAddress + va : 0; + if(vaExportDirectory && VmmRead(pProcess, vaExportDirectory, (PBYTE)&hdrExportDirectory, sizeof(IMAGE_EXPORT_DIRECTORY)) && (hdrExportDirectory.NumberOfNames < 0x00010000)) { + return hdrExportDirectory.NumberOfNames; + } + return 0; +} + +VOID VmmProcWindows_PE_SetSizeSectionIATEAT_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS64 pNtHeaders64; + BOOL fHdr32; + // check if function is required + if(pModule->fLoadedEAT && pModule->fLoadedIAT) { return; } + // load both 32/64 bit ntHeader (only one will be valid) + if(!(pNtHeaders64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, pModule->BaseAddress, pbModuleHeader, &fHdr32))) { return; } + // calculate display buffer size of: SECTIONS, EAT, IAT + pModule->cbDisplayBufferSections = VmmProcWindows_PE_GetNumberOfSection(pProcess, pModule, pNtHeaders64, fHdr32) * 52; // each display buffer human readable line == 52 bytes. + if(!pModule->fLoadedEAT) { + pModule->cbDisplayBufferEAT = VmmProcWindows_PE_GetNumberOfEAT(pProcess, pModule, pNtHeaders64, fHdr32) * 64; // each display buffer human readable line == 64 bytes. + pModule->fLoadedEAT = TRUE; + } + if(!pModule->fLoadedIAT) { + pModule->cbDisplayBufferIAT = VmmProcWindows_PE_GetNumberOfIAT(pProcess, pModule, pNtHeaders64, fHdr32) * 128; // each display buffer human readable line == 128 bytes. + pModule->fLoadedIAT = TRUE; + } +} + +// ---------------------------------------------------------------------------- +// WINDOWS SPECIFIC PROCESS RELATED FUNCTIONALITY BELOW: +// PEB/LDR USER MODE PARSING CODE (64-bit and 32-bit) +// ---------------------------------------------------------------------------- + +typedef struct _LDR_MODULE { + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID BaseAddress; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING FullDllName; + UNICODE_STRING BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + LIST_ENTRY HashTableEntry; + ULONG TimeDateStamp; +} LDR_MODULE, *PLDR_MODULE; + +QWORD VmmProcWindows_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModule, _In_ LPSTR lpProcName); + +VOID VmmProcWindows_ScanLdrModules64(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModules, _Inout_ PDWORD pcModules, _In_ DWORD cModulesMax, _Out_ PBOOL fWow64) +{ + QWORD vaModuleLdrFirst, vaModuleLdr = 0; + BYTE pbPEB[sizeof(PEB)], pbPEBLdrData[sizeof(PEB_LDR_DATA)], pbLdrModule[sizeof(LDR_MODULE)]; + PPEB pPEB = (PPEB)pbPEB; + PPEB_LDR_DATA pPEBLdrData = (PPEB_LDR_DATA)pbPEBLdrData; + PLDR_MODULE pLdrModule = (PLDR_MODULE)pbLdrModule; + PVMM_MODULEMAP_ENTRY pModule; + *fWow64 = FALSE; + if(!pProcess->os.win.vaPEB) { return; } + if(!VmmRead(pProcess, pProcess->os.win.vaPEB, pbPEB, sizeof(PEB))) { return; } + if(!VmmRead(pProcess, (QWORD)pPEB->Ldr, pbPEBLdrData, sizeof(PEB_LDR_DATA))) { return; } + vaModuleLdr = vaModuleLdrFirst = (QWORD)pPEBLdrData->InMemoryOrderModuleList.Flink - 0x10; // InLoadOrderModuleList == InMemoryOrderModuleList - 0x10 + do { + if(!VmmRead(pProcess, vaModuleLdr, pbLdrModule, sizeof(LDR_MODULE))) { break; } + pModule = pModules + *pcModules; + pModule->BaseAddress = (QWORD)pLdrModule->BaseAddress; + pModule->EntryPoint = (QWORD)pLdrModule->EntryPoint; + pModule->SizeOfImage = (DWORD)pLdrModule->SizeOfImage; + pModule->fWoW64 = FALSE; + if(!pLdrModule->BaseDllName.Length) { break; } + if(!VmmReadString_Unicode2Ansi(pProcess, (QWORD)pLdrModule->BaseDllName.Buffer, pModule->szName, min(31, pLdrModule->BaseDllName.Length))) { break; } + *fWow64 = *fWow64 || !memcmp(pModule->szName, "wow64.dll", 10); + vmmprintfvv("vmmproc.c!VmmProcWindows_ScanLdrModules: %016llx %016llx %016llx %08x %i %s\n", vaModuleLdr, pModule->BaseAddress, pModule->EntryPoint, pModule->SizeOfImage, (pModule->fWoW64 ? 1 : 0), pModule->szName); + vaModuleLdr = (QWORD)pLdrModule->InLoadOrderModuleList.Flink; + *pcModules = *pcModules + 1; + } while((vaModuleLdr != vaModuleLdrFirst) && (*pcModules < cModulesMax)); +} + +typedef struct _UNICODE_STRING32 { + USHORT Length; + USHORT MaximumLength; + DWORD Buffer; +} UNICODE_STRING32; + +typedef struct _LDR_MODULE32 { + LIST_ENTRY32 InLoadOrderModuleList; + LIST_ENTRY32 InMemoryOrderModuleList; + LIST_ENTRY32 InInitializationOrderModuleList; + DWORD BaseAddress; + DWORD EntryPoint; + ULONG SizeOfImage; + UNICODE_STRING32 FullDllName; + UNICODE_STRING32 BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + LIST_ENTRY32 HashTableEntry; + ULONG TimeDateStamp; +} LDR_MODULE32, *PLDR_MODULE32; + +typedef struct _PEB_LDR_DATA32 { + BYTE Reserved1[8]; + DWORD Reserved2[3]; + LIST_ENTRY32 InMemoryOrderModuleList; +} PEB_LDR_DATA32, *PPEB_LDR_DATA32; + +typedef struct _PEB32 { + BYTE Reserved1[2]; + BYTE BeingDebugged; + BYTE Reserved2[1]; + DWORD Reserved3[2]; + DWORD Ldr; + // ... +} PEB32, *PPEB32; + +_Success_(return) +BOOL VmmProcWindows_ScanLdrModules32(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModules, _Inout_ PDWORD pcModules, _In_ DWORD cModulesMax) +{ + DWORD vaModuleLdrFirst32, vaModuleLdr32 = 0; + BYTE pbPEB32[sizeof(PEB32)], pbPEBLdrData32[sizeof(PEB_LDR_DATA32)], pbLdrModule32[sizeof(LDR_MODULE32)]; + PPEB32 pPEB32 = (PPEB32)pbPEB32; + PPEB_LDR_DATA32 pPEBLdrData32 = (PPEB_LDR_DATA32)pbPEBLdrData32; + PLDR_MODULE32 pLdrModule32 = (PLDR_MODULE32)pbLdrModule32; + PVMM_MODULEMAP_ENTRY pModule; + if(!pProcess->os.win.vaPEB) { return FALSE; } + if(!VmmRead(pProcess, pProcess->os.win.vaPEB32, pbPEB32, sizeof(PEB32))) { return FALSE; } + if(!VmmRead(pProcess, (DWORD)pPEB32->Ldr, pbPEBLdrData32, sizeof(PEB_LDR_DATA32))) { return FALSE; } + vaModuleLdr32 = vaModuleLdrFirst32 = (DWORD)pPEBLdrData32->InMemoryOrderModuleList.Flink - 0x08; // InLoadOrderModuleList == InMemoryOrderModuleList - 0x08 + do { + if(!VmmRead(pProcess, vaModuleLdr32, pbLdrModule32, sizeof(LDR_MODULE32))) { break; } + pModule = pModules + *pcModules; + pModule->BaseAddress = (QWORD)pLdrModule32->BaseAddress; + pModule->EntryPoint = (QWORD)pLdrModule32->EntryPoint; + pModule->SizeOfImage = (DWORD)pLdrModule32->SizeOfImage; + pModule->fWoW64 = TRUE; + if(!pLdrModule32->BaseDllName.Length) { break; } + if(!VmmReadString_Unicode2Ansi(pProcess, (QWORD)pLdrModule32->BaseDllName.Buffer, pModule->szName, min(31, pLdrModule32->BaseDllName.Length))) { break; } + vmmprintfvv("vmmproc.c!VmmProcWindows_ScanLdrModules32: %08x %08x %08x %08x %s\n", vaModuleLdr32, (DWORD)pModule->BaseAddress, (DWORD)pModule->EntryPoint, pModule->SizeOfImage, pModule->szName); + vaModuleLdr32 = (QWORD)pLdrModule32->InLoadOrderModuleList.Flink; + *pcModules = *pcModules + 1; + } while((vaModuleLdr32 != vaModuleLdrFirst32) && (*pcModules < cModulesMax)); + return TRUE; +} + +VOID VmmProcWindows_InitializeLdrModules(_In_ PVMM_PROCESS pProcess) +{ + PVMM_MODULEMAP_ENTRY pModules, pModule; + PBYTE pbResult; + DWORD i, o, cModules; + BOOL result, fWow64; + // clear out any previous data + if(pProcess->os.win.pbLdrModulesDisplayCache) { + LocalFree(pProcess->os.win.pbLdrModulesDisplayCache); + pProcess->os.win.pbLdrModulesDisplayCache = NULL; + pProcess->os.win.cbLdrModulesDisplayCache = 0; + } + pProcess->os.win.vaENTRY = 0; + // allocate and enumerate + pModules = (PVMM_MODULEMAP_ENTRY)LocalAlloc(LMEM_ZEROINIT, 512 * sizeof(VMM_MODULEMAP_ENTRY)); + if(!pModules) { goto fail; } + cModules = 0; + VmmProcWindows_ScanLdrModules64(pProcess, pModules, &cModules, 512, &fWow64); + if((cModules > 0) && (!pModules[cModules - 1].BaseAddress)) { cModules--; } + if(fWow64) { + pProcess->os.win.vaPEB32 = (DWORD)pProcess->os.win.vaPEB - 0x1000; + result = VmmProcWindows_ScanLdrModules32(pProcess, pModules, &cModules, 512); + if(!result) { + pProcess->os.win.vaPEB32 = (DWORD)pProcess->os.win.vaPEB + 0x1000; + result = VmmProcWindows_ScanLdrModules32(pProcess, pModules, &cModules, 512); + } + if(!result) { + pProcess->os.win.vaPEB32 = 0; + } + } + if((cModules > 0) && (!pModules[cModules - 1].BaseAddress)) { cModules--; } + if(!cModules) { goto fail; } + // generate display cache + pProcess->os.win.vaENTRY = pModules[0].EntryPoint; + pbResult = pProcess->os.win.pbLdrModulesDisplayCache = (PBYTE)LocalAlloc(LMEM_ZEROINIT, 89 * cModules); + if(!pbResult) { goto fail; } + for(i = 0, o = 0; i < cModules; i++) { + pModule = pModules + i; + if(!pModule->BaseAddress) { continue; } + o += snprintf( + pbResult + o, + 89, + "%04x %8x %016llx-%016llx %s %s\n", + i, + pModule->SizeOfImage >> 12, + pModule->BaseAddress, + pModule->BaseAddress + pModule->SizeOfImage - 1, + pModule->fWoW64 ? "32" : " ", + pModule->szName + ); + } + pProcess->os.win.fWow64 = fWow64; + // update memory map with names + for(i = 0; i < cModules; i++) { + pModule = pModules + i; + VmmMapTag(pProcess, pModule->BaseAddress, pModule->BaseAddress + pModule->SizeOfImage, pModule->szName, NULL, pModule->fWoW64); + } + pProcess->os.win.cbLdrModulesDisplayCache = o; + // copy modules map into Process struct + pProcess->pModuleMap = (PVMM_MODULEMAP_ENTRY)LocalAlloc(0, cModules * sizeof(VMM_MODULEMAP_ENTRY)); + if(!pProcess->pModuleMap) { goto fail; } + memcpy(pProcess->pModuleMap, pModules, cModules * sizeof(VMM_MODULEMAP_ENTRY)); + pProcess->cModuleMap = cModules; +fail: + LocalFree(pModules); +} + +// ---------------------------------------------------------------------------- +// WINDOWS SPECIFIC PROCESS RELATED FUNCTIONALITY BELOW: +// ---------------------------------------------------------------------------- + +/* +* Locate the virtual base address of ntoskrnl.exe given any address inside the +* kernel. Localization will be done by a scan-back method. A maximum of 16MB +* will be scanned back. +*/ +QWORD VmmProcWindows_FindNtoskrnl(_In_ PVMM_PROCESS pSystemProcess, _In_ QWORD vaKernelEntry) +{ + PBYTE pb; + QWORD vaBase, oPage, o, vaNtosBase = 0; + BOOL fINITKDBG, fPOOLCODE; + DWORD cbRead; + pb = LocalAlloc(0, 0x200000); + if(!pb) { goto cleanup; } + // Scan back in 2MB chunks a time, (ntoskrnl.exe is loaded in 2MB pages). + for(vaBase = vaKernelEntry & ~0x1fffff; vaBase + 0x02000000 > vaKernelEntry; vaBase -= 0x200000) { + VmmReadEx(pSystemProcess, vaBase, pb, 0x200000, &cbRead, 0); + // only fail here if all virtual memory in read fails. reason is that kernel is + // properly mapped in memory (with NX MZ header in separate page) with empty + // space before next valid kernel pages when running Virtualization Based Security. + if(!cbRead) { goto cleanup; } + for(oPage = 0; oPage < 0x200000; oPage += 0x1000) { + if(*(PWORD)(pb + oPage) == 0x5a4d) { // MZ header + fINITKDBG = FALSE; + fPOOLCODE = FALSE; + for(o = 0; o < 0x1000; o += 8) { + if(*(PQWORD)(pb + oPage + o) == 0x4742444B54494E49) { // INITKDBG + fINITKDBG = TRUE; + } + if(*(PQWORD)(pb + oPage + o) == 0x45444F434C4F4F50) { // POOLCODE + fPOOLCODE = TRUE; + } + if(fINITKDBG && fPOOLCODE) { + vaNtosBase = vaBase + oPage; + goto cleanup; + } + } + } + } + } +cleanup: + LocalFree(pb); + return vaNtosBase; +} + +/* +* Perform GetProcAddress given a PE header. +* NB! very messy code due to lots of sanity checks on untrusted data. +*/ +QWORD VmmProcWindows_GetProcAddress(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModule, _In_ LPSTR lpProcName) +{ + BYTE pbModuleHeader[0x1000]; + PIMAGE_NT_HEADERS32 ntHeader32; + PIMAGE_NT_HEADERS64 ntHeader64; + PDWORD pdwRVAAddrNames, pdwRVAAddrFunctions; + PWORD pwNameOrdinals; + DWORD i, cbProcName, cbExportDirectoryOffset; + LPSTR sz; + QWORD vaFnPtr; + QWORD vaExportDirectory; + DWORD cbExportDirectory; + PBYTE pbExportDirectory = NULL; + QWORD vaRVAAddrNames, vaNameOrdinals, vaRVAAddrFunctions; + BOOL fHdr32; + if(!(ntHeader64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, vaModule, pbModuleHeader, &fHdr32))) { goto cleanup; } + if(fHdr32) { // 32-bit PE + ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; + vaExportDirectory = vaModule + ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + cbExportDirectory = ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + } else { // 64-bit PE + vaExportDirectory = vaModule + ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + cbExportDirectory = ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + } + if((cbExportDirectory < sizeof(IMAGE_EXPORT_DIRECTORY)) || (cbExportDirectory > 0x01000000) || (vaExportDirectory == vaModule) || (vaExportDirectory > vaModule + 0x80000000)) { goto cleanup; } + if(!(pbExportDirectory = LocalAlloc(0, cbExportDirectory))) { goto cleanup; } + if(!VmmRead(pProcess, vaExportDirectory, pbExportDirectory, cbExportDirectory)) { goto cleanup; } + PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY)pbExportDirectory; + if(!exp || !exp->NumberOfNames || !exp->AddressOfNames) { goto cleanup; } + vaRVAAddrNames = vaModule + exp->AddressOfNames; + vaNameOrdinals = vaModule + exp->AddressOfNameOrdinals; + vaRVAAddrFunctions = vaModule + exp->AddressOfFunctions; + if((vaRVAAddrNames < vaExportDirectory) || (vaRVAAddrNames > vaExportDirectory + cbExportDirectory - exp->NumberOfNames * sizeof(DWORD))) { goto cleanup; } + if((vaNameOrdinals < vaExportDirectory) || (vaNameOrdinals > vaExportDirectory + cbExportDirectory - exp->NumberOfNames * sizeof(WORD))) { goto cleanup; } + if((vaRVAAddrFunctions < vaExportDirectory) || (vaRVAAddrFunctions > vaExportDirectory + cbExportDirectory - exp->NumberOfNames * sizeof(DWORD))) { goto cleanup; } + cbProcName = (DWORD)strnlen_s(lpProcName, MAX_PATH) + 1; + cbExportDirectoryOffset = (DWORD)(vaExportDirectory - vaModule); + pdwRVAAddrNames = (PDWORD)(pbExportDirectory + exp->AddressOfNames - cbExportDirectoryOffset); + pwNameOrdinals = (PWORD)(pbExportDirectory + exp->AddressOfNameOrdinals - cbExportDirectoryOffset); + pdwRVAAddrFunctions = (PDWORD)(pbExportDirectory + exp->AddressOfFunctions - cbExportDirectoryOffset); + for(i = 0; i < exp->NumberOfNames; i++) { + if(pdwRVAAddrNames[i] - cbExportDirectoryOffset + cbProcName > cbExportDirectory) { continue; } + sz = (LPSTR)(pbExportDirectory + pdwRVAAddrNames[i] - cbExportDirectoryOffset); + if(0 == memcmp(sz, lpProcName, cbProcName)) { + if(pwNameOrdinals[i] >= exp->NumberOfFunctions) { goto cleanup; } + vaFnPtr = (QWORD)(vaModule + pdwRVAAddrFunctions[pwNameOrdinals[i]]); + LocalFree(pbExportDirectory); + return vaFnPtr; + } + } +cleanup: + LocalFree(pbExportDirectory); + return 0; +} + +/* +* Retrieve PE module name given a PE header. +* Function handles both 64-bit and 32-bit PE images. +* NB! very messy code due to lots of sanity checks on untrusted data. +*/ +_Success_(return) +BOOL VmmProcWindows_GetModuleName(_In_ PVMM_PROCESS pProcess, _In_ QWORD vaModule, _Out_ CHAR pbModuleName[MAX_PATH], _Out_ PDWORD pdwSize, _In_opt_ PBYTE pbPageMZHeaderPreCacheOpt, _In_ BOOL fDummyPENameOnExportDirectoryFail) +{ + PIMAGE_NT_HEADERS64 ntHeader64; + PIMAGE_NT_HEADERS32 ntHeader32; + PIMAGE_EXPORT_DIRECTORY exp; + QWORD vaExportDirectory; + DWORD cbImageSize, cbExportDirectory; + BYTE pbModuleHeader[0x1000], pbExportDirectory[sizeof(IMAGE_EXPORT_DIRECTORY)]; + BOOL fHdr32; + if(pbPageMZHeaderPreCacheOpt) { + memcpy(pbModuleHeader, pbPageMZHeaderPreCacheOpt, 0x1000); + ntHeader64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, 0, pbModuleHeader, &fHdr32); + } else { + ntHeader64 = VmmProcWindows_GetVerifyHeaderPE(pProcess, vaModule, pbModuleHeader, &fHdr32); + } + if(!ntHeader64) { return FALSE; } + if(!fHdr32) { // 64-bit PE + *pdwSize = ntHeader64->OptionalHeader.SizeOfImage; + vaExportDirectory = vaModule + ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + cbExportDirectory = ntHeader64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + cbImageSize = ntHeader64->OptionalHeader.SizeOfImage; + } else { // 32-bit PE + ntHeader32 = (PIMAGE_NT_HEADERS32)ntHeader64; + *pdwSize = ntHeader32->OptionalHeader.SizeOfImage; + vaExportDirectory = vaModule + ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + cbExportDirectory = ntHeader32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + cbImageSize = ntHeader32->OptionalHeader.SizeOfImage; + } + if((cbExportDirectory < sizeof(IMAGE_EXPORT_DIRECTORY)) || (vaExportDirectory == vaModule) || (cbExportDirectory > cbImageSize)) { goto fail; } + if(!VmmRead(pProcess, vaExportDirectory, pbExportDirectory, sizeof(IMAGE_EXPORT_DIRECTORY))) { goto fail; } + exp = (PIMAGE_EXPORT_DIRECTORY)pbExportDirectory; + if(!exp || !exp->Name || exp->Name > cbImageSize) { goto fail; } + pbModuleName[MAX_PATH - 1] = 0; + if(!VmmRead(pProcess, vaModule + exp->Name, pbModuleName, MAX_PATH - 1)) { goto fail; } + return TRUE; +fail: + if(fDummyPENameOnExportDirectoryFail) { + memcpy(pbModuleName, "UNKNOWN", 8); + return TRUE; + } + return FALSE; +} + +/* +* Load module proc names into memory map list if possible. +* NB! this function parallelize reads of MZ header candidates to speed things up. +*/ +VOID VmmProcWindows_ScanHeaderPE(_In_ PVMM_PROCESS pProcess) +{ + typedef struct tdMAP { + MEM_IO_SCATTER_HEADER dma; + PVMM_MEMMAP_ENTRY mme; + BYTE pb[0x1000]; + } MAP, *PMAP; + PMAP pMap, pMaps; + PPMEM_IO_SCATTER_HEADER ppDMAs; + PBYTE pbBuffer; + DWORD i, cDMAs = 0, cbImageSize; + BOOL result; + CHAR szBuffer[MAX_PATH]; + // 1: checks and allocate buffers for parallell read of MZ header candidates + if(!pProcess->cMemMap || !pProcess->pMemMap) { return; } + pbBuffer = LocalAlloc(LMEM_ZEROINIT, 0x400 * (sizeof(PMEM_IO_SCATTER_HEADER) + sizeof(MAP))); + if(!pbBuffer) { return; } + ppDMAs = (PPMEM_IO_SCATTER_HEADER)pbBuffer; + pMaps = (PMAP)(pbBuffer + 0x400 * sizeof(PMEM_IO_SCATTER_HEADER)); + // 2: scan memory map for MZ header candidates and put them on list for read + for(i = 0; i < pProcess->cMemMap - 1; i++) { + if( + (pProcess->pMemMap[i].cPages == 1) && // PE header is only 1 page + !(pProcess->pMemMap[i].AddrBase & 0xffff) && // starts at even 0x10000 offset + !pProcess->pMemMap[i].szTag[0] && // tag not already set + (pProcess->pMemMap[i].fPage & VMM_MEMMAP_FLAG_PAGE_NX) && // no-execute + !(pProcess->pMemMap[i + 1].fPage & VMM_MEMMAP_FLAG_PAGE_NX)) // next page is executable + { + pMap = pMaps + cDMAs; + pMap->mme = pProcess->pMemMap + i; + pMap->dma.cbMax = 0x1000; + pMap->dma.qwA = pProcess->pMemMap[i].AddrBase; + pMap->dma.pb = pMap->pb; + ppDMAs[cDMAs] = &pMap->dma; + cDMAs++; + if(cDMAs == 0x400) { break; } + } + } + // 3: read all MZ header candicates previously selected and try load name from them (after read is successful) + VmmReadScatterVirtual(pProcess, ppDMAs, cDMAs, 0); + for(i = 0; i < cDMAs; i++) { + if(pMaps[i].dma.cb == 0x1000) { + pMap = pMaps + i; + result = VmmProcWindows_GetModuleName(pProcess, pMap->mme->AddrBase, szBuffer, &cbImageSize, pMap->pb, TRUE); + if(result && (cbImageSize < 0x01000000)) { + VmmMapTag(pProcess, pMap->mme->AddrBase, pMap->mme->AddrBase + cbImageSize, szBuffer, NULL, FALSE); + } + } + } + LocalFree(pbBuffer); +} + +#define VMMPROC_EPROCESS_MAX_SIZE 0x500 + +/* +* Very ugly hack that tries to locate some offsets required withn the EPROCESS struct. +*/ +_Success_(return) +BOOL VmmProcWindows_OffsetLocatorEPROCESS(_In_ PVMM_PROCESS pSystemProcess, + _Out_ PDWORD pdwoState, _Out_ PDWORD pdwoPML4, _Out_ PDWORD pdwoName, _Out_ PDWORD pdwoPID, + _Out_ PDWORD pdwoFLink, _Out_ PDWORD pdwoPEB, _Out_ PDWORD dwoPML4_User) +{ + BOOL f; + DWORD i; + QWORD va1, vaPEB, paPEB; + BYTE pb0[VMMPROC_EPROCESS_MAX_SIZE], pb1[VMMPROC_EPROCESS_MAX_SIZE], pbPage[0x1000], pbZero[0x800]; + QWORD paMax, paPML4_0, paPML4_1; + if(!VmmRead(pSystemProcess, pSystemProcess->os.win.vaEPROCESS, pb0, 0x500)) { return FALSE; } + if(ctxMain->cfg.fVerboseExtra) { + vmmprintf("vmmproc.c!VmmProcWindows_OffsetLocatorEPROCESS: %016llx %016llx\n", pSystemProcess->paPML4, pSystemProcess->os.win.vaEPROCESS); + Util_PrintHexAscii(pb0, VMMPROC_EPROCESS_MAX_SIZE, 0); + } + // find offset State (static for now) + if(*(PDWORD)(pb0 + 0x04)) { return FALSE; } + *pdwoState = 0x04; + // find offset PML4 (static for now) + if(pSystemProcess->paPML4 != (0xfffffffffffff000 & *(PQWORD)(pb0 + 0x28))) { return FALSE; } + *pdwoPML4 = 0x28; + // find offset for Name + for(i = 0, f = FALSE; i < VMMPROC_EPROCESS_MAX_SIZE - 8; i += 8) { + if(*(PQWORD)(pb0 + i) == 0x00006D6574737953) { + *pdwoName = i; + f = TRUE; + break; + } + } + if(!f) { return FALSE; } + // find offset for PID, FLink, BLink (assumed to be following eachother) + for(i = 0, f = FALSE; i < VMMPROC_EPROCESS_MAX_SIZE - 8; i += 8) { + if(*(PQWORD)(pb0 + i) == 4) { + // PID = correct, this is a candidate + if(0xffff000000000000 != (0xffff000000000003 & *(PQWORD)(pb0 + i + 8))) { continue; } // FLink not valid kernel pointer + va1 = *(PQWORD)(pb0 + i + 8) - i - 8; + f = VmmRead(pSystemProcess, va1, pb1, VMMPROC_EPROCESS_MAX_SIZE); + if(!f) { continue; } + f = FALSE; + if((*(PQWORD)(pb1 + *pdwoName) != 0x6578652e73736d73) && // smss.exe + (*(PQWORD)(pb1 + *pdwoName) != 0x7972747369676552) && // Registry + (*(PQWORD)(pb1 + *pdwoName) != 0x5320657275636553)) // Secure System + { + continue; + } + if((*(PQWORD)(pb1 + i + 16) - i - 8) != pSystemProcess->os.win.vaEPROCESS) { + continue; + } + *pdwoPID = i; + *pdwoFLink = i + 8; + f = TRUE; + break; + } + } + if(!f) { return FALSE; } + // skip over "processes" without PEB + while((*(PQWORD)(pb1 + *pdwoName) == 0x5320657275636553) || // Secure System + (*(PQWORD)(pb1 + *pdwoName) == 0x7972747369676552)) // Registry + { + va1 = *(PQWORD)(pb1 + *pdwoFLink) - *pdwoFLink; + f = VmmRead(pSystemProcess, va1, pb1, VMMPROC_EPROCESS_MAX_SIZE); + if(!f) { return FALSE; } + } + if(ctxMain->cfg.fVerboseExtra) { + vmmprintf("---------------------------------------------------------------------------\n"); + Util_PrintHexAscii(pb1, VMMPROC_EPROCESS_MAX_SIZE, 0); + } + // find offset for PEB (in EPROCESS) + for(i = 0x300, f = FALSE; i < 0x480; i += 8) { + if(*(PQWORD)(pb0 + i)) { continue; } + vaPEB = *(PQWORD)(pb1 + i); + if(!vaPEB || (*(PQWORD)(pb1 + i) & 0xffff800000000fff)) { continue; } + // Verify potential PEB + if(!VmmReadPhysicalPage(*(PQWORD)(pb1 + *pdwoPML4), pbPage)) { continue; } + if(!VmmVirt2PhysEx(TRUE, vaPEB, 4, (PQWORD)pbPage, &paPEB)) { continue; } + if(!VmmReadPhysicalPage(paPEB, pbPage)) { continue; } + if(*(PWORD)pbPage == 0x5a4d) { continue; } // MZ header -> likely entry point or something not PEB ... + *pdwoPEB = i; + f = TRUE; + break; + } + if(!f) { return FALSE; } + // find "optional" offset for user cr3/pml4 (post meltdown only) + // System have an entry pointing to a shadow PML4 which has empty user part + // smss.exe do not have an entry since it's running as admin ... + *dwoPML4_User = 0; + ZeroMemory(pbZero, 0x800); + paMax = ctxMain->cfg.paAddrMax; + for(i = *pdwoPML4 + 8; i < VMMPROC_EPROCESS_MAX_SIZE - 8; i += 8) { + paPML4_0 = *(PQWORD)(pb0 + i); // EPROCESS entry item of System + paPML4_1 = *(PQWORD)(pb1 + i); // EPROCESS entry item of smss.exe + f = (paPML4_1 != 0); + f = f || (paPML4_0 == 0); + f = f || (paPML4_0 & 0xfff); + f = f || (paPML4_0 >= paMax); + f = f || !VmmReadPhysicalPage(paPML4_0, pbPage); + f = f || memcmp(pbPage, pbZero, 0x800); + f = f || !VmmTlbPageTableVerify(pbPage, paPML4_0, TRUE); + if(!f) { + *dwoPML4_User = i; + break; + } + } + return TRUE; +} + +/* +* Try walk the EPROCESS list in the Windows kernel to enumerate processes into +* the VMM/PROC file system. +* NB! This may be done to refresh an existing PID cache hence migration code. +* -- pSystemProcess +* -- return +*/ +BOOL VmmProcWindows_EnumerateEPROCESS(_In_ PVMM_PROCESS pSystemProcess) +{ + DWORD dwoState, dwoPML4, dwoPML4_User, dwoName, dwoPID, dwoFLink, dwoPEB, dwoMax; + PQWORD pqwPML4, pqwPML4_User, pqwFLink, pqwPEB; + PDWORD pdwState, pdwPID; + LPSTR szName; + BYTE pb[VMMPROC_EPROCESS_MAX_SIZE]; + BOOL result, fSystem, fKernel; + PVMM_PROCESS pVmmProcess; + QWORD vaSystemEPROCESS, vaEPROCESS, cPID = 0; + // retrieve offsets + vaSystemEPROCESS = pSystemProcess->os.win.vaEPROCESS; + result = VmmProcWindows_OffsetLocatorEPROCESS(pSystemProcess, &dwoState, &dwoPML4, &dwoName, &dwoPID, &dwoFLink, &dwoPEB, &dwoPML4_User); + if(!result) { + vmmprintf("VmmProc: Unable to locate EPROCESS offsets.\n"); + return FALSE; + } + vmmprintfvv("vmmproc.c!VmmProcWindows_EnumerateEPROCESS: %016llx %016llx\n", pSystemProcess->paPML4, vaSystemEPROCESS); + dwoMax = min(VMMPROC_EPROCESS_MAX_SIZE, 16 + max(max(dwoState, dwoPID), max(max(dwoPML4, dwoFLink), max(dwoName, dwoPEB)))); + pdwState = (PDWORD)(pb + dwoState); + pdwPID = (PDWORD)(pb + dwoPID); + pqwPML4 = (PQWORD)(pb + dwoPML4); + pqwPML4_User = (PQWORD)(pb + dwoPML4_User); + pqwFLink = (PQWORD)(pb + dwoFLink); + szName = (LPSTR)(pb + dwoName); + pqwPEB = (PQWORD)(pb + dwoPEB); + // SCAN! + if(!VmmRead(pSystemProcess, vaSystemEPROCESS, pb, dwoMax)) { return FALSE; } + vaEPROCESS = vaSystemEPROCESS; + while(TRUE) { + cPID++; + fSystem = (*pdwPID == 4); + fKernel = fSystem || ((*pdwState == 0) && (*pqwPEB == 0)); + // NB! Windows/Dokany does not support full 64-bit sizes on files, hence + // the max value 0x0001000000000000 for kernel space. Top 16-bits (ffff) + // are sign extended anyway so this should be fine if user skips them. + if(*pqwPML4 && *(PQWORD)szName) { + pVmmProcess = VmmProcessCreateEntry( + *pdwPID, + *pdwState, + ~0xfff & *pqwPML4, + dwoPML4_User ? (~0xfff & *pqwPML4_User) : 0, + szName, + !fKernel, + fSystem); + } else { + pVmmProcess = NULL; + } + if(pVmmProcess) { + pVmmProcess->os.win.vaEPROCESS = vaEPROCESS; + pVmmProcess->os.win.vaPEB = *pqwPEB; + vmmprintfvv("vmmproc.c!VmmProcWindows_EnumerateEPROCESS: %016llx %016llx %016llx %08x %s\n", + pVmmProcess->paPML4, + pVmmProcess->os.win.vaEPROCESS, + pVmmProcess->os.win.vaPEB, + pVmmProcess->dwPID, + pVmmProcess->szName); + } else { + szName[14] = 0; // in case of bad string data ... + vmmprintfv("VMM: Skipping process due to parsing error.\n PML4: %016llx PID: %i STATE: %i EPROCESS: %016llx NAME: %s\n", ~0xfff & *pqwPML4, *pdwPID, *pdwState, vaEPROCESS, szName); + } + vaEPROCESS = *pqwFLink - dwoFLink; + if(vaEPROCESS == vaSystemEPROCESS) { + break; + } + if(!VmmRead(pSystemProcess, vaEPROCESS, pb, dwoMax)) { + break; + } + if(*pqwPML4 & 0xffffff0000000000) { + break; + } + if(0xffff000000000000 != (0xffff000000000003 & *pqwFLink)) { + break; + } + } + VmmProcessCreateFinish(); + return (cPID > 10); +} + +// ---------------------------------------------------------------------------- +// WINDOWS SPECIFIC IMAGE IDENTIFYING BELOW +// ---------------------------------------------------------------------------- + +/* +* Find and validate the low stub (loaded <1MB if exists). The low stub almost +* always exists on real hardware. It may be missing on virtual machines though. +* Upon success the PML4 and ntoskrnl.ese KernelEntry point are returned. +* NB! KernelEntry != Kernel Base +*/ +_Success_(return) +BOOL VmmProcWindows_FindValidateLowStub(_Out_ PQWORD ppaPML4, _Out_ PQWORD pvaKernelEntry) +{ + PBYTE pbLowStub; + DWORD o; + if(!(pbLowStub = LocalAlloc(LMEM_ZEROINIT, 0x100000))) { return FALSE; } + DeviceReadMEMEx(0, pbLowStub, 0x100000, NULL); + o = 0; + while(o < 0x100000) { + o += 0x1000; + if(0x00000001000600E9 != (0xffffffffffff00ff & *(PQWORD)(pbLowStub + o + 0x000))) { continue; } // START BYTES + if(0xfffff80000000000 != (0xfffff80000000000 & *(PQWORD)(pbLowStub + o + 0x070))) { continue; } // KERNEL ENTRY + if(0xffffff0000000fff & *(PQWORD)(pbLowStub + o + 0x0a0)) { continue; } // PML4 + *ppaPML4 = *(PQWORD)(pbLowStub + o + 0x0a0); + *pvaKernelEntry = *(PQWORD)(pbLowStub + o + 0x070); + LocalFree(pbLowStub); + return TRUE; + } + LocalFree(pbLowStub); + return FALSE; +} + +/* +* Try initialize the VMM from scratch with new WINDOWS support. +*/ +BOOL VmmProcWindows_TryInitialize(_In_opt_ QWORD paPML4Opt, _In_opt_ QWORD vaKernelBaseOpt) +{ + BOOL result; + PVMM_PROCESS pSystemProcess; + QWORD paPML4, vaKernelEntry, vaKernelBase, vaPsInitialSystemProcess, vaSystemEPROCESS; + // Fetch Directory Base (PML4) and Kernel Entry (if optional hints not supplied) + if(!paPML4Opt || !vaKernelBaseOpt) { + result = VmmProcWindows_FindValidateLowStub(&paPML4, &vaKernelEntry); + if(!result) { + vmmprintfv("VmmProc: Initialization Failed. Bad data #1.\n"); + return FALSE; + } + vaKernelBase = 0; + } else { + paPML4 = paPML4Opt; + vaKernelBase = vaKernelBaseOpt; // not entry here, but at least inside kernel ... + } + // Spider PML4 to speed things up + VmmTlbSpider(paPML4, FALSE); + // Pre-initialize System PID (required by VMM) + pSystemProcess = VmmProcessCreateEntry(4, 0, paPML4, 0, "System", FALSE, TRUE); + VmmProcessCreateFinish(); + if(!pSystemProcess) { + vmmprintfv("VmmProc: Initialization Failed. #4.\n"); + return FALSE; + } + // Locate Kernel Base (if required) + if(!vaKernelBase) { + vaKernelBase = VmmProcWindows_FindNtoskrnl(pSystemProcess, vaKernelEntry); + if(!vaKernelBase) { + vmmprintfv("VmmProc: Initialization Failed. Unable to locate kernel #5\n"); + vmmprintfvv("VmmProc: PML4: 0x%016llx PTR: %016llx\n", pSystemProcess->paPML4, vaKernelEntry); + return FALSE; + } + } + vmmprintfvv("VmmProc: INFO: Kernel Base located at %016llx.\n", vaKernelBase); + // Locate System EPROCESS + vaPsInitialSystemProcess = VmmProcWindows_GetProcAddress(pSystemProcess, vaKernelBase, "PsInitialSystemProcess"); + result = VmmRead(pSystemProcess, vaPsInitialSystemProcess, (PBYTE)&vaSystemEPROCESS, 8); + if(!result) { + vmmprintfv("VmmProc: Initialization Failed. Unable to locate EPROCESS. #6\n"); + return FALSE; + } + pSystemProcess->os.win.vaEPROCESS = vaSystemEPROCESS; + vmmprintfvv("VmmProc: INFO: PsInitialSystemProcess located at %016llx.\n", vaPsInitialSystemProcess); + vmmprintfvv("VmmProc: INFO: EPROCESS located at %016llx.\n", vaSystemEPROCESS); + // Enumerate processes + result = VmmProcWindows_EnumerateEPROCESS(pSystemProcess); + if(!result) { + vmmprintfv("VmmProc: Initialization Failed. Unable to walk EPROCESS. #7\n"); + return FALSE; + } + ctxVmm->fTargetSystem = VMM_TARGET_WINDOWS_X64; + return TRUE; +} + +VOID VmmProcWindows_InitializeModuleNames(_In_ PVMM_PROCESS pProcess) +{ + VmmProcWindows_InitializeLdrModules(pProcess); + VmmProcWindows_ScanHeaderPE(pProcess); +} diff --git a/vmm/vmmproc_windows.h b/vmm/vmmproc_windows.h new file mode 100644 index 0000000..56ff08e --- /dev/null +++ b/vmm/vmmproc_windows.h @@ -0,0 +1,116 @@ +// vmmproc_windows.h : definitions related to windows operating system and processes. +// parsing of virtual memory. Windows related features only. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __VMMPROC_WINDOWS_H__ +#define __VMMPROC_WINDOWS_H__ +#include "vmm.h" + +typedef struct tdVMMPROC_WINDOWS_EAT_ENTRY { + QWORD vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMPROC_WINDOWS_EAT_ENTRY, *PVMMPROC_WINDOWS_EAT_ENTRY; + +typedef struct tdVMMPROC_WINDOWS_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMPROC_WINDOWS_IAT_ENTRY, *PVMMPROC_WINDOWS_IAT_ENTRY; + +/* +* Load the size of the required display buffer for sections, imports and export +* into the pModule struct. The size is a direct consequence of the number of +* functions since fixed line sizes are used for all these types. Loading is +* done in a recource efficient way to minimize I/O as much as possible. +* -- pProcess +* -- pModule +*/ +VOID VmmProcWindows_PE_SetSizeSectionIATEAT_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule); + +/* +* Walk the export address table (EAT) from a given pProcess and store it in the +* in the caller supplied pEATs/pcEATs structures. +* -- pProcess +* -- pModule +* -- pEATs +* -- pcEATs = number max items of pEATs on entry, number of actual items of pEATs on exit +*/ +VOID VmmProcWindows_PE_LoadEAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_ PVMMPROC_WINDOWS_EAT_ENTRY pEATs, _Inout_ PDWORD pcEATs); + +/* +* Walk the import address table (IAT) from a given pProcess and store it in the +* in the caller supplied pIATs/pcIATs structures. +* -- pProcess +* -- pModule +* -- pIATs +* -- pcIATs = number max items of pIATs on entry, number of actual items of pIATs on exit +*/ +VOID VmmProcWindows_PE_LoadIAT_DisplayBuffer(_Inout_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _Out_ PVMMPROC_WINDOWS_IAT_ENTRY pIATs, _Inout_ PDWORD pcIATs); + +/* +* Fill the pbDisplayBuffer with a human readable version of the data directories. +* This is guaranteed to be exactly 864 bytes (excluding NULL terminator). +* Alternatively copy the 16 data directories into pDataDirectoryOpt. +* -- pProcess +* -- pModule +* -- pbDisplayBufferOpt +* -- cbDisplayBufferMax +* -- pcbDisplayBuffer +* -- pDataDirectoryOpt +*/ +VOID VmmProcWindows_PE_DIRECTORY_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_opt_ PBYTE pbDisplayBufferOpt, _In_ DWORD cbDisplayBufferMax, _Out_ PDWORD pcbDisplayBuffer, _Out_opt_ PIMAGE_DATA_DIRECTORY pDataDirectoryOpt); + +/* +* Fill the pbDisplayBuffer with a human readable version of the PE sections. +* Alternatively copy the sections into the pSectionsOpt buffer. +* -- pProcess +* -- pModule +* -- pbDisplayBufferOpt +* -- cbDisplayBufferMax +* -- pcbDisplayBuffer +* -- pSectionsOpt +*/ +VOID VmmProcWindows_PE_SECTION_DisplayBuffer(_In_ PVMM_PROCESS pProcess, _In_ PVMM_MODULEMAP_ENTRY pModule, _Out_opt_ PBYTE pbDisplayBufferOpt, _In_ DWORD cbDisplayBufferMax, _Out_ PDWORD pcbDisplayBuffer, _Out_opt_ PIMAGE_SECTION_HEADER pSectionsOpt); + +/* +* Retrieve the number of: sections, EAT entries or IAT entries depending on the +* function that is called. +* -- pProcess +* -- pModule +* -- pbModuleHeaderOpt = optional PIMAGE_NT_HEADERS structure (either 32 or 64-bit) +* -- fHdr32 = specified whether pbModuleHeaderOpt is a 32-bit or 64-bit header. +* -- return = the number of entries +*/ +WORD VmmProcWindows_PE_GetNumberOfSection(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _In_opt_ PIMAGE_NT_HEADERS pbModuleHeaderOpt, _In_opt_ BOOL fHdr32); +DWORD VmmProcWindows_PE_GetNumberOfEAT(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _In_opt_ PIMAGE_NT_HEADERS pbModuleHeaderOpt, _In_opt_ BOOL fHdr32); +DWORD VmmProcWindows_PE_GetNumberOfIAT(_In_ PVMM_PROCESS pProcess, _Inout_ PVMM_MODULEMAP_ENTRY pModule, _In_opt_ PIMAGE_NT_HEADERS pbModuleHeaderOpt, _In_opt_ BOOL fHdr32); + +/* +* Initialize the module names into the ctxVMM. This is performed by a PEB/Ldr +* scan of in-process memory structures. This may be unreliable of process is +* obfuscated. +* -- pProcess +*/ +VOID VmmProcWindows_InitializeModuleNames(_In_ PVMM_PROCESS pProcess); + +/* +* Try walk the EPROCESS list in the Windows kernel to enumerate processes into +* the VMM/PROC file system. +* NB! This may be done to refresh an existing PID cache hence migration code. +* -- pSystemProcess +* -- return +*/ +BOOL VmmProcWindows_EnumerateEPROCESS(_In_ PVMM_PROCESS pSystemProcess); + +/* +* Try initialize the VMM from scratch with new WINDOWS support. +* -- paPML4Opt +* -- vaKernelBaseOpt +* -- return +*/ +BOOL VmmProcWindows_TryInitialize(_In_opt_ QWORD paPML4Opt, _In_opt_ QWORD vaKernelBaseOpt); + +#endif /* __VMMPROC_WINDOWS_H__ */ diff --git a/vmm/vmmvfs.c b/vmm/vmmvfs.c new file mode 100644 index 0000000..a1e89ec --- /dev/null +++ b/vmm/vmmvfs.c @@ -0,0 +1,337 @@ +// vmmvfs.c : implementation related to virtual memory management / virtual file system interfacing. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#include "vmm.h" +#include "vmmdll.h" +#include "pluginmanager.h" +#include "vmmproc.h" +#include "vmmproc_windows.h" +#include "util.h" + +typedef struct tdVMMVFS_PATH { + CHAR _sz[MAX_PATH]; + BOOL fRoot; + BOOL fNamePID; + DWORD dwPID; + LPSTR szPath1; + LPSTR szPath2; +} VMMVFS_PATH, *PVMMVFS_PATH; + +BOOL VmmVfs_UtilVmmGetPidDirFile(_In_ LPCWSTR wcsFileName, _Inout_ PVMMVFS_PATH pPath) +{ + DWORD i = 0, iPID, iPath1 = 0, iPath2 = 0; + // 1: convert to ascii string + ZeroMemory(pPath, sizeof(VMMVFS_PATH)); + while(TRUE) { + if(i >= MAX_PATH) { return FALSE; } + if(wcsFileName[i] > 255) { return FALSE; } + pPath->_sz[i] = (CHAR)wcsFileName[i]; + if(wcsFileName[i] == 0) { break; } + i++; + } + // 1: Check for root only item + pPath->fNamePID = !_stricmp(pPath->_sz, "\\name"); + pPath->fRoot = pPath->fNamePID || !_stricmp(pPath->_sz, "\\pid"); + if(pPath->fRoot) { return TRUE; } + // 2: Check if starting with PID or NAME and move start index + if(!strncmp(pPath->_sz, "\\pid\\", 5)) { i = 5; } + if(!strncmp(pPath->_sz, "\\name\\", 6)) { i = 6; } + if(i == 0) { return FALSE; } + // 3: Locate start of PID number and 1st Path item (if any) + while((i < MAX_PATH) && pPath->_sz[i] && (pPath->_sz[i] != '\\')) { i++; } + if(pPath->_sz[i]) { iPath1 = i + 1; } + pPath->_sz[i] = 0; + i--; + while((i > 0) && (pPath->_sz[i] >= '0') && (pPath->_sz[i] <= '9')) { i--; } + iPID = i + 1; + pPath->dwPID = (DWORD)Util_GetNumeric(&pPath->_sz[iPID]); + if(!iPath1) { return TRUE; } + // 4: Locate 2nd Path item (if any) + i = iPath1; + while((i < MAX_PATH) && pPath->_sz[i] && (pPath->_sz[i] != '\\')) { i++; } + if(pPath->_sz[i]) { + iPath2 = i + 1; + pPath->_sz[i] = 0; + } + // 7: Finish + pPath->szPath1 = &pPath->_sz[iPath1]; + if(iPath2) { + pPath->szPath2 = &pPath->_sz[iPath2]; + } + return TRUE; +} + +// ---------------------------------------------------------------------------- +// FUNCTIONALITY RELATED TO: READ +// ---------------------------------------------------------------------------- + +NTSTATUS VmmVfsReadFileProcess(_In_ PVMMVFS_PATH pPath, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + PVMM_PROCESS pProcess; + BYTE pbBuffer[0x800]; + DWORD cbBuffer; + ZeroMemory(pbBuffer, 48); + if(!ctxVmm) { return VMM_STATUS_FILE_INVALID; } + pProcess = VmmProcessGet(pPath->dwPID); + if(!pProcess) { return VMM_STATUS_FILE_INVALID; } + // read memory from "vmem" file + if(!_stricmp(pPath->szPath1, "vmem")) { + VmmReadEx(pProcess, cbOffset, pb, cb, NULL, 0); + *pcbRead = cb; + return VMM_STATUS_SUCCESS; + } + // read the memory map + if(!_stricmp(pPath->szPath1, "map")) { + if(!pProcess->pbMemMapDisplayCache) { + VmmMapDisplayBufferGenerate(pProcess); + if(!pProcess->pbMemMapDisplayCache) { + return VMM_STATUS_FILE_INVALID; + } + } + return Util_VfsReadFile_FromPBYTE(pProcess->pbMemMapDisplayCache, pProcess->cbMemMapDisplayCache, pb, cb, pcbRead, cbOffset); + } + // read genereal numeric values from files, pml4, pid, name, virt + if(!_stricmp(pPath->szPath1, "pml4")) { + return Util_VfsReadFile_FromQWORD(pProcess->paPML4, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(pPath->szPath1, "pml4-user")) { + return Util_VfsReadFile_FromQWORD(pProcess->paPML4_UserOpt, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(pPath->szPath1, "pid")) { + cbBuffer = snprintf(pbBuffer, 32, "%i", pProcess->dwPID); + return Util_VfsReadFile_FromPBYTE(pbBuffer, cbBuffer, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(pPath->szPath1, "name")) { + cbBuffer = snprintf(pbBuffer, 32, "%s", pProcess->szName); + return Util_VfsReadFile_FromPBYTE(pbBuffer, cbBuffer, pb, cb, pcbRead, cbOffset); + } + // windows specific reads below: + if(ctxVmm->fTargetSystem & VMM_TARGET_WINDOWS_X64) { + if(!_stricmp(pPath->szPath1, "win-eprocess")) { + return Util_VfsReadFile_FromQWORD(pProcess->os.win.vaEPROCESS, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(pPath->szPath1, "win-entry")) { + return Util_VfsReadFile_FromQWORD(pProcess->os.win.vaENTRY, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(pPath->szPath1, "win-peb")) { + return Util_VfsReadFile_FromQWORD(pProcess->os.win.vaPEB, pb, cb, pcbRead, cbOffset, FALSE); + } + if(!_stricmp(pPath->szPath1, "win-modules") && pProcess->os.win.pbLdrModulesDisplayCache) { + return Util_VfsReadFile_FromPBYTE(pProcess->os.win.pbLdrModulesDisplayCache, pProcess->os.win.cbLdrModulesDisplayCache, pb, cb, pcbRead, cbOffset); + } + if(!_stricmp(pPath->szPath1, "win-peb32")) { + return Util_VfsReadFile_FromDWORD(pProcess->os.win.vaPEB32, pb, cb, pcbRead, cbOffset, FALSE); + } + } + // no hit - call down the loadable modules chain for potential hits + return PluginManager_Read(pProcess, pPath->szPath1, pPath->szPath2, pb, cb, pcbRead, cbOffset); +} + +NTSTATUS VmmVfs_Read(LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset) +{ + NTSTATUS nt = VMM_STATUS_FILE_INVALID; + VMMVFS_PATH path; + CHAR _szBuf[MAX_PATH]; + LPSTR szModule, szModulePath; + if(!ctxVmm) { return nt; } + // read '\\pmem' - physical memory file: + if(!_wcsicmp(wcsFileName, L"\\pmem")) { + VmmReadEx(NULL, cbOffset, pb, cb, pcbRead, VMM_FLAG_ZEROPAD_ON_FAIL); + return VMM_STATUS_SUCCESS; + } + // read files in process directories: + if(!_wcsnicmp(wcsFileName, L"\\name", 5) || !_wcsnicmp(wcsFileName, L"\\pid", 4)) { + if(!ctxVmm->ptPROC) { return nt; } + if(!VmmVfs_UtilVmmGetPidDirFile(wcsFileName, &path)) { return nt; } + return VmmVfsReadFileProcess(&path, pb, cb, pcbRead, cbOffset); + } + // list files in any non-process modules directories + Util_PathSplit2_WCHAR((LPWSTR)(wcsFileName + 1), _szBuf, &szModule, &szModulePath); + return PluginManager_Read(NULL, szModule, szModulePath, pb, cb, pcbRead, cbOffset); +} + +// ---------------------------------------------------------------------------- +// FUNCTIONALITY RELATED TO: WRITE +// ---------------------------------------------------------------------------- + +NTSTATUS VmmVfsWriteFileProcess(_In_ PVMMVFS_PATH pPath, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset) +{ + PVMM_PROCESS pProcess; + BOOL fFound, result; + if(!pPath->szPath1) { return VMM_STATUS_FILE_INVALID; } + pProcess = VmmProcessGet(pPath->dwPID); + if(!pProcess) { return VMM_STATUS_FILE_INVALID; } + // read only files - report zero bytes written + fFound = + !_stricmp(pPath->szPath1, "map") || + !_stricmp(pPath->szPath1, "pml4") || + !_stricmp(pPath->szPath1, "pid") || + !_stricmp(pPath->szPath1, "name"); + if(fFound) { + *pcbWrite = 0; + return VMM_STATUS_SUCCESS; + } + // windows specific writes below: + if(ctxVmm->fTargetSystem & VMM_TARGET_WINDOWS_X64) { + fFound = + !_stricmp(pPath->szPath1, "win-eprocess") || + !_stricmp(pPath->szPath1, "win-peb") || + !_stricmp(pPath->szPath1, "win-entry") || + !_stricmp(pPath->szPath1, "win-modules"); + if(fFound) { + *pcbWrite = 0; + return VMM_STATUS_SUCCESS; + } + } + // write memory to "vmem" file + if(!_stricmp(pPath->szPath1, "vmem")) { + result = VmmWrite(pProcess, cbOffset, pb, cb); + *pcbWrite = cb; + return VMM_STATUS_SUCCESS; + } + // no hit - call down the loadable modules chain for potential hits + return PluginManager_Write(pProcess, pPath->szPath1, pPath->szPath2, pb, cb, pcbWrite, cbOffset); +} + +NTSTATUS VmmVfs_Write(LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset) +{ + NTSTATUS nt = VMM_STATUS_FILE_INVALID; + BOOL result; + VMMVFS_PATH path; + CHAR _szBuf[MAX_PATH]; + LPSTR szModule, szModulePath; + if(!ctxVmm) { return nt; } + // read '\\pmem' - physical memory file: + if(!_wcsicmp(wcsFileName, L"\\pmem")) { + result = VmmWritePhysical(cbOffset, pb, cb); + *pcbWrite = cb; + return result ? VMM_STATUS_SUCCESS : VMM_STATUS_FILE_SYSTEM_LIMITATION; + } + // read files in process directories: + if(!_wcsnicmp(wcsFileName, L"\\name", 5) || !_wcsnicmp(wcsFileName, L"\\pid", 4)) { + if(!ctxVmm->ptPROC) { return nt; } + if(!VmmVfs_UtilVmmGetPidDirFile(wcsFileName, &path)) { return nt; } + return VmmVfsWriteFileProcess(&path, pb, cb, pcbWrite, cbOffset); + } + // list files in any non-process modules directories + Util_PathSplit2_WCHAR((LPWSTR)(wcsFileName + 1), _szBuf, &szModule, &szModulePath); + return PluginManager_Write(NULL, szModule, szModulePath, pb, cb, pcbWrite, cbOffset); +} + +// ---------------------------------------------------------------------------- +// FUNCTIONALITY RELATED TO: LIST +// ---------------------------------------------------------------------------- + +VOID VmmVfsListFiles_OsSpecific(_In_ PVMM_PROCESS pProcess, _Inout_ PHANDLE pFileList) +{ + // WINDOWS + if(ctxVmm->fTargetSystem & VMM_TARGET_WINDOWS_X64) { + VMMDLL_VfsList_AddFile(pFileList, "win-eprocess", 16); + if(pProcess->os.win.vaENTRY) { + VMMDLL_VfsList_AddFile(pFileList, "win-entry", 16); + } + // 64-bit PEB and modules + VMMDLL_VfsList_AddFile(pFileList, "win-peb", 16); + if(pProcess->os.win.cbLdrModulesDisplayCache) { + VMMDLL_VfsList_AddFile(pFileList, "win-modules", pProcess->os.win.cbLdrModulesDisplayCache); + } + // 32-bit PEB and modules + if(pProcess->os.win.vaPEB32) { + VMMDLL_VfsList_AddFile(pFileList, "win-peb32", 8); + } + } +} + +_Success_(return) +BOOL VmmVfsListFilesProcess(_In_ PVMMVFS_PATH pPath, _Inout_ PHANDLE pFileList) +{ + PVMM_PROCESS pProcess; + WORD iProcess; + CHAR szBufferFileName[MAX_PATH]; + if(!ctxVmm || !ctxVmm->ptPROC) { return FALSE; } + // populate root node - list processes as directories + if(pPath->fRoot) { + iProcess = ctxVmm->ptPROC->iFLink; + pProcess = ctxVmm->ptPROC->M[iProcess]; + while(pProcess) { + { + if(pPath->fNamePID) { + if(pProcess->dwState) { + sprintf_s(szBufferFileName, MAX_PATH - 1, "%s-(%x)-%i", pProcess->szName, pProcess->dwState, pProcess->dwPID); + } else { + sprintf_s(szBufferFileName, MAX_PATH - 1, "%s-%i", pProcess->szName, pProcess->dwPID); + } + } else { + sprintf_s(szBufferFileName, MAX_PATH - 1, "%i", pProcess->dwPID); + } + VMMDLL_VfsList_AddDirectory(pFileList, szBufferFileName); + } + iProcess = ctxVmm->ptPROC->iFLinkM[iProcess]; + pProcess = ctxVmm->ptPROC->M[iProcess]; + if(!iProcess || iProcess == ctxVmm->ptPROC->iFLink) { break; } + } + return TRUE; + } + // generate memmap, if not already done. required by following steps + pProcess = VmmProcessGet(pPath->dwPID); + if(!pProcess) { return FALSE; } + if(!pProcess->pMemMap || !pProcess->cMemMap) { + if(!pProcess->fSpiderPageTableDone) { + VmmTlbSpider(0, pProcess->fUserOnly); + pProcess->fSpiderPageTableDone = TRUE; + } + VmmMapInitialize(pProcess); + VmmProc_InitializeModuleNames(pProcess); + VmmMapDisplayBufferGenerate(pProcess); + } + // populate process directory - list standard files and subdirectories + if(!pPath->szPath1) { + VMMDLL_VfsList_AddFile(pFileList, "map", pProcess->cbMemMapDisplayCache); + VMMDLL_VfsList_AddFile(pFileList, "name", 16); + VMMDLL_VfsList_AddFile(pFileList, "pid", 10); + VMMDLL_VfsList_AddFile(pFileList, "pml4", 16); + VMMDLL_VfsList_AddFile(pFileList, "vmem", 0x0001000000000000); + if(pProcess->paPML4_UserOpt) { + VMMDLL_VfsList_AddFile(pFileList, "pml4-user", 16); + } + VmmVfsListFiles_OsSpecific(pProcess, pFileList); + PluginManager_ListAll(pProcess, pFileList); + return TRUE; + } + // no hit - call down the loadable modules chain for potential hits + return PluginManager_List(pProcess, pPath->szPath1, pPath->szPath2, pFileList); +} + +_Success_(return) +BOOL VmmVfsListFilesRoot(_Inout_ PHANDLE pFileList) +{ + VMMDLL_VfsList_AddDirectory(pFileList, "name"); + VMMDLL_VfsList_AddDirectory(pFileList, "pid"); + VMMDLL_VfsList_AddFile(pFileList, "pmem", ctxMain->cfg.paAddrMax); + PluginManager_ListAll(NULL, pFileList); + return TRUE; +} + +BOOL VmmVfs_List(_In_ LPCWSTR wcsPath, _Inout_ PHANDLE pFileList) +{ + BOOL result = FALSE; + VMMVFS_PATH path; + CHAR _szBuf[MAX_PATH]; + LPSTR szModule, szModulePath; + if(!ctxVmm) { return FALSE; } + // list files in root directory + if(!_wcsicmp(wcsPath, L"\\")) { + return VmmVfsListFilesRoot(pFileList); + } + // list files in name or pid directories: + if(!_wcsnicmp(wcsPath, L"\\name", 5) || !_wcsnicmp(wcsPath, L"\\pid", 4)) { + if(!ctxVmm->ptPROC) { return FALSE; } + if(!VmmVfs_UtilVmmGetPidDirFile(wcsPath, &path)) { return FALSE; } + return VmmVfsListFilesProcess(&path, pFileList); + } + // list files in any non-process modules directories + Util_PathSplit2_WCHAR((LPWSTR)(wcsPath + 1), _szBuf, &szModule, &szModulePath); + return PluginManager_List(NULL, szModule, szModulePath, pFileList); +} diff --git a/vmm/vmmvfs.h b/vmm/vmmvfs.h new file mode 100644 index 0000000..9fca8a8 --- /dev/null +++ b/vmm/vmmvfs.h @@ -0,0 +1,40 @@ +// vmmvfs.h : definitions related to virtual memory management / virtual file system interfacing. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __VMMVFS_H__ +#define __VMMVFS_H__ +#include "vmm.h" + +/* +* List files in the virtual file system directory specified by the path name. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VmmVfs_List(_In_ LPCWSTR wcsPath, _Inout_ PHANDLE pFileList); + +/* +* Read the contents of a file into the caller supplied buffer. This file may be +* a memory file or any other file in the "proc" virtual file system. +* -- wcsFileName = full path file name +* -- pb = buffer +* -- cb = bytes to read/size of pb +* -- pcbRead = bytes actually read +* -- cbOffset = offset where to start read compared to file start +*/ +NTSTATUS VmmVfs_Read(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ QWORD cbOffset); + +/* +* Write the contents of a file into the caller supplied buffer. This file may be +* a memory file or any other file in the "proc" virtual file system. +* -- wcsFileName = full path file name +* -- pb = buffer +* -- cb = bytes to read/size of pb +* -- pcbWrite = bytes actually read +* -- cbOffset = offset where to start read compared to file start +*/ +NTSTATUS VmmVfs_Write(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ QWORD cbOffset); + +#endif /* __VMMVFS_H__ */ diff --git a/vmm_example/vmm_example.vcxproj b/vmm_example/vmm_example.vcxproj new file mode 100644 index 0000000..508ee0a --- /dev/null +++ b/vmm_example/vmm_example.vcxproj @@ -0,0 +1,140 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {7B05E64E-569F-4EF5-8F37-FA5B39B64227} + vmmexample + 8.1 + + + + Application + true + v141 + MultiByte + + + Application + false + v141 + true + MultiByte + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + $(OutDir)\lib\$(TargetName).pdb + UseLinkTimeCodeGeneration + $(SolutionDir)\files\vmm.lib + + + + + Level3 + Disabled + true + true + + + + + Level3 + Disabled + true + true + + + $(OutDir)\lib\$(TargetName).pdb + $(SolutionDir)\files\vmm.lib + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/vmm_example/vmm_example.vcxproj.filters b/vmm_example/vmm_example.vcxproj.filters new file mode 100644 index 0000000..bd51325 --- /dev/null +++ b/vmm_example/vmm_example.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {dd6bdb7f-228f-409d-9a7b-4bd9742210f4} + + + + + Source Files + + + + + Header Files\vmm + + + \ No newline at end of file diff --git a/vmm_example/vmm_example.vcxproj.user b/vmm_example/vmm_example.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/vmm_example/vmm_example.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/vmm_example/vmmdll.h b/vmm_example/vmmdll.h new file mode 100644 index 0000000..623a09b --- /dev/null +++ b/vmm_example/vmmdll.h @@ -0,0 +1,566 @@ +// vmmdll.h : header file to include in projects that use vmm.dll either as +// stand anlone projects or as native plugins to vmm.dll. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include + +#ifndef __VMMDLL_H__ +#define __VMMDLL_H__ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +/* +* RESERVED FUNCTION! DO NOT USE! +* Call other VMMDLL_Intialize functions to initialize VMM.DLL and the memory +* process file system. +*/ +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]); + +/* +* Initialize VMM.DLL from a memory dump file in raw format. VMM.DLL will be +* initialized in read-only mode. It's possible to optionally specify the page +* table base of the windows kernel (for full vmm features) or the page table +* base of a single 64-bit process in any x64 operating system. NB! usually it +* is not necessary to specify the PageTableBase - it will be auto-identified +* most often if the target is Windows. +* -- szFileName = the file name of the raw memory dump to use. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Intiailize VMM.DLL from a supported FPGA device over USB. VMM.DLL will be +* initialized in read/write mode upon success. Optionally it will be possible +* to specify the max physical address and the page table base of the kernel or +* process that should be investigated. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* -- szMaxPhysicalAddressOpt = max physical address of the target system as a +* hex string. Example: "0x8000000000". If zero value is given the max +* address will be auto-identified. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Initialize VMM.DLL from a the "Total Meltdown" CVE-2018-1038 vulnerability. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* initialized in read/write mode upon success. +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeTotalMeltdown(); + +/* +* Close an initialized instance of VMM.DLL and clean up all allocated resources +* including plugins, linked PCILeech.DLL and other memory resources. +* -- return = success/fail. +*/ +BOOL VMMDLL_Close(); + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +/* +* Options used together with the functions: VMMDLL_GetOption & VMMDLL_SetOption +* If VMM.DLL is chained with PCILEECH.DLL then required values will be passed +* along to PCILEECH.DLL when necessary. +* For more detailed information check the sources for individual device types. +*/ +#define VMMDLL_OPT_DEVICE_FPGA_PROBE_MAXPAGES 0x01 // RW +#define VMMDLL_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_RX 0x03 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_TX 0x04 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_READ 0x05 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE 0x06 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_WRITE 0x07 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_READ 0x08 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_RETRY_ON_ERROR 0x09 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DEVICE_ID 0x80 // R +#define VMMDLL_OPT_DEVICE_FPGA_FPGA_ID 0x81 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR 0x82 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR 0x83 // R + +#define VMMDLL_OPT_CORE_PRINTF_ENABLE 0x80000001 // RW +#define VMMDLL_OPT_CORE_VERBOSE 0x80000002 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R +#define VMMDLL_OPT_CORE_TARGET_SYSTEM 0x80000007 // R + +#define VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED 0x40000001 // R - 1/0 +#define VMMDLL_OPT_CONFIG_TICK_PERIOD 0x40000002 // RW - base tick period in ms +#define VMMDLL_OPT_CONFIG_READCACHE_TICKS 0x40000003 // RW - memory cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_TLBCACHE_TICKS 0x40000004 // RW - page table (tlb) cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL 0x40000005 // RW - process refresh (partial) period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL 0x40000006 // RW - process refresh (full) period (in ticks) +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR 0x40000007 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR 0x40000008 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION 0x40000009 // R +#define VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL 0x4000000A // RW - enable function call statistics (.status/statistics_fncall file) + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- pqwValue = pointer to ULONG64 to receive option value. +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- qwValue +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +// This is the core of the memory process file system. All implementation and +// analysis towards the file system is possible by using functionality below. +//----------------------------------------------------------------------------- + +#define VMMDLL_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMMDLL_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMMDLL_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMMDLL_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMMDLL_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +typedef struct tdVMMDLL_VFS_FILELIST { + VOID(*pfnAddFile) (_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved); + VOID(*pfnAddDirectory)(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved); + HANDLE h; +} VMMDLL_VFS_FILELIST, *PVMMDLL_VFS_FILELIST; + +/* +* Helper function macros for callbacks into the VMM_VFS_FILELIST structure. +*/ +#define VMMDLL_VfsList_AddFile(pFileList, szName, cb) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddFile(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, cb, NULL); } +#define VMMDLL_VfsList_AddDirectory(pFileList, szName) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddDirectory(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, NULL); } + +/* +* List a directory of files in the memory process file system. Directories and +* files will be listed by callbacks into functions supplied in the pFileList +* parameter. If information of an individual file is needed it's neccessary +* to list all files in its directory. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + +/* +* Read select parts of a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +* +*/ +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + +/* +* Write select parts to a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + +/* +* Utility functions for memory process file system read/write towards different +* underlying data representations. +*/ +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow); + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +// Function and structures to initialize and use the memory process file system +// plugin functionality. The plugin manager is started by a call to function: +// VMM_VfsInitializePlugins. Each built-in plugin and external plugin of which +// the DLL name matches m_*.dll will receive a call to its InitializeVmmPlugin +// function. The plugin/module may decide to call pfnPluginManager_Register to +// register plugins in the form of different names one or more times. +// Example of registration function in a plugin DLL below: +// 'VOID InitializeVmmPlugin(_In_ PVMM_PLUGIN_REGINFO pRegInfo)' +//----------------------------------------------------------------------------- + +/* +* Initialize all potential plugins, both built-in and external, that maps into +* the memory process file system. Please note that plugins are not loaded by +* default - they have to be explicitly loaded by calling this function. They +* will be unloaded on a general close of the vmm dll. +* -- return +*/ +BOOL VMMDLL_VfsInitializePlugins(); + +#define VMMDLL_PLUGIN_CONTEXT_MAGIC 0xc0ffee663df9301c +#define VMMDLL_PLUGIN_CONTEXT_VERSION 1 +#define VMMDLL_PLUGIN_REGINFO_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PLUGIN_REGINFO_VERSION 1 + +#define VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE 0x01 + +typedef struct tdVMMDLL_PLUGIN_CONTEXT { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD dwPID; + PHANDLE phModulePrivate; + PHANDLE phProcessPrivate; + PVOID pProcess; + LPSTR szModule; + LPSTR szPath; + PVOID pvReserved1; + PVOID pvReserved2; +} VMMDLL_PLUGIN_CONTEXT, *PVMMDLL_PLUGIN_CONTEXT; + +typedef struct tdVMMDLL_PLUGIN_REGINFO { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; + HMODULE hDLL; + HMODULE hReservedDll; // not for general use (only used for python). + BOOL(*pfnPluginManager_Register)(struct tdVMMDLL_PLUGIN_REGINFO *pPluginRegInfo); + PVOID pvReserved1; + PVOID pvReserved2; + // general plugin registration info to be filled out by the plugin below: + struct { + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + PVOID pvReserved1; + PVOID pvReserved2; + } reg_info; + // function plugin registration info to be filled out by the plugin below: + struct { + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + PVOID pvReserved1; + PVOID pvReserved2; + } reg_fn; +} VMMDLL_PLUGIN_REGINFO, *PVMMDLL_PLUGIN_REGINFO; + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +// Vmm core functaionlity such as read (and write) to both virtual and physical +// memory. NB! writing will only work if the target is supported - i.e. not a +// memory dump file... +// To read physical memory specify dwPID as (DWORD)-1 +//----------------------------------------------------------------------------- + +// FLAG used to supress the default read cache in calls to VMM_MemReadEx() +// which will lead to the read being fetched from the target system always. +// Cached page tables (used for translating virtual2physical) are still used. +#define VMMDLL_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMMDLL_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMMDLL_TARGET_UNKNOWN_X64 0x0001 +#define VMMDLL_TARGET_WINDOWS_X64 0x0002 + +typedef struct tdVMMDLL_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} VMMDLL_MEM_IO_SCATTER_HEADER, *PVMMDLL_MEM_IO_SCATTER_HEADER, **PPVMMDLL_MEM_IO_SCATTER_HEADER; + +/* +* Read memory in various non-contigious locations specified by the pointers to +* the items in the ppDMAs array. Result for each unit of work will be given +* individually. No upper limit of number of items to read, but no performance +* boost will be given if above hardware limit. Max size of each unit of work is +* one 4k page (4096 bytes). +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpDMAsRead = optional count of number of successfully read ppDMAs. +* -- flags = optional flags as given by VMM_FLAG_* +* -- return = the number of successfully read items. +*/ +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); + +/* +* Read a single 4096-byte page of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pbPage +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Read a contigious arbitrary amount of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious amount of memory and report the number of bytes read in pcbRead. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +* -- return = success/fail. NB! reads may report as success even if 0 bytes are +* read - it's recommended to verify pcbReadOpt parameter. +*/ +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + +/* +* Write a contigious arbitrary amount of memory. Please note some virtual memory +* such as pages of executables (such as DLLs) may be shared between different +* virtual memory over different processes. As an example a write to kernel32.dll +* in one process is likely to affect kernel32 in the whole system - in all +* processes. Heaps and Stacks and other memory are usually safe to write to. +* Please take care when writing to memory! +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Translate a virtual address to a physical address by walking the page tables +* of the specified process. +* -- dwPID +* -- qwVA +* -- pqwPA +* -- return = success/fail. +*/ +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +// Functionality below is mostly relating to Windows processes. +//----------------------------------------------------------------------------- + +/* +* Retrieve an active process given it's name. Please note that if multiple +* processes with the same name exists only one will be returned. If required to +* parse all processes with the same name please iterate over the PID list by +* calling VMMDLL_PidList together with VMMDLL_ProcessGetInformation. +* -- szProcName = process name (truncated max 15 chars) case insensitive. +* -- pdwPID = pointer that will receive PID on success. +* -- return +*/ +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + +/* +* List the PIDs in the system. +* -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. +* -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. +* -- return = success/fail. +*/ +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + +// flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define VMMDLL_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMMDLL_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMMDLL_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMMDLL_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +typedef struct tdVMMDLL_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMMDLL_MEMMAP_ENTRY, *PVMMDLL_MEMMAP_ENTRY; + +/* +* Retrieve memory map entries from the specified process. Memory map entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries bytes. +* If the pMemMapEntries is set to NULL the number of memory map entries will be +* given in the pcMemMapEntries parameter. +* -- dwPID +* -- pMemMapEntries = buffer of minimum length sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. +* -- pcMemMapEntries = pointer to number of memory map entries. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + +/* +* Retrieve a single memory map entry given a virtual address within that entrys +* range. +* -- dwPID +* -- pMemMapEntry +* -- va = virtual address in the memory map entry to retrieve. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules); + +typedef struct tdVMMDLL_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; +} VMMDLL_MODULEMAP_ENTRY, *PVMMDLL_MODULEMAP_ENTRY; + +/* +* Retrieve the module entries from the specified process. The module entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the +* pcModuleEntries is set to NULL the number of module entries will be given +* in the pcModuleEntries parameter. +* -- dwPID +* -- pModuleEntries = buffer of minimum length sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. +* -- pcModuleEntries = pointer to number of memory map entries. +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + +/* +* Retrieve a module (.exe or .dll or similar) given a module name. +* -- dwPID +* -- szModuleName +* -- pModuleEntry +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry); + +#define VMMDLL_PROCESS_INFORMATION_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PROCESS_INFORMATION_VERSION 1 + +typedef struct tdVMMDLL_PROCESS_INFORMATION { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; // as given by VMMDLL_TARGET_* + BOOL fUserOnly; // only user mode pages listed + DWORD dwPID; + DWORD dwState; + CHAR szName[16]; + ULONG64 paPML4; + ULONG64 paPML4_UserOpt; // may not exist + union { + struct { + ULONG64 vaEPROCESS; + ULONG64 vaPEB; + ULONG64 vaENTRY; + BOOL fWow64; + DWORD vaPEB32; // WoW64 only + } win; + } os; +} VMMDLL_PROCESS_INFORMATION, *PVMMDLL_PROCESS_INFORMATION; + +/* +* Retrieve various process information from a PID. Process information such as +* name, page directory bases and the process state may be retrieved. +* -- dwPID +* -- pProcessInformation = if null, size is given in *pcbProcessInfo +* -- pcbProcessInformation = size of pProcessInfo (in bytes) on entry and exit +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation); + +typedef struct tdVMMDLL_EAT_ENTRY { + ULONG64 vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMDLL_EAT_ENTRY, *PVMMDLL_EAT_ENTRY; + +typedef struct tdVMMDLL_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMDLL_IAT_ENTRY, *PVMMDLL_IAT_ENTRY; + +/* +* Retrieve information about: Data Directories, Sections, Export Address Table +* and Import Address Table (IAT). +* If the pData == NULL upon entry the number of entries of the pData array must +* have in order to be able to hold the data is returned. +* -- dwPID +* -- szModule +* -- pData +* -- cData +* -- pcData +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + + + +//----------------------------------------------------------------------------- +// VMM UTIL FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __VMMDLL_H__ */ diff --git a/vmm_example/vmmdll_example.c b/vmm_example/vmmdll_example.c new file mode 100644 index 0000000..1f7458f --- /dev/null +++ b/vmm_example/vmmdll_example.c @@ -0,0 +1,581 @@ +// vmmdll_example.c - Memory Process File System / Virtual Memory Manager DLL API usage examples +// +// Note that this is not a complete list of the VMM API. For the complete list please consult the vmmdll.h header file. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include +#include +#include +#include "vmmdll.h" + +#pragma comment(lib, "vmm") + +// ---------------------------------------------------------------------------- +// Initialize from type of device, FILE, FPGA or Total Meltdown (CVE-2018-1038). +// Ensure only one is active below at one single time! +// INITIALIZE_FROM_FILE contains file name to a raw memory dump. +// ---------------------------------------------------------------------------- +#define _INITIALIZE_FROM_FILE "z:\\media\\vm\\memdump\\WIN10-17134-48-1.raw" +//#define _INITIALIZE_FROM_FPGA +//#define _INITIALIZE_FROM_TOTALMELTDOWN + +// ---------------------------------------------------------------------------- +// Utility functions below: +// ---------------------------------------------------------------------------- + +VOID ShowKeyPress() +{ + printf("PRESS ANY KEY TO CONTINUE ...\n"); + Sleep(250); + _getch(); +} + +VOID PrintHexAscii(_In_ PBYTE pb, _In_ DWORD cb) +{ + DWORD szMax; + LPSTR sz; + VMMDLL_UtilFillHexAscii(pb, cb, 0, NULL, &szMax); + if(!(sz = LocalAlloc(0, szMax))) { return; } + VMMDLL_UtilFillHexAscii(pb, cb, 0, sz, &szMax); + printf(sz); + LocalFree(sz); +} + +VOID CallbackList_AddFile(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved) +{ + printf(" FILE: '%s'\tSize: %i\n", szName, (DWORD)cb); +} + +VOID CallbackList_AddDirectory(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved) +{ + printf(" DIR: '%s'\n", szName); +} + +// ---------------------------------------------------------------------------- +// Main entry point which contains various sample code how to use PCILeech DLL. +// Please walk though for different API usage examples. To select device ensure +// one device type only is uncommented in the #defines above. +// ---------------------------------------------------------------------------- +int main(_In_ int argc, _In_ char* argv[]) +{ + BOOL result; + NTSTATUS nt; + DWORD i, dwPID; + BYTE pbPage1[0x1000], pbPage2[0x1000]; + +#ifdef _INITIALIZE_FROM_FILE + // Initialize PCILeech DLL with a memory dump file. + printf("------------------------------------------------------------\n"); + printf("#01: Initialize from file: \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_InitializeFile\n"); + result = VMMDLL_InitializeFile(_INITIALIZE_FROM_FILE, NULL); + if(result) { + printf("SUCCESS: VMMDLL_InitializeFile\n"); + } else { + printf("FAIL: VMMDLL_InitializeFile\n"); + return 1; + } +#endif /* _INITIALIZE_FROM_FILE */ + +#ifdef _INITIALIZE_FROM_TOTALMELTDOWN + // Initialize VMM DLL from a linked PCILeech with the TotalMeltdown exploit. + printf("------------------------------------------------------------\n"); + printf("#01: Initialize from TotalMeltdown: \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_InitializeTotalMeltdown\n"); + result = VMMDLL_InitializeTotalMeltdown(); + if(result) { + printf("SUCCESS: VMMDLL_InitializeTotalMeltdown\n"); + } else { + printf("FAIL: VMMDLL_InitializeTotalMeltdown\n"); + return 1; + } +#endif /* _INITIALIZE_FROM_TOTALMELTDOWN */ + +#ifdef _INITIALIZE_FROM_FPGA + // Initialize VMM DLL from a linked PCILeech with a FPGA hardware device + printf("------------------------------------------------------------\n"); + printf("#01: Initialize from FPGA: \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_InitializeFPGA\n"); + result = VMMDLL_InitializeFPGA(NULL, NULL); + if(result) { + printf("SUCCESS: VMMDLL_InitializeFPGA\n"); + } else { + printf("FAIL: VMMDLL_InitializeFPGA\n"); + return 1; + } + // Retrieve the ID of the FPPA (SP605/PCIeScreamer/AC701 ...) and the bitstream version + ULONG64 qwID, qwVersionMajor, qwVersionMinor; + ShowKeyPress(); + printf("CALL: VMMDLL_ConfigGet\n"); + result = + VMMDLL_ConfigGet(VMMDLL_OPT_DEVICE_FPGA_FPGA_ID, &qwID) && + VMMDLL_ConfigGet(VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR, &qwVersionMajor) && + VMMDLL_ConfigGet(VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR, &qwVersionMinor); + if(result) { + printf("SUCCESS: VMMDLL_ConfigGet\n"); + printf(" ID = %lli\n", qwID); + printf(" VERSION = %lli.%lli\n", qwVersionMajor, qwVersionMinor); + } else { + printf("FAIL: VMMDLL_ConfigGet\n"); + return 1; + } + // Retrieve the read delay value (in microseconds uS) that is used by the + // FPGA to pause in every read. Sometimes it may be a good idea to adjust + // this (and other related values) to lower versions if the FPGA device + // still works stable without errors. Use PCIleech_DeviceConfigSet to set + // values. + ULONG64 qwReadDelay; + ShowKeyPress(); + printf("CALL: VMMDLL_ConfigGet\n"); + result = VMMDLL_ConfigGet(VMMDLL_OPT_DEVICE_FPGA_DELAY_READ, &qwReadDelay); + if(result) { + printf("SUCCESS: VMMDLL_ConfigGet\n"); + printf(" FPGA Read Delay in microseconds (uS) = %lli\n", qwReadDelay); + } else { + printf("FAIL: VMMDLL_ConfigGet\n"); + return 1; + } +#endif /* _INITIALIZE_FROM_FPGA */ + + + // Read physical memory at physical address 0x1000 and display the first + // 0x100 bytes on-screen. + printf("------------------------------------------------------------\n"); + printf("#02: Read from physical memory (0x1000 bytes @ 0x1000). \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_MemRead\n"); + result = VMMDLL_MemRead(-1, 0x1000, pbPage1, 0x1000); + if(result) { + printf("SUCCESS: VMMDLL_MemRead\n"); + PrintHexAscii(pbPage1, 0x100); + } else { + printf("FAIL: VMMDLL_MemRead\n"); + return 1; + } + + + // Retrieve PID of explorer.exe + // NB! if multiple explorer.exe exists only one will be returned by this + // specific function call. Please see .h file for additional information + // about how to retrieve the complete list of PIDs in the system by using + // the function PCILeech_VmmProcessListPIDs instead. + printf("------------------------------------------------------------\n"); + printf("#03: Get PID from the first 'explorer.exe' process found. \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_PidGetFromName\n"); + result = VMMDLL_PidGetFromName("explorer.exe", &dwPID); + if(result) { + printf("SUCCESS: VMMDLL_PidGetFromName\n"); + printf(" PID = %i\n", dwPID); + } else { + printf("FAIL: VMMDLL_PidGetFromName\n"); + return 1; + } + + + // Retrieve additional process information such as: name of the process, + // PML4 (PageDirectoryBase) PML4-USER (if exists) and Process State. + printf("------------------------------------------------------------\n"); + printf("#04: Get Process Information from 'explorer.exe'. \n"); + ShowKeyPress(); + VMMDLL_PROCESS_INFORMATION ProcessInformation; + SIZE_T cbProcessInformation = sizeof(VMMDLL_PROCESS_INFORMATION); + ZeroMemory(&ProcessInformation, sizeof(VMMDLL_PROCESS_INFORMATION)); + ProcessInformation.magic = VMMDLL_PROCESS_INFORMATION_MAGIC; + ProcessInformation.wVersion = VMMDLL_PROCESS_INFORMATION_VERSION; + printf("CALL: VMMDLL_ProcessGetInformation\n"); + result = VMMDLL_ProcessGetInformation(dwPID, &ProcessInformation, &cbProcessInformation); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetInformation\n"); + printf(" Name = %s\n", ProcessInformation.szName); + printf(" PageDirectoryBase = 0x%016llx\n", ProcessInformation.paPML4); + printf(" PageDirectoryBaseUser = 0x%016llx\n", ProcessInformation.paPML4_UserOpt); + printf(" ProcessState = 0x%08x\n", ProcessInformation.dwState); + } else { + printf("FAIL: VMMDLL_ProcessGetInformation\n"); + return 1; + } + + + // Retrieve the memory map from the page table. This function also tries to + // make additional parsing to identify modules and tag the memory map with + // them. This is done by multiple methods internally and may sometimes be + // more resilient against anti-reversing techniques that may be employed in + // some processes. + printf("------------------------------------------------------------\n"); + printf("#05: Get Memory Map of 'explorer.exe'. \n"); + ShowKeyPress(); + ULONG64 cMemMapEntries; + PVMMDLL_MEMMAP_ENTRY pMemMapEntries; + printf("CALL: VMMDLL_ProcessGetMemoryMap #1\n"); + result = VMMDLL_ProcessGetMemoryMap(dwPID, NULL, &cMemMapEntries, TRUE); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetMemoryMap #1\n"); + printf(" Count = %lli\n", cMemMapEntries); + } else { + printf("FAIL: VMMDLL_ProcessGetMemoryMap #1\n"); + return 1; + } + pMemMapEntries = (PVMMDLL_MEMMAP_ENTRY)LocalAlloc(0, cMemMapEntries * sizeof(VMMDLL_MEMMAP_ENTRY)); + if(!pMemMapEntries) { + printf("FAIL: OutOfMemory\n"); + return 1; + } + printf("CALL: VMMDLL_ProcessGetMemoryMap #2\n"); + result = VMMDLL_ProcessGetMemoryMap(dwPID, pMemMapEntries, &cMemMapEntries, TRUE); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetMemoryMap #2\n"); + printf(" # #PAGES ADRESS_RANGE SRWX\n"); + printf(" ====================================================\n"); + for(i = 0; i < cMemMapEntries; i++) { + printf( + " %04x %8x %016llx-%016llx %sr%s%s%s%s\n", + i, + (DWORD)pMemMapEntries[i].cPages, + pMemMapEntries[i].AddrBase, + pMemMapEntries[i].AddrBase + (pMemMapEntries[i].cPages << 12) - 1, + pMemMapEntries[i].fPage & VMMDLL_MEMMAP_FLAG_PAGE_NS ? "-" : "s", + pMemMapEntries[i].fPage & VMMDLL_MEMMAP_FLAG_PAGE_W ? "w" : "-", + pMemMapEntries[i].fPage & VMMDLL_MEMMAP_FLAG_PAGE_NX ? "-" : "x", + pMemMapEntries[i].szTag[0] ? (pMemMapEntries[i].fWoW64 ? " 32 " : " ") : "", + pMemMapEntries[i].szTag + ); + } + } else { + printf("FAIL: VMMDLL_ProcessGetMemoryMap #2\n"); + return 1; + } + + + // Retrieve the list of loaded DLLs from the process. Please note that this + // list is retrieved by parsing in-process memory structures such as the + // process environment block (PEB) which may be partly destroyed in some + // processes due to obfuscation and anti-reversing. If that is the case the + // memory map may use alternative parsing techniques to list DLLs. + printf("------------------------------------------------------------\n"); + printf("#06: Get Module Map of 'explorer.exe'. \n"); + ShowKeyPress(); + ULONG64 cModules; + PVMMDLL_MODULEMAP_ENTRY pModules; + printf("CALL: VMMDLL_ProcessGetModuleMap #1\n"); + result = VMMDLL_ProcessGetModuleMap(dwPID, NULL, &cModules); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetModuleMap #1\n"); + printf(" Count = %lli\n", cModules); + } else { + printf("FAIL: VMMDLL_ProcessGetModuleMap #1\n"); + return 1; + } + pModules = (PVMMDLL_MODULEMAP_ENTRY)LocalAlloc(0, cModules * sizeof(VMMDLL_MODULEMAP_ENTRY)); + if(!pModules) { + printf("FAIL: OutOfMemory\n"); + return 1; + } + printf("CALL: VMMDLL_ProcessGetModuleMap #2\n"); + result = VMMDLL_ProcessGetModuleMap(dwPID, pModules, &cModules); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetModuleMap #2\n"); + printf(" MODULE_NAME BASE SIZE ENTRY\n"); + printf(" ======================================================================================\n"); + for(i = 0; i < cModules; i++) { + printf( + " %-40.40s %i %016llx %08x %016llx\n", + pModules[i].szName, + pModules[i].fWoW64 ? 32 : 64, + pModules[i].BaseAddress, + pModules[i].SizeOfImage, + pModules[i].EntryPoint + ); + } + } else { + printf("FAIL: VMMDLL_ProcessGetModuleMap #2\n"); + return 1; + } + + + // Retrieve the module of crypt32.dll by its name. Note it is also possible + // to retrieve it by retrieving the complete module map (list) and iterate + // over it. But if the name of the module is known this is more convenient. + // This required that the PEB and LDR list in-process haven't been tampered + // with ... + printf("------------------------------------------------------------\n"); + printf("#07: Get by name 'crypt32.dll' in 'explorer.exe'. \n"); + ShowKeyPress(); + VMMDLL_MODULEMAP_ENTRY ModuleEntry; + printf("CALL: VMMDLL_ProcessGetModuleFromName\n"); + result = VMMDLL_ProcessGetModuleFromName(dwPID, "crypt32.dll", &ModuleEntry); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetModuleFromName\n"); + printf(" MODULE_NAME BASE SIZE ENTRY\n"); + printf(" ======================================================================================\n"); + printf( + " %-40.40s %i %016llx %08x %016llx\n", + ModuleEntry.szName, + ModuleEntry.fWoW64 ? 32 : 64, + ModuleEntry.BaseAddress, + ModuleEntry.SizeOfImage, + ModuleEntry.EntryPoint + ); + } else { + printf("FAIL: VMMDLL_ProcessGetModuleFromName\n"); + return 1; + } + + + // Retrieve the memory at the base of crypt32.dll previously fetched and + // display the first 0x200 bytes of it. This read is fetched from the cache + // by default (if possible). If reads should be forced from the DMA device + // please specify the flag: VMM_FLAG_NOCACHE + printf("------------------------------------------------------------\n"); + printf("#08: Read 0x200 bytes of 'crypt32.dll' in 'explorer.exe'. \n"); + ShowKeyPress(); + DWORD cRead; + printf("CALL: VMMDLL_MemReadEx\n"); + result = VMMDLL_MemReadEx(dwPID, ModuleEntry.BaseAddress, pbPage2, 0x1000, &cRead, 0); // standard cached read + //result = VMMDLL_MemReadEx(dwPID, ModuleEntry.BaseAddress, pbPage2, 0x1000, &cRead, VMMDLL_FLAG_NOCACHE); // uncached read + if(result) { + printf("SUCCESS: VMMDLL_MemReadEx\n"); + PrintHexAscii(pbPage2, min(cRead, 0x200)); + } else { + printf("FAIL: VMMDLL_MemReadEx\n"); + return 1; + } + + + // List the sections from the module of crypt32.dll. + printf("------------------------------------------------------------\n"); + printf("#09: List sections of 'crypt32.dll' in 'explorer.exe'. \n"); + ShowKeyPress(); + DWORD cSections; + PIMAGE_SECTION_HEADER pSectionHeaders; + printf("CALL: VMMDLL_ProcessGetSections #1\n"); + result = VMMDLL_ProcessGetSections(dwPID, "crypt32.dll", NULL, 0, &cSections); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetSections #1\n"); + printf(" Count = %lli\n", cModules); + } else { + printf("FAIL: VMMDLL_ProcessGetSections #1\n"); + return 1; + } + pSectionHeaders = (PIMAGE_SECTION_HEADER)LocalAlloc(LMEM_ZEROINIT, cSections * sizeof(IMAGE_SECTION_HEADER)); + if(!pModules) { + printf("FAIL: OutOfMemory\n"); + return 1; + } + printf("CALL: VMMDLL_ProcessGetSections #2\n"); + result = VMMDLL_ProcessGetSections(dwPID, "crypt32.dll", pSectionHeaders, cSections, &cSections); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetSections #2\n"); + printf(" # NAME OFFSET SIZE RWX\n"); + printf(" =================================\n"); + for(i = 0; i < cSections; i++) { + printf( + " %02lx %-8.8s %08x %08x %c%c%c\n", + i, + pSectionHeaders[i].Name, + pSectionHeaders[i].VirtualAddress, + pSectionHeaders[i].Misc.VirtualSize, + (pSectionHeaders[i].Characteristics & IMAGE_SCN_MEM_READ) ? 'r' : '-', + (pSectionHeaders[i].Characteristics & IMAGE_SCN_MEM_WRITE) ? 'w' : '-', + (pSectionHeaders[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) ? 'x' : '-' + ); + } + } else { + printf("FAIL: VMMDLL_ProcessGetSections #2\n"); + return 1; + } + + + // Retrieve and display the data directories of crypt32.dll. The number of + // data directories in a PE is always 16 - so this can be used to simplify + // calling the functionality somewhat. + printf("------------------------------------------------------------\n"); + printf("#10: List directories of 'crypt32.dll' in 'explorer.exe'. \n"); + ShowKeyPress(); + LPCSTR DIRECTORIES[16] = { "EXPORT", "IMPORT", "RESOURCE", "EXCEPTION", "SECURITY", "BASERELOC", "DEBUG", "ARCHITECTURE", "GLOBALPTR", "TLS", "LOAD_CONFIG", "BOUND_IMPORT", "IAT", "DELAY_IMPORT", "COM_DESCRIPTOR", "RESERVED" }; + DWORD cDirectories; + IMAGE_DATA_DIRECTORY pDirectories[16]; + printf("CALL: VMMDLL_ProcessGetDirectories\n"); + result = VMMDLL_ProcessGetDirectories(dwPID, "crypt32.dll", pDirectories, 16, &cDirectories); + if(result) { + printf("SUCCESS: PCIleech_VmmProcess_GetDirectories\n"); + printf(" # NAME OFFSET SIZE\n"); + printf(" =====================================\n"); + for(i = 0; i < 16; i++) { + printf( + " %02lx %-16.16s %08x %08x\n", + i, + DIRECTORIES[i], + pDirectories[i].VirtualAddress, + pDirectories[i].Size + ); + } + } else { + printf("FAIL: VMMDLL_ProcessGetDirectories\n"); + return 1; + } + + + // Retrieve the export address table (EAT) of crypt32.dll + printf("------------------------------------------------------------\n"); + printf("#11: exports of 'crypt32.dll' in 'explorer.exe'. \n"); + ShowKeyPress(); + DWORD cEATs; + PVMMDLL_EAT_ENTRY pEATs; + printf("CALL: VMMDLL_ProcessGetEAT #1\n"); + result = VMMDLL_ProcessGetEAT(dwPID, "crypt32.dll", NULL, 0, &cEATs); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetEAT #1\n"); + printf(" Count = %i\n", cEATs); + } else { + printf("FAIL: VMMDLL_ProcessGetEAT #1\n"); + return 1; + } + pEATs = (PVMMDLL_EAT_ENTRY)LocalAlloc(LMEM_ZEROINIT, cEATs * sizeof(VMMDLL_EAT_ENTRY)); + if(!pEATs) { + printf("FAIL: OutOfMemory\n"); + return 1; + } + printf("CALL: VMMDLL_ProcessGetEAT #2\n"); + result = VMMDLL_ProcessGetEAT(dwPID, "crypt32.dll", pEATs, cEATs, &cEATs); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetEAT #2\n"); + printf(" # OFFSET NAME\n"); + printf(" =================================\n"); + for(i = 0; i < cEATs; i++) { + printf( + " %04lx %08x %s\n", + i, + pEATs[i].vaFunctionOffset, + pEATs[i].szFunction + ); + } + } else { + printf("FAIL: VMMDLL_ProcessGetEAT #2\n"); + return 1; + } + + + // Retrieve the import address table (IAT) of crypt32.dll + printf("------------------------------------------------------------\n"); + printf("#12: imports of 'crypt32.dll' in 'explorer.exe'. \n"); + ShowKeyPress(); + DWORD cIATs; + PVMMDLL_IAT_ENTRY pIATs; + printf("CALL: VMMDLL_ProcessGetIAT #1\n"); + result = VMMDLL_ProcessGetIAT(dwPID, "crypt32.dll", NULL, 0, &cIATs); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetIAT #1\n"); + printf(" Count = %i\n", cIATs); + } else { + printf("FAIL: VMMDLL_ProcessGetIAT #1\n"); + return 1; + } + pIATs = (PVMMDLL_IAT_ENTRY)LocalAlloc(LMEM_ZEROINIT, cIATs * sizeof(VMMDLL_IAT_ENTRY)); + if(!pIATs) { + printf("FAIL: OutOfMemory\n"); + return 1; + } + printf("CALL: VMMDLL_ProcessGetIAT #2\n"); + result = VMMDLL_ProcessGetIAT(dwPID, "crypt32.dll", pIATs, cIATs, &cIATs); + if(result) { + printf("SUCCESS: VMMDLL_ProcessGetIAT #2\n"); + printf(" # VIRTUAL_ADDRESS MODULE!NAME\n"); + printf(" ===================================\n"); + for(i = 0; i < cIATs; i++) { + printf( + " %04lx %016llx %s!%s\n", + i, + pIATs[i].vaFunction, + pIATs[i].szModule, + pIATs[i].szFunction + ); + } + } else { + printf("FAIL: VMMDLL_ProcessGetIAT #2\n"); + return 1; + } + + + // The Memory Process File System exists virtually in the form of a virtual + // file system even if it may not be mounted at a mount point or drive. + // It is possible to call the functions 'List', 'Read' and 'Write' by using + // the API. + // Virtual File System: 'List'. + printf("------------------------------------------------------------\n"); + printf("#13: call the file system 'List' function on the root dir. \n"); + ShowKeyPress(); + VMMDLL_VFS_FILELIST VfsFileList; + VfsFileList.h = 0; // your handle passed to the callback functions (not used in example). + VfsFileList.pfnAddDirectory = CallbackList_AddDirectory; + VfsFileList.pfnAddFile = CallbackList_AddFile; + printf("CALL: VMMDLL_VfsList\n"); + result = VMMDLL_VfsList(L"\\", &VfsFileList); + if(result) { + printf("SUCCESS: VMMDLL_VfsList\n"); + } else { + printf("FAIL: VMMDLL_VfsList\n"); + return 1; + } + + + // Virtual File System: 'Read' of 0x100 bytes from the offset 0x1000 + // in the physical memory by reading the /pmem physical memory file. + printf("------------------------------------------------------------\n"); + printf("#14: call the file system 'Read' function on the pmem file. \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_VfsRead\n"); + nt = VMMDLL_VfsRead(L"\\pmem", pbPage1, 0x100, &i, 0x1000); + if(nt == VMMDLL_STATUS_SUCCESS) { + printf("SUCCESS: VMMDLL_VfsRead\n"); + PrintHexAscii(pbPage1, i); + } else { + printf("FAIL: VMMDLL_VfsRead\n"); + return 1; + } + + + // Initialize plugin manager so that statistics may be read in the + // following read call to the .status built-in module/plugin. + printf("------------------------------------------------------------\n"); + printf("#15: initialize virtual file system plugins \n"); + printf(" (this is required for following read call) \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_VfsInitializePlugins\n"); + result = VMMDLL_VfsInitializePlugins(); + if(result) { + printf("SUCCESS: VMMDLL_VfsInitializePlugins\n"); + } else { + printf("FAIL: VMMDLL_VfsInitializePlugins\n"); + return 1; + } + + + // Virtual File System: 'Read' statistics from the .status module/plugin. + printf("------------------------------------------------------------\n"); + printf("#16: call file system 'Read' on .status\\statistics \n"); + ShowKeyPress(); + printf("CALL: VMMDLL_VfsRead\n"); + nt = VMMDLL_VfsRead(L"\\.status\\statistics", pbPage1, 0x1000, &i, 0); + if(nt == VMMDLL_STATUS_SUCCESS) { + printf("SUCCESS: VMMDLL_VfsRead\n"); + printf("%s", (LPSTR)pbPage1); + } else { + printf("FAIL: VMMDLL_VfsRead\n"); + return 1; + } + + + // Finish everything and exit! + printf("------------------------------------------------------------\n"); + printf("#99: FINISHED EXAMPLES! \n"); + ShowKeyPress(); + printf("FINISHED TEST CASES - EXITING!\n"); + return 0; +} diff --git a/vmmpyc/vmmdll.h b/vmmpyc/vmmdll.h new file mode 100644 index 0000000..623a09b --- /dev/null +++ b/vmmpyc/vmmdll.h @@ -0,0 +1,566 @@ +// vmmdll.h : header file to include in projects that use vmm.dll either as +// stand anlone projects or as native plugins to vmm.dll. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include + +#ifndef __VMMDLL_H__ +#define __VMMDLL_H__ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +/* +* RESERVED FUNCTION! DO NOT USE! +* Call other VMMDLL_Intialize functions to initialize VMM.DLL and the memory +* process file system. +*/ +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]); + +/* +* Initialize VMM.DLL from a memory dump file in raw format. VMM.DLL will be +* initialized in read-only mode. It's possible to optionally specify the page +* table base of the windows kernel (for full vmm features) or the page table +* base of a single 64-bit process in any x64 operating system. NB! usually it +* is not necessary to specify the PageTableBase - it will be auto-identified +* most often if the target is Windows. +* -- szFileName = the file name of the raw memory dump to use. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Intiailize VMM.DLL from a supported FPGA device over USB. VMM.DLL will be +* initialized in read/write mode upon success. Optionally it will be possible +* to specify the max physical address and the page table base of the kernel or +* process that should be investigated. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* -- szMaxPhysicalAddressOpt = max physical address of the target system as a +* hex string. Example: "0x8000000000". If zero value is given the max +* address will be auto-identified. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Initialize VMM.DLL from a the "Total Meltdown" CVE-2018-1038 vulnerability. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* initialized in read/write mode upon success. +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeTotalMeltdown(); + +/* +* Close an initialized instance of VMM.DLL and clean up all allocated resources +* including plugins, linked PCILeech.DLL and other memory resources. +* -- return = success/fail. +*/ +BOOL VMMDLL_Close(); + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +/* +* Options used together with the functions: VMMDLL_GetOption & VMMDLL_SetOption +* If VMM.DLL is chained with PCILEECH.DLL then required values will be passed +* along to PCILEECH.DLL when necessary. +* For more detailed information check the sources for individual device types. +*/ +#define VMMDLL_OPT_DEVICE_FPGA_PROBE_MAXPAGES 0x01 // RW +#define VMMDLL_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_RX 0x03 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_TX 0x04 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_READ 0x05 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE 0x06 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_WRITE 0x07 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_READ 0x08 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_RETRY_ON_ERROR 0x09 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DEVICE_ID 0x80 // R +#define VMMDLL_OPT_DEVICE_FPGA_FPGA_ID 0x81 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR 0x82 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR 0x83 // R + +#define VMMDLL_OPT_CORE_PRINTF_ENABLE 0x80000001 // RW +#define VMMDLL_OPT_CORE_VERBOSE 0x80000002 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R +#define VMMDLL_OPT_CORE_TARGET_SYSTEM 0x80000007 // R + +#define VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED 0x40000001 // R - 1/0 +#define VMMDLL_OPT_CONFIG_TICK_PERIOD 0x40000002 // RW - base tick period in ms +#define VMMDLL_OPT_CONFIG_READCACHE_TICKS 0x40000003 // RW - memory cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_TLBCACHE_TICKS 0x40000004 // RW - page table (tlb) cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL 0x40000005 // RW - process refresh (partial) period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL 0x40000006 // RW - process refresh (full) period (in ticks) +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR 0x40000007 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR 0x40000008 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION 0x40000009 // R +#define VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL 0x4000000A // RW - enable function call statistics (.status/statistics_fncall file) + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- pqwValue = pointer to ULONG64 to receive option value. +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- qwValue +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +// This is the core of the memory process file system. All implementation and +// analysis towards the file system is possible by using functionality below. +//----------------------------------------------------------------------------- + +#define VMMDLL_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMMDLL_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMMDLL_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMMDLL_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMMDLL_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +typedef struct tdVMMDLL_VFS_FILELIST { + VOID(*pfnAddFile) (_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved); + VOID(*pfnAddDirectory)(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved); + HANDLE h; +} VMMDLL_VFS_FILELIST, *PVMMDLL_VFS_FILELIST; + +/* +* Helper function macros for callbacks into the VMM_VFS_FILELIST structure. +*/ +#define VMMDLL_VfsList_AddFile(pFileList, szName, cb) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddFile(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, cb, NULL); } +#define VMMDLL_VfsList_AddDirectory(pFileList, szName) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddDirectory(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, NULL); } + +/* +* List a directory of files in the memory process file system. Directories and +* files will be listed by callbacks into functions supplied in the pFileList +* parameter. If information of an individual file is needed it's neccessary +* to list all files in its directory. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + +/* +* Read select parts of a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +* +*/ +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + +/* +* Write select parts to a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + +/* +* Utility functions for memory process file system read/write towards different +* underlying data representations. +*/ +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow); + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +// Function and structures to initialize and use the memory process file system +// plugin functionality. The plugin manager is started by a call to function: +// VMM_VfsInitializePlugins. Each built-in plugin and external plugin of which +// the DLL name matches m_*.dll will receive a call to its InitializeVmmPlugin +// function. The plugin/module may decide to call pfnPluginManager_Register to +// register plugins in the form of different names one or more times. +// Example of registration function in a plugin DLL below: +// 'VOID InitializeVmmPlugin(_In_ PVMM_PLUGIN_REGINFO pRegInfo)' +//----------------------------------------------------------------------------- + +/* +* Initialize all potential plugins, both built-in and external, that maps into +* the memory process file system. Please note that plugins are not loaded by +* default - they have to be explicitly loaded by calling this function. They +* will be unloaded on a general close of the vmm dll. +* -- return +*/ +BOOL VMMDLL_VfsInitializePlugins(); + +#define VMMDLL_PLUGIN_CONTEXT_MAGIC 0xc0ffee663df9301c +#define VMMDLL_PLUGIN_CONTEXT_VERSION 1 +#define VMMDLL_PLUGIN_REGINFO_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PLUGIN_REGINFO_VERSION 1 + +#define VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE 0x01 + +typedef struct tdVMMDLL_PLUGIN_CONTEXT { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD dwPID; + PHANDLE phModulePrivate; + PHANDLE phProcessPrivate; + PVOID pProcess; + LPSTR szModule; + LPSTR szPath; + PVOID pvReserved1; + PVOID pvReserved2; +} VMMDLL_PLUGIN_CONTEXT, *PVMMDLL_PLUGIN_CONTEXT; + +typedef struct tdVMMDLL_PLUGIN_REGINFO { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; + HMODULE hDLL; + HMODULE hReservedDll; // not for general use (only used for python). + BOOL(*pfnPluginManager_Register)(struct tdVMMDLL_PLUGIN_REGINFO *pPluginRegInfo); + PVOID pvReserved1; + PVOID pvReserved2; + // general plugin registration info to be filled out by the plugin below: + struct { + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + PVOID pvReserved1; + PVOID pvReserved2; + } reg_info; + // function plugin registration info to be filled out by the plugin below: + struct { + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + PVOID pvReserved1; + PVOID pvReserved2; + } reg_fn; +} VMMDLL_PLUGIN_REGINFO, *PVMMDLL_PLUGIN_REGINFO; + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +// Vmm core functaionlity such as read (and write) to both virtual and physical +// memory. NB! writing will only work if the target is supported - i.e. not a +// memory dump file... +// To read physical memory specify dwPID as (DWORD)-1 +//----------------------------------------------------------------------------- + +// FLAG used to supress the default read cache in calls to VMM_MemReadEx() +// which will lead to the read being fetched from the target system always. +// Cached page tables (used for translating virtual2physical) are still used. +#define VMMDLL_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMMDLL_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMMDLL_TARGET_UNKNOWN_X64 0x0001 +#define VMMDLL_TARGET_WINDOWS_X64 0x0002 + +typedef struct tdVMMDLL_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} VMMDLL_MEM_IO_SCATTER_HEADER, *PVMMDLL_MEM_IO_SCATTER_HEADER, **PPVMMDLL_MEM_IO_SCATTER_HEADER; + +/* +* Read memory in various non-contigious locations specified by the pointers to +* the items in the ppDMAs array. Result for each unit of work will be given +* individually. No upper limit of number of items to read, but no performance +* boost will be given if above hardware limit. Max size of each unit of work is +* one 4k page (4096 bytes). +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpDMAsRead = optional count of number of successfully read ppDMAs. +* -- flags = optional flags as given by VMM_FLAG_* +* -- return = the number of successfully read items. +*/ +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); + +/* +* Read a single 4096-byte page of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pbPage +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Read a contigious arbitrary amount of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious amount of memory and report the number of bytes read in pcbRead. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +* -- return = success/fail. NB! reads may report as success even if 0 bytes are +* read - it's recommended to verify pcbReadOpt parameter. +*/ +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + +/* +* Write a contigious arbitrary amount of memory. Please note some virtual memory +* such as pages of executables (such as DLLs) may be shared between different +* virtual memory over different processes. As an example a write to kernel32.dll +* in one process is likely to affect kernel32 in the whole system - in all +* processes. Heaps and Stacks and other memory are usually safe to write to. +* Please take care when writing to memory! +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Translate a virtual address to a physical address by walking the page tables +* of the specified process. +* -- dwPID +* -- qwVA +* -- pqwPA +* -- return = success/fail. +*/ +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +// Functionality below is mostly relating to Windows processes. +//----------------------------------------------------------------------------- + +/* +* Retrieve an active process given it's name. Please note that if multiple +* processes with the same name exists only one will be returned. If required to +* parse all processes with the same name please iterate over the PID list by +* calling VMMDLL_PidList together with VMMDLL_ProcessGetInformation. +* -- szProcName = process name (truncated max 15 chars) case insensitive. +* -- pdwPID = pointer that will receive PID on success. +* -- return +*/ +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + +/* +* List the PIDs in the system. +* -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. +* -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. +* -- return = success/fail. +*/ +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + +// flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define VMMDLL_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMMDLL_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMMDLL_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMMDLL_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +typedef struct tdVMMDLL_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMMDLL_MEMMAP_ENTRY, *PVMMDLL_MEMMAP_ENTRY; + +/* +* Retrieve memory map entries from the specified process. Memory map entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries bytes. +* If the pMemMapEntries is set to NULL the number of memory map entries will be +* given in the pcMemMapEntries parameter. +* -- dwPID +* -- pMemMapEntries = buffer of minimum length sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. +* -- pcMemMapEntries = pointer to number of memory map entries. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + +/* +* Retrieve a single memory map entry given a virtual address within that entrys +* range. +* -- dwPID +* -- pMemMapEntry +* -- va = virtual address in the memory map entry to retrieve. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules); + +typedef struct tdVMMDLL_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; +} VMMDLL_MODULEMAP_ENTRY, *PVMMDLL_MODULEMAP_ENTRY; + +/* +* Retrieve the module entries from the specified process. The module entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the +* pcModuleEntries is set to NULL the number of module entries will be given +* in the pcModuleEntries parameter. +* -- dwPID +* -- pModuleEntries = buffer of minimum length sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. +* -- pcModuleEntries = pointer to number of memory map entries. +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + +/* +* Retrieve a module (.exe or .dll or similar) given a module name. +* -- dwPID +* -- szModuleName +* -- pModuleEntry +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry); + +#define VMMDLL_PROCESS_INFORMATION_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PROCESS_INFORMATION_VERSION 1 + +typedef struct tdVMMDLL_PROCESS_INFORMATION { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; // as given by VMMDLL_TARGET_* + BOOL fUserOnly; // only user mode pages listed + DWORD dwPID; + DWORD dwState; + CHAR szName[16]; + ULONG64 paPML4; + ULONG64 paPML4_UserOpt; // may not exist + union { + struct { + ULONG64 vaEPROCESS; + ULONG64 vaPEB; + ULONG64 vaENTRY; + BOOL fWow64; + DWORD vaPEB32; // WoW64 only + } win; + } os; +} VMMDLL_PROCESS_INFORMATION, *PVMMDLL_PROCESS_INFORMATION; + +/* +* Retrieve various process information from a PID. Process information such as +* name, page directory bases and the process state may be retrieved. +* -- dwPID +* -- pProcessInformation = if null, size is given in *pcbProcessInfo +* -- pcbProcessInformation = size of pProcessInfo (in bytes) on entry and exit +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation); + +typedef struct tdVMMDLL_EAT_ENTRY { + ULONG64 vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMDLL_EAT_ENTRY, *PVMMDLL_EAT_ENTRY; + +typedef struct tdVMMDLL_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMDLL_IAT_ENTRY, *PVMMDLL_IAT_ENTRY; + +/* +* Retrieve information about: Data Directories, Sections, Export Address Table +* and Import Address Table (IAT). +* If the pData == NULL upon entry the number of entries of the pData array must +* have in order to be able to hold the data is returned. +* -- dwPID +* -- szModule +* -- pData +* -- cData +* -- pcData +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + + + +//----------------------------------------------------------------------------- +// VMM UTIL FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __VMMDLL_H__ */ diff --git a/vmmpyc/vmmpyc.c b/vmmpyc/vmmpyc.c new file mode 100644 index 0000000..a64a7e0 --- /dev/null +++ b/vmmpyc/vmmpyc.c @@ -0,0 +1,853 @@ +#ifdef _DEBUG +#undef _DEBUG +#include +#define _DEBUG +#else +#include +#endif +#include +#include "vmmdll.h" + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +// [STR] -> None +static PyObject* +VMMPYC_InitializeReserved(PyObject *self, PyObject *args) +{ + PyObject *pyList, *pyString; + BOOL result; + DWORD i, cDstArgs; + LPSTR *pszDstArgs; + if(!PyArg_ParseTuple(args, "O!", &PyList_Type, &pyList)) { return NULL; } // borrowed reference + cDstArgs = (DWORD)PyList_Size(pyList); + if(cDstArgs == 0) { + Py_DECREF(pyList); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_InitializeReserved: Required argument list is empty."); + } + // allocate & initialize buffer+basic + pszDstArgs = (LPSTR*)LocalAlloc(LMEM_ZEROINIT, sizeof(LPSTR) * cDstArgs); + if(!pszDstArgs) { + Py_DECREF(pyList); + return PyErr_NoMemory(); + } + // iterate over # entries and build argument list + for(i = 0; i < cDstArgs; i++) { + pyString = PyList_GetItem(pyList, i); // borrowed reference + if(!PyUnicode_Check(pyString)) { + Py_DECREF(pyList); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_InitializeReserved: Argument list contains non string item."); + } + pszDstArgs[i] = (char*)PyUnicode_1BYTE_DATA(pyString); + } + Py_DECREF(pyList); + result = VMMDLL_InitializeReserved(cDstArgs, pszDstArgs); + if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_InitializeReserved: Initialization of VMM failed."); } + return Py_BuildValue("s", NULL); // None returned on success. +} + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +// (ULONG64) -> ULONG64 +static PyObject* +VMMPYC_ConfigGet(PyObject *self, PyObject *args) +{ + BOOL result; + ULONG64 fOption, qwValue = 0; + if(!PyArg_ParseTuple(args, "K", &fOption)) { return NULL; } + Py_BEGIN_ALLOW_THREADS + result = VMMDLL_ConfigGet(fOption, &qwValue); + Py_END_ALLOW_THREADS + if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ConfigGet: Unable to retrieve config value for setting."); } + return PyLong_FromUnsignedLongLong(qwValue); +} + +// (ULONG64, ULONG64) -> None +static PyObject* +VMMPYC_ConfigSet(PyObject *self, PyObject *args) +{ + BOOL result; + ULONG64 fOption, qwValue = 0; + if(!PyArg_ParseTuple(args, "KK", &fOption, &qwValue)) { return NULL; } + Py_BEGIN_ALLOW_THREADS + result = VMMDLL_ConfigSet(fOption, qwValue); + Py_END_ALLOW_THREADS + if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ConfigSet: Unable to set config value for setting."); } + return Py_BuildValue("s", NULL); // None returned on success. +} + + + +//----------------------------------------------------------------------------- +// VMMPYC C-PYTHON FUNCTIONS BELOW: +//----------------------------------------------------------------------------- + +// (DWORD, [STR], (DWORD)) -> [{...}] +static PyObject* +VMMPYC_MemReadScatter(PyObject *self, PyObject *args) +{ + PyObject *pyListSrc, *pyListItemSrc, *pyListDst, *pyDict; + BOOL result; + DWORD dwPID, cMEMs, i, flags = 0; + ULONG64 qwA; + PVMMDLL_MEM_IO_SCATTER_HEADER pMEM, pMEMs; + PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs; + PBYTE pb, pbDataBuffer; + if(!PyArg_ParseTuple(args, "kO!|k", &dwPID, &PyList_Type, &pyListSrc, &flags)) { return NULL; } // borrowed reference + cMEMs = (DWORD)PyList_Size(pyListSrc); + if(cMEMs == 0) { + Py_DECREF(pyListSrc); + return PyList_New(0); + } + // allocate & initialize buffer+basic + pb = LocalAlloc(0, cMEMs * (sizeof(PVMMDLL_MEM_IO_SCATTER_HEADER) + sizeof(VMMDLL_MEM_IO_SCATTER_HEADER) + 0x1000)); + if(!pb) { + Py_DECREF(pyListSrc); + return PyErr_NoMemory(); + } + ppMEMs = (PPVMMDLL_MEM_IO_SCATTER_HEADER)pb; + pMEMs = (PVMMDLL_MEM_IO_SCATTER_HEADER)(pb + cMEMs * sizeof(PVMMDLL_MEM_IO_SCATTER_HEADER)); + pbDataBuffer = pb + cMEMs * (sizeof(PVMMDLL_MEM_IO_SCATTER_HEADER) + sizeof(VMMDLL_MEM_IO_SCATTER_HEADER)); + ZeroMemory(pb, pbDataBuffer - pb); + // iterate over # entries and build scatter data structure + for(i = 0; i < cMEMs; i++) { + pMEM = pMEMs + i; + pyListItemSrc = PyList_GetItem(pyListSrc, i); // borrowed reference + if(!pyListItemSrc || !PyLong_Check(pyListItemSrc)) { + Py_DECREF(pyListSrc); + LocalFree(pb); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemReadScatter: Argument list contains non numeric item."); + } + qwA = PyLong_AsUnsignedLongLong(pyListItemSrc); + if(qwA == (ULONG64)-1) { + Py_DECREF(pyListSrc); + LocalFree(pb); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemReadScatter: Argument list contains out-of-range numeric item."); + } + pMEM->cbMax = 0x1000; + pMEM->pb = pbDataBuffer + (i << 12); + pMEM->qwA = qwA; + ppMEMs[i] = pMEM; + } + Py_DECREF(pyListSrc); + // call c-dll for vmm + Py_BEGIN_ALLOW_THREADS + result = VMMDLL_MemReadScatter(dwPID, ppMEMs, cMEMs, flags); + Py_END_ALLOW_THREADS + if(!result) { + LocalFree(pb); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemReadScatter: Failed."); + } + if(!(pyListDst = PyList_New(0))) { + LocalFree(pb); + return PyErr_NoMemory(); + } + for(i = 0; i < cMEMs; i++) { + pMEM = pMEMs + i; + if((pyDict = PyDict_New())) { + PyDict_SetItemString(pyDict, "addr", PyLong_FromUnsignedLongLong(pMEM->qwA)); + PyDict_SetItemString(pyDict, ((dwPID == -1) ? "pa" : "va"), PyLong_FromUnsignedLongLong(pMEM->qwA)); + PyDict_SetItemString(pyDict, "data", PyBytes_FromStringAndSize(pMEM->pb, 0x1000)); + PyDict_SetItemString(pyDict, "size", PyLong_FromUnsignedLong(pMEM->cb)); + PyList_Append(pyListDst, pyDict); + } + } + LocalFree(pb); + return pyListDst; +} + +// (DWORD, ULONG64, DWORD, (ULONG64)) -> PBYTE +static PyObject* +VMMPYC_MemRead(PyObject *self, PyObject *args) +{ + PyObject *pyBytes; + BOOL result; + DWORD dwPID, cb, cbRead = 0; + ULONG64 qwA, flags = 0; + PBYTE pb; + if(!PyArg_ParseTuple(args, "kKk|K", &dwPID, &qwA, &cb, &flags)) { return NULL; } + if(cb > 0x01000000) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemRead: Read larger than maxium supported (0x01000000) bytes requested."); } + pb = LocalAlloc(0, cb); + if(!pb) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = VMMDLL_MemReadEx(dwPID, qwA, pb, cb, &cbRead, flags); + Py_END_ALLOW_THREADS + if(!result) { + LocalFree(pb); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemRead: Failed."); + } + pyBytes = PyBytes_FromStringAndSize(pb, cbRead); + LocalFree(pb); + return pyBytes; +} + +// (DWORD, ULONG64, PBYTE) -> None +static PyObject* +VMMPYC_MemWrite(PyObject *self, PyObject *args) +{ + Py_buffer pyBuffer; + BOOL result; + int iResult; + DWORD dwPID; + ULONG64 va; + PBYTE pb; + SIZE_T cb; + if(!PyArg_ParseTuple(args, "kKy*", &dwPID, &va, &pyBuffer)) { return NULL; } + cb = pyBuffer.len; + if(cb == 0) { + PyBuffer_Release(&pyBuffer); + return Py_BuildValue("s", NULL); // zero-byte write is always successful. + } + pb = LocalAlloc(0, cb); + if(!pb) { + PyBuffer_Release(&pyBuffer); + return PyErr_NoMemory(); + } + iResult = PyBuffer_ToContiguous(pb, &pyBuffer, cb, 'C'); + PyBuffer_Release(&pyBuffer); + Py_BEGIN_ALLOW_THREADS + result = (iResult == 0) && VMMDLL_MemWrite(dwPID, va, pb, (DWORD)cb); + LocalFree(pb); + Py_END_ALLOW_THREADS + if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemWrite: Failed."); } + return Py_BuildValue("s", NULL); // None returned on success. +} + +// (DWORD, ULONG64) -> ULONG64 +static PyObject* +VMMPYC_MemVirt2Phys(PyObject *self, PyObject *args) +{ + BOOL result; + DWORD dwPID; + ULONG64 va, pa; + if(!PyArg_ParseTuple(args, "kK", &dwPID, &va)) { return NULL; } + Py_BEGIN_ALLOW_THREADS + result = VMMDLL_MemVirt2Phys(dwPID, va, &pa); + Py_END_ALLOW_THREADS + if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_MemVirt2Phys: Failed."); } + return PyLong_FromUnsignedLongLong(pa); +} + +// (DWORD, (BOOL)) -> [{...}] +static PyObject* +VMMPYC_ProcessGetMemoryMap(PyObject *self, PyObject *args) +{ + PyObject *pyList, *pyDict; + BOOL result, fIdentifyModules; + DWORD dwPID, i; + ULONG64 cMemMapEntries; + PVMMDLL_MEMMAP_ENTRY pe, pMemMapEntries = NULL; + CHAR sz[5]; + if(!PyArg_ParseTuple(args, "k|p", &dwPID, &fIdentifyModules)) { return NULL; } + if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = + VMMDLL_ProcessGetMemoryMap(dwPID, NULL, &cMemMapEntries, fIdentifyModules) && + cMemMapEntries && + (pMemMapEntries = LocalAlloc(0, cMemMapEntries * sizeof(VMMDLL_MEMMAP_ENTRY))) && + VMMDLL_ProcessGetMemoryMap(dwPID, pMemMapEntries, &cMemMapEntries, fIdentifyModules); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyList); + LocalFree(pMemMapEntries); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetMemoryMap: Failed."); + } + for(i = 0; i < cMemMapEntries; i++) { + if((pyDict = PyDict_New())) { + pe = pMemMapEntries + i; + PyDict_SetItemString(pyDict, "va", PyLong_FromUnsignedLongLong(pe->AddrBase)); + PyDict_SetItemString(pyDict, "size", PyLong_FromUnsignedLongLong(pe->cPages << 12)); + PyDict_SetItemString(pyDict, "pages", PyLong_FromUnsignedLongLong(pe->cPages)); + PyDict_SetItemString(pyDict, "wow64", PyBool_FromLong((long)pe->fWoW64)); + PyDict_SetItemString(pyDict, "tag", PyUnicode_FromString(pe->szTag)); + PyDict_SetItemString(pyDict, "flags-pte", PyLong_FromUnsignedLongLong(pe->fPage)); + sz[0] = (pe->fPage & VMMDLL_MEMMAP_FLAG_PAGE_NS) ? '-' : 's'; + sz[1] = 'r'; + sz[2] = (pe->fPage & VMMDLL_MEMMAP_FLAG_PAGE_W) ? 'w' : '-'; + sz[3] = (pe->fPage & VMMDLL_MEMMAP_FLAG_PAGE_NX) ? '-' : 'x'; + sz[4] = 0; + PyDict_SetItemString(pyDict, "flags", PyUnicode_FromString(sz)); + PyList_Append(pyList, pyDict); + } + } + LocalFree(pMemMapEntries); + return pyList; +} + +// (DWORD, ULONG64, (DWORD)) -> {} +static PyObject* +VMMPYC_ProcessGetMemoryMapEntry(PyObject *self, PyObject *args) +{ + PyObject *pyDict; + BOOL result, fIdentifyModules; + DWORD dwPID; + ULONG64 va; + VMMDLL_MEMMAP_ENTRY e; + CHAR sz[5]; + if(!PyArg_ParseTuple(args, "kK|p", &dwPID, &va, &fIdentifyModules)) { return NULL; } + if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = VMMDLL_ProcessGetMemoryMapEntry(dwPID, &e, va, fIdentifyModules); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyDict); + return PyErr_Format(PyExc_RuntimeError, "VMMDLL_ProcessGetMemoryMapEntry: Failed."); + } + PyDict_SetItemString(pyDict, "va", PyLong_FromUnsignedLongLong(e.AddrBase)); + PyDict_SetItemString(pyDict, "size", PyLong_FromUnsignedLongLong(e.cPages << 12)); + PyDict_SetItemString(pyDict, "pages", PyLong_FromUnsignedLongLong(e.cPages)); + PyDict_SetItemString(pyDict, "wow64", PyBool_FromLong((long)e.fWoW64)); + PyDict_SetItemString(pyDict, "tag", PyUnicode_FromString(e.szTag)); + PyDict_SetItemString(pyDict, "flags-pte", PyLong_FromUnsignedLongLong(e.fPage)); + sz[0] = (e.fPage & VMMDLL_MEMMAP_FLAG_PAGE_NS) ? '-' : 's'; + sz[1] = 'r'; + sz[2] = (e.fPage & VMMDLL_MEMMAP_FLAG_PAGE_W) ? 'w' : '-'; + sz[3] = (e.fPage & VMMDLL_MEMMAP_FLAG_PAGE_NX) ? '-' : 'x'; + sz[4] = 0; + PyDict_SetItemString(pyDict, "flags", PyUnicode_FromString(sz)); + return pyDict; +} + +// (DWORD) -> [{...}] +static PyObject* +VMMPYC_ProcessGetModuleMap(PyObject *self, PyObject *args) +{ + PyObject *pyList, *pyDict; + BOOL result; + DWORD dwPID; + ULONG64 i, cModuleEntries; + PVMMDLL_MODULEMAP_ENTRY pe, pModuleEntries = NULL; + if(!PyArg_ParseTuple(args, "k", &dwPID)) { return NULL; } + if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = + VMMDLL_ProcessGetModuleMap(dwPID, NULL, &cModuleEntries) && + cModuleEntries && + (pModuleEntries = LocalAlloc(0, cModuleEntries * sizeof(VMMDLL_MODULEMAP_ENTRY))) && + VMMDLL_ProcessGetModuleMap(dwPID, pModuleEntries, &cModuleEntries); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyList); + LocalFree(pModuleEntries); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetModuleMap: Failed."); + } + for(i = 0; i < cModuleEntries; i++) { + if((pyDict = PyDict_New())) { + pe = pModuleEntries + i; + PyDict_SetItemString(pyDict, "va", PyLong_FromUnsignedLongLong(pe->BaseAddress)); + PyDict_SetItemString(pyDict, "va-entry", PyLong_FromUnsignedLongLong(pe->EntryPoint)); + PyDict_SetItemString(pyDict, "size", PyLong_FromUnsignedLong(pe->SizeOfImage)); + PyDict_SetItemString(pyDict, "wow64", PyBool_FromLong((long)pe->fWoW64)); + PyDict_SetItemString(pyDict, "name", PyUnicode_FromString(pe->szName)); + PyList_Append(pyList, pyDict); + } + } + LocalFree(pModuleEntries); + return pyList; +} + +// (DWORD, STR) -> {...} +static PyObject* +VMMPYC_ProcessGetModuleFromName(PyObject *self, PyObject *args) +{ + PyObject *pyDict; + BOOL result; + DWORD dwPID; + LPSTR szModuleName; + VMMDLL_MODULEMAP_ENTRY e; + if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModuleName)) { return NULL; } + if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + ZeroMemory(&e, sizeof(VMMDLL_MODULEMAP_ENTRY)); + result = VMMDLL_ProcessGetModuleFromName(dwPID, szModuleName, &e); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyDict); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetModuleFromName: Failed."); + } + PyDict_SetItemString(pyDict, "va", PyLong_FromUnsignedLongLong(e.BaseAddress)); + PyDict_SetItemString(pyDict, "va-entry", PyLong_FromUnsignedLongLong(e.EntryPoint)); + PyDict_SetItemString(pyDict, "wow64", PyBool_FromLong((long)e.fWoW64)); + PyDict_SetItemString(pyDict, "size", PyLong_FromUnsignedLong(e.SizeOfImage)); + PyDict_SetItemString(pyDict, "name", PyUnicode_FromString(e.szName)); + return pyDict; +} + +// (STR) -> DWORD +static PyObject* +VMMPYC_PidGetFromName(PyObject *self, PyObject *args) +{ + BOOL result; + DWORD dwPID; + LPSTR szProcessName; + if(!PyArg_ParseTuple(args, "s", &szProcessName)) { return NULL; } + Py_BEGIN_ALLOW_THREADS + result = VMMDLL_PidGetFromName(szProcessName, &dwPID); + Py_END_ALLOW_THREADS + if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_PidGetFromName: Failed."); } + return PyLong_FromLong(dwPID); +} + +// () -> [DWORD] +static PyObject* +VMMPYC_PidList(PyObject *self, PyObject *args) +{ + PyObject *pyList; + BOOL result; + ULONG64 cPIDs; + DWORD i, *pPIDs = NULL; + if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = + VMMDLL_PidList(NULL, &cPIDs) && + (pPIDs = LocalAlloc(LMEM_ZEROINIT, cPIDs * sizeof(DWORD))) && + VMMDLL_PidList(pPIDs, &cPIDs); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyList); + LocalFree(pPIDs); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_PidList: Failed."); + } + for(i = 0; i < cPIDs; i++) { + PyList_Append(pyList, PyLong_FromUnsignedLong(pPIDs[i])); + } + LocalFree(pPIDs); + return pyList; +} + +// (DWORD) -> {...} +static PyObject* +VMMPYC_ProcessGetInformation(PyObject *self, PyObject *args) +{ + PyObject *pyDict; + BOOL result; + DWORD dwPID; + VMMDLL_PROCESS_INFORMATION info; + SIZE_T cbInfo = sizeof(VMMDLL_PROCESS_INFORMATION); + if(!PyArg_ParseTuple(args, "k", &dwPID)) { return NULL; } + if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + ZeroMemory(&info, sizeof(VMMDLL_PROCESS_INFORMATION)); + info.magic = VMMDLL_PROCESS_INFORMATION_MAGIC; + info.wVersion = VMMDLL_PROCESS_INFORMATION_VERSION; + result = VMMDLL_ProcessGetInformation(dwPID, &info, &cbInfo); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyDict); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetInformation: Failed."); + } + PyDict_SetItemString(pyDict, "pid", PyLong_FromUnsignedLong(info.dwPID)); + PyDict_SetItemString(pyDict, "pa-pml4", PyLong_FromUnsignedLongLong(info.paPML4)); + PyDict_SetItemString(pyDict, "pa-pml4-user", PyLong_FromUnsignedLongLong(info.paPML4_UserOpt)); + PyDict_SetItemString(pyDict, "state", PyLong_FromUnsignedLong(info.dwState)); + PyDict_SetItemString(pyDict, "target", PyLong_FromUnsignedLong(info.fTargetSystem)); + PyDict_SetItemString(pyDict, "usermode", PyBool_FromLong(info.fUserOnly)); + PyDict_SetItemString(pyDict, "name", PyUnicode_FromString(info.szName)); + if(info.fTargetSystem == VMMDLL_TARGET_WINDOWS_X64) { + PyDict_SetItemString(pyDict, "wow64", PyBool_FromLong((long)info.os.win.fWow64)); + PyDict_SetItemString(pyDict, "va-entry", PyLong_FromUnsignedLongLong(info.os.win.vaENTRY)); + PyDict_SetItemString(pyDict, "va-eprocess", PyLong_FromUnsignedLongLong(info.os.win.vaEPROCESS)); + PyDict_SetItemString(pyDict, "va-peb", PyLong_FromUnsignedLongLong(info.os.win.vaPEB)); + PyDict_SetItemString(pyDict, "va-peb32", PyLong_FromUnsignedLongLong(info.os.win.vaPEB32)); + } + return pyDict; +} + +// (DWORD, STR) -> [{...}] +static PyObject* +VMMPYC_ProcessGetDirectories(PyObject *self, PyObject *args) +{ + PyObject *pyList, *pyDict; + BOOL result; + DWORD i, dwPID, cDirectories; + PIMAGE_DATA_DIRECTORY pe, pDirectories = NULL; + LPSTR szModule; + LPCSTR DIRECTORIES[16] = { "EXPORT", "IMPORT", "RESOURCE", "EXCEPTION", "SECURITY", "BASERELOC", "DEBUG", "ARCHITECTURE", "GLOBALPTR", "TLS", "LOAD_CONFIG", "BOUND_IMPORT", "IAT", "DELAY_IMPORT", "COM_DESCRIPTOR", "RESERVED" }; + if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } + if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = + (pDirectories = LocalAlloc(0, 16 * sizeof(IMAGE_DATA_DIRECTORY))) && + VMMDLL_ProcessGetDirectories(dwPID, szModule, pDirectories, 16, &cDirectories); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyList); + LocalFree(pDirectories); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetDirectories: Failed."); + } + for(i = 0; i < 16; i++) { + if((pyDict = PyDict_New())) { + pe = pDirectories + i; + PyDict_SetItemString(pyDict, "i", PyLong_FromUnsignedLong(i)); + PyDict_SetItemString(pyDict, "size", PyLong_FromUnsignedLong(pe->Size)); + PyDict_SetItemString(pyDict, "offset", PyLong_FromUnsignedLong(pe->VirtualAddress)); + PyDict_SetItemString(pyDict, "name", PyUnicode_FromString(DIRECTORIES[i])); + PyList_Append(pyList, pyDict); + } + } + LocalFree(pDirectories); + return pyList; +} + +// (DWORD, STR) -> [{...}] +static PyObject* +VMMPYC_ProcessGetSections(PyObject *self, PyObject *args) +{ + PyObject *pyList, *pyDict; + BOOL result; + DWORD i, dwPID, cSections; + PIMAGE_SECTION_HEADER pe, pSections = NULL; + LPSTR szModule; + CHAR szName[9]; + szName[8] = 0; + if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } + if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = + VMMDLL_ProcessGetSections(dwPID, szModule, NULL, 0, &cSections) && + cSections && + (pSections = LocalAlloc(0, cSections * sizeof(IMAGE_SECTION_HEADER))) && + VMMDLL_ProcessGetSections(dwPID, szModule, pSections, cSections, &cSections); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyList); + LocalFree(pSections); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetSections: Failed."); + } + for(i = 0; i < cSections; i++) { + if((pyDict = PyDict_New())) { + pe = pSections + i; + PyDict_SetItemString(pyDict, "i", PyLong_FromUnsignedLong(i)); + PyDict_SetItemString(pyDict, "Characteristics", PyLong_FromUnsignedLong(pe->Characteristics)); + PyDict_SetItemString(pyDict, "misc-PhysicalAddress", PyLong_FromUnsignedLong(pe->Misc.PhysicalAddress)); + PyDict_SetItemString(pyDict, "misc-VirtualSize", PyLong_FromUnsignedLong(pe->Misc.VirtualSize)); + *(PULONG64)szName = *(PULONG64)pe->Name; + PyDict_SetItemString(pyDict, "Name", PyUnicode_FromString(szName)); + PyDict_SetItemString(pyDict, "NumberOfLinenumbers", PyLong_FromUnsignedLong(pe->NumberOfLinenumbers)); + PyDict_SetItemString(pyDict, "NumberOfRelocations", PyLong_FromUnsignedLong(pe->NumberOfRelocations)); + PyDict_SetItemString(pyDict, "PointerToLinenumbers", PyLong_FromUnsignedLong(pe->PointerToLinenumbers)); + PyDict_SetItemString(pyDict, "PointerToRawData", PyLong_FromUnsignedLong(pe->PointerToRawData)); + PyDict_SetItemString(pyDict, "PointerToRelocations", PyLong_FromUnsignedLong(pe->PointerToRelocations)); + PyDict_SetItemString(pyDict, "SizeOfRawData", PyLong_FromUnsignedLong(pe->SizeOfRawData)); + PyDict_SetItemString(pyDict, "VirtualAddress", PyLong_FromUnsignedLong(pe->VirtualAddress)); + PyList_Append(pyList, pyDict); + } + } + LocalFree(pSections); + return pyList; +} + +// (DWORD, STR) -> [{...}] +static PyObject* +VMMPYC_ProcessGetEAT(PyObject *self, PyObject *args) +{ + PyObject *pyList, *pyDict; + BOOL result; + DWORD i, dwPID, cEATs; + PVMMDLL_EAT_ENTRY pe, pEATs = NULL; + LPSTR szModule; + if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } + if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = + VMMDLL_ProcessGetEAT(dwPID, szModule, NULL, 0, &cEATs) && + cEATs && + (pEATs = LocalAlloc(0, cEATs * sizeof(VMMDLL_EAT_ENTRY))) && + VMMDLL_ProcessGetEAT(dwPID, szModule, pEATs, cEATs, &cEATs); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyList); + LocalFree(pEATs); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetEAT: Failed."); + } + for(i = 0; i < cEATs; i++) { + if((pyDict = PyDict_New())) { + pe = pEATs + i; + PyDict_SetItemString(pyDict, "i", PyLong_FromUnsignedLong(i)); + PyDict_SetItemString(pyDict, "va", PyLong_FromUnsignedLongLong(pe->vaFunction)); + PyDict_SetItemString(pyDict, "offset", PyLong_FromUnsignedLong(pe->vaFunctionOffset)); + PyDict_SetItemString(pyDict, "fn", PyUnicode_FromString(pe->szFunction)); + PyList_Append(pyList, pyDict); + } + } + LocalFree(pEATs); + return pyList; +} + +// (DWORD, STR) -> [{...}] +static PyObject* +VMMPYC_ProcessGetIAT(PyObject *self, PyObject *args) +{ + PyObject *pyList, *pyDict; + BOOL result; + DWORD i, dwPID, cIATs; + PVMMDLL_IAT_ENTRY pe, pIATs = NULL; + LPSTR szModule; + if(!PyArg_ParseTuple(args, "ks", &dwPID, &szModule)) { return NULL; } + if(!(pyList = PyList_New(0))) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + result = + VMMDLL_ProcessGetIAT(dwPID, szModule, NULL, 0, &cIATs) && + cIATs && + (pIATs = LocalAlloc(0, cIATs * sizeof(VMMDLL_IAT_ENTRY))) && + VMMDLL_ProcessGetIAT(dwPID, szModule, pIATs, cIATs, &cIATs); + Py_END_ALLOW_THREADS + if(!result) { + Py_DECREF(pyList); + LocalFree(pIATs); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_ProcessGetIAT: Failed."); + } + for(i = 0; i < cIATs; i++) { + if((pyDict = PyDict_New())) { + pe = pIATs + i; + PyDict_SetItemString(pyDict, "i", PyLong_FromUnsignedLong(i)); + PyDict_SetItemString(pyDict, "va", PyLong_FromUnsignedLongLong(pe->vaFunction)); + PyDict_SetItemString(pyDict, "fn", PyUnicode_FromString(pe->szFunction)); + PyDict_SetItemString(pyDict, "dll", PyUnicode_FromString(pe->szModule)); + PyList_Append(pyList, pyDict); + } + } + LocalFree(pIATs); + return pyList; +} + +// (PBYTE, (DWORD)) -> STR +static PyObject* +VMMPYC_UtilFillHexAscii(PyObject *self, PyObject *args) +{ + Py_buffer pyBuffer; + PyObject *pyString; + DWORD cb, cbInitialOffset = 0, iResult, csz = 0; + PBYTE pb; + LPSTR sz = NULL; + BOOL result; + if(!PyArg_ParseTuple(args, "y*|k", &pyBuffer, &cbInitialOffset)) { return NULL; } + cb = (DWORD)pyBuffer.len; + if(cb == 0) { + PyBuffer_Release(&pyBuffer); + return PyUnicode_FromString(""); + } + pb = LocalAlloc(0, cb); + if(!pb) { + PyBuffer_Release(&pyBuffer); + return PyErr_NoMemory(); + } + iResult = PyBuffer_ToContiguous(pb, &pyBuffer, cb, 'C'); + PyBuffer_Release(&pyBuffer); + Py_BEGIN_ALLOW_THREADS + result = + (iResult == 0) && + VMMDLL_UtilFillHexAscii(pb, cb, cbInitialOffset, NULL, &csz) && + csz && + (sz = (LPSTR)LocalAlloc(0, csz)) && + VMMDLL_UtilFillHexAscii(pb, cb, cbInitialOffset, sz, &csz); + LocalFree(pb); + Py_END_ALLOW_THREADS + if(!result || !sz) { + LocalFree(sz); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_UtilFillHexAscii: Failed."); + } + pyString = PyUnicode_FromString(sz); + LocalFree(sz); + return pyString; +} + +// (STR, DWORD, (ULONG64)) -> PBYTE +static PyObject* +VMMPYC_VfsRead(PyObject *self, PyObject *args) +{ + PyObject *pyBytes; + NTSTATUS nt; + DWORD i, cb, cbRead = 0; + ULONG64 cbOffset = 0; + PBYTE pb; + LPSTR szFileName; + WCHAR wszFileName[MAX_PATH]; + if(!PyArg_ParseTuple(args, "sk|K", &szFileName, &cb, &cbOffset)) { return NULL; } + if(cb > 0x01000000) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_VfsRead: Read larger than maxium supported (0x01000000) bytes requested."); } + { // char* -> wchar* + for(i = 0; i < MAX_PATH - 1; i++) { + wszFileName[i] = szFileName[i]; + if(0 == szFileName[i]) { break; } + } + wszFileName[MAX_PATH - 1] = 0; + } + pb = LocalAlloc(0, cb); + if(!pb) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + nt = VMMDLL_VfsRead(wszFileName, pb, cb, &cbRead, cbOffset); + Py_END_ALLOW_THREADS + if(nt != VMMDLL_STATUS_SUCCESS) { + LocalFree(pb); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_VfsRead: Failed."); + } + pyBytes = PyBytes_FromStringAndSize(pb, cbRead); + LocalFree(pb); + return pyBytes; +} + +// (STR, PBYTE, (ULONG64)) -> None +static PyObject* +VMMPYC_VfsWrite(PyObject *self, PyObject *args) +{ + Py_buffer pyBuffer; + BOOL result; + int iResult; + DWORD i, cb, cbWritten; + ULONG64 cbOffset; + PBYTE pb; + LPSTR szFileName; + WCHAR wszFileName[MAX_PATH]; + if(!PyArg_ParseTuple(args, "sy*|K", &szFileName, &pyBuffer, &cbOffset)) { return NULL; } + cb = (DWORD)pyBuffer.len; + if(cb == 0) { + PyBuffer_Release(&pyBuffer); + return Py_BuildValue("s", NULL); // zero-byte write is always successful. + } + { // char* -> wchar* + for(i = 0; i < MAX_PATH - 1; i++) { + wszFileName[i] = szFileName[i]; + if(0 == szFileName[i]) { break; } + } + wszFileName[MAX_PATH - 1] = 0; + } + pb = LocalAlloc(0, cb); + if(!pb) { + PyBuffer_Release(&pyBuffer); + return PyErr_NoMemory(); + } + iResult = PyBuffer_ToContiguous(pb, &pyBuffer, cb, 'C'); + PyBuffer_Release(&pyBuffer); + Py_BEGIN_ALLOW_THREADS + result = (iResult == 0) && (VMMDLL_STATUS_SUCCESS == VMMDLL_VfsWrite(wszFileName, pb, cb, &cbWritten, cbOffset)); + LocalFree(pb); + Py_END_ALLOW_THREADS + if(!result) { return PyErr_Format(PyExc_RuntimeError, "VMMPYC_VfsWrite: Failed."); } + return Py_BuildValue("s", NULL); // None returned on success. +} + + +typedef struct tdVMMPYC_VFSLIST { + struct tdVMMPYC_VFSLIST *FLink; + CHAR szName[MAX_PATH]; + BOOL fIsDir; + ULONG64 qwSize; +} VMMPYC_VFSLIST, *PVMMPYC_VFSLIST; + + +VOID VMMPYC_VfsList_AddInternal(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 size, _In_ BOOL fIsDirectory) +{ + PVMMPYC_VFSLIST *ppE = (PVMMPYC_VFSLIST*)h; + PVMMPYC_VFSLIST pE; + if((pE = LocalAlloc(0, sizeof(VMMPYC_VFSLIST)))) { + strncpy_s(pE->szName, MAX_PATH - 1, szName, _TRUNCATE); + pE->fIsDir = fIsDirectory; + pE->qwSize = size; + pE->FLink = *ppE; + *ppE = pE; + } +} + +VOID VMMPYC_VfsList_AddFile(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 size, _In_ PVOID pvReserved) +{ + VMMPYC_VfsList_AddInternal(h, szName, size, FALSE); +} + +VOID VMMPYC_VfsList_AddDirectory(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved) +{ + VMMPYC_VfsList_AddInternal(h, szName, 0, TRUE); +} + + +// (STR) -> {{...}} +static PyObject* +VMMPYC_VfsList(PyObject *self, PyObject *args) +{ + PyObject *pyDict, *PyDict_Attr; + BOOL result; + DWORD i; + LPSTR szPath; + WCHAR wszPath[MAX_PATH]; + VMMDLL_VFS_FILELIST hFileList; + PVMMPYC_VFSLIST pE = NULL, pE_Next; + if(!PyArg_ParseTuple(args, "s", &szPath)) { return NULL; } + if(!(pyDict = PyDict_New())) { return PyErr_NoMemory(); } + Py_BEGIN_ALLOW_THREADS + { // char* -> wchar* + for(i = 0; i < MAX_PATH - 1; i++) { + wszPath[i] = szPath[i]; + if(0 == szPath[i]) { break; } + } + wszPath[MAX_PATH - 1] = 0; + } + hFileList.h = &pE; + hFileList.pfnAddFile = VMMPYC_VfsList_AddFile; + hFileList.pfnAddDirectory = VMMPYC_VfsList_AddDirectory; + result = VMMDLL_VfsList(wszPath, &hFileList); + pE = *(PVMMPYC_VFSLIST*)hFileList.h; + Py_END_ALLOW_THREADS + while(pE) { + if((PyDict_Attr = PyDict_New())) { + PyDict_SetItemString(PyDict_Attr, "f_isdir", PyBool_FromLong(pE->fIsDir ? 1 : 0)); + PyDict_SetItemString(PyDict_Attr, "size", PyLong_FromUnsignedLongLong(pE->qwSize)); + PyDict_SetItemString(pyDict, pE->szName, PyDict_Attr); + } + pE_Next = pE->FLink; + LocalFree(pE); + pE = pE_Next; + } + if(!result) { + Py_DECREF(pyDict); + return PyErr_Format(PyExc_RuntimeError, "VMMPYC_VfsList: Failed."); + } + return pyDict; +} + +//----------------------------------------------------------------------------- +// PY2C common functionality below: +//----------------------------------------------------------------------------- + +static PyMethodDef VMMPYC_EmbMethods[] = { + {"VMMPYC_InitializeReserved", VMMPYC_InitializeReserved, METH_VARARGS, "Initialize the VMM"}, + {"VMMPYC_ConfigGet", VMMPYC_ConfigGet, METH_VARARGS, "Get a device specific option value."}, + {"VMMPYC_ConfigSet", VMMPYC_ConfigSet, METH_VARARGS, "Set a device specific option value."}, + {"VMMPYC_MemReadScatter", VMMPYC_MemReadScatter, METH_VARARGS, "Read multiple 4kB page sized and aligned chunks of memory given as an address list."}, + {"VMMPYC_MemRead", VMMPYC_MemRead, METH_VARARGS, "Read memory."}, + {"VMMPYC_MemWrite", VMMPYC_MemWrite, METH_VARARGS, "Write memory."}, + {"VMMPYC_MemVirt2Phys", VMMPYC_MemVirt2Phys, METH_VARARGS, "Translate a virtual address into a physical address."}, + {"VMMPYC_PidGetFromName", VMMPYC_PidGetFromName, METH_VARARGS, "Locate a process by name and return the PID."}, + {"VMMPYC_PidList", VMMPYC_PidList, METH_VARARGS, "List all process PIDs."}, + {"VMMPYC_ProcessGetMemoryMap", VMMPYC_ProcessGetMemoryMap, METH_VARARGS, "Retrieve the memory map for a given process."}, + {"VMMPYC_ProcessGetMemoryMapEntry", VMMPYC_ProcessGetMemoryMapEntry, METH_VARARGS, "Retrieve a single memory map entry for a given process and virtual address."}, + {"VMMPYC_ProcessGetModuleMap", VMMPYC_ProcessGetModuleMap, METH_VARARGS, "Retrieve the module map for a given process."}, + {"VMMPYC_ProcessGetModuleFromName", VMMPYC_ProcessGetModuleFromName, METH_VARARGS, "Locate a module by name and return its information."}, + {"VMMPYC_ProcessGetInformation", VMMPYC_ProcessGetInformation, METH_VARARGS, "Retrieve process information for a specific process."}, + {"VMMPYC_ProcessGetDirectories", VMMPYC_ProcessGetDirectories, METH_VARARGS, "Retrieve the data directories for a specific process and module."}, + {"VMMPYC_ProcessGetSections", VMMPYC_ProcessGetSections, METH_VARARGS, "Retrieve the sections for a specific process and module."}, + {"VMMPYC_ProcessGetEAT", VMMPYC_ProcessGetEAT, METH_VARARGS, "Retrieve the export address table (EAT) for a specific process and module."}, + {"VMMPYC_ProcessGetIAT", VMMPYC_ProcessGetIAT, METH_VARARGS, "Retrieve the import address table (IAT) for a specific process and module."}, + {"VMMPYC_VfsRead", VMMPYC_VfsRead, METH_VARARGS, "Read from a file in the virtual file system."}, + {"VMMPYC_VfsWrite", VMMPYC_VfsWrite, METH_VARARGS, "Write to a file in the virtual file system."}, + {"VMMPYC_VfsList", VMMPYC_VfsList, METH_VARARGS, "List files and folder for a specific directory in the Virutal File System."}, + {"VMMPYC_UtilFillHexAscii", VMMPYC_UtilFillHexAscii, METH_VARARGS, "Convert a bytes object into a human readable 'memory dump' style type of string."}, + {NULL, NULL, 0, NULL} +}; + +static PyModuleDef VMMPYC_EmbModule = { + PyModuleDef_HEAD_INIT, "vmmpyc", NULL, -1, VMMPYC_EmbMethods, + NULL, NULL, NULL, NULL +}; + +__declspec(dllexport) +PyObject* PyInit_vmmpyc(void) +{ + return PyModule_Create(&VMMPYC_EmbModule); +} diff --git a/vmmpyc/vmmpyc.vcxproj b/vmmpyc/vmmpyc.vcxproj new file mode 100644 index 0000000..ce6661b --- /dev/null +++ b/vmmpyc/vmmpyc.vcxproj @@ -0,0 +1,100 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {8F227E6C-6156-4F7D-965F-BDB9B75FFB4E} + vmmpyc + 8.1 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + .pyd + $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\include\; + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\libs; + + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + .pyd + $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\include\; + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\libs; + + + + Level3 + Disabled + true + true + + + $(SolutionDir)\files\vmm.lib + $(OutDir)\lib\$(TargetName).pdb + $(OutDir)\lib\$(TargetName).lib + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + $(SolutionDir)\files\vmm.lib + UseLinkTimeCodeGeneration + $(OutDir)\lib\$(TargetName).pdb + $(OutDir)\lib\$(TargetName).lib + + + + + + + + + + + + \ No newline at end of file diff --git a/vmmpyc/vmmpyc.vcxproj.filters b/vmmpyc/vmmpyc.vcxproj.filters new file mode 100644 index 0000000..2937ad6 --- /dev/null +++ b/vmmpyc/vmmpyc.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {b6c15f41-24a1-401e-8ad1-c4b158f9d770} + + + + + Header Files\vmm + + + + + Source Files + + + \ No newline at end of file diff --git a/vmmpyc/vmmpyc.vcxproj.user b/vmmpyc/vmmpyc.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/vmmpyc/vmmpyc.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/vmmpycplugin/vmmdll.h b/vmmpycplugin/vmmdll.h new file mode 100644 index 0000000..623a09b --- /dev/null +++ b/vmmpycplugin/vmmdll.h @@ -0,0 +1,566 @@ +// vmmdll.h : header file to include in projects that use vmm.dll either as +// stand anlone projects or as native plugins to vmm.dll. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// + +#include + +#ifndef __VMMDLL_H__ +#define __VMMDLL_H__ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +//----------------------------------------------------------------------------- +// INITIALIZATION FUNCTIONALITY BELOW: +// Choose one way of initialzing the VMM / Memory Process File System. +//----------------------------------------------------------------------------- + +/* +* RESERVED FUNCTION! DO NOT USE! +* Call other VMMDLL_Intialize functions to initialize VMM.DLL and the memory +* process file system. +*/ +BOOL VMMDLL_InitializeReserved(_In_ DWORD argc, _In_ LPSTR argv[]); + +/* +* Initialize VMM.DLL from a memory dump file in raw format. VMM.DLL will be +* initialized in read-only mode. It's possible to optionally specify the page +* table base of the windows kernel (for full vmm features) or the page table +* base of a single 64-bit process in any x64 operating system. NB! usually it +* is not necessary to specify the PageTableBase - it will be auto-identified +* most often if the target is Windows. +* -- szFileName = the file name of the raw memory dump to use. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFile(_In_ LPSTR szFileName, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Intiailize VMM.DLL from a supported FPGA device over USB. VMM.DLL will be +* initialized in read/write mode upon success. Optionally it will be possible +* to specify the max physical address and the page table base of the kernel or +* process that should be investigated. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* -- szMaxPhysicalAddressOpt = max physical address of the target system as a +* hex string. Example: "0x8000000000". If zero value is given the max +* address will be auto-identified. +* -- szPageTableBaseOpt = optionally the Page Table Base of kernel or process +* as hex string. NB! this is usally not required. Example: "0x1ab000". +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeFPGA(_In_opt_ LPSTR szMaxPhysicalAddressOpt, _In_opt_ LPSTR szPageTableBaseOpt); + +/* +* Initialize VMM.DLL from a the "Total Meltdown" CVE-2018-1038 vulnerability. +* NB! Requires pcileech.dll to be placed in the same directory as vmm.dll. +* initialized in read/write mode upon success. +* -- return = success/fail. +*/ +BOOL VMMDLL_InitializeTotalMeltdown(); + +/* +* Close an initialized instance of VMM.DLL and clean up all allocated resources +* including plugins, linked PCILeech.DLL and other memory resources. +* -- return = success/fail. +*/ +BOOL VMMDLL_Close(); + + + +//----------------------------------------------------------------------------- +// CONFIGURATION SETTINGS BELOW: +// Configure the memory process file system or the underlying memory +// acquisition devices. +//----------------------------------------------------------------------------- + +/* +* Options used together with the functions: VMMDLL_GetOption & VMMDLL_SetOption +* If VMM.DLL is chained with PCILEECH.DLL then required values will be passed +* along to PCILEECH.DLL when necessary. +* For more detailed information check the sources for individual device types. +*/ +#define VMMDLL_OPT_DEVICE_FPGA_PROBE_MAXPAGES 0x01 // RW +#define VMMDLL_OPT_DEVICE_FPGA_RX_FLUSH_LIMIT 0x02 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_RX 0x03 // RW +#define VMMDLL_OPT_DEVICE_FPGA_MAX_SIZE_TX 0x04 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_READ 0x05 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_PROBE_WRITE 0x06 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_WRITE 0x07 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_DELAY_READ 0x08 // RW - uS +#define VMMDLL_OPT_DEVICE_FPGA_RETRY_ON_ERROR 0x09 // RW +#define VMMDLL_OPT_DEVICE_FPGA_DEVICE_ID 0x80 // R +#define VMMDLL_OPT_DEVICE_FPGA_FPGA_ID 0x81 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MAJOR 0x82 // R +#define VMMDLL_OPT_DEVICE_FPGA_VERSION_MINOR 0x83 // R + +#define VMMDLL_OPT_CORE_PRINTF_ENABLE 0x80000001 // RW +#define VMMDLL_OPT_CORE_VERBOSE 0x80000002 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA 0x80000003 // RW +#define VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP 0x80000004 // RW +#define VMMDLL_OPT_CORE_MAX_NATIVE_ADDRESS 0x80000005 // R +#define VMMDLL_OPT_CORE_MAX_NATIVE_IOSIZE 0x80000006 // R +#define VMMDLL_OPT_CORE_TARGET_SYSTEM 0x80000007 // R + +#define VMMDLL_OPT_CONFIG_IS_REFRESH_ENABLED 0x40000001 // R - 1/0 +#define VMMDLL_OPT_CONFIG_TICK_PERIOD 0x40000002 // RW - base tick period in ms +#define VMMDLL_OPT_CONFIG_READCACHE_TICKS 0x40000003 // RW - memory cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_TLBCACHE_TICKS 0x40000004 // RW - page table (tlb) cache validity period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_PARTIAL 0x40000005 // RW - process refresh (partial) period (in ticks) +#define VMMDLL_OPT_CONFIG_PROCCACHE_TICKS_TOTAL 0x40000006 // RW - process refresh (full) period (in ticks) +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MAJOR 0x40000007 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_MINOR 0x40000008 // R +#define VMMDLL_OPT_CONFIG_VMM_VERSION_REVISION 0x40000009 // R +#define VMMDLL_OPT_CONFIG_STATISTICS_FUNCTIONCALL 0x4000000A // RW - enable function call statistics (.status/statistics_fncall file) + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- pqwValue = pointer to ULONG64 to receive option value. +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigGet(_In_ ULONG64 fOption, _Out_ PULONG64 pqwValue); + +/* +* Set a device specific option value. Please see defines VMMDLL_OPT_* for infor- +* mation about valid option values. Please note that option values may overlap +* between different device types with different meanings. +* -- fOption +* -- qwValue +* -- return = success/fail. +*/ +BOOL VMMDLL_ConfigSet(_In_ ULONG64 fOption, _In_ ULONG64 qwValue); + + + +//----------------------------------------------------------------------------- +// VFS - VIRTUAL FILE SYSTEM FUNCTIONALITY BELOW: +// This is the core of the memory process file system. All implementation and +// analysis towards the file system is possible by using functionality below. +//----------------------------------------------------------------------------- + +#define VMMDLL_STATUS_SUCCESS ((NTSTATUS)0x00000000L) +#define VMMDLL_STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L) +#define VMMDLL_STATUS_END_OF_FILE ((NTSTATUS)0xC0000011L) +#define VMMDLL_STATUS_FILE_INVALID ((NTSTATUS)0xC0000098L) +#define VMMDLL_STATUS_FILE_SYSTEM_LIMITATION ((NTSTATUS)0xC0000427L) + +typedef struct tdVMMDLL_VFS_FILELIST { + VOID(*pfnAddFile) (_Inout_ HANDLE h, _In_ LPSTR szName, _In_ ULONG64 cb, _In_ PVOID pvReserved); + VOID(*pfnAddDirectory)(_Inout_ HANDLE h, _In_ LPSTR szName, _In_ PVOID pvReserved); + HANDLE h; +} VMMDLL_VFS_FILELIST, *PVMMDLL_VFS_FILELIST; + +/* +* Helper function macros for callbacks into the VMM_VFS_FILELIST structure. +*/ +#define VMMDLL_VfsList_AddFile(pFileList, szName, cb) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddFile(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, cb, NULL); } +#define VMMDLL_VfsList_AddDirectory(pFileList, szName) { ((PVMMDLL_VFS_FILELIST)pFileList)->pfnAddDirectory(((PVMMDLL_VFS_FILELIST)pFileList)->h, szName, NULL); } + +/* +* List a directory of files in the memory process file system. Directories and +* files will be listed by callbacks into functions supplied in the pFileList +* parameter. If information of an individual file is needed it's neccessary +* to list all files in its directory. +* -- wcsPath +* -- pFileList +* -- return +*/ +BOOL VMMDLL_VfsList(_In_ LPCWSTR wcsPath, _Inout_ PVMMDLL_VFS_FILELIST pFileList); + +/* +* Read select parts of a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbRead +* -- cbOffset +* -- return +* +*/ +NTSTATUS VMMDLL_VfsRead(_In_ LPCWSTR wcsFileName, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + +/* +* Write select parts to a file in the memory process file system. +* -- wcsFileName +* -- pb +* -- cb +* -- pcbWrite +* -- cbOffset +* -- return +*/ +NTSTATUS VMMDLL_VfsWrite(_In_ LPCWSTR wcsFileName, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + +/* +* Utility functions for memory process file system read/write towards different +* underlying data representations. +*/ +NTSTATUS VMMDLL_UtilVfsReadFile_FromPBYTE(_In_ PBYTE pbFile, _In_ ULONG64 cbFile, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsReadFile_FromQWORD(_In_ ULONG64 qwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromDWORD(_In_ DWORD dwValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset, _In_ BOOL fPrefix); +NTSTATUS VMMDLL_UtilVfsReadFile_FromBOOL(_In_ BOOL fValue, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_BOOL(_Inout_ PBOOL pfTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); +NTSTATUS VMMDLL_UtilVfsWriteFile_DWORD(_Inout_ PDWORD pdwTarget, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset, _In_ DWORD dwMinAllow); + + +//----------------------------------------------------------------------------- +// PLUGIN MANAGER FUNCTIONALITY BELOW: +// Function and structures to initialize and use the memory process file system +// plugin functionality. The plugin manager is started by a call to function: +// VMM_VfsInitializePlugins. Each built-in plugin and external plugin of which +// the DLL name matches m_*.dll will receive a call to its InitializeVmmPlugin +// function. The plugin/module may decide to call pfnPluginManager_Register to +// register plugins in the form of different names one or more times. +// Example of registration function in a plugin DLL below: +// 'VOID InitializeVmmPlugin(_In_ PVMM_PLUGIN_REGINFO pRegInfo)' +//----------------------------------------------------------------------------- + +/* +* Initialize all potential plugins, both built-in and external, that maps into +* the memory process file system. Please note that plugins are not loaded by +* default - they have to be explicitly loaded by calling this function. They +* will be unloaded on a general close of the vmm dll. +* -- return +*/ +BOOL VMMDLL_VfsInitializePlugins(); + +#define VMMDLL_PLUGIN_CONTEXT_MAGIC 0xc0ffee663df9301c +#define VMMDLL_PLUGIN_CONTEXT_VERSION 1 +#define VMMDLL_PLUGIN_REGINFO_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PLUGIN_REGINFO_VERSION 1 + +#define VMMDLL_PLUGIN_EVENT_VERBOSITYCHANGE 0x01 + +typedef struct tdVMMDLL_PLUGIN_CONTEXT { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD dwPID; + PHANDLE phModulePrivate; + PHANDLE phProcessPrivate; + PVOID pProcess; + LPSTR szModule; + LPSTR szPath; + PVOID pvReserved1; + PVOID pvReserved2; +} VMMDLL_PLUGIN_CONTEXT, *PVMMDLL_PLUGIN_CONTEXT; + +typedef struct tdVMMDLL_PLUGIN_REGINFO { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; + HMODULE hDLL; + HMODULE hReservedDll; // not for general use (only used for python). + BOOL(*pfnPluginManager_Register)(struct tdVMMDLL_PLUGIN_REGINFO *pPluginRegInfo); + PVOID pvReserved1; + PVOID pvReserved2; + // general plugin registration info to be filled out by the plugin below: + struct { + HANDLE hModulePrivate; + CHAR szModuleName[32]; + BOOL fRootModule; + BOOL fProcessModule; + PVOID pvReserved1; + PVOID pvReserved2; + } reg_info; + // function plugin registration info to be filled out by the plugin below: + struct { + BOOL(*pfnList)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList); + NTSTATUS(*pfnRead)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset); + NTSTATUS(*pfnWrite)(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset); + VOID(*pfnNotify)(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent); + VOID(*pfnCloseHandleModule)(_Inout_opt_ PHANDLE phModulePrivate); + VOID(*pfnCloseHandleProcess)(_Inout_opt_ PHANDLE phModulePrivate, _Inout_ PHANDLE phProcessPrivate); + PVOID pvReserved1; + PVOID pvReserved2; + } reg_fn; +} VMMDLL_PLUGIN_REGINFO, *PVMMDLL_PLUGIN_REGINFO; + +//----------------------------------------------------------------------------- +// VMM CORE FUNCTIONALITY BELOW: +// Vmm core functaionlity such as read (and write) to both virtual and physical +// memory. NB! writing will only work if the target is supported - i.e. not a +// memory dump file... +// To read physical memory specify dwPID as (DWORD)-1 +//----------------------------------------------------------------------------- + +// FLAG used to supress the default read cache in calls to VMM_MemReadEx() +// which will lead to the read being fetched from the target system always. +// Cached page tables (used for translating virtual2physical) are still used. +#define VMMDLL_FLAG_NOCACHE 0x0001 // do not use the data cache (force reading from memory acquisition device) +#define VMMDLL_FLAG_ZEROPAD_ON_FAIL 0x0002 // zero pad failed physical memory reads and report success if read within range of physical memory. + +#define VMMDLL_TARGET_UNKNOWN_X64 0x0001 +#define VMMDLL_TARGET_WINDOWS_X64 0x0002 + +typedef struct tdVMMDLL_MEM_IO_SCATTER_HEADER { + ULONG64 qwA; // base address (DWORD boundry). + DWORD cbMax; // bytes to read (DWORD boundry, max 0x1000); pbResult must have room for this. + DWORD cb; // bytes read into result buffer. + PBYTE pb; // ptr to 0x1000 sized buffer to receive read bytes. + PVOID pvReserved1; // reserved for use by caller. + PVOID pvReserved2; // reserved for use by caller. + struct { + PVOID pvReserved1; + PVOID pvReserved2; + BYTE pbReserved[32]; + } sReserved; // reserved for future use. +} VMMDLL_MEM_IO_SCATTER_HEADER, *PVMMDLL_MEM_IO_SCATTER_HEADER, **PPVMMDLL_MEM_IO_SCATTER_HEADER; + +/* +* Read memory in various non-contigious locations specified by the pointers to +* the items in the ppDMAs array. Result for each unit of work will be given +* individually. No upper limit of number of items to read, but no performance +* boost will be given if above hardware limit. Max size of each unit of work is +* one 4k page (4096 bytes). +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- ppMEMs = array of scatter read headers. +* -- cpMEMs = count of ppDMAs. +* -- pcpDMAsRead = optional count of number of successfully read ppDMAs. +* -- flags = optional flags as given by VMM_FLAG_* +* -- return = the number of successfully read items. +*/ +DWORD VMMDLL_MemReadScatter(_In_ DWORD dwPID, _Inout_ PPVMMDLL_MEM_IO_SCATTER_HEADER ppMEMs, _In_ DWORD cpMEMs, _In_ DWORD flags); + +/* +* Read a single 4096-byte page of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pbPage +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemReadPage(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_bytecount_(4096) PBYTE pbPage); + +/* +* Read a contigious arbitrary amount of memory. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = success/fail (depending if all requested bytes are read or not). +*/ +BOOL VMMDLL_MemRead(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Read a contigious amount of memory and report the number of bytes read in pcbRead. +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- pcbRead +* -- flags = flags as in VMM_FLAG_* +* -- return = success/fail. NB! reads may report as success even if 0 bytes are +* read - it's recommended to verify pcbReadOpt parameter. +*/ +BOOL VMMDLL_MemReadEx(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Inout_ PBYTE pb, _In_ DWORD cb, _Out_opt_ PDWORD pcbReadOpt, _In_ ULONG64 flags); + +/* +* Write a contigious arbitrary amount of memory. Please note some virtual memory +* such as pages of executables (such as DLLs) may be shared between different +* virtual memory over different processes. As an example a write to kernel32.dll +* in one process is likely to affect kernel32 in the whole system - in all +* processes. Heaps and Stacks and other memory are usually safe to write to. +* Please take care when writing to memory! +* -- dwPID - PID of target process, (DWORD)-1 to read physical memory. +* -- qwVA +* -- pb +* -- cb +* -- return = TRUE on success, FALSE on partial or zero write. +*/ +BOOL VMMDLL_MemWrite(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PBYTE pb, _In_ DWORD cb); + +/* +* Translate a virtual address to a physical address by walking the page tables +* of the specified process. +* -- dwPID +* -- qwVA +* -- pqwPA +* -- return = success/fail. +*/ +BOOL VMMDLL_MemVirt2Phys(_In_ DWORD dwPID, _In_ ULONG64 qwVA, _Out_ PULONG64 pqwPA); + + + +//----------------------------------------------------------------------------- +// VMM PROCESS FUNCTIONALITY BELOW: +// Functionality below is mostly relating to Windows processes. +//----------------------------------------------------------------------------- + +/* +* Retrieve an active process given it's name. Please note that if multiple +* processes with the same name exists only one will be returned. If required to +* parse all processes with the same name please iterate over the PID list by +* calling VMMDLL_PidList together with VMMDLL_ProcessGetInformation. +* -- szProcName = process name (truncated max 15 chars) case insensitive. +* -- pdwPID = pointer that will receive PID on success. +* -- return +*/ +BOOL VMMDLL_PidGetFromName(_In_ LPSTR szProcName, _Out_ PDWORD pdwPID); + +/* +* List the PIDs in the system. +* -- pPIDs = DWORD array of at least number of PIDs in system, or NULL. +* -- pcPIDs = size of (in number of DWORDs) pPIDs array on entry, number of PIDs in system on exit. +* -- return = success/fail. +*/ +BOOL VMMDLL_PidList(_Out_ PDWORD pPIDs, _Inout_ PULONG64 pcPIDs); + +// flags to check for existence in the fPage field of PCILEECH_VMM_MEMMAP_ENTRY +#define VMMDLL_MEMMAP_FLAG_PAGE_W 0x0000000000000002 +#define VMMDLL_MEMMAP_FLAG_PAGE_NS 0x0000000000000004 +#define VMMDLL_MEMMAP_FLAG_PAGE_NX 0x8000000000000000 +#define VMMDLL_MEMMAP_FLAG_PAGE_MASK 0x8000000000000006 + +typedef struct tdVMMDLL_MEMMAP_ENTRY { + ULONG64 AddrBase; + ULONG64 cPages; + ULONG64 fPage; + BOOL fWoW64; + CHAR szTag[32]; +} VMMDLL_MEMMAP_ENTRY, *PVMMDLL_MEMMAP_ENTRY; + +/* +* Retrieve memory map entries from the specified process. Memory map entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries bytes. +* If the pMemMapEntries is set to NULL the number of memory map entries will be +* given in the pcMemMapEntries parameter. +* -- dwPID +* -- pMemMapEntries = buffer of minimum length sizeof(VMMDLL_MEMMAP_ENTRY)*pcMemMapEntries, or NULL. +* -- pcMemMapEntries = pointer to number of memory map entries. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMap(_In_ DWORD dwPID, _Out_opt_ PVMMDLL_MEMMAP_ENTRY pMemMapEntries, _Inout_ PULONG64 pcMemMapEntries, _In_ BOOL fIdentifyModules); + +/* +* Retrieve a single memory map entry given a virtual address within that entrys +* range. +* -- dwPID +* -- pMemMapEntry +* -- va = virtual address in the memory map entry to retrieve. +* -- fIdentifyModules = try identify modules as well (= slower) +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetMemoryMapEntry(_In_ DWORD dwPID, _Out_ PVMMDLL_MEMMAP_ENTRY pMemMapEntry, _In_ ULONG64 va, _In_ BOOL fIdentifyModules); + +typedef struct tdVMMDLL_MODULEMAP_ENTRY { + ULONG64 BaseAddress; + ULONG64 EntryPoint; + DWORD SizeOfImage; + BOOL fWoW64; + CHAR szName[32]; +} VMMDLL_MODULEMAP_ENTRY, *PVMMDLL_MODULEMAP_ENTRY; + +/* +* Retrieve the module entries from the specified process. The module entries +* are copied into the user supplied buffer that must be at least of size: +* sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries bytes long. If the +* pcModuleEntries is set to NULL the number of module entries will be given +* in the pcModuleEntries parameter. +* -- dwPID +* -- pModuleEntries = buffer of minimum length sizeof(VMMDLL_MODULEMAP_ENTRY)*pcModuleEntries, or NULL. +* -- pcModuleEntries = pointer to number of memory map entries. +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleMap(_In_ DWORD dwPID, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntries, _Inout_ PULONG64 pcModuleEntries); + +/* +* Retrieve a module (.exe or .dll or similar) given a module name. +* -- dwPID +* -- szModuleName +* -- pModuleEntry +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetModuleFromName(_In_ DWORD dwPID, _In_ LPSTR szModuleName, _Out_ PVMMDLL_MODULEMAP_ENTRY pModuleEntry); + +#define VMMDLL_PROCESS_INFORMATION_MAGIC 0xc0ffee663df9301d +#define VMMDLL_PROCESS_INFORMATION_VERSION 1 + +typedef struct tdVMMDLL_PROCESS_INFORMATION { + ULONG64 magic; + WORD wVersion; + WORD wSize; + DWORD fTargetSystem; // as given by VMMDLL_TARGET_* + BOOL fUserOnly; // only user mode pages listed + DWORD dwPID; + DWORD dwState; + CHAR szName[16]; + ULONG64 paPML4; + ULONG64 paPML4_UserOpt; // may not exist + union { + struct { + ULONG64 vaEPROCESS; + ULONG64 vaPEB; + ULONG64 vaENTRY; + BOOL fWow64; + DWORD vaPEB32; // WoW64 only + } win; + } os; +} VMMDLL_PROCESS_INFORMATION, *PVMMDLL_PROCESS_INFORMATION; + +/* +* Retrieve various process information from a PID. Process information such as +* name, page directory bases and the process state may be retrieved. +* -- dwPID +* -- pProcessInformation = if null, size is given in *pcbProcessInfo +* -- pcbProcessInformation = size of pProcessInfo (in bytes) on entry and exit +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetInformation(_In_ DWORD dwPID, _Inout_opt_ PVMMDLL_PROCESS_INFORMATION pProcessInformation, _In_ PSIZE_T pcbProcessInformation); + +typedef struct tdVMMDLL_EAT_ENTRY { + ULONG64 vaFunction; + DWORD vaFunctionOffset; + CHAR szFunction[40]; +} VMMDLL_EAT_ENTRY, *PVMMDLL_EAT_ENTRY; + +typedef struct tdVMMDLL_IAT_ENTRY { + ULONG64 vaFunction; + CHAR szFunction[40]; + CHAR szModule[64]; +} VMMDLL_IAT_ENTRY, *PVMMDLL_IAT_ENTRY; + +/* +* Retrieve information about: Data Directories, Sections, Export Address Table +* and Import Address Table (IAT). +* If the pData == NULL upon entry the number of entries of the pData array must +* have in order to be able to hold the data is returned. +* -- dwPID +* -- szModule +* -- pData +* -- cData +* -- pcData +* -- return = success/fail. +*/ +BOOL VMMDLL_ProcessGetDirectories(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_DATA_DIRECTORY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetSections(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PIMAGE_SECTION_HEADER pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetEAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_EAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); +BOOL VMMDLL_ProcessGetIAT(_In_ DWORD dwPID, _In_ LPSTR szModule, _Out_ PVMMDLL_IAT_ENTRY pData, _In_ DWORD cData, _Out_ PDWORD pcData); + + + +//----------------------------------------------------------------------------- +// VMM UTIL FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +/* +* Fill a human readable hex ascii memory dump into the caller supplied sz buffer. +* -- pb +* -- cb +* -- cbInitialOffset = offset, must be max 0x1000 and multiple of 0x10. +* -- sz = buffer to fill, NULL to retrieve size in pcsz parameter. +* -- pcsz = ptr to size of buffer on entry, size of characters on exit. +*/ +BOOL VMMDLL_UtilFillHexAscii(_In_ PBYTE pb, _In_ DWORD cb, _In_ DWORD cbInitialOffset, _Inout_ LPSTR sz, _Inout_ PDWORD pcsz); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __VMMDLL_H__ */ diff --git a/vmmpycplugin/vmmpycplugin.c b/vmmpycplugin/vmmpycplugin.c new file mode 100644 index 0000000..3d85489 --- /dev/null +++ b/vmmpycplugin/vmmpycplugin.c @@ -0,0 +1,363 @@ +// vmmpycplugin.c : implementation related to the python wrapper native plugin +// for the memory process file system. NB! this is a special plugin since it's +// not residing in the plugin directory. +// +// (c) Ulf Frisk, 2018 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifdef _DEBUG +#undef _DEBUG +#include +#define _DEBUG +#else +#include +#endif +#include +#include +#include "vmmdll.h" + + +//----------------------------------------------------------------------------- +// PY2C PYTHON CALLBACK FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +typedef struct tdPY2C_CONTEXT { + BOOL fPrintf; + BOOL fVerbose; + BOOL fVerboseExtra; + BOOL fVerboseExtraTlp; + BOOL fInitialized; + PyObject *fnList; + PyObject *fnRead; + PyObject *fnWrite; + PyObject *fnNotify; + PyObject *fnClose; +} PY2C_CONTEXT, *PPY2C_CONTEXT; + +PPY2C_CONTEXT ctxPY2C = NULL; + +static PyObject* +PY2C_CallbackRegister(PyObject *self, PyObject *args) +{ + if(!ctxPY2C->fInitialized) { + Py_XDECREF(ctxPY2C->fnList); + Py_XDECREF(ctxPY2C->fnRead); + Py_XDECREF(ctxPY2C->fnWrite); + Py_XDECREF(ctxPY2C->fnNotify); + Py_XDECREF(ctxPY2C->fnClose); + if(!PyArg_ParseTuple(args, "OOOOO", &ctxPY2C->fnList, &ctxPY2C->fnRead, &ctxPY2C->fnWrite, &ctxPY2C->fnNotify, &ctxPY2C->fnClose)) { return NULL; } + Py_XINCREF(ctxPY2C->fnList); + Py_XINCREF(ctxPY2C->fnRead); + Py_XINCREF(ctxPY2C->fnWrite); + Py_XINCREF(ctxPY2C->fnNotify); + Py_XINCREF(ctxPY2C->fnClose); + ctxPY2C->fInitialized = TRUE; + } + return Py_BuildValue("s", NULL); // None returned on success. +} + +BOOL PY2C_Util_TranslatePathDelimiter(_Inout_ CHAR dst[MAX_PATH], LPSTR src) +{ + DWORD i; + for(i = 0; i < MAX_PATH; i++) { + dst[i] = (src[i] == '\\') ? '/' : src[i]; + if(src[i] == 0) { return TRUE; } + } + return FALSE; +} + +BOOL PY2C_Callback_List(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Inout_ PHANDLE pFileList) +{ + BOOL result = FALSE; + PyObject *args, *pyList = NULL, *pyDict, *pyPid; + PyObject *pyDict_Name, *pyDict_Size, *pyDict_IsDir; + PyGILState_STATE gstate; + SIZE_T i, cList; + CHAR szPathBuffer[MAX_PATH]; + if(!ctxPY2C->fInitialized) { return FALSE; } + if(!PY2C_Util_TranslatePathDelimiter(szPathBuffer, ctx->szPath)) { return FALSE; } + gstate = PyGILState_Ensure(); + // pyPid is "consumed" by Py_BuildValue and does not need to be Py_DECREF'ed. + if(ctx->dwPID == (DWORD)-1) { + Py_INCREF(Py_None); + pyPid = Py_None; + } else { + pyPid = PyLong_FromUnsignedLong(ctx->dwPID); + } + args = Py_BuildValue("Ns", pyPid, szPathBuffer); + if(!args) { goto fail; } + pyList = PyObject_CallObject(ctxPY2C->fnList, args); + Py_DECREF(args); + if(!pyList || !PyList_Check(pyList)) { goto fail; } + cList = PyList_Size(pyList); + for(i = 0; i < cList; i++) { + pyDict = PyList_GetItem(pyList, i); // borrowed reference + if(!PyDict_Check(pyDict)) { continue; } + pyDict_Name = PyDict_GetItemString(pyDict, "name"); + pyDict_Size = PyDict_GetItemString(pyDict, "size"); + pyDict_IsDir = PyDict_GetItemString(pyDict, "f_isdir"); + if(!pyDict_Name || !PyUnicode_Check(pyDict_Name) || !pyDict_IsDir || !PyBool_Check(pyDict_IsDir)) { continue; } + if(pyDict_IsDir == Py_True) { + VMMDLL_VfsList_AddDirectory(pFileList, PyUnicode_AsUTF8AndSize(pyDict_Name, NULL)); + } else { + if(!pyDict_Size || !PyLong_Check(pyDict_Size)) { continue; } + VMMDLL_VfsList_AddFile(pFileList, PyUnicode_AsUTF8AndSize(pyDict_Name, NULL), PyLong_AsUnsignedLongLong(pyDict_Size)); + } + } + result = TRUE; + // fall through to cleanup +fail: + if(pyList) { Py_DECREF(pyList); } + PyGILState_Release(gstate); + return result; +} + +NTSTATUS PY2C_Callback_Read(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _Out_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbRead, _In_ ULONG64 cbOffset) +{ + NTSTATUS nt = VMMDLL_STATUS_FILE_INVALID; + PyObject *args, *pyBytes = NULL, *pyPid; + PyGILState_STATE gstate; + CHAR szPathBuffer[MAX_PATH]; + if(!ctxPY2C->fInitialized) { return FALSE; } + if(!PY2C_Util_TranslatePathDelimiter(szPathBuffer, ctx->szPath)) { return FALSE; } + gstate = PyGILState_Ensure(); + // pyPid is "consumed" by Py_BuildValue and does not need to be Py_DECREF'ed. + if(ctx->dwPID == (DWORD)-1) { + Py_INCREF(Py_None); + pyPid = Py_None; + } else { + pyPid = PyLong_FromUnsignedLong(ctx->dwPID); + } + args = Py_BuildValue("NskK", + pyPid, + szPathBuffer, + cb, + cbOffset); + if(!args) { goto fail; } + pyBytes = PyObject_CallObject(ctxPY2C->fnRead, args); + Py_DECREF(args); + if(!pyBytes || !PyBytes_Check(pyBytes)) { goto fail; } + *pcbRead = min(cb, (DWORD)PyBytes_Size(pyBytes)); + if(*pcbRead) { + memcpy(pb, PyBytes_AsString(pyBytes), *pcbRead); + } + nt = *pcbRead ? VMMDLL_STATUS_SUCCESS : VMMDLL_STATUS_END_OF_FILE; + // fall through to cleanup +fail: + if(pyBytes) { Py_DECREF(pyBytes); } + PyGILState_Release(gstate); + return nt; +} + +NTSTATUS PY2C_Callback_Write(_In_ PVMMDLL_PLUGIN_CONTEXT ctx, _In_ LPVOID pb, _In_ DWORD cb, _Out_ PDWORD pcbWrite, _In_ ULONG64 cbOffset) +{ + NTSTATUS nt = VMMDLL_STATUS_FILE_INVALID; + PyObject *args, *pyLong = NULL, *pyPid; + PyGILState_STATE gstate; + CHAR szPathBuffer[MAX_PATH]; + if(!ctxPY2C->fInitialized) { return FALSE; } + if(!PY2C_Util_TranslatePathDelimiter(szPathBuffer, ctx->szPath)) { return FALSE; } + *pcbWrite = 0; + gstate = PyGILState_Ensure(); + // pyPid is "consumed" by Py_BuildValue and does not need to be Py_DECREF'ed. + if(ctx->dwPID == (DWORD)-1) { + Py_INCREF(Py_None); + pyPid = Py_None; + } else { + pyPid = PyLong_FromUnsignedLong(ctx->dwPID); + } + args = Py_BuildValue("Nsy#K", + pyPid, + szPathBuffer, + pb, + cb, + cbOffset); + if(!args) { goto fail; } + pyLong = PyObject_CallObject(ctxPY2C->fnWrite, args); + Py_DECREF(args); + if(!pyLong || !PyLong_Check(pyLong)) { goto fail; } + nt = PyLong_AsUnsignedLong(pyLong); + if(!nt) { *pcbWrite = cb; } + // fall through to cleanup +fail: + if(pyLong) { Py_DECREF(pyLong); } + PyGILState_Release(gstate); + return nt; +} + +VOID PY2C_Callback_Notify(_Inout_opt_ PHANDLE phModulePrivate, _In_ DWORD fEvent, _In_opt_ PVOID pvEvent, _In_opt_ DWORD cbEvent) +{ + PyObject *args, *pyResult = NULL; + PyGILState_STATE gstate; + if(!ctxPY2C->fInitialized) { return; } + gstate = PyGILState_Ensure(); + args = Py_BuildValue("ky#", fEvent, (char*)pvEvent, cbEvent); + if(!args) { goto fail; } + pyResult = PyObject_CallObject(ctxPY2C->fnNotify, args); + Py_DECREF(args); + // fall through to cleanup +fail: + if(pyResult) { Py_DECREF(pyResult); } + PyGILState_Release(gstate); +} + +BOOL PY2C_Callback_Close() +{ + NTSTATUS nt = VMMDLL_STATUS_FILE_INVALID; + PyObject *args, *pyResult = NULL; + PyGILState_STATE gstate; + if(!ctxPY2C->fInitialized) { return FALSE; } + gstate = PyGILState_Ensure(); + args = Py_BuildValue(""); + if(!args) { goto fail; } + pyResult = PyObject_CallObject(ctxPY2C->fnClose, args); + Py_DECREF(args); + // fall through to cleanup +fail: + if(pyResult) { Py_DECREF(pyResult); } + PyGILState_Release(gstate); + return nt; +} + +//----------------------------------------------------------------------------- +// PY2C common functionality below: +//----------------------------------------------------------------------------- + +static PyMethodDef VMMPYCC_EmbMethods[] = { + {"VMMPYCC_CallbackRegister", PY2C_CallbackRegister, METH_VARARGS, "Register callback functions: List, Read, Write, Close"}, + {NULL, NULL, 0, NULL} +}; + +static PyModuleDef VMMPYCC_EmbModule = { + PyModuleDef_HEAD_INIT, "vmmpycc", NULL, -1, VMMPYCC_EmbMethods, + NULL, NULL, NULL, NULL +}; + +static PyObject* VMMPYCC_PyInit(void) +{ + return PyModule_Create(&VMMPYCC_EmbModule); +} + +void PY2C_InitializeModuleVMMPYCC() +{ + PyImport_AppendInittab("vmmpycc", &VMMPYCC_PyInit); +} + + +//----------------------------------------------------------------------------- +// CORE NATIVE MODULE FUNCTIONALITY BELOW: +//----------------------------------------------------------------------------- + +VOID Util_GetPathDll(_Out_ WCHAR wszPath[MAX_PATH], _In_opt_ HMODULE hModule) +{ + SIZE_T i; + GetModuleFileNameW(hModule, wszPath, MAX_PATH - 4); + for(i = wcslen(wszPath) - 1; i > 0; i--) { + if(wszPath[i] == L'/' || wszPath[i] == L'\\') { + wszPath[i + 1] = L'\0'; + return; + } + } +} + +/* +* Set the verbosity level of the Python C - plugin. +* Also set the verbosity level of the Python plugin manager (if already loaded). +*/ +VOID VmmPyPlugin_UpdateVerbosity() +{ + ULONG64 f; + VMMDLL_ConfigGet(VMMDLL_OPT_CORE_PRINTF_ENABLE, &f); ctxPY2C->fPrintf = f ? TRUE : FALSE; + if(ctxPY2C->fPrintf) { + VMMDLL_ConfigGet(VMMDLL_OPT_CORE_VERBOSE, &f); ctxPY2C->fVerbose = f ? TRUE : FALSE; + VMMDLL_ConfigGet(VMMDLL_OPT_CORE_VERBOSE_EXTRA, &f); ctxPY2C->fVerboseExtra = f ? TRUE : FALSE; + VMMDLL_ConfigGet(VMMDLL_OPT_CORE_VERBOSE_EXTRA_TLP, &f); ctxPY2C->fVerboseExtraTlp = f ? TRUE : FALSE; + } else { + ctxPY2C->fVerbose = FALSE; + ctxPY2C->fVerboseExtra = FALSE; + ctxPY2C->fVerboseExtraTlp = FALSE; + } +} + +#define PYTHON_PATH_MAX 4*MAX_PATH +#define PYTHON_PATH_DELIMITER L";" +BOOL VmmPyPlugin_PythonInitialize(_In_ HMODULE hDllPython) +{ + PyObject *pName = NULL, *pModule = NULL; + WCHAR wszPathBaseExe[MAX_PATH], wszPathBasePython[MAX_PATH], wszPathPython[PYTHON_PATH_MAX]; + // 1: Allocate context (if required) and fetch verbosity settings + if(!ctxPY2C && !(ctxPY2C = LocalAlloc(LMEM_ZEROINIT, sizeof(PY2C_CONTEXT)))) { + return FALSE; + } + VmmPyPlugin_UpdateVerbosity(); + // 2: Construct Python Path + Util_GetPathDll(wszPathBaseExe, NULL); + Util_GetPathDll(wszPathBasePython, hDllPython); + // 2.1: python base directory (where python dll is located) + wcscpy_s(wszPathPython, PYTHON_PATH_MAX, wszPathBasePython); + // 2.2: python zip + wcscat_s(wszPathPython, PYTHON_PATH_MAX, PYTHON_PATH_DELIMITER); + wcscat_s(wszPathPython, PYTHON_PATH_MAX, wszPathBasePython); + wcscat_s(wszPathPython, PYTHON_PATH_MAX, L"python36.zip"); + // 2.3: python lib + wcscat_s(wszPathPython, PYTHON_PATH_MAX, PYTHON_PATH_DELIMITER); + wcscat_s(wszPathPython, PYTHON_PATH_MAX, wszPathBasePython); + wcscat_s(wszPathPython, PYTHON_PATH_MAX, L"Lib\\"); + // 2.4: .exe location of this process + wcscat_s(wszPathPython, PYTHON_PATH_MAX, PYTHON_PATH_DELIMITER); + wcscat_s(wszPathPython, PYTHON_PATH_MAX, wszPathBaseExe); + // 3: Initialize Embedded Python. + Py_SetProgramName(L"VmmPyPluginManager"); + Py_SetPath(wszPathPython); + if(ctxPY2C->fVerboseExtra) { + wprintf(L"VmmPyPluginManager: Python Path: %s\n", wszPathPython); + } + PY2C_InitializeModuleVMMPYCC(); + Py_Initialize(); + // 4: Import VmmPyPlugin library/file to start the python part of the plugin manager. + pName = PyUnicode_DecodeFSDefault("vmmpyplugin"); + if(!pName) { goto fail; } + pModule = PyImport_Import(pName); + if(!pModule) { goto fail; } + // 5: Cleanups + Py_DECREF(pName); + Py_DECREF(pModule); + return TRUE; +fail: + if(pName) { Py_DECREF(pName); } + if(pModule) { Py_DECREF(pModule); } + Py_FinalizeEx(); + return FALSE; +} + +VOID PYTHON_Close(_Inout_ PHANDLE phModulePrivate) +{ + PY2C_Callback_Close(); + Py_FinalizeEx(); +} + +/* +* Initialization function for the vmemd native plugin module. +* It's important that the function is exported in the DLL and that it is +* declared exactly as below. The plugin manager will call into this function +* after the DLL is loaded. The DLL then must fill the appropriate information +* into the supplied struct and call the pfnPluginManager_Register function to +* register itself with the plugin manager. +* -- pRegInfo +*/ +__declspec(dllexport) +VOID InitializeVmmPlugin(_In_ PVMMDLL_PLUGIN_REGINFO pRegInfo) +{ + if(0 == (pRegInfo->fTargetSystem & (VMMDLL_TARGET_UNKNOWN_X64 | VMMDLL_TARGET_WINDOWS_X64))) { return; } + if(VmmPyPlugin_PythonInitialize(pRegInfo->hReservedDll)) { + strcpy_s(pRegInfo->reg_info.szModuleName, 32, "py"); // module name - 'py'. + pRegInfo->reg_info.fRootModule = TRUE; // module shows in root directory. + pRegInfo->reg_info.fProcessModule = TRUE; // module shows in process directory. + pRegInfo->reg_fn.pfnList = PY2C_Callback_List; // List function supported. + pRegInfo->reg_fn.pfnRead = PY2C_Callback_Read; // Read function supported. + pRegInfo->reg_fn.pfnWrite = PY2C_Callback_Write; // Write function supported. + pRegInfo->reg_fn.pfnNotify = PY2C_Callback_Notify; // Notify function supported. + pRegInfo->reg_fn.pfnCloseHandleModule = PYTHON_Close; // Close module handle. + pRegInfo->pfnPluginManager_Register(pRegInfo); // Register with the plugin maanger. + } +} diff --git a/vmmpycplugin/vmmpycplugin.vcxproj b/vmmpycplugin/vmmpycplugin.vcxproj new file mode 100644 index 0000000..ca8a6ac --- /dev/null +++ b/vmmpycplugin/vmmpycplugin.vcxproj @@ -0,0 +1,101 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {D32F2480-E640-4239-96DA-E31CCA2F13BA} + vmmpycplugin + 8.1 + + + + DynamicLibrary + true + v141 + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + .dll + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\include\; + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\libs; + + + $(SolutionDir)\files\ + $(SolutionDir)\files\temp\$(ProjectName)\ + $(VC_IncludePath);$(WindowsSDK_IncludePath);C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\include\; + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64;C:\Program Files (x86)\Microsoft Visual Studio\Shared\Python36_64\libs; + + + + + Level3 + Disabled + true + true + + + + + $(OutDir)\lib\$(TargetName).pdb + $(OutDir)\lib\$(TargetName).lib + $(SolutionDir)\files\vmm.lib + + + + + Level3 + MaxSpeed + true + true + true + true + + + true + true + UseLinkTimeCodeGeneration + $(OutDir)\lib\$(TargetName).pdb + $(OutDir)\lib\$(TargetName).lib + $(SolutionDir)\files\vmm.lib + + + + + + + + + + + + \ No newline at end of file diff --git a/vmmpycplugin/vmmpycplugin.vcxproj.filters b/vmmpycplugin/vmmpycplugin.vcxproj.filters new file mode 100644 index 0000000..99fabfa --- /dev/null +++ b/vmmpycplugin/vmmpycplugin.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {d63c33a6-676d-4984-8f47-17080030dad1} + + + + + Header Files\vmm + + + + + Source Files + + + \ No newline at end of file diff --git a/vmmpycplugin/vmmpycplugin.vcxproj.user b/vmmpycplugin/vmmpycplugin.vcxproj.user new file mode 100644 index 0000000..be25078 --- /dev/null +++ b/vmmpycplugin/vmmpycplugin.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file