├── .gitignore ├── .gitmodules ├── Regs ├── classic_context_menu_AllUsers.reg ├── revert_context_menu_AllUsers.reg ├── classic_context_menu_CurrentUser.reg └── revert_context_menu_CurrentUser.reg ├── FakeDLL ├── FakeDLL.vcxproj.user ├── FakeDLL.vcxproj.filters ├── FakeDLL.vcxproj └── FakeDLL.cpp ├── Shell32Patcher ├── Shell32Patcher.vcxproj.user ├── Shell32Patcher.vcxproj.filters ├── ntdll.h ├── Shell32Patcher.vcxproj └── Shell32Patcher.cpp ├── README.md └── Shell32Patcher.sln /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | x64/ 3 | [Rr]elease/ 4 | [Dd]ebug/ 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "zydis"] 2 | path = zydis 3 | url = https://github.com/zyantific/zydis/ 4 | -------------------------------------------------------------------------------- /Regs/classic_context_menu_AllUsers.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llccd/Shell32Patcher/HEAD/Regs/classic_context_menu_AllUsers.reg -------------------------------------------------------------------------------- /Regs/revert_context_menu_AllUsers.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llccd/Shell32Patcher/HEAD/Regs/revert_context_menu_AllUsers.reg -------------------------------------------------------------------------------- /Regs/classic_context_menu_CurrentUser.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llccd/Shell32Patcher/HEAD/Regs/classic_context_menu_CurrentUser.reg -------------------------------------------------------------------------------- /Regs/revert_context_menu_CurrentUser.reg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/llccd/Shell32Patcher/HEAD/Regs/revert_context_menu_CurrentUser.reg -------------------------------------------------------------------------------- /FakeDLL/FakeDLL.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Shell32Patcher/Shell32Patcher.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WindowsLocalDebugger 7 | 8 | -------------------------------------------------------------------------------- /FakeDLL/FakeDLL.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /Shell32Patcher/Shell32Patcher.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | 23 | 24 | Header Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /Shell32Patcher/ntdll.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #define DIRECTORY_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0xF) 6 | #define SYMBOLIC_LINK_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | 0x1) 7 | 8 | extern "C" NTSTATUS NTAPI NtCreateDirectoryObjectEx( 9 | OUT PHANDLE SymbolicLinkHandle, 10 | IN ACCESS_MASK DesiredAccess, 11 | IN POBJECT_ATTRIBUTES ObjectAttributes, 12 | HANDLE ShadowDir, 13 | ULONG Something 14 | ); 15 | 16 | extern "C" NTSTATUS NTAPI NtCreateSymbolicLinkObject( 17 | OUT PHANDLE SymbolicLinkHandle, 18 | IN ACCESS_MASK DesiredAccess, 19 | IN POBJECT_ATTRIBUTES ObjectAttributes, 20 | IN PUNICODE_STRING DestinationName 21 | ); 22 | 23 | extern "C" NTSTATUS NTAPI NtCreateSection( 24 | OUT PHANDLE SectionHandle, 25 | IN ACCESS_MASK DesiredAccess, 26 | IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, 27 | IN PLARGE_INTEGER MaximumSize OPTIONAL, 28 | IN ULONG SectionPageProtection, 29 | IN ULONG AllocationAttributes, 30 | IN HANDLE FileHandle OPTIONAL 31 | ); 32 | 33 | extern "C" NTSTATUS NTAPI NtOpenSymbolicLinkObject( 34 | OUT PHANDLE SymbolicLinkHandle, 35 | IN ACCESS_MASK DesiredAccess, 36 | IN POBJECT_ATTRIBUTES ObjectAttributes 37 | ); 38 | 39 | extern "C" NTSTATUS NTAPI NtOpenSection( 40 | OUT PHANDLE SectionHandle, 41 | IN ACCESS_MASK DesiredAccess, 42 | IN POBJECT_ATTRIBUTES ObjectAttributes 43 | ); 44 | 45 | extern "C" NTSTATUS NTAPI NtMakeTemporaryObject( 46 | IN HANDLE ObjectHandle 47 | ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Classic Context Menu for Windows 11 2 | 3 | Shell32Patcher allows you to use classic context menu in Windows 11 file explorer. 4 | 5 | This repo demonstrates a method for persist program patching without using DLL hijack or function hook. For end users, it's more convenient to use the registry tweak in `Regs` folder. `TrustedInstaller` rights is needed for 'AllUsers' registry tweak. 6 | 7 | ## Usage 8 | 9 | ### Non-persist patch 10 | 11 | Uncheck 'Launch folder windows in a separate process' 12 | 13 | Run program. The patch will take effect immediately and apply to your current session only 14 | 15 | Newly started explorer.exe remains unpatched. To revert, simply restart explorer.exe 16 | 17 | ### Systemwide patch 18 | 19 | **Note: Systemwide patch will not work after July 2022 update. Because the mechanism it uses, which is the same as [PPLdump](https://github.com/itm4n/PPLdump), is blocked by this update.** 20 | 21 | To perform systemwide patch, you need to run program using an administrative account (but **not** `NT AUTHORITY\SYSTEM`) with command line option `-p` 22 | 23 | The patch will apply to all users and all newly started processes 24 | 25 | To revert systemwide patch, you must restart your computer 26 | 27 | ## FAQ 28 | 29 | ### Context menu on desktop is still in new style 30 | 31 | You may have some program that takes over processing of desktop items (like desktop organizers). In that case, the context menu is not handled by explorer. You can try command line option `-a` which will patch all applications to see if it works. 32 | -------------------------------------------------------------------------------- /Shell32Patcher.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31424.327 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shell32Patcher", "Shell32Patcher\Shell32Patcher.vcxproj", "{72A07A64-5D50-4561-A3A1-0BF42710A1BB}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FakeDLL", "FakeDLL\FakeDLL.vcxproj", "{16EEF1DC-BBFF-4B4A-93EA-9F9162AC4902}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Release|x64 = Release|x64 14 | ReleaseW|x64 = ReleaseW|x64 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {72A07A64-5D50-4561-A3A1-0BF42710A1BB}.Debug|x64.ActiveCfg = Debug|x64 18 | {72A07A64-5D50-4561-A3A1-0BF42710A1BB}.Debug|x64.Build.0 = Debug|x64 19 | {72A07A64-5D50-4561-A3A1-0BF42710A1BB}.Release|x64.ActiveCfg = Release|x64 20 | {72A07A64-5D50-4561-A3A1-0BF42710A1BB}.Release|x64.Build.0 = Release|x64 21 | {72A07A64-5D50-4561-A3A1-0BF42710A1BB}.ReleaseW|x64.ActiveCfg = ReleaseW|x64 22 | {72A07A64-5D50-4561-A3A1-0BF42710A1BB}.ReleaseW|x64.Build.0 = ReleaseW|x64 23 | {16EEF1DC-BBFF-4B4A-93EA-9F9162AC4902}.Debug|x64.ActiveCfg = Release|x64 24 | {16EEF1DC-BBFF-4B4A-93EA-9F9162AC4902}.Release|x64.ActiveCfg = Release|x64 25 | {16EEF1DC-BBFF-4B4A-93EA-9F9162AC4902}.Release|x64.Build.0 = Release|x64 26 | {16EEF1DC-BBFF-4B4A-93EA-9F9162AC4902}.ReleaseW|x64.ActiveCfg = Release|x64 27 | {16EEF1DC-BBFF-4B4A-93EA-9F9162AC4902}.ReleaseW|x64.Build.0 = Release|x64 28 | EndGlobalSection 29 | GlobalSection(SolutionProperties) = preSolution 30 | HideSolutionNode = FALSE 31 | EndGlobalSection 32 | GlobalSection(ExtensibilityGlobals) = postSolution 33 | SolutionGuid = {5D342EA3-B87D-42F2-ACED-1ACF2EB62427} 34 | EndGlobalSection 35 | EndGlobal 36 | -------------------------------------------------------------------------------- /FakeDLL/FakeDLL.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Release 6 | x64 7 | 8 | 9 | 10 | 16.0 11 | Win32Proj 12 | {16eef1dc-bbff-4b4a-93ea-9f9162ac4902} 13 | FakeDLL 14 | 10.0 15 | 16 | 17 | 18 | DynamicLibrary 19 | false 20 | v143 21 | true 22 | Unicode 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | false 35 | false 36 | 37 | 38 | 39 | Level3 40 | true 41 | true 42 | false 43 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 44 | true 45 | None 46 | false 47 | false 48 | MultiThreaded 49 | $(SolutionDir)Shell32Patcher 50 | 51 | 52 | Console 53 | true 54 | true 55 | false 56 | ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /FakeDLL/FakeDLL.cpp: -------------------------------------------------------------------------------- 1 | #include "ntdll.h" 2 | #include 3 | #include 4 | 5 | extern "C" __declspec(dllexport) void APIENTRY BriCreateBrokeredEvent(); 6 | extern "C" __declspec(dllexport) void APIENTRY BriDeleteBrokeredEvent(); 7 | extern "C" __declspec(dllexport) void APIENTRY EaCreateAggregatedEvent(); 8 | extern "C" __declspec(dllexport) void APIENTRY EACreateAggregateEvent(); 9 | extern "C" __declspec(dllexport) void APIENTRY EaQueryAggregatedEventParameters(); 10 | extern "C" __declspec(dllexport) void APIENTRY EAQueryAggregateEventData(); 11 | extern "C" __declspec(dllexport) void APIENTRY EaFreeAggregatedEventParameters(); 12 | extern "C" __declspec(dllexport) void APIENTRY EaDeleteAggregatedEvent(); 13 | extern "C" __declspec(dllexport) void APIENTRY EADeleteAggregateEvent(); 14 | 15 | BOOL DeleteSection(LPCWSTR path) 16 | { 17 | HANDLE hLink; 18 | UNICODE_STRING name; 19 | 20 | RtlInitUnicodeString(&name, path); 21 | OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, OBJ_CASE_INSENSITIVE, NULL, NULL }; 22 | 23 | if (NtOpenSection(&hLink, DELETE, &oa)) 24 | return FALSE; 25 | 26 | BOOL returnValue = NtMakeTemporaryObject(hLink) == 0; 27 | 28 | NtClose(hLink); 29 | 30 | return returnValue; 31 | } 32 | 33 | BOOL DeleteObjectLink(LPCWSTR path) 34 | { 35 | HANDLE hLink; 36 | UNICODE_STRING name; 37 | SECURITY_DESCRIPTOR sd; 38 | 39 | RtlInitUnicodeString(&name, path); 40 | OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, OBJ_CASE_INSENSITIVE, NULL, NULL }; 41 | 42 | if (NtOpenSymbolicLinkObject(&hLink, WRITE_DAC, &oa)) 43 | return FALSE; 44 | 45 | InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); 46 | #pragma warning( suppress : 6248 ) // Disable warning as setting a NULL DACL is intentional here 47 | SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE); 48 | 49 | if (!SetKernelObjectSecurity(hLink, DACL_SECURITY_INFORMATION, &sd) | NtClose(hLink)) 50 | return FALSE; 51 | 52 | if (NtOpenSymbolicLinkObject(&hLink, DELETE, &oa)) 53 | return FALSE; 54 | 55 | BOOL returnValue = NtMakeTemporaryObject(hLink) == 0; 56 | 57 | NtClose(hLink); 58 | 59 | return returnValue; 60 | } 61 | 62 | HANDLE ObjectManagerCreateSymlink(LPCWSTR linkname, LPCWSTR targetname) 63 | { 64 | UNICODE_STRING name, target; 65 | HANDLE hLink; 66 | PSECURITY_DESCRIPTOR sd; 67 | 68 | if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( 69 | L"D:(A;;GA;;;BA)(A;;GR;;;RC)(A;;GR;;;WD)(A;;GR;;;AC)(A;;GR;;;S-1-15-2-2)S:(ML;;NW;;;LW)", 70 | SDDL_REVISION_1, &sd, 0)) return NULL; 71 | 72 | RtlInitUnicodeString(&name, linkname); 73 | RtlInitUnicodeString(&target, targetname); 74 | OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, NULL }; 75 | 76 | if (NtCreateSymbolicLinkObject(&hLink, SYMBOLIC_LINK_ALL_ACCESS, &oa, &target)) 77 | return NULL; 78 | 79 | SetKernelObjectSecurity(hLink, DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, sd); 80 | 81 | return hLink; 82 | } 83 | 84 | BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 85 | { 86 | if (ul_reason_for_call == DLL_PROCESS_ATTACH) 87 | { 88 | DeleteObjectLink(L"\\KnownDlls\\EventAggregation.dll"); 89 | if (DeleteSection(L"\\KnownDlls\\shell32.dll")) 90 | ObjectManagerCreateSymlink(L"\\KnownDlls\\shell32.dll", L"\\BaseNamedObjects\\shell32.dll"); 91 | } 92 | return TRUE; 93 | } 94 | 95 | void APIENTRY BriCreateBrokeredEvent() { } 96 | void APIENTRY BriDeleteBrokeredEvent() { } 97 | void APIENTRY EaCreateAggregatedEvent() { } 98 | void APIENTRY EACreateAggregateEvent() { } 99 | void APIENTRY EaQueryAggregatedEventParameters() { } 100 | void APIENTRY EAQueryAggregateEventData() { } 101 | void APIENTRY EaFreeAggregatedEventParameters() { } 102 | void APIENTRY EaDeleteAggregatedEvent() { } 103 | void APIENTRY EADeleteAggregateEvent() { } -------------------------------------------------------------------------------- /Shell32Patcher/Shell32Patcher.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | ReleaseW 10 | x64 11 | 12 | 13 | Release 14 | x64 15 | 16 | 17 | 18 | 16.0 19 | Win32Proj 20 | {72a07a64-5d50-4561-a3a1-0bf42710a1bb} 21 | ConsoleApplication3 22 | 10.0 23 | Shell32Patcher 24 | 25 | 26 | 27 | Application 28 | true 29 | v143 30 | Unicode 31 | 32 | 33 | Application 34 | false 35 | v143 36 | true 37 | Unicode 38 | 39 | 40 | Application 41 | false 42 | v143 43 | true 44 | Unicode 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | true 63 | 64 | 65 | false 66 | false 67 | 68 | 69 | false 70 | false 71 | 72 | 73 | 74 | Level3 75 | true 76 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 77 | true 78 | $(SolutionDir)zydis\include;$(SolutionDir)zydis\dependencies\zycore\include;$(SolutionDir)zydis\msvc 79 | 80 | 81 | Console 82 | true 83 | $(SolutionDir)zydis\msvc\bin\DebugX64\Zydis.lib;ntdll.lib;Wtsapi32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 84 | 85 | 86 | 87 | 88 | Level3 89 | true 90 | true 91 | false 92 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 93 | true 94 | None 95 | false 96 | false 97 | $(SolutionDir)zydis\include;$(SolutionDir)zydis\dependencies\zycore\include;$(SolutionDir)zydis\msvc 98 | 99 | 100 | Console 101 | true 102 | true 103 | false 104 | 105 | 106 | false 107 | $(SolutionDir)zydis\msvc\bin\ReleaseX64\Zydis.lib;ntdll.lib;Wtsapi32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 108 | 109 | 110 | 111 | 112 | Level3 113 | true 114 | true 115 | false 116 | NDEBUG;%(PreprocessorDefinitions) 117 | true 118 | None 119 | false 120 | false 121 | $(SolutionDir)zydis\include;$(SolutionDir)zydis\dependencies\zycore\include;$(SolutionDir)zydis\msvc 122 | 123 | 124 | Windows 125 | true 126 | true 127 | false 128 | main 129 | false 130 | $(SolutionDir)zydis\msvc\bin\ReleaseX64\Zydis.lib;ntdll.lib;Wtsapi32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 131 | true 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /Shell32Patcher/Shell32Patcher.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ntdll.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef _CONSOLE 9 | #define printf(...) {} 10 | #define EXIT(msg, x) {ExitProcess(x);} 11 | #else 12 | #define EXIT(msg, x) {puts(msg); return x;} 13 | #endif 14 | 15 | static HANDLE heap = 0; 16 | 17 | static WCHAR szShell32[MAX_PATH]; 18 | 19 | // xor rdi, rdi 20 | // nop 21 | static const char patchData[] = "\x48\x31\xFF\x0F\x18\x24\x00"; 22 | 23 | constexpr const char guid[] = "\xB1\xC5\x06\xB3\xF2\xB4\x3C\x47\xB6\xFF\x70\x1B\x24\x6C\xE2\xD2"; 24 | 25 | PIMAGE_SECTION_HEADER findSection(PIMAGE_NT_HEADERS64 pNT, const char* str) 26 | { 27 | auto pSection = IMAGE_FIRST_SECTION(pNT); 28 | 29 | for (DWORD64 i = 0; i < pNT->FileHeader.NumberOfSections; i++) 30 | if (CSTR_EQUAL == CompareStringA(LOCALE_INVARIANT, 0, (char*)pSection[i].Name, -1, str, -1)) 31 | return pSection + i; 32 | 33 | return NULL; 34 | } 35 | 36 | DWORD64 pattenMatch(DWORD64 base, PIMAGE_SECTION_HEADER pSection, const void* str, DWORD64 size) 37 | { 38 | auto rdata = base + pSection->VirtualAddress; 39 | 40 | for (DWORD64 i = 0; i < pSection->SizeOfRawData; i += 4) 41 | if (!memcmp((void*)(rdata + i), str, size)) return pSection->VirtualAddress + i; 42 | 43 | return -1; 44 | } 45 | 46 | DWORD64 searchXref(ZydisDecoder* decoder, DWORD64 base, PRUNTIME_FUNCTION func, DWORD64 target) 47 | { 48 | auto IP = base + func->BeginAddress; 49 | auto length = (ZyanUSize)func->EndAddress - func->BeginAddress; 50 | ZydisDecodedInstruction instruction; 51 | ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]; 52 | 53 | while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(decoder, (void*)IP, length, &instruction, operands))) 54 | { 55 | IP += instruction.length; 56 | length -= instruction.length; 57 | if (instruction.operand_count == 2 && 58 | instruction.mnemonic == ZYDIS_MNEMONIC_LEA && 59 | operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY && 60 | operands[1].mem.base == ZYDIS_REGISTER_RIP && 61 | operands[1].mem.disp.value + IP == target + base && 62 | operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) 63 | return IP - base; 64 | } 65 | 66 | return 0; 67 | } 68 | 69 | HMODULE GetModule(HANDLE hProcess, LPCWSTR target) 70 | { 71 | DWORD cb, cbNeeded; 72 | WCHAR szModName[MAX_PATH]; 73 | EnumProcessModulesEx(hProcess, NULL, 0, &cb, LIST_MODULES_64BIT); 74 | if (GetLastError() != ERROR_ACCESS_DENIED) return NULL; 75 | auto hMods = (HMODULE*)HeapAlloc(heap, 0, cb); 76 | if (!hMods) return NULL; 77 | EnumProcessModulesEx(hProcess, hMods, cb, &cbNeeded, LIST_MODULES_64BIT); 78 | if (cbNeeded < cb) cb = cbNeeded; 79 | 80 | HMODULE hMod = NULL; 81 | for (DWORD i = 0; i < cb / sizeof(HMODULE); i++) { 82 | if (!GetModuleFileNameExW(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(WCHAR))) continue; 83 | if (!lstrcmpiW(szModName, target)) { 84 | hMod = hMods[i]; 85 | break; 86 | } 87 | } 88 | HeapFree(heap, 0, hMods); 89 | return hMod; 90 | } 91 | 92 | LPVOID get_token_info(HANDLE token, const TOKEN_INFORMATION_CLASS& type) 93 | { 94 | DWORD length; 95 | void* buf = NULL; 96 | GetTokenInformation(token, type, NULL, 0, &length); 97 | if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) 98 | { 99 | buf = (void*)HeapAlloc(heap, 0, length); 100 | GetTokenInformation(token, type, buf, length, &length); 101 | } 102 | return buf; 103 | } 104 | 105 | void enable_all_privileges(HANDLE token) 106 | { 107 | auto privileges = (PTOKEN_PRIVILEGES)get_token_info(token, TokenPrivileges); 108 | if (privileges) 109 | { 110 | for (DWORD i = 0; i < privileges->PrivilegeCount; ++i) 111 | privileges->Privileges[i].Attributes = SE_PRIVILEGE_ENABLED; 112 | 113 | AdjustTokenPrivileges(token, false, privileges, 0, NULL, NULL); 114 | HeapFree(heap, 0, privileges); 115 | } 116 | } 117 | 118 | BOOL get_token_pid(const DWORD& ProcessId, PHANDLE TokenHandle) 119 | { 120 | auto process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, ProcessId); 121 | if (GetLastError() == ERROR_ACCESS_DENIED) process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, ProcessId); 122 | if (!process) return false; 123 | 124 | const auto ret = OpenProcessToken(process, MAXIMUM_ALLOWED, TokenHandle); 125 | 126 | CloseHandle(process); 127 | return ret; 128 | } 129 | 130 | DWORD get_lsass_pid() 131 | { 132 | const auto scm = OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT); 133 | if (!scm) return -1; 134 | 135 | const auto service = OpenServiceW(scm, L"SamSs", SERVICE_QUERY_STATUS); 136 | if (!service) 137 | { 138 | CloseServiceHandle(scm); 139 | return -1; 140 | } 141 | 142 | SERVICE_STATUS_PROCESS status; 143 | DWORD bytes_needed = sizeof(status); 144 | DWORD pid = -1; 145 | if (QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(status), &bytes_needed)) 146 | if (SERVICE_STOPPED != status.dwCurrentState) pid = status.dwProcessId; 147 | 148 | CloseServiceHandle(service); 149 | CloseServiceHandle(scm); 150 | return pid; 151 | } 152 | 153 | HANDLE ObjectManagerCreateSymlink(LPCWSTR linkname, LPCWSTR targetname) 154 | { 155 | UNICODE_STRING name, target; 156 | HANDLE hLink; 157 | 158 | RtlInitUnicodeString(&name, linkname); 159 | RtlInitUnicodeString(&target, targetname); 160 | OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, OBJ_CASE_INSENSITIVE, NULL, NULL}; 161 | 162 | if (NtCreateSymbolicLinkObject(&hLink, SYMBOLIC_LINK_ALL_ACCESS, &oa, &target)) 163 | return NULL; 164 | 165 | return hLink; 166 | } 167 | 168 | HANDLE ObjectManagerCreateDirectory(LPCWSTR dirname) 169 | { 170 | UNICODE_STRING name; 171 | HANDLE hDirectory; 172 | 173 | RtlInitUnicodeString(&name, dirname); 174 | OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, OBJ_CASE_INSENSITIVE, NULL, NULL }; 175 | 176 | if (NtCreateDirectoryObjectEx(&hDirectory, DIRECTORY_ALL_ACCESS, &oa, NULL, FALSE)) 177 | return NULL; 178 | 179 | return hDirectory; 180 | } 181 | 182 | _Success_(return) BOOL MapDll(_In_ LPCWSTR sectionName, _In_ HANDLE file, _Out_ PHANDLE section, _In_ DWORD flags) 183 | { 184 | UNICODE_STRING name; 185 | 186 | RtlInitUnicodeString(&name, sectionName); 187 | OBJECT_ATTRIBUTES oa = { sizeof(oa), NULL, &name, OBJ_CASE_INSENSITIVE | flags, NULL, NULL }; 188 | 189 | if (flags & OBJ_PERMANENT && !NtOpenSection(section, DELETE, &oa)) 190 | { 191 | NtMakeTemporaryObject(*section); 192 | NtClose(*section); 193 | } 194 | 195 | if (NtCreateSection(section, SECTION_ALL_ACCESS, &oa, NULL, PAGE_READONLY, SEC_IMAGE, file)) 196 | return FALSE; 197 | 198 | return TRUE; 199 | } 200 | 201 | _Success_(return) BOOL CreateProtectedProcessAsUser(_In_ HANDLE token, _In_ LPWSTR cmdline, _Out_ PHANDLE phProcess) 202 | { 203 | STARTUPINFOW si; 204 | PROCESS_INFORMATION pi; 205 | 206 | si.cb = sizeof(STARTUPINFOW); 207 | si.cbReserved2 = 0; 208 | si.lpDesktop = NULL; 209 | si.lpTitle = NULL; 210 | si.lpReserved = NULL; 211 | si.lpReserved2 = NULL; 212 | si.dwFlags = 0; 213 | 214 | if (!CreateProcessAsUserW(token, NULL, cmdline, NULL, NULL, FALSE, CREATE_PROTECTED_PROCESS | CREATE_UNICODE_ENVIRONMENT, NULL, NULL, &si, &pi)) 215 | return FALSE; 216 | 217 | *phProcess = pi.hProcess; 218 | CloseHandle(pi.hThread); 219 | 220 | return TRUE; 221 | } 222 | 223 | DWORD64 getPatchOffset(DWORD64 base, DWORD64 target, PRUNTIME_FUNCTION funcTable, DWORD64 funcTableSize) 224 | { 225 | ZydisDecoder decoder; 226 | ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); 227 | 228 | for (DWORD i = 0; i < funcTableSize; i++) { 229 | DWORD64 RVA = searchXref(&decoder, base, funcTable + i, target); 230 | if (!RVA) continue; 231 | 232 | auto IP = base + RVA; 233 | auto length = (funcTable + i)->EndAddress - RVA; 234 | ZydisDecodedInstruction instruction; 235 | 236 | while (ZYAN_SUCCESS(ZydisDecoderDecodeInstruction(&decoder, (ZydisDecoderContext *)0, (void*)IP, length, &instruction))) 237 | { 238 | if (instruction.length == 7 && 239 | instruction.mnemonic == ZYDIS_MNEMONIC_CALL) 240 | return IP - base; 241 | IP += instruction.length; 242 | length -= instruction.length; 243 | } 244 | } 245 | 246 | return 0; 247 | } 248 | 249 | HANDLE patch(DWORD64 offset) 250 | { 251 | WCHAR szTemp[MAX_PATH]; 252 | lstrcpyW(szTemp + GetTempPathW(sizeof(szTemp) / sizeof(WCHAR), szTemp), L"shell32.dll"); 253 | CopyFileW(szShell32, szTemp, FALSE); 254 | 255 | auto hFile = CreateFileW(szTemp, FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, 7, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL); 256 | if (hFile == INVALID_HANDLE_VALUE) return hFile; 257 | 258 | DWORD written = 0; 259 | if(SetFilePointerEx(hFile, *(LARGE_INTEGER*)&offset, NULL, FILE_BEGIN)) 260 | WriteFile(hFile, patchData, sizeof(patchData) - 1, &written, NULL); 261 | printf("Write() wrote %d\n", written); 262 | 263 | return hFile; 264 | } 265 | 266 | int patchProcess(DWORD64 RVA, DWORD session, bool patchAll) 267 | { 268 | PWTS_PROCESS_INFOW processList; 269 | DWORD processCount = 0, pLevel = 0; 270 | if (!WTSEnumerateProcessesExW(WTS_CURRENT_SERVER_HANDLE, &pLevel, session, (LPWSTR*)&processList, &processCount)) 271 | EXIT("Cannot enumerate process", -25); 272 | 273 | for (DWORD i = 0; i < processCount; i++) { 274 | if (!patchAll && lstrcmpiW(processList[i].pProcessName, L"explorer.exe")) continue; 275 | auto hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, TRUE, processList[i].ProcessId); 276 | if (!hProcess) continue; 277 | 278 | auto hModShell32 = GetModule(hProcess, szShell32); 279 | if (hModShell32) 280 | { 281 | printf("Patching PID:%u\n", processList[i].ProcessId); 282 | size_t written = 0; 283 | WriteProcessMemory(hProcess, (void*)((size_t)hModShell32 + RVA), patchData, sizeof(patchData) - 1, &written); 284 | printf("WriteProcessMemory() wrote %llu\n", written); 285 | } 286 | CloseHandle(hProcess); 287 | } 288 | EXIT("Patch finished", 0); 289 | } 290 | 291 | int main() 292 | { 293 | heap = GetProcessHeap(); 294 | if (!heap) EXIT("GetProcessHeap() Failed", -1); 295 | 296 | int argc; 297 | const auto current_cmdline = GetCommandLineW(); 298 | const auto argv = CommandLineToArgvW(current_cmdline, &argc); 299 | if (!argv) EXIT("CommandLineToArgv() Failed", 0x101); 300 | 301 | bool persist = false; 302 | bool patchAll = false; 303 | for (int i = 1; i < argc; ++i) { 304 | if (!lstrcmpiW(argv[i], L"-p")) 305 | persist = true; 306 | else if (!lstrcmpiW(argv[i], L"-a")) 307 | patchAll = true; 308 | } 309 | LocalFree(argv); 310 | 311 | lstrcpyW(szShell32 + GetSystemDirectoryW(szShell32, sizeof(szShell32) / sizeof(WCHAR)), L"\\shell32.dll"); 312 | 313 | auto base = (size_t)LoadLibraryExW(szShell32, NULL, DONT_RESOLVE_DLL_REFERENCES); 314 | if (!base) EXIT("Load shell32.dll Failed", -2); 315 | auto pNT = (PIMAGE_NT_HEADERS64)(base + ((PIMAGE_DOS_HEADER)base)->e_lfanew); 316 | 317 | auto pSection = findSection(pNT, ".rdata"); 318 | if (!pSection) EXIT("Cannot find .rdata", -3); 319 | 320 | auto ContextMenuPresenter = pattenMatch(base, pSection, guid, sizeof(guid) - 1); 321 | if (ContextMenuPresenter == -1) EXIT("GUID patten not found", -4); 322 | 323 | auto pExceptionDirectory = pNT->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXCEPTION; 324 | auto funcTable = (PRUNTIME_FUNCTION)(base + pExceptionDirectory->VirtualAddress); 325 | auto funcTableSize = pExceptionDirectory->Size / (DWORD)sizeof(RUNTIME_FUNCTION); 326 | if (!funcTableSize) EXIT("Exception directory not found", -5); 327 | 328 | DWORD64 RVA = getPatchOffset(base, ContextMenuPresenter, funcTable, funcTableSize); 329 | if (!RVA) EXIT("Patch patten not found, already patched?", -6); 330 | printf("Found offset %llX\n", RVA); 331 | 332 | pSection = IMAGE_FIRST_SECTION(pNT); 333 | auto rawOffset = RVA + pSection->PointerToRawData - pSection->VirtualAddress; 334 | 335 | HANDLE token; 336 | if (!OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &token)) EXIT("Open token of CurrentProcess failed", -7); 337 | enable_all_privileges(token); 338 | CloseHandle(token); 339 | 340 | if (!persist) return patchProcess(RVA, WTS_CURRENT_SESSION, patchAll); 341 | 342 | if (!ObjectManagerCreateSymlink(L"\\??\\GLOBALROOT", L"\\GLOBAL??")) EXIT("Create GLOBALROOT symlink failed", -8); 343 | 344 | if (!get_token_pid(get_lsass_pid(), &token)) EXIT("Open token of lsass.exe failed", -9); 345 | HANDLE dup_token; 346 | if (!DuplicateTokenEx(token, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenImpersonation, &dup_token)) EXIT("Duplicate impersonation token of lsass.exe failed", -10); 347 | CloseHandle(token); 348 | enable_all_privileges(dup_token); 349 | 350 | if (!DuplicateTokenEx(dup_token, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &token)) EXIT("Duplicate primary token of lsass.exe failed", -11); 351 | if (!(SetThreadToken(NULL, dup_token) || ImpersonateLoggedOnUser(dup_token))) EXIT("Impersonate SYSTEM failed", -12); 352 | 353 | if (!ObjectManagerCreateDirectory(L"\\GLOBAL??\\KnownDlls")) EXIT("Create KnownDlls directory failed", -13); 354 | if (!ObjectManagerCreateSymlink(L"\\GLOBAL??\\KnownDlls\\EventAggregation.dll", L"CreazyUniverse")) EXIT("Create EventAggregation symlink failed", -14); 355 | 356 | if (!RevertToSelf()) EXIT("RevertToSelf() failed", -15); 357 | 358 | if (!DefineDosDevice(DDD_NO_BROADCAST_SYSTEM | DDD_RAW_TARGET_PATH, L"GLOBALROOT\\KnownDlls\\EventAggregation.dll", L"\\BaseNamedObjects\\EventAggregation.dll")) 359 | if(GetLastError() != ERROR_ALREADY_EXISTS) EXIT("DefineDosDevice() failed", -16); 360 | 361 | if (!(SetThreadToken(NULL, dup_token) || ImpersonateLoggedOnUser(dup_token))) EXIT("Impersonate SYSTEM failed", -17); 362 | 363 | auto hFile = CreateFileW(L"FakeDLL.dll", GENERIC_READ, 7, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 364 | if (hFile == INVALID_HANDLE_VALUE) EXIT("Cannot open FakeDLL.dll", -18); 365 | HANDLE hDllSection; 366 | if (!MapDll(L"\\BaseNamedObjects\\EventAggregation.dll", hFile, &hDllSection, 0)) EXIT("Cannot create EventAggregation.dll section", -19); 367 | CloseHandle(hFile); 368 | 369 | hFile = patch(rawOffset); 370 | if (hFile == INVALID_HANDLE_VALUE) EXIT("Cannot copy and patch shell32.dll", -20); 371 | if (!MapDll(L"\\BaseNamedObjects\\shell32.dll", hFile, &hDllSection, OBJ_PERMANENT)) EXIT("Cannot create shell32.dll section", -21); 372 | CloseHandle(hFile); 373 | 374 | PSECURITY_DESCRIPTOR sd; 375 | if(!ConvertStringSecurityDescriptorToSecurityDescriptorW( 376 | L"D:(A;;GA;;;BA)(A;;0x2000f;;;RC)(A;;0x2000f;;;WD)(A;;0x2000f;;;AC)(A;;0x2000f;;;S-1-15-2-2)S:(ML;;NW;;;LW)", 377 | SDDL_REVISION_1, &sd, 0)) EXIT("ConvertStringSecurityDescriptorToSecurityDescriptorW() failed", -22); 378 | if (!SetKernelObjectSecurity(hDllSection, DACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, sd)) 379 | EXIT("Cannot set security information of shell32 section", -23); 380 | 381 | HANDLE hNewProcess; 382 | WCHAR szServices[MAX_PATH]; 383 | lstrcpyW(szServices + GetSystemDirectoryW(szServices, sizeof(szServices) / sizeof(WCHAR)), L"\\services.exe"); 384 | if (!CreateProtectedProcessAsUser(token, szServices, &hNewProcess)) EXIT("Start services.exe failed", -24); 385 | 386 | return patchProcess(RVA, WTS_ANY_SESSION, patchAll); 387 | } --------------------------------------------------------------------------------