├── .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 | }
--------------------------------------------------------------------------------