diff --git a/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.cpp b/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.cpp index cc861db..fcdebce 100644 --- a/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.cpp +++ b/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.cpp @@ -3,10 +3,9 @@ #include "interface.h" -#include "..\Utils\Utils.h" +#include "utils.h" const unsigned long PLUGIN_POOL_TAG = 'LEDS'; -const wchar_t* backup_directory = L"\\??\\C:\\deleted"; #pragma warning(disable: 6011) PluginApis g_Apis; @@ -24,7 +23,7 @@ enum PROBE_IDS : ULONG64 { extern "C" __declspec(dllexport) void StpInitialize(PluginApis & pApis) { g_Apis = pApis; LOG_INFO("Plugin Initializing...\r\n"); - + g_Apis.pSetCallback("SetInformationFile", PROBE_IDS::IdSetInformationFile); LOG_INFO("Plugin Initialized\r\n"); } @@ -69,6 +68,29 @@ void PrintStackTrace(CallerInfo& callerinfo) { } } +OBJECT_NAME_INFORMATION* getFilePathFromHandle(HANDLE hFile) { + ULONG dwSize = 0; + OBJECT_NAME_INFORMATION* pObjectName = nullptr; + NTSTATUS status = ZwQueryObject(hFile, (OBJECT_INFORMATION_CLASS)1 /*ObjectNameInformation*/, pObjectName, 0, &dwSize); + if (dwSize) + { + pObjectName = (OBJECT_NAME_INFORMATION*)ExAllocatePoolWithTag(NonPagedPoolNx, dwSize, PLUGIN_POOL_TAG); + if (pObjectName) { + status = ZwQueryObject(hFile, (OBJECT_INFORMATION_CLASS)1 /*ObjectNameInformation*/, pObjectName, dwSize, &dwSize); + } + } + + if (status == STATUS_SUCCESS && pObjectName) { + return pObjectName; + } + + if (pObjectName) { + ExFreePoolWithTag(pObjectName, PLUGIN_POOL_TAG); + pObjectName = nullptr; + } + return nullptr; +} + extern "C" __declspec(dllexport) void StpCallbackEntry(ULONG64 pService, ULONG32 probeId, MachineState & ctx, CallerInfo & callerinfo) { //LOG_INFO("[ENTRY] %s[0x%x](%d) Id: %d Parameters: [%d]\r\n", callerinfo.processName, callerinfo.processId, callerinfo.isWow64 ? 32 : 64, pService, probeId, ctx.paramCount); @@ -77,30 +99,30 @@ extern "C" __declspec(dllexport) void StpCallbackEntry(ULONG64 pService, ULONG32 UNREFERENCED_PARAMETER(ctx); UNREFERENCED_PARAMETER(callerinfo); switch (probeId) { - case PROBE_IDS::IdSetInformationFile: { - auto hFile = (HANDLE)ctx.read_argument(0); - auto InformationClass = ctx.read_argument(4); - if (InformationClass == 13) { // FileDispositionInformation - auto pInformation = (char*)ctx.read_argument(2); // 1 == DeleteFile - if (*pInformation == 1) { - auto pFilePath = getFilePathFromHandle(hFile); - - if (pFilePath) { - LOG_INFO("File %wZ deleted\r\n", pFilePath->Name); - backupFile((wchar_t*)backup_directory, pFilePath->Name, hFile); - ExFreePoolWithTag(pFilePath, PLUGIN_POOL_TAG); - pFilePath = nullptr; - LOG_INFO("File Backup Complete\r\n"); - } - else { - LOG_INFO("File [unknown] deleted\r\n"); - } - - PrintStackTrace(callerinfo); + case PROBE_IDS::IdSetInformationFile: { + auto hFile = (HANDLE)ctx.read_argument(0); + auto InformationClass = ctx.read_argument(4); + if (InformationClass == 13) { // FileDispositionInformation + auto pInformation = (char*)ctx.read_argument(2); // 1 == DeleteFile + if (*pInformation == 1) { + auto pFilePath = getFilePathFromHandle(hFile); + + if (pFilePath) { + LOG_INFO("File %wZ deleted\r\n", pFilePath->Name); + //backupFile((wchar_t*)backup_directory, pFilePath->Name, hFile); + //ExFreePoolWithTag(pFilePath, PLUGIN_POOL_TAG); + //pFilePath = nullptr; + LOG_INFO("File Backup Complete\r\n"); } + else { + LOG_INFO("File [unknown] deleted\r\n"); + } + + PrintStackTrace(callerinfo); } - break; } + break; + } } } ASSERT_INTERFACE_IMPLEMENTED(StpCallbackEntry, tStpCallbackEntryPlugin, "StpCallbackEntry does not match the interface type"); @@ -132,8 +154,7 @@ VOID DeviceUnload(_In_ PDRIVER_OBJECT DriverObject) DBGPRINT("FileDeleteRecord::DeviceUnload"); } - -/* +/* * /GS- must be set to disable stack cookies and have DriverEntry * be the entrypoint. GsDriverEntry sets up stack cookie and calls * Driver Entry normally. @@ -144,7 +165,7 @@ NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) DBGPRINT("FileDeleteRecord::DriverEntry()"); - + DriverObject->MajorFunction[IRP_MJ_CREATE] = DeviceCreateClose; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DeviceCreateClose; DriverObject->DriverUnload = DeviceUnload; diff --git a/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.vcxproj b/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.vcxproj index 23ea11b..a71fc4a 100644 --- a/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.vcxproj +++ b/C/FileDeleteRecordPluginDriver/FileDeleteRecordPluginDriver.vcxproj @@ -70,10 +70,12 @@ DbgengKernelDebugger false + $(VC_IncludePath);$(CRT_IncludePath);$(KM_IncludePath);$(KIT_SHARED_IncludePath) DbgengKernelDebugger false + $(VC_IncludePath);$(CRT_IncludePath);$(KM_IncludePath);$(KIT_SHARED_IncludePath) DbgengKernelDebugger @@ -97,7 +99,7 @@ DriverEntry - %(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;Utils.lib + %(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib $(outdir) @@ -115,7 +117,7 @@ DriverEntry - %(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib;Utils.lib + %(AdditionalDependencies);$(KernelBufferOverflowLib);$(DDK_LIB_PATH)ntoskrnl.lib;$(DDK_LIB_PATH)hal.lib;$(DDK_LIB_PATH)wmilib.lib $(outdir) diff --git a/C/FileDeleteRecordPluginDriver/utils.h b/C/FileDeleteRecordPluginDriver/utils.h index 6fa7c31..a9f0f6b 100644 --- a/C/FileDeleteRecordPluginDriver/utils.h +++ b/C/FileDeleteRecordPluginDriver/utils.h @@ -5,26 +5,12 @@ #include "Interface.h" #include "string.h" -namespace kstl { - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; - - template - using remove_reference_t = typename remove_reference<_Ty>::type; - - template - constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept { - return static_cast<_Ty&&>(_Arg); - }; - - // - template - typename remove_reference::type&& move(T&& arg) - { - return static_cast::type&&>(arg); - } -} +// $(VC_IncludePath); required for these imports to work in the driver +#define _ITERATOR_DEBUG_LEVEL 0 +#include +#include +#include +#include #define ObjectNameInformation (OBJECT_INFORMATION_CLASS)1 @@ -35,7 +21,7 @@ template int string_printf(String& str, T printer, Args&&... args) { char tmp[512] = { 0 }; - int size = printer(tmp, sizeof(tmp), kstl::forward(args)...); + int size = printer(tmp, sizeof(tmp), std::forward(args)...); if (size < 0) { return -1; } @@ -67,7 +53,7 @@ consteval uint64_t get_type_id() { template class FinalAction { public: - FinalAction(Func f) :FinalActionFunc(kstl::move(f)) {} + FinalAction(Func f) :FinalActionFunc(std::move(f)) {} ~FinalAction() { FinalActionFunc(); diff --git a/C/STrace.sln b/C/STrace.sln index e58276f..4372585 100644 --- a/C/STrace.sln +++ b/C/STrace.sln @@ -7,7 +7,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "STrace", "STrace\STrace.vcx ProjectSection(ProjectDependencies) = postProject {4349310C-30F9-48A9-9AE7-13D181F958B5} = {4349310C-30F9-48A9-9AE7-13D181F958B5} {C09F1082-CDCA-4320-AB91-CC3EAB12560C} = {C09F1082-CDCA-4320-AB91-CC3EAB12560C} - {D237889B-F553-478C-857A-A6BF4B883AE9} = {D237889B-F553-478C-857A-A6BF4B883AE9} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "STraceCLI", "STraceCLI\STraceCLI.vcxproj", "{4349310C-30F9-48A9-9AE7-13D181F958B5}" @@ -22,8 +21,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AddNewEtwEventPlugin", "Add EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileDeleteRecordPluginDriver", "FileDeleteRecordPluginDriver\FileDeleteRecordPluginDriver.vcxproj", "{CD47158C-73E3-4197-AE90-92DC38D8BC0E}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Utils", "Utils\Utils.vcxproj", "{D237889B-F553-478C-857A-A6BF4B883AE9}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -130,18 +127,6 @@ Global {CD47158C-73E3-4197-AE90-92DC38D8BC0E}.Release|x86.ActiveCfg = Release|x64 {CD47158C-73E3-4197-AE90-92DC38D8BC0E}.Release|x86.Build.0 = Release|x64 {CD47158C-73E3-4197-AE90-92DC38D8BC0E}.Release|x86.Deploy.0 = Release|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Debug|ARM64.ActiveCfg = Debug|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Debug|ARM64.Build.0 = Debug|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Debug|x64.ActiveCfg = Debug|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Debug|x64.Build.0 = Debug|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Debug|x86.ActiveCfg = Debug|Win32 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Debug|x86.Build.0 = Debug|Win32 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Release|ARM64.ActiveCfg = Release|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Release|ARM64.Build.0 = Release|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Release|x64.ActiveCfg = Release|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Release|x64.Build.0 = Release|x64 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Release|x86.ActiveCfg = Release|Win32 - {D237889B-F553-478C-857A-A6BF4B883AE9}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/C/Utils/Utils.cpp b/C/Utils/Utils.cpp deleted file mode 100644 index 6d8e997..0000000 --- a/C/Utils/Utils.cpp +++ /dev/null @@ -1,280 +0,0 @@ -#include "..\FileDeleteRecordPlugin\Interface.h" -#include "..\FileDeleteRecordPlugin\KernelApis.h" -#include "..\FileDeleteRecordPlugin\config.h" -#include "..\FileDeleteRecordPlugin\string.h" - -#include -#include -#include -#include - -#include "Utils.h" - -template -int string_printf(String& str, T printer, Args&&... args) { - char tmp[512] = { 0 }; - - int size = printer(tmp, sizeof(tmp), std::forward(args)...); - if (size < 0) { - return -1; - } - - str += (char*)tmp; - return size; -} - -using hash_t = std::uint64_t; - -consteval uint64_t fnv1a_imp(uint64_t h, const char* s) -{ - return (*s == 0) ? h : - fnv1a_imp((h ^ static_cast(*s)) * 1099511628211ull, s + 1); -} - -consteval uint64_t fnv1a(const char* s) -{ - return fnv1a_imp(14695981039346656037ull, s); -} - -// Abuse template instantion rules to generate a unique name for a given type. Each template is a unique function in C++. -// Then, convert that string to a numeric hash. Stable for the lifetime of the application, may change between compilations. -template -consteval uint64_t get_type_id() { - return fnv1a(__FUNCSIG__); -} - -// given a typedef, match the arg list and convert each arg to a typeid. Store results in an array. -template -struct arg_types {}; - -template -struct arg_types { - static constexpr std::array value = { get_type_id()... }; -}; - -// msvc doesn't implement a constructor for std::span from iterators. This does that... -template -constexpr auto make_span(It begin, It end) { - return std::span>>(&(*begin), std::distance(begin, end)); -} - -template -class FinalAction { -public: - FinalAction(Func f) :FinalActionFunc(std::move(f)) {} - ~FinalAction() - { - FinalActionFunc(); - } -private: - Func FinalActionFunc; - - /*Uses RAII to call a final function on destruction - C++ 11 version of java's finally (kindof)*/ -}; - -template -FinalAction finally(F f) { - return FinalAction(f); -} - -template -T FnCast(uint64_t fnToCast, T pFnCastTo) { - PH_UNUSED(pFnCastTo); - return (T)fnToCast; -} - -template -T FnCast(void* fnToCast, T pFnCastTo) { - PH_UNUSED(pFnCastTo); - return (T)fnToCast; -} - -// analog of dtrace_copyin. Given a pointer to a usermode structure, safely read that structure in. -// Dtrace returns a pointer to that result. We can be slightly nicer and give a copy of the value exactly. -template -std::remove_pointer_t readUserArg(T2 pUserAddress, PluginApis pApis) { - std::remove_pointer_t tmp = { 0 }; - pApis.pTraceAccessMemory(&tmp, (uint64_t)pUserAddress, sizeof(tmp), 1, TRUE); - return tmp; -} - -bool createFile(PUNICODE_STRING filePath, PHANDLE hFileOut) { - *hFileOut = INVALID_HANDLE_VALUE; - - OBJECT_ATTRIBUTES attrs = { 0 }; - InitializeObjectAttributes(&attrs, filePath, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL); - - IO_STATUS_BLOCK IoStatus; - NTSTATUS Status = ZwCreateFile(hFileOut, - FILE_WRITE_DATA | SYNCHRONIZE, - &attrs, - &IoStatus, - NULL, - FILE_ATTRIBUTE_SYSTEM, - FILE_SHARE_READ, - FILE_OPEN_IF, - FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE, - NULL, - 0); - return Status == STATUS_SUCCESS; -} - -bool openFile(PUNICODE_STRING filePath, PHANDLE hFileOut) { - *hFileOut = INVALID_HANDLE_VALUE; - - OBJECT_ATTRIBUTES attrs = { 0 }; - InitializeObjectAttributes(&attrs, filePath, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL); - - IO_STATUS_BLOCK IoStatus; - NTSTATUS Status = ZwOpenFile(hFileOut, - FILE_READ_DATA | SYNCHRONIZE, - &attrs, - &IoStatus, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE - ); - return Status == STATUS_SUCCESS; -} - -// backupFileName must begin with a slash, or be a filepath with slashes -bool __stdcall backupFile(PWSTR backupDir, UNICODE_STRING backupFileName, HANDLE hFileSource) { - if (!backupFileName.Buffer || backupFileName.Length <= sizeof(wchar_t)) - return false; - - // scan backwards until first slash - PWCHAR fileName = backupFileName.Buffer + (backupFileName.Length - sizeof(wchar_t)); - size_t fileNameLen = 0; - while (*fileName != L'\\') { - fileName--; - fileNameLen++; - } - - UNICODE_STRING backupPath = { 0 }; - backupPath.Length = (USHORT)(wcslen(backupDir) * sizeof(wchar_t)); - backupPath.MaximumLength = (USHORT)(backupPath.Length + (fileNameLen * sizeof(wchar_t))); - backupPath.Buffer = (PWSTR)ExAllocatePoolWithTag(NonPagedPoolNx, backupPath.MaximumLength, POOL_TAG); - memcpy(backupPath.Buffer, backupDir, backupPath.Length); - - NTSTATUS status = RtlAppendUnicodeToString(&backupPath, fileName); - if (status != STATUS_SUCCESS) - return false; - - HANDLE hFileCopy = INVALID_HANDLE_VALUE; - auto close_handles = finally([&] { - if (hFileCopy != INVALID_HANDLE_VALUE) { - ZwClose(hFileCopy); - } - }); - - if (!createFile(&backupPath, &hFileCopy)) - return false; - - LARGE_INTEGER pos = { 0 }; - IO_STATUS_BLOCK iosb = { 0 }; - - LARGE_INTEGER pos_write = { 0 }; - IO_STATUS_BLOCK iosb_write = { 0 }; - - while (true) { - char pBuf[512] = { 0 }; - status = ZwReadFile( - hFileSource, - NULL, NULL, NULL, - &iosb, - pBuf, (ULONG)sizeof(pBuf), - &pos, - NULL - ); - - if (status == STATUS_END_OF_FILE) { - if (iosb.Information == 0) { - break; - } - } - - if (status != STATUS_SUCCESS) { - break; - } - - status = ZwWriteFile(hFileCopy, - NULL, NULL, NULL, - &iosb_write, - pBuf, (ULONG)sizeof(pBuf), - &pos_write, - NULL - ); - - if (iosb_write.Status != STATUS_SUCCESS) { - break; - } - - if (status != STATUS_SUCCESS) { - break; - } - - pos.QuadPart += iosb.Information; - pos_write.QuadPart += iosb_write.Information; - } - return true; -} - -VOID NTAPI FreeUnicodeString(PUNICODE_STRING UnicodeString, ULONG Tag) -{ - if (UnicodeString->Buffer) - { - ExFreePoolWithTag(UnicodeString->Buffer, Tag); - } -} - -NTSTATUS DuplicateUnicodeString(PCUNICODE_STRING SourceString, PUNICODE_STRING DestinationString, ULONG Tag) -{ - if (SourceString == NULL || DestinationString == NULL || - SourceString->Length > SourceString->MaximumLength || - (SourceString->Length == 0 && SourceString->MaximumLength > 0 && SourceString->Buffer == NULL)) - { - return STATUS_INVALID_PARAMETER; - } - - if (SourceString->Length == 0) - { - DestinationString->Length = 0; - DestinationString->MaximumLength = 0; - DestinationString->Buffer = NULL; - } - else { - UINT DestMaxLength = SourceString->Length; - - DestinationString->Buffer = (PWSTR)ExAllocatePoolWithTag(NonPagedPoolNx, DestMaxLength, Tag); - if (DestinationString->Buffer == NULL) - return STATUS_NO_MEMORY; - - memcpy(DestinationString->Buffer, SourceString->Buffer, SourceString->Length); - DestinationString->Length = SourceString->Length; - DestinationString->MaximumLength = DestMaxLength; - } - return STATUS_SUCCESS; -} - -OBJECT_NAME_INFORMATION* __stdcall getFilePathFromHandle(HANDLE hFile) { - ULONG dwSize = 0; - OBJECT_NAME_INFORMATION* pObjectName = nullptr; - NTSTATUS status = ZwQueryObject(hFile, ObjectNameInformation, pObjectName, 0, &dwSize); - if (dwSize) - { - pObjectName = (OBJECT_NAME_INFORMATION*)ExAllocatePoolWithTag(NonPagedPoolNx, dwSize, POOL_TAG); - if (pObjectName) { - status = ZwQueryObject(hFile, ObjectNameInformation, pObjectName, dwSize, &dwSize); - } - } - - if (status == STATUS_SUCCESS && pObjectName) { - return pObjectName; - } - - if (pObjectName) { - ExFreePoolWithTag(pObjectName, POOL_TAG); - pObjectName = nullptr; - } - return nullptr; -} \ No newline at end of file diff --git a/C/Utils/Utils.h b/C/Utils/Utils.h deleted file mode 100644 index 8758ff3..0000000 --- a/C/Utils/Utils.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - - -extern "C" bool __stdcall backupFile(PWSTR backupDir, UNICODE_STRING backupFileName, HANDLE hFileSource); -extern "C" OBJECT_NAME_INFORMATION * __stdcall getFilePathFromHandle(HANDLE hFile); \ No newline at end of file diff --git a/C/Utils/Utils.vcxproj b/C/Utils/Utils.vcxproj deleted file mode 100644 index 2f2256d..0000000 --- a/C/Utils/Utils.vcxproj +++ /dev/null @@ -1,168 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {d237889b-f553-478c-857a-a6bf4b883ae9} - Utils - 10.0 - - - - StaticLibrary - true - v143 - Unicode - - - StaticLibrary - false - v143 - true - Unicode - - - StaticLibrary - true - v143 - Unicode - - - StaticLibrary - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - Level3 - true - WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) - true - Use - pch.h - - - - - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) - true - Use - pch.h - - - - - true - true - true - - - - - Level3 - false - _DEBUG;_LIB;%(PreprocessorDefinitions) - true - NotUsing - pch.h - MultiThreadedDebug - false - stdcpp20 - ProgramDatabase - /kernel %(AdditionalOptions) - Default - false - - - - - true - - - true - - - - - Level3 - true - true - false - NDEBUG;_LIB;%(PreprocessorDefinitions) - true - NotUsing - pch.h - MultiThreaded - false - stdcpp20 - /kernel %(AdditionalOptions) - false - - - - - true - true - true - - - true - - - - - - - - - - - - \ No newline at end of file diff --git a/C/Utils/Utils.vcxproj.filters b/C/Utils/Utils.vcxproj.filters deleted file mode 100644 index e53c4ac..0000000 --- a/C/Utils/Utils.vcxproj.filters +++ /dev/null @@ -1,27 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;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 - - - - - Source Files - - - - - Header Files - - - \ No newline at end of file